Browse References Dialog: Allow to filter for merged and unmerged branches
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobba8a2814a58d59ecbdc4b50b1f3e3d6301cd6a9b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2016 - 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"
38 #include "LoglistUtils.h"
39 #include "GitRevRefBrowser.h"
40 #include "StringUtils.h"
42 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
44 if (ref.Left(13) == _T("refs/remotes/"))
45 ref = ref.Mid(13);
46 else if (ref.Left(8) == _T("remotes/"))
47 ref = ref.Mid(8);
49 STRING_VECTOR list;
50 int result = g_Git.GetRemoteList(list);
51 if (result != 0)
52 return result;
54 for (size_t i = 0; i < list.size(); ++i)
56 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
58 remote = list[i];
59 branch = ref.Mid(list[i].GetLength() + 1);
60 return 0;
62 if (ref == list[i])
64 remote = list[i];
65 branch.Empty();
66 return 0;
70 return -1;
73 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
75 if (!control)
76 return;
77 // set the sort arrow
78 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
79 HDITEM HeaderItem = {0};
80 HeaderItem.mask = HDI_FORMAT;
81 for (int i=0; i<pHeader->GetItemCount(); ++i)
83 pHeader->GetItem(i, &HeaderItem);
84 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
85 pHeader->SetItem(i, &HeaderItem);
87 if (nColumn >= 0)
89 pHeader->GetItem(nColumn, &HeaderItem);
90 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
91 pHeader->SetItem(nColumn, &HeaderItem);
95 class CRefLeafListCompareFunc
97 public:
98 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
99 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
100 if (m_bSortLogical)
101 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
104 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
106 return reinterpret_cast<CRefLeafListCompareFunc*>(lParamSort)->Compare(lParam1, lParam2);
109 int Compare(LPARAM lParam1, LPARAM lParam2)
111 return Compare(
112 reinterpret_cast<CShadowTree*>(m_pList->GetItemData((int)lParam1)),
113 reinterpret_cast<CShadowTree*>(m_pList->GetItemData((int)lParam2)));
116 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
118 int result=CompareNoDesc(pLeft,pRight);
119 if(m_desc)
120 return -result;
121 return result;
124 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
126 switch(m_col)
128 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
129 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
130 case CBrowseRefsDlg::eCol_Date: return ((pLeft->m_csDate == pRight->m_csDate) ? 0 : ((pLeft->m_csDate > pRight->m_csDate) ? 1 : -1));
131 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
132 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
133 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
134 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
136 return 0;
138 int SortStrCmp(const CString& left, const CString& right)
140 if (m_bSortLogical)
141 return StrCmpLogicalW(left, right);
142 return StrCmpI(left, right);
145 int m_col;
146 bool m_desc;
147 CListCtrl* m_pList;
148 bool m_bSortLogical;
151 // CBrowseRefsDlg dialog
153 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
155 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=nullptr*/)
156 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
157 m_cmdPath(cmdPath),
158 m_currSortCol(0),
159 m_currSortDesc(false),
160 m_regCurrSortCol(L"Software\\TortoiseGit\\RefBrowserSortCol", 0),
161 m_regCurrSortDesc(L"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE),
162 m_initialRef(L"HEAD"),
163 m_pickRef_Kind(gPickRef_All),
164 m_pListCtrlRoot(nullptr),
165 m_bHasWC(true),
166 m_SelectedFilters(LOGFILTER_ALL),
167 m_bPickOne(false),
168 m_bIncludeNestedRefs(TRUE),
169 m_bPickedRefSet(false)
171 // get short/long datetime setting from registry
172 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
173 if (RegUseShortDateFormat)
174 m_DateFormat = DATE_SHORTDATE;
175 else
176 m_DateFormat = DATE_LONGDATE;
177 // get relative time display setting from registry
178 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
179 m_bRelativeTimes = (regRelativeTimes != 0);
181 m_regIncludeNestedRefs = CRegDWORD(_T("Software\\TortoiseGit\\RefBrowserIncludeNestedRefs"), TRUE);
183 m_currSortCol = m_regCurrSortCol;
184 m_currSortDesc = m_regCurrSortDesc == TRUE;
187 CBrowseRefsDlg::~CBrowseRefsDlg()
189 m_regCurrSortCol = m_currSortCol;
190 m_regCurrSortDesc = m_currSortDesc;
193 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
195 CDialog::DoDataExchange(pDX);
196 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
197 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
198 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
199 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
200 DDX_Control(pDX, IDC_BROWSE_REFS_BRANCHFILTER, m_cBranchFilter);
204 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
205 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
206 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
207 ON_WM_CONTEXTMENU()
208 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
209 ON_WM_DESTROY()
210 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
211 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
212 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
213 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
214 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
215 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
216 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
217 ON_WM_TIMER()
218 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
219 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
220 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter)
221 END_MESSAGE_MAP()
224 // CBrowseRefsDlg message handlers
226 void CBrowseRefsDlg::OnBnClickedOk()
228 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
230 OnOK();
231 return;
234 CIconMenu popupMenu;
235 popupMenu.CreatePopupMenu();
237 std::vector<CShadowTree*> selectedLeafs;
238 GetSelectedLeaves(selectedLeafs);
240 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
241 popupMenu.SetDefaultItem(1);
242 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
243 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
245 RECT rect;
246 GetDlgItem(IDOK)->GetWindowRect(&rect);
247 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, nullptr);
248 switch (selection)
250 case 1:
251 OnOK();
252 break;
253 case 2:
255 m_bPickedRefSet = true;
256 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
257 OnOK();
259 break;
260 case 3:
262 m_bPickedRefSet = true;
263 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
264 OnOK();
266 break;
267 default:
268 break;
272 BOOL CBrowseRefsDlg::OnInitDialog()
274 CResizableStandAloneDialog::OnInitDialog();
275 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
277 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
278 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
279 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
280 SetFilterCueText();
282 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
283 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
284 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
285 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_CENTER);
286 AddAnchor(IDC_BROWSE_REFS_BRANCHFILTER, TOP_CENTER, TOP_RIGHT);
287 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
288 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
289 AddAnchor(IDHELP, BOTTOM_RIGHT);
291 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP);
292 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
293 static int columnWidths[] = { 0, 0, 0, 300, 0, 0, 80 };
294 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
295 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
296 m_ListRefLeafs.m_bAllowHiding = false;
297 m_ListRefLeafs.Init();
298 m_ListRefLeafs.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
299 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
300 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
301 m_bPickedRefSet = false;
303 AddAnchor(IDOK,BOTTOM_RIGHT);
304 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
305 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
307 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
308 UpdateData();
310 Refresh(m_initialRef);
312 EnableSaveRestore(L"BrowseRefs");
314 CString sWindowTitle;
315 GetWindowText(sWindowTitle);
316 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
318 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
320 if (m_bPickOne)
321 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
323 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_ALL)));
324 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED)));
325 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED)));
326 m_cBranchFilter.SetCurSel(0);
328 m_ListRefLeafs.SetFocus();
329 return FALSE;
332 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
334 int posSlash=nameLeft.Find('/');
335 CString nameSub;
336 if(posSlash<0)
338 nameSub=nameLeft;
339 nameLeft.Empty();//Nothing left
341 else
343 nameSub=nameLeft.Left(posSlash);
344 nameLeft=nameLeft.Mid(posSlash+1);
346 if(nameSub.IsEmpty())
347 return nullptr;
349 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
350 return nullptr;
352 CShadowTree& nextNode=m_ShadowTree[nameSub];
353 nextNode.m_csRefName=nameSub;
354 nextNode.m_pParent=this;
355 return &nextNode;
358 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
360 if(IsLeaf())
362 if(m_csRefName.GetLength() > partialRefName.GetLength())
363 return nullptr;
364 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
366 //Match of leaf name. Try match on total name.
367 CString totalRefName = GetRefName();
368 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
369 return this; //Also match. Found.
372 else
374 //Not a leaf. Search all nodes.
375 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
377 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
378 if (pSubtree)
379 return pSubtree; //Found
382 return nullptr; //Not found
385 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
387 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
388 //List ctrl selection?
389 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
391 //A leaf is selected
392 return GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos))->GetRefName();
394 else if (pos && !pickFirstSelIfMultiSel)
396 // at least one leaf is selected
397 CString refs;
398 int index;
399 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
401 CString ref = GetListEntry(index)->GetRefName();
402 if (CStringUtils::StartsWith(ref, L"refs/"))
403 ref = ref.Mid(5);
404 if (CStringUtils::StartsWith(ref, L"heads/"))
405 ref = ref.Mid(6);
406 refs += ref + _T(" ");
408 return refs.Trim();
410 else if(!onlyIfLeaf)
412 //Tree ctrl selection?
413 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
414 if (hTree)
415 return GetTreeEntry(hTree)->GetRefName();
417 return CString();//None
420 void CBrowseRefsDlg::Refresh(CString selectRef)
422 remotes.clear();
423 if (g_Git.GetRemoteList(remotes))
424 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
426 if(!selectRef.IsEmpty())
428 if (selectRef == L"HEAD")
430 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
431 selectRef.Empty();
432 else
433 selectRef = L"refs/heads/" + selectRef;
436 else
437 selectRef = GetSelectedRef(false, true);
439 m_RefTreeCtrl.DeleteAllItems();
440 m_ListRefLeafs.DeleteAllItems();
441 m_TreeRoot.m_ShadowTree.clear();
442 m_TreeRoot.m_csRefName = L"refs";
443 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs", nullptr, nullptr);
444 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
446 CString err;
447 MAP_REF_GITREVREFBROWSER refMap;
448 if (GitRevRefBrowser::GetGitRevRefMap(refMap, m_cBranchFilter.GetCurSel(), err, [&](const CString& refName)
450 //Use ref based on m_pickRef_Kind
451 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
452 return false; //Skip
453 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
454 return false; //Skip
455 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
456 return false; //Skip
457 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
458 return false;
459 return true;
462 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
465 //Populate ref tree
466 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
468 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
469 GitRevRefBrowser ref = iterRefMap->second;
471 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
472 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
473 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
474 treeLeaf.m_csSubject = ref.GetSubject();
475 treeLeaf.m_csAuthor = ref.GetAuthorName();
476 treeLeaf.m_csDate = ref.GetAuthorDate();
477 treeLeaf.m_csDescription = ref.m_Description;
480 // always expand the tree first
481 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
483 // try exact match first
484 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
485 SelectRef(selectRef, false);
488 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
490 if(!bExactMatch)
492 CString newRefName = GetFullRefName(refName);
493 if(!newRefName.IsEmpty())
494 refName = newRefName;
495 //else refName is not a valid ref. Try to select as good as possible.
497 if (!CStringUtils::StartsWith(refName, L"refs"))
498 return false; // Not a ref name
500 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
501 if (treeLeafHead.m_hTree)
503 //Not a leaf. Select tree node and return
504 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
505 return true;
508 if (!treeLeafHead.m_pParent)
509 return false; //Weird... should not occur.
511 //This is the current head.
512 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
514 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
516 auto pCurrShadowTree = GetListEntry(indexPos);
517 if(pCurrShadowTree == &treeLeafHead)
519 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
520 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
524 return true;
527 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
529 if (!pTreePos)
531 if (CStringUtils::StartsWith(refName, L"refs/"))
532 refName=refName.Mid(5);
533 pTreePos=&m_TreeRoot;
535 if(refName.IsEmpty())
536 return *pTreePos;//Found leaf
538 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
539 if (!pNextTree)
541 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
542 ASSERT(!bCreateIfNotExist);
543 return *pTreePos;
546 if(!refName.IsEmpty())
548 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
549 //Leafs are for the list control.
550 if (!pNextTree->m_hTree)
552 //New tree. Create node in control.
553 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree, nullptr);
554 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
558 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
562 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
564 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
565 *pResult = 0;
567 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
570 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
572 m_ListRefLeafs.DeleteAllItems();
574 auto pTree = GetTreeEntry(treeNode);
575 if (!pTree)
576 return;
578 FillListCtrlForShadowTree(pTree,L"",true);
579 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
580 m_ListRefLeafs.AdjustColumnWidths();
583 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
585 if(pTree->IsLeaf())
587 CString filter;
588 m_ctrlFilter.GetWindowText(filter);
589 filter.MakeLower();
590 bool positive = filter[0] != '!';
591 if (!positive)
592 filter = filter.Mid(1);
593 CString ref = refNamePrefix + pTree->m_csRefName;
594 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == L"refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
596 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
598 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
599 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
600 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
601 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
602 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
603 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
604 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
605 CString descrition = pTree->m_csDescription;
606 descrition.Replace(L"\n", L" ");
607 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
610 else
612 CString csThisName;
613 if (!isFirstLevel && !m_bIncludeNestedRefs)
614 return;
615 else if (!isFirstLevel)
616 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
617 else
618 m_pListCtrlRoot = pTree;
619 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
621 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
624 if (isFirstLevel)
626 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
627 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
629 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
633 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
635 if (m_SelectedFilters & LOGFILTER_REFNAME)
637 CString msg = ref;
638 msg = msg.MakeLower();
640 if (msg.Find(filter) >= 0)
641 return positive;
644 if (m_SelectedFilters & LOGFILTER_SUBJECT)
646 CString msg = pTree->m_csSubject;
647 msg = msg.MakeLower();
649 if (msg.Find(filter) >= 0)
650 return positive;
653 if (m_SelectedFilters & LOGFILTER_AUTHORS)
655 CString msg = pTree->m_csAuthor;
656 msg = msg.MakeLower();
658 if (msg.Find(filter) >= 0)
659 return positive;
662 if (m_SelectedFilters & LOGFILTER_REVS)
664 CString msg = pTree->m_csRefHash;
665 msg = msg.MakeLower();
667 if (msg.Find(filter) >= 0)
668 return positive;
670 return !positive;
673 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
675 ASSERT(!leafs.empty());
677 CString csMessage;
678 UINT mbIcon=MB_ICONQUESTION;
680 bool bIsRemoteBranch = false;
681 bool bIsBranch = false;
682 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
683 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
685 if(bIsBranch)
687 if(leafs.size() == 1)
689 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
690 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
692 //Check if branch is fully merged in HEAD
693 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
695 csMessage += L"\r\n\r\n";
696 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
697 mbIcon = MB_ICONWARNING;
700 if(bIsRemoteBranch)
702 csMessage += L"\r\n\r\n";
703 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
704 mbIcon = MB_ICONWARNING;
707 else
709 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
711 csMessage += L"\r\n\r\n";
712 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
713 mbIcon = MB_ICONWARNING;
715 if(bIsRemoteBranch)
717 csMessage += L"\r\n\r\n";
718 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
719 mbIcon = MB_ICONWARNING;
724 else if(leafs[0]->IsFrom(L"refs/tags/"))
726 if(leafs.size() == 1)
728 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
729 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
731 else
732 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
735 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
738 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
740 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
741 if(!DoDeleteRef((*i)->GetRefName()))
742 return false;
743 return true;
746 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
748 bool bIsRemoteBranch = false;
749 bool bIsBranch = false;
750 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
752 bIsBranch = true;
753 bIsRemoteBranch = true;
755 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
756 bIsBranch = true;
758 if (bIsRemoteBranch)
760 CString branchToDelete = completeRefName.Mid(13);
761 CString remoteName, remoteBranchToDelete;
762 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
763 return false;
765 if (CAppUtils::IsSSHPutty())
766 CAppUtils::LaunchPAgent(nullptr, &remoteName);
768 CSysProgressDlg sysProgressDlg;
769 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
770 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
771 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
772 sysProgressDlg.SetShowProgressBar(false);
773 sysProgressDlg.ShowModal(this, true);
775 STRING_VECTOR list;
776 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
777 if (g_Git.DeleteRemoteRefs(remoteName, list))
779 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
780 sysProgressDlg.Stop();
781 BringWindowToTop();
782 return false;
784 sysProgressDlg.Stop();
785 BringWindowToTop();
787 else if (bIsBranch)
789 if (g_Git.DeleteRef(completeRefName))
791 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
792 return false;
795 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
797 if (g_Git.DeleteRef(completeRefName))
799 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
800 return false;
803 return true;
806 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
808 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
809 if (!pLeaf)
810 return CString();
811 return pLeaf->GetRefName();
815 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
817 if (pWndFrom == &m_RefTreeCtrl)
818 OnContextMenu_RefTreeCtrl(point);
821 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
823 CPoint clientPoint=point;
824 m_RefTreeCtrl.ScreenToClient(&clientPoint);
826 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
827 if (hTreeItem)
828 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
830 VectorPShadowTree tree;
831 ShowContextMenu(point,hTreeItem,tree);
834 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
836 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
837 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
838 while (pos)
840 selectedLeafs.push_back(GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos)));
844 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
846 std::vector<CShadowTree*> selectedLeafs;
847 GetSelectedLeaves(selectedLeafs);
848 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
851 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
853 ASSERT(selectedLeafs.size() == 2);
855 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
856 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
857 else
858 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
861 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
863 int i = 0;
864 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
866 if (*it == entry)
867 return i;
869 return -1;
872 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
874 CIconMenu popupMenu;
875 popupMenu.CreatePopupMenu();
877 bool bAddSeparator = false;
878 CString remoteName;
880 if(selectedLeafs.size()==1)
882 bAddSeparator = true;
884 bool bShowReflogOption = false;
885 bool bShowFetchOption = false;
886 bool bShowRenameOption = false;
887 bool bShowCreateBranchOption = false;
888 bool bShowEditBranchDescriptionOption = false;
890 CString fetchFromCmd;
892 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
894 bShowReflogOption = true;
895 bShowRenameOption = true;
896 bShowEditBranchDescriptionOption = true;
898 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
900 bShowReflogOption = true;
901 bShowFetchOption = true;
902 bShowCreateBranchOption = true;
904 CString remoteBranch;
905 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
906 bShowFetchOption = false;
907 else
908 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
911 popupMenu.AppendMenuIcon(eCmd_ViewLog, IDS_MENULOG, IDI_LOG);
912 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
913 if(bShowReflogOption)
914 popupMenu.AppendMenuIcon(eCmd_ShowReflog, IDS_MENUREFLOG, IDI_LOG);
916 if (m_bHasWC)
918 popupMenu.AppendMenu(MF_SEPARATOR);
919 popupMenu.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
922 popupMenu.AppendMenu(MF_SEPARATOR);
923 bAddSeparator = false;
925 if(bShowFetchOption)
927 bAddSeparator = true;
928 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
931 if(bAddSeparator)
932 popupMenu.AppendMenu(MF_SEPARATOR);
934 bAddSeparator = false;
935 if (m_bHasWC)
937 CString str;
938 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
940 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
941 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
943 popupMenu.AppendMenuIcon(eCmd_Switch, IDS_SWITCH_TO_THIS, IDI_SWITCH);
944 popupMenu.AppendMenu(MF_SEPARATOR);
947 if(bShowCreateBranchOption)
949 bAddSeparator = true;
950 popupMenu.AppendMenuIcon(eCmd_CreateBranch, IDS_MENUBRANCH, IDI_COPY);
953 if (bShowEditBranchDescriptionOption)
955 bAddSeparator = true;
956 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, IDS_PROC_BROWSEREFS_EDITDESCRIPTION, IDI_RENAME);
958 if(bShowRenameOption)
960 bAddSeparator = true;
961 popupMenu.AppendMenuIcon(eCmd_Rename, IDS_PROC_BROWSEREFS_RENAME, IDI_RENAME);
964 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
966 if (bAddSeparator)
967 popupMenu.AppendMenu(MF_SEPARATOR);
968 bAddSeparator = true;
969 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
970 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH);
971 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH);
974 else if(selectedLeafs.size() == 2)
976 bAddSeparator = true;
977 popupMenu.AppendMenuIcon(eCmd_Diff, IDS_PROC_BROWSEREFS_COMPAREREFS, IDI_DIFF);
978 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
979 CString menu;
980 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
981 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
982 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
983 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
986 if(!selectedLeafs.empty())
988 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
990 if(bAddSeparator)
991 popupMenu.AppendMenu(MF_SEPARATOR);
992 CString menuItemName;
993 if(selectedLeafs.size() == 1)
994 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
995 else
996 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
998 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
999 bAddSeparator = true;
1001 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1003 if(bAddSeparator)
1004 popupMenu.AppendMenu(MF_SEPARATOR);
1005 CString menuItemName;
1006 if(selectedLeafs.size() == 1)
1007 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1008 else
1009 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1011 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1012 bAddSeparator = true;
1014 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1016 if(bAddSeparator)
1017 popupMenu.AppendMenu(MF_SEPARATOR);
1018 CString menuItemName;
1019 if(selectedLeafs.size() == 1)
1020 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1021 else
1022 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1024 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1025 bAddSeparator = true;
1030 if (hTreePos && selectedLeafs.empty())
1032 auto pTree = GetTreeEntry(hTreePos);
1033 if(pTree->IsFrom(L"refs/remotes"))
1035 if(bAddSeparator)
1036 popupMenu.AppendMenu(MF_SEPARATOR);
1037 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, IDS_PROC_BROWSEREFS_MANAGEREMOTES, IDI_SETTINGS);
1038 bAddSeparator = true;
1039 if(selectedLeafs.empty())
1041 CString remoteBranch;
1042 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1043 remoteName.Empty();
1044 int pos = findVectorPosition(remotes, remoteName);
1045 if (pos >= 0)
1047 CString temp;
1048 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1049 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1051 temp.LoadString(IDS_DELETEREMOTETAG);
1052 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1056 if(pTree->IsFrom(L"refs/heads"))
1058 if(bAddSeparator)
1059 popupMenu.AppendMenu(MF_SEPARATOR);
1060 CString temp;
1061 temp.LoadString(IDS_MENUBRANCH);
1062 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1064 if(pTree->IsFrom(L"refs/tags"))
1066 if(bAddSeparator)
1067 popupMenu.AppendMenu(MF_SEPARATOR);
1068 popupMenu.AppendMenuIcon(eCmd_CreateTag, IDS_MENUTAG, IDI_TAG);
1069 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, IDS_PROC_BROWSEREFS_DELETEALLTAGS, IDI_DELETE);
1070 if (!remotes.empty())
1072 popupMenu.AppendMenu(MF_SEPARATOR);
1073 int i = 0;
1074 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1076 CString temp;
1077 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1078 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1085 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
1086 switch ((eCmd)(selection & 0xFFFF))
1088 case eCmd_ViewLog:
1090 CString sCmd;
1091 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1092 CAppUtils::RunTortoiseGitProc(sCmd);
1094 break;
1095 case eCmd_ViewLogRange:
1097 CString sCmd;
1098 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1099 CAppUtils::RunTortoiseGitProc(sCmd);
1101 break;
1102 case eCmd_ViewLogRangeReachableFromOnlyOne:
1104 CString sCmd;
1105 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1106 CAppUtils::RunTortoiseGitProc(sCmd);
1108 break;
1109 case eCmd_RepoBrowser:
1110 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1111 break;
1112 case eCmd_DeleteBranch:
1113 case eCmd_DeleteRemoteBranch:
1115 if(ConfirmDeleteRef(selectedLeafs))
1116 DoDeleteRefs(selectedLeafs);
1117 Refresh();
1119 break;
1120 case eCmd_DeleteTag:
1122 if(ConfirmDeleteRef(selectedLeafs))
1123 DoDeleteRefs(selectedLeafs);
1124 Refresh();
1126 break;
1127 case eCmd_ShowReflog:
1129 CRefLogDlg refLogDlg(this);
1130 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1131 refLogDlg.DoModal();
1133 break;
1134 case eCmd_Fetch:
1136 CAppUtils::Fetch(remoteName);
1137 Refresh();
1139 break;
1140 case eCmd_DeleteRemoteTag:
1142 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1143 int remoteInx = selection >> 16;
1144 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1145 return;
1146 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1147 deleteRemoteTagDlg.DoModal();
1149 break;
1150 case eCmd_Merge:
1152 CString ref = selectedLeafs[0]->GetRefName();
1153 CAppUtils::Merge(&ref);
1155 break;
1156 case eCmd_Switch:
1158 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1160 break;
1161 case eCmd_Rename:
1163 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1164 if (pos)
1165 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1167 break;
1168 case eCmd_AddRemote:
1170 CAddRemoteDlg(this).DoModal();
1171 Refresh();
1173 break;
1174 case eCmd_ManageRemotes:
1176 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1177 // CSettingGitRemote W_Remotes(m_cmdPath);
1178 // W_Remotes.DoModal();
1179 Refresh();
1181 break;
1182 case eCmd_CreateBranch:
1184 CString* commitHash = nullptr;
1185 if (selectedLeafs.size() == 1)
1186 commitHash = &(selectedLeafs[0]->m_csRefHash);
1187 CAppUtils::CreateBranchTag(false, commitHash);
1188 Refresh();
1190 break;
1191 case eCmd_CreateTag:
1193 CAppUtils::CreateBranchTag(true);
1194 Refresh();
1196 break;
1197 case eCmd_DeleteAllTags:
1199 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1201 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1202 selectedLeafs.push_back(GetListEntry(i));
1204 if (ConfirmDeleteRef(selectedLeafs))
1205 DoDeleteRefs(selectedLeafs);
1206 Refresh();
1208 break;
1209 case eCmd_Diff:
1211 CFileDiffDlg dlg;
1212 dlg.SetDiff(
1213 nullptr,
1214 selectedLeafs[0]->GetRefName() + L"^{}",
1215 selectedLeafs[1]->GetRefName() + L"^{}");
1216 dlg.DoModal();
1218 break;
1219 case eCmd_UnifiedDiff:
1221 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1223 break;
1224 case eCmd_DiffWC:
1226 CString sCmd;
1227 sCmd.Format(_T("/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s"), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)selectedLeafs[0]->GetRefName(), (LPCTSTR)GitRev::GetWorkingCopy());
1228 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
1229 sCmd += L" /alternative";
1231 CAppUtils::RunTortoiseGitProc(sCmd);
1233 break;
1234 case eCmd_EditBranchDescription:
1236 CInputDlg dlg;
1237 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1238 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1239 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1240 dlg.m_bUseLogWidth = true;
1241 if(dlg.DoModal() == IDOK)
1243 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1244 Refresh();
1247 break;
1248 case eCmd_UpstreamDrop:
1250 CString key;
1251 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1252 g_Git.UnsetConfigValue(key);
1253 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1254 g_Git.UnsetConfigValue(key);
1256 Refresh();
1257 break;
1258 case eCmd_UpstreamSet:
1260 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1261 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1262 return;
1263 CString remote, branch;
1264 if (SplitRemoteBranchName(newRef, remote, branch))
1265 return;
1266 CString key;
1267 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1268 g_Git.SetConfigValue(key, remote);
1269 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1270 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1271 Refresh();
1273 break;
1277 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1279 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1280 if(!(*i)->IsFrom(from))
1281 return false;
1282 return true;
1285 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1287 if (pMsg->message == WM_KEYDOWN)
1289 switch (pMsg->wParam)
1291 /* case VK_RETURN:
1293 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1295 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1297 PostMessage(WM_COMMAND, IDOK);
1299 return TRUE;
1302 break;
1303 */ case VK_F2:
1305 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1307 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1308 if (pos)
1309 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1312 break;
1314 case VK_F5:
1316 Refresh();
1318 break;
1319 case L'E':
1321 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1323 m_ctrlFilter.SetSel(0, -1, FALSE);
1324 m_ctrlFilter.SetFocus();
1325 return TRUE;
1328 break;
1329 case VK_ESCAPE:
1330 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER) && m_ctrlFilter.GetWindowTextLength())
1332 OnClickedCancelFilter(NULL, NULL);
1333 return TRUE;
1335 break;
1340 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1343 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1345 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1346 *pResult = 0;
1348 if(m_currSortCol == pNMLV->iSubItem)
1349 m_currSortDesc = !m_currSortDesc;
1350 else
1352 m_currSortCol = pNMLV->iSubItem;
1353 m_currSortDesc = false;
1356 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1357 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1359 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1362 void CBrowseRefsDlg::OnDestroy()
1364 if (!m_bPickedRefSet)
1365 m_pickedRef = GetSelectedRef(true, false);
1367 CResizableStandAloneDialog::OnDestroy();
1370 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1372 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1373 *pResult = 0;
1375 auto item = GetListEntry(pNMListView->iItem);
1376 if (item && pNMListView->uNewState == LVIS_SELECTED)
1377 m_sLastSelected = item->GetRefName();
1379 UpdateInfoLabel();
1382 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1384 *pResult = 0;
1386 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1387 return;
1388 EndDialog(IDOK);
1391 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1393 CBrowseRefsDlg dlg(CString(), nullptr);
1395 if(initialRef.IsEmpty())
1396 initialRef = L"HEAD";
1397 dlg.m_initialRef = initialRef;
1398 dlg.m_pickRef_Kind = pickRef_Kind;
1399 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1401 if(dlg.DoModal() != IDOK)
1402 return CString();
1404 return dlg.m_pickedRef;
1407 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1409 CString origRef;
1410 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1411 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1412 if(resultRef.IsEmpty())
1413 return false;
1414 if (CStringUtils::StartsWith(resultRef, L"refs/"))
1415 resultRef = resultRef.Mid(5);
1416 // if(wcsncmp(resultRef,L"heads/",6)==0)
1417 // resultRef = resultRef.Mid(6);
1419 //Find closest match of choice in combobox
1420 int ixFound = -1;
1421 int matchLength = 0;
1422 CString comboRefName;
1423 for(int i = 0; i < pComboBox->GetCount(); ++i)
1425 pComboBox->GetLBText(i, comboRefName);
1426 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1427 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1428 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1430 matchLength = comboRefName.GetLength();
1431 ixFound = i;
1434 if(ixFound >= 0)
1435 pComboBox->SetCurSel(ixFound);
1436 else
1437 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)
1439 return true;
1442 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1444 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1445 *pResult = FALSE;
1447 if (!pDispInfo->item.pszText)
1448 return; //User canceled changing
1450 auto pTree = GetListEntry(pDispInfo->item.iItem);
1451 if(!pTree->IsFrom(L"refs/heads/"))
1453 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1454 return;
1457 CString selectedTreeRef;
1458 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1459 if (!hTree)
1461 auto pTree2 = GetTreeEntry(hTree);
1462 selectedTreeRef = pTree2->GetRefName();
1465 CString origName = pTree->GetRefName().Mid(11);
1467 CString newName;
1468 if (m_pListCtrlRoot)
1469 newName = m_pListCtrlRoot->GetRefName() + L'/';
1470 newName += pDispInfo->item.pszText;
1472 if (!CStringUtils::StartsWith(newName, L"refs/heads/"))
1474 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1475 return;
1478 CString newNameTrunced = newName.Mid(11);
1480 CString errorMsg;
1481 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1483 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1484 return;
1486 //Do as if it failed to rename. Let Refresh() do the job.
1487 //*pResult = TRUE;
1489 Refresh(selectedTreeRef);
1491 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1493 // AfxMessageBox(W_csPopup);
1496 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1498 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1499 *pResult = FALSE;
1501 auto pTree = GetListEntry(pDispInfo->item.iItem);
1502 if(!pTree->IsFrom(L"refs/heads/"))
1504 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1505 return;
1509 void CBrowseRefsDlg::OnEnChangeEditFilter()
1511 SetTimer(IDT_FILTER, 1000, nullptr);
1514 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1516 if (nIDEvent == IDT_FILTER)
1518 KillTimer(IDT_FILTER);
1519 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1522 CResizableStandAloneDialog::OnTimer(nIDEvent);
1525 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1527 // FIXME: x64 version would get this function called with unexpected parameters.
1528 if (!lParam)
1529 return 0;
1531 RECT * rect = (LPRECT)lParam;
1532 CPoint point;
1533 CString temp;
1534 point = CPoint(rect->left, rect->bottom);
1535 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1536 CMenu popup;
1537 if (popup.CreatePopupMenu())
1539 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1540 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1542 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1543 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1545 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1546 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1548 temp.LoadString(IDS_LOG_FILTER_REVS);
1549 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1551 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1552 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1554 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1555 if (selection != 0)
1557 if (selection == LOGFILTER_TOGGLE)
1558 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1559 else
1560 m_SelectedFilters ^= selection;
1561 SetFilterCueText();
1562 SetTimer(IDT_FILTER, 1000, nullptr);
1565 return 0L;
1568 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1570 KillTimer(LOGFILTER_TIMER);
1571 m_ctrlFilter.SetWindowText(_T(""));
1572 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1573 return 0L;
1576 void CBrowseRefsDlg::SetFilterCueText()
1578 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1579 temp += _T(" ");
1581 if (m_SelectedFilters & LOGFILTER_REFNAME)
1582 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1584 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1586 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1587 temp += _T(", ");
1588 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1591 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1593 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1594 temp += _T(", ");
1595 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1598 if (m_SelectedFilters & LOGFILTER_REVS)
1600 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1601 temp += _T(", ");
1602 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1605 // to make the cue banner text appear more to the right of the edit control
1606 temp = _T(" ") + temp;
1607 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1610 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1612 m_pickedRef = g_Git.GetCurrentBranch(true);
1613 m_bPickedRefSet = true;
1614 OnOK();
1617 void CBrowseRefsDlg::UpdateInfoLabel()
1619 CString temp;
1620 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1621 SetDlgItemText(IDC_INFOLABEL, temp);
1624 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1626 UpdateData(TRUE);
1627 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1628 Refresh();
1631 CShadowTree* CBrowseRefsDlg::GetListEntry(int index)
1633 auto entry = reinterpret_cast<CShadowTree*>(m_ListRefLeafs.GetItemData(index));
1634 ASSERT(entry);
1635 return entry;
1638 CShadowTree* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem)
1640 auto entry = reinterpret_cast<CShadowTree*>(m_RefTreeCtrl.GetItemData(treeItem));
1641 ASSERT(entry);
1642 return entry;
1646 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()
1648 Refresh();