Merge branch 'git-describe'
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob4f403b22ff8d1b9d7a08ed0064c1884c514ff786
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2014 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // BrowseRefsDlg.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
25 #include "LogDlg.h"
26 #include "AddRemoteDlg.h"
27 #include "AppUtils.h"
28 #include "Settings\SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
32 #include "IconMenu.h"
33 #include "FileDiffDlg.h"
34 #include "DeleteRemoteTagDlg.h"
35 #include "UnicodeUtils.h"
36 #include "InputDlg.h"
37 #include "SysProgressDlg.h"
39 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
41 if (ref.Left(13) == _T("refs/remotes/"))
42 ref = ref.Mid(13);
43 else if (ref.Left(8) == _T("remotes/"))
44 ref = ref.Mid(8);
46 STRING_VECTOR list;
47 int result = g_Git.GetRemoteList(list);
48 if (result != 0)
49 return result;
51 for (size_t i = 0; i < list.size(); ++i)
53 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
55 remote = list[i];
56 branch = ref.Mid(list[i].GetLength() + 1);
57 return 0;
59 if (ref == list[i])
61 remote = list[i];
62 branch = _T("");
63 return 0;
67 return -1;
70 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
72 if (control == NULL)
73 return;
74 // set the sort arrow
75 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
76 HDITEM HeaderItem = {0};
77 HeaderItem.mask = HDI_FORMAT;
78 for (int i=0; i<pHeader->GetItemCount(); ++i)
80 pHeader->GetItem(i, &HeaderItem);
81 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
82 pHeader->SetItem(i, &HeaderItem);
84 if (nColumn >= 0)
86 pHeader->GetItem(nColumn, &HeaderItem);
87 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
88 pHeader->SetItem(nColumn, &HeaderItem);
92 class CRefLeafListCompareFunc
94 public:
95 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
96 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
97 if (m_bSortLogical)
98 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
101 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
103 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
106 int Compare(LPARAM lParam1, LPARAM lParam2)
108 return Compare(
109 (CShadowTree*)m_pList->GetItemData((int)lParam1),
110 (CShadowTree*)m_pList->GetItemData((int)lParam2));
113 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
115 int result=CompareNoDesc(pLeft,pRight);
116 if(m_desc)
117 return -result;
118 return result;
121 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
123 switch(m_col)
125 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
126 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
127 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
128 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
129 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
130 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
131 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
133 return 0;
135 int SortStrCmp(const CString& left, const CString& right)
137 if (m_bSortLogical)
138 return StrCmpLogicalW(left, right);
139 return StrCmpI(left, right);
142 int m_col;
143 bool m_desc;
144 CListCtrl* m_pList;
145 bool m_bSortLogical;
148 // CBrowseRefsDlg dialog
150 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
152 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
153 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
154 m_cmdPath(cmdPath),
155 m_currSortCol(0),
156 m_currSortDesc(false),
157 m_initialRef(L"HEAD"),
158 m_pickRef_Kind(gPickRef_All),
159 m_pListCtrlRoot(NULL),
160 m_bHasWC(true),
161 m_SelectedFilters(LOGFILTER_ALL),
162 m_ColumnManager(&m_ListRefLeafs),
163 m_bPickOne(false),
164 m_bPickedRefSet(false)
169 CBrowseRefsDlg::~CBrowseRefsDlg()
173 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
175 CDialog::DoDataExchange(pDX);
176 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
177 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
178 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
182 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
183 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
184 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
185 ON_WM_CONTEXTMENU()
186 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
187 ON_WM_DESTROY()
188 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
189 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
190 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
191 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
192 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
193 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
194 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
195 ON_WM_TIMER()
196 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
197 END_MESSAGE_MAP()
200 // CBrowseRefsDlg message handlers
202 void CBrowseRefsDlg::OnBnClickedOk()
204 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
206 OnOK();
207 return;
210 CIconMenu popupMenu;
211 popupMenu.CreatePopupMenu();
213 std::vector<CShadowTree*> selectedLeafs;
214 GetSelectedLeaves(selectedLeafs);
216 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
217 popupMenu.SetDefaultItem(1);
218 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
219 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
221 RECT rect;
222 GetDlgItem(IDOK)->GetWindowRect(&rect);
223 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
224 switch (selection)
226 case 1:
227 OnOK();
228 break;
229 case 2:
231 m_bPickedRefSet = true;
232 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
233 OnOK();
235 break;
236 case 3:
238 m_bPickedRefSet = true;
239 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
240 OnOK();
242 break;
243 default:
244 break;
248 BOOL CBrowseRefsDlg::OnInitDialog()
250 CResizableStandAloneDialog::OnInitDialog();
251 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
253 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
254 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
255 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
256 SetFilterCueText();
258 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
259 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
260 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
261 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
262 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
263 AddAnchor(IDHELP, BOTTOM_RIGHT);
265 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
266 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
267 static int columnWidths[] = { 150, 100, 100, 300, 100, 80, 80 };
268 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
269 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
270 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
271 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
272 m_bPickedRefSet = false;
274 AddAnchor(IDOK,BOTTOM_RIGHT);
275 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
276 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
278 Refresh(m_initialRef);
280 EnableSaveRestore(L"BrowseRefs");
282 CString sWindowTitle;
283 GetWindowText(sWindowTitle);
284 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
286 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
288 if (m_bPickOne)
289 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
291 m_ListRefLeafs.SetFocus();
292 return FALSE;
295 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
297 int posSlash=nameLeft.Find('/');
298 CString nameSub;
299 if(posSlash<0)
301 nameSub=nameLeft;
302 nameLeft.Empty();//Nothing left
304 else
306 nameSub=nameLeft.Left(posSlash);
307 nameLeft=nameLeft.Mid(posSlash+1);
309 if(nameSub.IsEmpty())
310 return NULL;
312 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
313 return NULL;
315 CShadowTree& nextNode=m_ShadowTree[nameSub];
316 nextNode.m_csRefName=nameSub;
317 nextNode.m_pParent=this;
318 return &nextNode;
321 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
323 if(IsLeaf())
325 if(m_csRefName.GetLength() > partialRefName.GetLength())
326 return NULL;
327 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
329 //Match of leaf name. Try match on total name.
330 CString totalRefName = GetRefName();
331 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
332 return this; //Also match. Found.
335 else
337 //Not a leaf. Search all nodes.
338 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
340 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
341 if(pSubtree != NULL)
342 return pSubtree; //Found
345 return NULL;//Not found
349 typedef std::map<CString,CString> MAP_STRING_STRING;
351 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
353 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
354 //List ctrl selection?
355 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
357 //A leaf is selected
358 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
359 m_ListRefLeafs.GetNextSelectedItem(pos));
360 return pTree->GetRefName();
362 else if (pos && !pickFirstSelIfMultiSel)
364 // at least one leaf is selected
365 CString refs;
366 int index;
367 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
369 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
370 if(wcsncmp(ref, L"refs/", 5) == 0)
371 ref = ref.Mid(5);
372 if(wcsncmp(ref, L"heads/", 6) == 0)
373 ref = ref.Mid(6);
374 refs += ref + _T(" ");
376 return refs.Trim();
378 else if(!onlyIfLeaf)
380 //Tree ctrl selection?
381 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
382 if(hTree!=NULL)
384 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
385 return pTree->GetRefName();
388 return CString();//None
391 static int GetBranchDescriptionsCallback(const git_config_entry *entry, void *data)
393 MAP_STRING_STRING *descriptions = (MAP_STRING_STRING *) data;
394 CString key = CUnicodeUtils::GetUnicode(entry->name, CP_UTF8);
395 CString val = CUnicodeUtils::GetUnicode(entry->value, CP_UTF8);
396 descriptions->insert(std::make_pair(key.Mid(7, key.GetLength() - 7 - 12), val)); // 7: branch., 12: .description
397 return 0;
400 MAP_STRING_STRING GetBranchDescriptions()
402 MAP_STRING_STRING descriptions;
403 CAutoConfig config(true);
404 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(g_Git.GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, FALSE);
405 git_config_foreach_match(config, "branch\\..*\\.description", GetBranchDescriptionsCallback, &descriptions);
406 return descriptions;
409 void CBrowseRefsDlg::Refresh(CString selectRef)
411 // m_RefMap.clear();
412 // g_Git.GetMapHashToFriendName(m_RefMap);
414 remotes.clear();
415 if (g_Git.GetRemoteList(remotes))
416 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
418 if(!selectRef.IsEmpty())
420 if(selectRef == "HEAD")
422 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
423 selectRef.Empty();
424 else
425 selectRef = L"refs/heads/" + selectRef;
428 else
430 selectRef = GetSelectedRef(false, true);
433 m_RefTreeCtrl.DeleteAllItems();
434 m_ListRefLeafs.DeleteAllItems();
435 m_TreeRoot.m_ShadowTree.clear();
436 m_TreeRoot.m_csRefName = "refs";
437 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"refs",NULL,NULL);
438 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
440 CString allRefs, error;
441 if (g_Git.Run(L"git.exe for-each-ref --format="
442 L"%(refname)%04"
443 L"%(objectname)%04"
444 L"%(upstream)%04"
445 L"%(authordate:relative)%04"
446 L"%(subject)%04"
447 L"%(authorname)%04"
448 L"%(authordate:iso8601)%03",
449 &allRefs, &error, CP_UTF8))
451 CMessageBox::Show(NULL, CString(_T("Get refs failed\n")) + error, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
454 int linePos=0;
455 CString singleRef;
457 MAP_STRING_STRING refMap;
459 //First sort on ref name
460 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
462 singleRef.TrimLeft(L"\r\n");
463 int valuePos=0;
464 CString refName=singleRef.Tokenize(L"\04",valuePos);
465 if(refName.IsEmpty())
466 continue;
467 CString refRest=singleRef.Mid(valuePos);
470 //Use ref based on m_pickRef_Kind
471 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
472 continue; //Skip
473 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
474 continue; //Skip
475 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
476 continue; //Skip
478 refMap[refName] = refRest; //Use
481 MAP_STRING_STRING descriptions = GetBranchDescriptions();
483 //Populate ref tree
484 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
486 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
487 CString values=iterRefMap->second;
488 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
490 int valuePos=0;
491 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
492 treeLeaf.m_csUpstream = values.Tokenize(L"\04", valuePos); if (valuePos < 0) continue;
493 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
494 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
495 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
496 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
497 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
499 if (wcsncmp(iterRefMap->first, L"refs/heads/", 11) == 0)
500 treeLeaf.m_csDescription = descriptions[treeLeaf.m_csRefName];
503 // always expand the tree first
504 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
506 // try exact match first
507 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
508 SelectRef(selectRef, false);
511 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
513 if(!bExactMatch)
515 CString newRefName = GetFullRefName(refName);
516 if(!newRefName.IsEmpty())
517 refName = newRefName;
518 //else refName is not a valid ref. Try to select as good as possible.
520 if(_wcsnicmp(refName, L"refs/", 5) != 0)
521 return false; // Not a ref name
523 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
524 if(treeLeafHead.m_hTree != NULL)
526 //Not a leaf. Select tree node and return
527 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
528 return true;
531 if(treeLeafHead.m_pParent==NULL)
532 return false; //Weird... should not occur.
534 //This is the current head.
535 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
537 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
539 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
540 if(pCurrShadowTree == &treeLeafHead)
542 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
543 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
547 return true;
550 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
552 if(pTreePos==NULL)
554 if(_wcsnicmp(refName, L"refs/", 5) == 0)
555 refName=refName.Mid(5);
556 pTreePos=&m_TreeRoot;
558 if(refName.IsEmpty())
559 return *pTreePos;//Found leaf
561 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
562 if(pNextTree==NULL)
564 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
565 ASSERT(!bCreateIfNotExist);
566 return *pTreePos;
569 if(!refName.IsEmpty())
571 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
572 //Leafs are for the list control.
573 if(pNextTree->m_hTree==NULL)
575 //New tree. Create node in control.
576 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
577 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
581 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
585 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
587 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
588 *pResult = 0;
590 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
593 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
595 m_ListRefLeafs.DeleteAllItems();
597 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
598 if(pTree==NULL)
600 ASSERT(FALSE);
601 return;
603 FillListCtrlForShadowTree(pTree,L"",true);
604 m_ColumnManager.SetVisible(eCol_Upstream, (wcsncmp(pTree->GetRefName(), L"refs/heads", 11) == 0));
607 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
609 if(pTree->IsLeaf())
611 CString filter;
612 m_ctrlFilter.GetWindowText(filter);
613 filter.MakeLower();
614 bool positive = filter[0] != '!';
615 if (!positive)
616 filter = filter.Mid(1);
617 CString ref = refNamePrefix + pTree->m_csRefName;
618 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter, positive))
620 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
622 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
623 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
624 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
625 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
626 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
627 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
628 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
629 int pos = 0;
630 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
633 else
636 CString csThisName;
637 if(!isFirstLevel)
638 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
639 else
640 m_pListCtrlRoot = pTree;
641 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
643 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
646 if (isFirstLevel)
648 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
649 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
651 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
655 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
657 if (m_SelectedFilters & LOGFILTER_REFNAME)
659 CString msg = ref;
660 msg = msg.MakeLower();
662 if (msg.Find(filter) >= 0)
663 return positive;
666 if (m_SelectedFilters & LOGFILTER_SUBJECT)
668 CString msg = pTree->m_csSubject;
669 msg = msg.MakeLower();
671 if (msg.Find(filter) >= 0)
672 return positive;
675 if (m_SelectedFilters & LOGFILTER_AUTHORS)
677 CString msg = pTree->m_csAuthor;
678 msg = msg.MakeLower();
680 if (msg.Find(filter) >= 0)
681 return positive;
684 if (m_SelectedFilters & LOGFILTER_REVS)
686 CString msg = pTree->m_csRefHash;
687 msg = msg.MakeLower();
689 if (msg.Find(filter) >= 0)
690 return positive;
692 return !positive;
695 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
697 ASSERT(!leafs.empty());
699 CString csMessage;
700 UINT mbIcon=MB_ICONQUESTION;
702 bool bIsRemoteBranch = false;
703 bool bIsBranch = false;
704 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
705 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
707 if(bIsBranch)
709 if(leafs.size() == 1)
711 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
712 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
714 //Check if branch is fully merged in HEAD
715 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
717 csMessage += L"\r\n\r\n";
718 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
719 mbIcon = MB_ICONWARNING;
722 if(bIsRemoteBranch)
724 csMessage += L"\r\n\r\n";
725 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
726 mbIcon = MB_ICONWARNING;
729 else
731 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
733 csMessage += L"\r\n\r\n";
734 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
735 mbIcon = MB_ICONWARNING;
737 if(bIsRemoteBranch)
739 csMessage += L"\r\n\r\n";
740 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
741 mbIcon = MB_ICONWARNING;
746 else if(leafs[0]->IsFrom(L"refs/tags/"))
748 if(leafs.size() == 1)
750 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
751 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
753 else
755 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
759 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
763 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
765 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
766 if(!DoDeleteRef((*i)->GetRefName()))
767 return false;
768 return true;
771 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
773 bool bIsRemoteBranch = false;
774 bool bIsBranch = false;
775 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
776 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
778 if (bIsRemoteBranch)
780 CString branchToDelete = completeRefName.Mid(13);
781 CString remoteName, remoteBranchToDelete;
782 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
783 return false;
785 if (CAppUtils::IsSSHPutty())
786 CAppUtils::LaunchPAgent(NULL, &remoteName);
788 CSysProgressDlg sysProgressDlg;
789 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
790 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
791 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
792 sysProgressDlg.SetShowProgressBar(false);
793 sysProgressDlg.ShowModal(this, true);
795 STRING_VECTOR list;
796 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
797 if (g_Git.DeleteRemoteRefs(remoteName, list))
799 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
800 sysProgressDlg.Stop();
801 BringWindowToTop();
802 return false;
804 sysProgressDlg.Stop();
805 BringWindowToTop();
807 else if (bIsBranch)
809 if (g_Git.DeleteRef(completeRefName))
811 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
812 return false;
815 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
817 if (g_Git.DeleteRef(completeRefName))
819 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
820 return false;
823 return true;
826 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
828 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
829 if(pLeaf == NULL)
830 return CString();
831 return pLeaf->GetRefName();
835 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
837 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
838 else if (pWndFrom == &m_ListRefLeafs)
840 CRect headerPosition;
841 m_ListRefLeafs.GetHeaderCtrl()->GetWindowRect(headerPosition);
842 if (!headerPosition.PtInRect(point))
843 OnContextMenu_ListRefLeafs(point);
847 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
849 CPoint clientPoint=point;
850 m_RefTreeCtrl.ScreenToClient(&clientPoint);
852 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
853 if(hTreeItem!=NULL)
854 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
856 VectorPShadowTree tree;
857 ShowContextMenu(point,hTreeItem,tree);
860 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
862 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
863 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
864 while (pos)
866 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
870 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
872 std::vector<CShadowTree*> selectedLeafs;
873 GetSelectedLeaves(selectedLeafs);
874 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
877 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
879 ASSERT(selectedLeafs.size() == 2);
881 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
882 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
883 else
884 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
887 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
889 int i = 0;
890 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
892 if (*it == entry)
893 return i;
895 return -1;
898 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
900 CIconMenu popupMenu;
901 popupMenu.CreatePopupMenu();
903 bool bAddSeparator = false;
904 CString remoteName;
906 if(selectedLeafs.size()==1)
908 bAddSeparator = true;
910 bool bShowReflogOption = false;
911 bool bShowFetchOption = false;
912 bool bShowRenameOption = false;
913 bool bShowCreateBranchOption = false;
914 bool bShowEditBranchDescriptionOption = false;
916 CString fetchFromCmd;
918 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
920 bShowReflogOption = true;
921 bShowRenameOption = true;
922 bShowEditBranchDescriptionOption = true;
924 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
926 bShowReflogOption = true;
927 bShowFetchOption = true;
928 bShowCreateBranchOption = true;
930 CString remoteBranch;
931 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
932 bShowFetchOption = false;
933 else
934 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
936 else if(selectedLeafs[0]->IsFrom(L"refs/tags/"))
940 CString temp;
941 temp.LoadString(IDS_MENULOG);
942 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
943 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
944 if(bShowReflogOption)
946 temp.LoadString(IDS_MENUREFLOG);
947 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
950 popupMenu.AppendMenu(MF_SEPARATOR);
951 bAddSeparator = false;
953 if(bShowFetchOption)
955 bAddSeparator = true;
956 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
959 if(bAddSeparator)
960 popupMenu.AppendMenu(MF_SEPARATOR);
962 bAddSeparator = false;
963 if (m_bHasWC)
965 CString format, str;
966 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
968 format.LoadString(IDS_LOG_POPUP_MERGEREV);
969 str.Format(format, g_Git.GetCurrentBranch());
970 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
972 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
973 popupMenu.AppendMenu(MF_SEPARATOR);
976 if(bShowCreateBranchOption)
978 bAddSeparator = true;
979 temp.LoadString(IDS_MENUBRANCH);
980 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
983 if (bShowEditBranchDescriptionOption)
985 bAddSeparator = true;
986 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
988 if(bShowRenameOption)
990 bAddSeparator = true;
991 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
994 else if(selectedLeafs.size() == 2)
996 bAddSeparator = true;
997 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
998 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
999 CString menu;
1000 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1001 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
1002 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1003 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
1006 if(!selectedLeafs.empty())
1008 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
1010 if(bAddSeparator)
1011 popupMenu.AppendMenu(MF_SEPARATOR);
1012 CString menuItemName;
1013 if(selectedLeafs.size() == 1)
1014 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1015 else
1016 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1018 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1019 bAddSeparator = true;
1021 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1023 if(bAddSeparator)
1024 popupMenu.AppendMenu(MF_SEPARATOR);
1025 CString menuItemName;
1026 if(selectedLeafs.size() == 1)
1027 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1028 else
1029 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1031 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1032 bAddSeparator = true;
1034 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1036 if(bAddSeparator)
1037 popupMenu.AppendMenu(MF_SEPARATOR);
1038 CString menuItemName;
1039 if(selectedLeafs.size() == 1)
1040 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1041 else
1042 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1044 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1045 bAddSeparator = true;
1050 if(hTreePos!=NULL && selectedLeafs.empty())
1052 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1053 if(pTree->IsFrom(L"refs/remotes"))
1055 if(bAddSeparator)
1056 popupMenu.AppendMenu(MF_SEPARATOR);
1057 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1058 bAddSeparator = true;
1059 if(selectedLeafs.empty())
1061 CString remoteBranch;
1062 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1063 remoteName = _T("");
1064 int pos = findVectorPosition(remotes, remoteName);
1065 if (pos >= 0)
1067 CString temp;
1068 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
1069 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1071 temp.LoadString(IDS_DELETEREMOTETAG);
1072 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1076 if(pTree->IsFrom(L"refs/heads"))
1078 if(bAddSeparator)
1079 popupMenu.AppendMenu(MF_SEPARATOR);
1080 CString temp;
1081 temp.LoadString(IDS_MENUBRANCH);
1082 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1084 if(pTree->IsFrom(L"refs/tags"))
1086 if(bAddSeparator)
1087 popupMenu.AppendMenu(MF_SEPARATOR);
1088 CString temp;
1089 temp.LoadString(IDS_MENUTAG);
1090 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1091 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1092 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1093 if (!remotes.empty())
1095 popupMenu.AppendMenu(MF_SEPARATOR);
1096 int i = 0;
1097 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1099 temp.Format(IDS_DELETEREMOTETAGON, *it);
1100 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1107 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1108 switch ((eCmd)(selection & 0xFFFF))
1110 case eCmd_ViewLog:
1112 CLogDlg dlg;
1113 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1114 dlg.DoModal();
1116 break;
1117 case eCmd_ViewLogRange:
1119 CLogDlg dlg;
1120 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1121 dlg.DoModal();
1123 break;
1124 case eCmd_ViewLogRangeReachableFromOnlyOne:
1126 CLogDlg dlg;
1127 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1128 dlg.DoModal();
1130 break;
1131 case eCmd_RepoBrowser:
1132 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1133 break;
1134 case eCmd_DeleteBranch:
1135 case eCmd_DeleteRemoteBranch:
1137 if(ConfirmDeleteRef(selectedLeafs))
1138 DoDeleteRefs(selectedLeafs);
1139 Refresh();
1141 break;
1142 case eCmd_DeleteTag:
1144 if(ConfirmDeleteRef(selectedLeafs))
1145 DoDeleteRefs(selectedLeafs);
1146 Refresh();
1148 break;
1149 case eCmd_ShowReflog:
1151 CRefLogDlg refLogDlg(this);
1152 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1153 refLogDlg.DoModal();
1155 break;
1156 case eCmd_Fetch:
1158 CAppUtils::Fetch(remoteName);
1159 Refresh();
1161 break;
1162 case eCmd_DeleteRemoteTag:
1164 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1165 int remoteInx = selection >> 16;
1166 if (remoteInx < 0 || remoteInx >= remotes.size())
1167 return;
1168 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1169 deleteRemoteTagDlg.DoModal();
1171 break;
1172 case eCmd_Merge:
1174 CString ref = selectedLeafs[0]->GetRefName();
1175 CAppUtils::Merge(&ref);
1177 break;
1178 case eCmd_Switch:
1180 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1182 break;
1183 case eCmd_Rename:
1185 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1186 if(pos != NULL)
1187 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1189 break;
1190 case eCmd_AddRemote:
1192 CAddRemoteDlg(this).DoModal();
1193 Refresh();
1195 break;
1196 case eCmd_ManageRemotes:
1198 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
1199 // CSettingGitRemote W_Remotes(m_cmdPath);
1200 // W_Remotes.DoModal();
1201 Refresh();
1203 break;
1204 case eCmd_CreateBranch:
1206 CString *commitHash = NULL;
1207 if (selectedLeafs.size() == 1)
1208 commitHash = &(selectedLeafs[0]->m_csRefHash);
1209 CAppUtils::CreateBranchTag(false, commitHash);
1210 Refresh();
1212 break;
1213 case eCmd_CreateTag:
1215 CAppUtils::CreateBranchTag(true);
1216 Refresh();
1218 break;
1219 case eCmd_DeleteAllTags:
1221 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1223 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1224 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1226 if (ConfirmDeleteRef(selectedLeafs))
1227 DoDeleteRefs(selectedLeafs);
1228 Refresh();
1230 break;
1231 case eCmd_Diff:
1233 CFileDiffDlg dlg;
1234 dlg.SetDiff(
1235 NULL,
1236 selectedLeafs[1]->GetRefName() + L"^{}",
1237 selectedLeafs[0]->GetRefName() + L"^{}");
1238 dlg.DoModal();
1240 break;
1241 case eCmd_UnifiedDiff:
1243 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1245 break;
1246 case eCmd_EditBranchDescription:
1248 CInputDlg dlg;
1249 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1250 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1251 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1252 dlg.m_bUseLogWidth = true;
1253 if(dlg.DoModal() == IDOK)
1255 CString key;
1256 key.Format(_T("branch.%s.description"), selectedLeafs[0]->m_csRefName);
1257 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1258 dlg.m_sInputText.Trim();
1259 if (dlg.m_sInputText.IsEmpty())
1260 g_Git.UnsetConfigValue(key);
1261 else
1262 g_Git.SetConfigValue(key, dlg.m_sInputText);
1263 Refresh();
1266 break;
1270 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1272 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1273 if(!(*i)->IsFrom(from))
1274 return false;
1275 return true;
1278 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1280 if (pMsg->message == WM_KEYDOWN)
1282 switch (pMsg->wParam)
1284 /* case VK_RETURN:
1286 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1288 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1290 PostMessage(WM_COMMAND, IDOK);
1292 return TRUE;
1295 break;
1296 */ case VK_F2:
1298 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1300 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1301 if(pos != NULL)
1302 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1305 break;
1307 case VK_F5:
1309 Refresh();
1311 break;
1316 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1319 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1321 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1322 *pResult = 0;
1324 if(m_currSortCol == pNMLV->iSubItem)
1325 m_currSortDesc = !m_currSortDesc;
1326 else
1328 m_currSortCol = pNMLV->iSubItem;
1329 m_currSortDesc = false;
1332 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1333 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1335 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1338 void CBrowseRefsDlg::OnDestroy()
1340 if (!m_bPickedRefSet)
1341 m_pickedRef = GetSelectedRef(true, false);
1343 int maxcol = m_ColumnManager.GetColumnCount();
1344 for (int col = 0; col < maxcol; ++col)
1345 if (m_ColumnManager.IsVisible(col))
1346 m_ColumnManager.ColumnResized(col);
1347 m_ColumnManager.WriteSettings();
1349 CResizableStandAloneDialog::OnDestroy();
1352 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1354 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1355 *pResult = 0;
1357 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1358 if (item && pNMListView->uNewState == LVIS_SELECTED)
1359 m_sLastSelected = item->GetRefName();
1361 UpdateInfoLabel();
1364 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1366 *pResult = 0;
1368 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1369 return;
1370 EndDialog(IDOK);
1373 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1375 CBrowseRefsDlg dlg(CString(),NULL);
1377 if(initialRef.IsEmpty())
1378 initialRef = L"HEAD";
1379 dlg.m_initialRef = initialRef;
1380 dlg.m_pickRef_Kind = pickRef_Kind;
1381 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1383 if(dlg.DoModal() != IDOK)
1384 return CString();
1386 return dlg.m_pickedRef;
1389 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1391 CString origRef;
1392 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1393 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1394 if(resultRef.IsEmpty())
1395 return false;
1396 if(wcsncmp(resultRef,L"refs/",5)==0)
1397 resultRef = resultRef.Mid(5);
1398 // if(wcsncmp(resultRef,L"heads/",6)==0)
1399 // resultRef = resultRef.Mid(6);
1401 //Find closest match of choice in combobox
1402 int ixFound = -1;
1403 int matchLength = 0;
1404 CString comboRefName;
1405 for(int i = 0; i < pComboBox->GetCount(); ++i)
1407 pComboBox->GetLBText(i, comboRefName);
1408 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1409 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1410 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1412 matchLength = comboRefName.GetLength();
1413 ixFound = i;
1416 if(ixFound >= 0)
1417 pComboBox->SetCurSel(ixFound);
1418 else
1419 ASSERT(FALSE);//No match found. So either pickRef_Kind is wrong or the combobox does not contain the ref specified in the picker (which it should unless the repo has changed before creating the CBrowseRef dialog)
1421 return true;
1424 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1426 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1427 *pResult = FALSE;
1429 if(pDispInfo->item.pszText == NULL)
1430 return; //User canceled changing
1432 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1434 if(!pTree->IsFrom(L"refs/heads/"))
1436 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1437 return;
1440 CString origName = pTree->GetRefName().Mid(11);
1442 CString newName;
1443 if(m_pListCtrlRoot != NULL)
1444 newName = m_pListCtrlRoot->GetRefName() + L'/';
1445 newName += pDispInfo->item.pszText;
1447 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1449 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1450 return;
1453 CString newNameTrunced = newName.Mid(11);
1455 CString errorMsg;
1456 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1458 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1459 return;
1461 //Do as if it failed to rename. Let Refresh() do the job.
1462 //*pResult = TRUE;
1464 Refresh(newName);
1466 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1468 // AfxMessageBox(W_csPopup);
1472 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1474 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1475 *pResult = FALSE;
1477 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1479 if(!pTree->IsFrom(L"refs/heads/"))
1481 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1482 return;
1486 void CBrowseRefsDlg::OnEnChangeEditFilter()
1488 SetTimer(IDT_FILTER, 1000, NULL);
1491 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1493 if (nIDEvent == IDT_FILTER)
1495 KillTimer(IDT_FILTER);
1496 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1499 CResizableStandAloneDialog::OnTimer(nIDEvent);
1502 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1504 // FIXME: x64 version would get this function called with unexpected parameters.
1505 if (!lParam)
1506 return 0;
1508 RECT * rect = (LPRECT)lParam;
1509 CPoint point;
1510 CString temp;
1511 point = CPoint(rect->left, rect->bottom);
1512 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1513 CMenu popup;
1514 if (popup.CreatePopupMenu())
1516 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1517 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1519 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1520 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1522 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1523 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1525 temp.LoadString(IDS_LOG_FILTER_REVS);
1526 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1528 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1529 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1531 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1532 if (selection != 0)
1534 if (selection == LOGFILTER_TOGGLE)
1535 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1536 else
1537 m_SelectedFilters ^= selection;
1538 SetFilterCueText();
1539 SetTimer(IDT_FILTER, 1000, NULL);
1542 return 0L;
1545 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1547 KillTimer(LOGFILTER_TIMER);
1548 m_ctrlFilter.SetWindowText(_T(""));
1549 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1550 return 0L;
1553 void CBrowseRefsDlg::SetFilterCueText()
1555 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1556 temp += _T(" ");
1558 if (m_SelectedFilters & LOGFILTER_REFNAME)
1559 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1561 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1563 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1564 temp += _T(", ");
1565 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1568 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1570 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1571 temp += _T(", ");
1572 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1575 if (m_SelectedFilters & LOGFILTER_REVS)
1577 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1578 temp += _T(", ");
1579 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1582 // to make the cue banner text appear more to the right of the edit control
1583 temp = _T(" ") + temp;
1584 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1587 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1589 m_pickedRef = g_Git.GetCurrentBranch(true);
1590 m_bPickedRefSet = true;
1591 OnOK();
1594 void CBrowseRefsDlg::UpdateInfoLabel()
1596 CString temp;
1597 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1598 SetDlgItemText(IDC_INFOLABEL, temp);