Allow to clear filters with escape
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob9cffecbe36347d1434bbaa03b830dc0631143a3a
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 ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
109 int Compare(LPARAM lParam1, LPARAM lParam2)
111 return Compare(
112 (CShadowTree*)m_pList->GetItemData((int)lParam1),
113 (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);
203 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
204 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
205 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
206 ON_WM_CONTEXTMENU()
207 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
208 ON_WM_DESTROY()
209 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
210 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
211 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
212 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
213 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
214 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
215 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
216 ON_WM_TIMER()
217 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
218 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
219 END_MESSAGE_MAP()
222 // CBrowseRefsDlg message handlers
224 void CBrowseRefsDlg::OnBnClickedOk()
226 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
228 OnOK();
229 return;
232 CIconMenu popupMenu;
233 popupMenu.CreatePopupMenu();
235 std::vector<CShadowTree*> selectedLeafs;
236 GetSelectedLeaves(selectedLeafs);
238 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
239 popupMenu.SetDefaultItem(1);
240 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
241 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
243 RECT rect;
244 GetDlgItem(IDOK)->GetWindowRect(&rect);
245 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, nullptr);
246 switch (selection)
248 case 1:
249 OnOK();
250 break;
251 case 2:
253 m_bPickedRefSet = true;
254 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
255 OnOK();
257 break;
258 case 3:
260 m_bPickedRefSet = true;
261 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
262 OnOK();
264 break;
265 default:
266 break;
270 BOOL CBrowseRefsDlg::OnInitDialog()
272 CResizableStandAloneDialog::OnInitDialog();
273 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
275 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
276 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
277 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
278 SetFilterCueText();
280 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
281 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
282 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
283 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
284 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
285 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
286 AddAnchor(IDHELP, BOTTOM_RIGHT);
288 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP);
289 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
290 static int columnWidths[] = { 0, 0, 0, 300, 0, 0, 80 };
291 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
292 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
293 m_ListRefLeafs.m_bAllowHiding = false;
294 m_ListRefLeafs.Init();
295 m_ListRefLeafs.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
296 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
297 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
298 m_bPickedRefSet = false;
300 AddAnchor(IDOK,BOTTOM_RIGHT);
301 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
302 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
304 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
305 UpdateData();
307 Refresh(m_initialRef);
309 EnableSaveRestore(L"BrowseRefs");
311 CString sWindowTitle;
312 GetWindowText(sWindowTitle);
313 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
315 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
317 if (m_bPickOne)
318 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
320 m_ListRefLeafs.SetFocus();
321 return FALSE;
324 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
326 int posSlash=nameLeft.Find('/');
327 CString nameSub;
328 if(posSlash<0)
330 nameSub=nameLeft;
331 nameLeft.Empty();//Nothing left
333 else
335 nameSub=nameLeft.Left(posSlash);
336 nameLeft=nameLeft.Mid(posSlash+1);
338 if(nameSub.IsEmpty())
339 return nullptr;
341 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
342 return nullptr;
344 CShadowTree& nextNode=m_ShadowTree[nameSub];
345 nextNode.m_csRefName=nameSub;
346 nextNode.m_pParent=this;
347 return &nextNode;
350 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
352 if(IsLeaf())
354 if(m_csRefName.GetLength() > partialRefName.GetLength())
355 return nullptr;
356 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
358 //Match of leaf name. Try match on total name.
359 CString totalRefName = GetRefName();
360 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
361 return this; //Also match. Found.
364 else
366 //Not a leaf. Search all nodes.
367 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
369 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
370 if (pSubtree)
371 return pSubtree; //Found
374 return nullptr; //Not found
377 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
379 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
380 //List ctrl selection?
381 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
383 //A leaf is selected
384 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
385 m_ListRefLeafs.GetNextSelectedItem(pos));
386 return pTree->GetRefName();
388 else if (pos && !pickFirstSelIfMultiSel)
390 // at least one leaf is selected
391 CString refs;
392 int index;
393 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
395 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
396 if (CStringUtils::StartsWith(ref, L"refs/"))
397 ref = ref.Mid(5);
398 if (CStringUtils::StartsWith(ref, L"heads/"))
399 ref = ref.Mid(6);
400 refs += ref + _T(" ");
402 return refs.Trim();
404 else if(!onlyIfLeaf)
406 //Tree ctrl selection?
407 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
408 if (hTree)
410 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
411 return pTree->GetRefName();
414 return CString();//None
417 void CBrowseRefsDlg::Refresh(CString selectRef)
419 remotes.clear();
420 if (g_Git.GetRemoteList(remotes))
421 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
423 if(!selectRef.IsEmpty())
425 if(selectRef == "HEAD")
427 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
428 selectRef.Empty();
429 else
430 selectRef = L"refs/heads/" + selectRef;
433 else
434 selectRef = GetSelectedRef(false, true);
436 m_RefTreeCtrl.DeleteAllItems();
437 m_ListRefLeafs.DeleteAllItems();
438 m_TreeRoot.m_ShadowTree.clear();
439 m_TreeRoot.m_csRefName = "refs";
440 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs", nullptr, nullptr);
441 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
443 CString err;
444 MAP_REF_GITREVREFBROWSER refMap;
445 if (GitRevRefBrowser::GetGitRevRefMap(refMap, err, [&](const CString& refName)
447 //Use ref based on m_pickRef_Kind
448 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
449 return false; //Skip
450 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
451 return false; //Skip
452 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
453 return false; //Skip
454 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
455 return false;
456 return true;
459 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
462 //Populate ref tree
463 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
465 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
466 GitRevRefBrowser ref = iterRefMap->second;
468 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
469 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
470 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
471 treeLeaf.m_csSubject = ref.GetSubject();
472 treeLeaf.m_csAuthor = ref.GetAuthorName();
473 treeLeaf.m_csDate = ref.GetAuthorDate();
474 treeLeaf.m_csDescription = ref.m_Description;
477 // always expand the tree first
478 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
480 // try exact match first
481 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
482 SelectRef(selectRef, false);
485 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
487 if(!bExactMatch)
489 CString newRefName = GetFullRefName(refName);
490 if(!newRefName.IsEmpty())
491 refName = newRefName;
492 //else refName is not a valid ref. Try to select as good as possible.
494 if (!CStringUtils::StartsWith(refName, L"refs"))
495 return false; // Not a ref name
497 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
498 if (treeLeafHead.m_hTree)
500 //Not a leaf. Select tree node and return
501 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
502 return true;
505 if (!treeLeafHead.m_pParent)
506 return false; //Weird... should not occur.
508 //This is the current head.
509 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
511 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
513 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
514 if(pCurrShadowTree == &treeLeafHead)
516 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
517 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
521 return true;
524 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
526 if (!pTreePos)
528 if (CStringUtils::StartsWith(refName, L"refs/"))
529 refName=refName.Mid(5);
530 pTreePos=&m_TreeRoot;
532 if(refName.IsEmpty())
533 return *pTreePos;//Found leaf
535 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
536 if (!pNextTree)
538 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
539 ASSERT(!bCreateIfNotExist);
540 return *pTreePos;
543 if(!refName.IsEmpty())
545 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
546 //Leafs are for the list control.
547 if (!pNextTree->m_hTree)
549 //New tree. Create node in control.
550 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree, nullptr);
551 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
555 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
559 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
561 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
562 *pResult = 0;
564 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
567 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
569 m_ListRefLeafs.DeleteAllItems();
571 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
572 if (!pTree)
574 ASSERT(FALSE);
575 return;
577 FillListCtrlForShadowTree(pTree,L"",true);
578 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
579 m_ListRefLeafs.AdjustColumnWidths();
582 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
584 if(pTree->IsLeaf())
586 CString filter;
587 m_ctrlFilter.GetWindowText(filter);
588 filter.MakeLower();
589 bool positive = filter[0] != '!';
590 if (!positive)
591 filter = filter.Mid(1);
592 CString ref = refNamePrefix + pTree->m_csRefName;
593 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
595 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
597 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
598 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
599 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
600 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
601 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
602 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
603 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
604 CString descrition = pTree->m_csDescription;
605 descrition.Replace(L"\n", L" ");
606 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
609 else
611 CString csThisName;
612 if (!isFirstLevel && !m_bIncludeNestedRefs)
613 return;
614 else if (!isFirstLevel)
615 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
616 else
617 m_pListCtrlRoot = pTree;
618 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
620 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
623 if (isFirstLevel)
625 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
626 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
628 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
632 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
634 if (m_SelectedFilters & LOGFILTER_REFNAME)
636 CString msg = ref;
637 msg = msg.MakeLower();
639 if (msg.Find(filter) >= 0)
640 return positive;
643 if (m_SelectedFilters & LOGFILTER_SUBJECT)
645 CString msg = pTree->m_csSubject;
646 msg = msg.MakeLower();
648 if (msg.Find(filter) >= 0)
649 return positive;
652 if (m_SelectedFilters & LOGFILTER_AUTHORS)
654 CString msg = pTree->m_csAuthor;
655 msg = msg.MakeLower();
657 if (msg.Find(filter) >= 0)
658 return positive;
661 if (m_SelectedFilters & LOGFILTER_REVS)
663 CString msg = pTree->m_csRefHash;
664 msg = msg.MakeLower();
666 if (msg.Find(filter) >= 0)
667 return positive;
669 return !positive;
672 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
674 ASSERT(!leafs.empty());
676 CString csMessage;
677 UINT mbIcon=MB_ICONQUESTION;
679 bool bIsRemoteBranch = false;
680 bool bIsBranch = false;
681 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
682 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
684 if(bIsBranch)
686 if(leafs.size() == 1)
688 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
689 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
691 //Check if branch is fully merged in HEAD
692 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
694 csMessage += L"\r\n\r\n";
695 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
696 mbIcon = MB_ICONWARNING;
699 if(bIsRemoteBranch)
701 csMessage += L"\r\n\r\n";
702 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
703 mbIcon = MB_ICONWARNING;
706 else
708 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
710 csMessage += L"\r\n\r\n";
711 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
712 mbIcon = MB_ICONWARNING;
714 if(bIsRemoteBranch)
716 csMessage += L"\r\n\r\n";
717 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
718 mbIcon = MB_ICONWARNING;
723 else if(leafs[0]->IsFrom(L"refs/tags/"))
725 if(leafs.size() == 1)
727 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
728 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
730 else
731 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
734 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
737 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
739 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
740 if(!DoDeleteRef((*i)->GetRefName()))
741 return false;
742 return true;
745 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
747 bool bIsRemoteBranch = false;
748 bool bIsBranch = false;
749 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
751 bIsBranch = true;
752 bIsRemoteBranch = true;
754 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
755 bIsBranch = true;
757 if (bIsRemoteBranch)
759 CString branchToDelete = completeRefName.Mid(13);
760 CString remoteName, remoteBranchToDelete;
761 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
762 return false;
764 if (CAppUtils::IsSSHPutty())
765 CAppUtils::LaunchPAgent(nullptr, &remoteName);
767 CSysProgressDlg sysProgressDlg;
768 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
769 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
770 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
771 sysProgressDlg.SetShowProgressBar(false);
772 sysProgressDlg.ShowModal(this, true);
774 STRING_VECTOR list;
775 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
776 if (g_Git.DeleteRemoteRefs(remoteName, list))
778 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
779 sysProgressDlg.Stop();
780 BringWindowToTop();
781 return false;
783 sysProgressDlg.Stop();
784 BringWindowToTop();
786 else if (bIsBranch)
788 if (g_Git.DeleteRef(completeRefName))
790 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
791 return false;
794 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
796 if (g_Git.DeleteRef(completeRefName))
798 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
799 return false;
802 return true;
805 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
807 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
808 if (!pLeaf)
809 return CString();
810 return pLeaf->GetRefName();
814 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
816 if (pWndFrom == &m_RefTreeCtrl)
817 OnContextMenu_RefTreeCtrl(point);
820 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
822 CPoint clientPoint=point;
823 m_RefTreeCtrl.ScreenToClient(&clientPoint);
825 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
826 if (hTreeItem)
827 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
829 VectorPShadowTree tree;
830 ShowContextMenu(point,hTreeItem,tree);
833 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
835 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
836 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
837 while (pos)
839 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
843 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
845 std::vector<CShadowTree*> selectedLeafs;
846 GetSelectedLeaves(selectedLeafs);
847 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
850 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
852 ASSERT(selectedLeafs.size() == 2);
854 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
855 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
856 else
857 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
860 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
862 int i = 0;
863 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
865 if (*it == entry)
866 return i;
868 return -1;
871 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
873 CIconMenu popupMenu;
874 popupMenu.CreatePopupMenu();
876 bool bAddSeparator = false;
877 CString remoteName;
879 if(selectedLeafs.size()==1)
881 bAddSeparator = true;
883 bool bShowReflogOption = false;
884 bool bShowFetchOption = false;
885 bool bShowRenameOption = false;
886 bool bShowCreateBranchOption = false;
887 bool bShowEditBranchDescriptionOption = false;
889 CString fetchFromCmd;
891 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
893 bShowReflogOption = true;
894 bShowRenameOption = true;
895 bShowEditBranchDescriptionOption = true;
897 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
899 bShowReflogOption = true;
900 bShowFetchOption = true;
901 bShowCreateBranchOption = true;
903 CString remoteBranch;
904 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
905 bShowFetchOption = false;
906 else
907 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
910 popupMenu.AppendMenuIcon(eCmd_ViewLog, IDS_MENULOG, IDI_LOG);
911 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
912 if(bShowReflogOption)
913 popupMenu.AppendMenuIcon(eCmd_ShowReflog, IDS_MENUREFLOG, IDI_LOG);
915 if (m_bHasWC)
917 popupMenu.AppendMenu(MF_SEPARATOR);
918 popupMenu.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
921 popupMenu.AppendMenu(MF_SEPARATOR);
922 bAddSeparator = false;
924 if(bShowFetchOption)
926 bAddSeparator = true;
927 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
930 if(bAddSeparator)
931 popupMenu.AppendMenu(MF_SEPARATOR);
933 bAddSeparator = false;
934 if (m_bHasWC)
936 CString str;
937 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
939 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
940 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
942 popupMenu.AppendMenuIcon(eCmd_Switch, IDS_SWITCH_TO_THIS, IDI_SWITCH);
943 popupMenu.AppendMenu(MF_SEPARATOR);
946 if(bShowCreateBranchOption)
948 bAddSeparator = true;
949 popupMenu.AppendMenuIcon(eCmd_CreateBranch, IDS_MENUBRANCH, IDI_COPY);
952 if (bShowEditBranchDescriptionOption)
954 bAddSeparator = true;
955 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, IDS_PROC_BROWSEREFS_EDITDESCRIPTION, IDI_RENAME);
957 if(bShowRenameOption)
959 bAddSeparator = true;
960 popupMenu.AppendMenuIcon(eCmd_Rename, IDS_PROC_BROWSEREFS_RENAME, IDI_RENAME);
963 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
965 if (bAddSeparator)
966 popupMenu.AppendMenu(MF_SEPARATOR);
967 bAddSeparator = true;
968 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
969 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH);
970 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH);
973 else if(selectedLeafs.size() == 2)
975 bAddSeparator = true;
976 popupMenu.AppendMenuIcon(eCmd_Diff, IDS_PROC_BROWSEREFS_COMPAREREFS, IDI_DIFF);
977 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
978 CString menu;
979 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
980 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
981 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
982 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
985 if(!selectedLeafs.empty())
987 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
989 if(bAddSeparator)
990 popupMenu.AppendMenu(MF_SEPARATOR);
991 CString menuItemName;
992 if(selectedLeafs.size() == 1)
993 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
994 else
995 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
997 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
998 bAddSeparator = true;
1000 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1002 if(bAddSeparator)
1003 popupMenu.AppendMenu(MF_SEPARATOR);
1004 CString menuItemName;
1005 if(selectedLeafs.size() == 1)
1006 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1007 else
1008 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1010 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1011 bAddSeparator = true;
1013 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1015 if(bAddSeparator)
1016 popupMenu.AppendMenu(MF_SEPARATOR);
1017 CString menuItemName;
1018 if(selectedLeafs.size() == 1)
1019 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1020 else
1021 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1023 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1024 bAddSeparator = true;
1029 if (hTreePos && selectedLeafs.empty())
1031 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1032 if(pTree->IsFrom(L"refs/remotes"))
1034 if(bAddSeparator)
1035 popupMenu.AppendMenu(MF_SEPARATOR);
1036 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, IDS_PROC_BROWSEREFS_MANAGEREMOTES, IDI_SETTINGS);
1037 bAddSeparator = true;
1038 if(selectedLeafs.empty())
1040 CString remoteBranch;
1041 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1042 remoteName.Empty();
1043 int pos = findVectorPosition(remotes, remoteName);
1044 if (pos >= 0)
1046 CString temp;
1047 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1048 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1050 temp.LoadString(IDS_DELETEREMOTETAG);
1051 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1055 if(pTree->IsFrom(L"refs/heads"))
1057 if(bAddSeparator)
1058 popupMenu.AppendMenu(MF_SEPARATOR);
1059 CString temp;
1060 temp.LoadString(IDS_MENUBRANCH);
1061 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1063 if(pTree->IsFrom(L"refs/tags"))
1065 if(bAddSeparator)
1066 popupMenu.AppendMenu(MF_SEPARATOR);
1067 popupMenu.AppendMenuIcon(eCmd_CreateTag, IDS_MENUTAG, IDI_TAG);
1068 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, IDS_PROC_BROWSEREFS_DELETEALLTAGS, IDI_DELETE);
1069 if (!remotes.empty())
1071 popupMenu.AppendMenu(MF_SEPARATOR);
1072 int i = 0;
1073 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1075 CString temp;
1076 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1077 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1084 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
1085 switch ((eCmd)(selection & 0xFFFF))
1087 case eCmd_ViewLog:
1089 CString sCmd;
1090 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1091 CAppUtils::RunTortoiseGitProc(sCmd);
1093 break;
1094 case eCmd_ViewLogRange:
1096 CString sCmd;
1097 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1098 CAppUtils::RunTortoiseGitProc(sCmd);
1100 break;
1101 case eCmd_ViewLogRangeReachableFromOnlyOne:
1103 CString sCmd;
1104 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1105 CAppUtils::RunTortoiseGitProc(sCmd);
1107 break;
1108 case eCmd_RepoBrowser:
1109 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1110 break;
1111 case eCmd_DeleteBranch:
1112 case eCmd_DeleteRemoteBranch:
1114 if(ConfirmDeleteRef(selectedLeafs))
1115 DoDeleteRefs(selectedLeafs);
1116 Refresh();
1118 break;
1119 case eCmd_DeleteTag:
1121 if(ConfirmDeleteRef(selectedLeafs))
1122 DoDeleteRefs(selectedLeafs);
1123 Refresh();
1125 break;
1126 case eCmd_ShowReflog:
1128 CRefLogDlg refLogDlg(this);
1129 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1130 refLogDlg.DoModal();
1132 break;
1133 case eCmd_Fetch:
1135 CAppUtils::Fetch(remoteName);
1136 Refresh();
1138 break;
1139 case eCmd_DeleteRemoteTag:
1141 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1142 int remoteInx = selection >> 16;
1143 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1144 return;
1145 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1146 deleteRemoteTagDlg.DoModal();
1148 break;
1149 case eCmd_Merge:
1151 CString ref = selectedLeafs[0]->GetRefName();
1152 CAppUtils::Merge(&ref);
1154 break;
1155 case eCmd_Switch:
1157 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1159 break;
1160 case eCmd_Rename:
1162 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1163 if (pos)
1164 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1166 break;
1167 case eCmd_AddRemote:
1169 CAddRemoteDlg(this).DoModal();
1170 Refresh();
1172 break;
1173 case eCmd_ManageRemotes:
1175 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1176 // CSettingGitRemote W_Remotes(m_cmdPath);
1177 // W_Remotes.DoModal();
1178 Refresh();
1180 break;
1181 case eCmd_CreateBranch:
1183 CString* commitHash = nullptr;
1184 if (selectedLeafs.size() == 1)
1185 commitHash = &(selectedLeafs[0]->m_csRefHash);
1186 CAppUtils::CreateBranchTag(false, commitHash);
1187 Refresh();
1189 break;
1190 case eCmd_CreateTag:
1192 CAppUtils::CreateBranchTag(true);
1193 Refresh();
1195 break;
1196 case eCmd_DeleteAllTags:
1198 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1200 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1201 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1203 if (ConfirmDeleteRef(selectedLeafs))
1204 DoDeleteRefs(selectedLeafs);
1205 Refresh();
1207 break;
1208 case eCmd_Diff:
1210 CFileDiffDlg dlg;
1211 dlg.SetDiff(
1212 nullptr,
1213 selectedLeafs[0]->GetRefName() + L"^{}",
1214 selectedLeafs[1]->GetRefName() + L"^{}");
1215 dlg.DoModal();
1217 break;
1218 case eCmd_UnifiedDiff:
1220 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1222 break;
1223 case eCmd_DiffWC:
1225 CString sCmd;
1226 sCmd.Format(_T("/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s"), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)selectedLeafs[0]->GetRefName(), (LPCTSTR)GitRev::GetWorkingCopy());
1227 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
1228 sCmd += L" /alternative";
1230 CAppUtils::RunTortoiseGitProc(sCmd);
1232 break;
1233 case eCmd_EditBranchDescription:
1235 CInputDlg dlg;
1236 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1237 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1238 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1239 dlg.m_bUseLogWidth = true;
1240 if(dlg.DoModal() == IDOK)
1242 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1243 Refresh();
1246 break;
1247 case eCmd_UpstreamDrop:
1249 CString key;
1250 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1251 g_Git.UnsetConfigValue(key);
1252 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1253 g_Git.UnsetConfigValue(key);
1255 Refresh();
1256 break;
1257 case eCmd_UpstreamSet:
1259 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1260 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1261 return;
1262 CString remote, branch;
1263 if (SplitRemoteBranchName(newRef, remote, branch))
1264 return;
1265 CString key;
1266 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1267 g_Git.SetConfigValue(key, remote);
1268 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1269 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1270 Refresh();
1272 break;
1276 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1278 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1279 if(!(*i)->IsFrom(from))
1280 return false;
1281 return true;
1284 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1286 if (pMsg->message == WM_KEYDOWN)
1288 switch (pMsg->wParam)
1290 /* case VK_RETURN:
1292 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1294 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1296 PostMessage(WM_COMMAND, IDOK);
1298 return TRUE;
1301 break;
1302 */ case VK_F2:
1304 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1306 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1307 if (pos)
1308 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1311 break;
1313 case VK_F5:
1315 Refresh();
1317 break;
1318 case L'E':
1320 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1322 m_ctrlFilter.SetSel(0, -1, FALSE);
1323 m_ctrlFilter.SetFocus();
1324 return TRUE;
1327 break;
1328 case VK_ESCAPE:
1329 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER) && m_ctrlFilter.GetWindowTextLength())
1331 OnClickedCancelFilter(NULL, NULL);
1332 return TRUE;
1334 break;
1339 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1342 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1344 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1345 *pResult = 0;
1347 if(m_currSortCol == pNMLV->iSubItem)
1348 m_currSortDesc = !m_currSortDesc;
1349 else
1351 m_currSortCol = pNMLV->iSubItem;
1352 m_currSortDesc = false;
1355 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1356 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1358 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1361 void CBrowseRefsDlg::OnDestroy()
1363 if (!m_bPickedRefSet)
1364 m_pickedRef = GetSelectedRef(true, false);
1366 CResizableStandAloneDialog::OnDestroy();
1369 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1371 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1372 *pResult = 0;
1374 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1375 if (item && pNMListView->uNewState == LVIS_SELECTED)
1376 m_sLastSelected = item->GetRefName();
1378 UpdateInfoLabel();
1381 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1383 *pResult = 0;
1385 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1386 return;
1387 EndDialog(IDOK);
1390 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1392 CBrowseRefsDlg dlg(CString(), nullptr);
1394 if(initialRef.IsEmpty())
1395 initialRef = L"HEAD";
1396 dlg.m_initialRef = initialRef;
1397 dlg.m_pickRef_Kind = pickRef_Kind;
1398 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1400 if(dlg.DoModal() != IDOK)
1401 return CString();
1403 return dlg.m_pickedRef;
1406 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1408 CString origRef;
1409 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1410 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1411 if(resultRef.IsEmpty())
1412 return false;
1413 if (CStringUtils::StartsWith(resultRef, L"refs/"))
1414 resultRef = resultRef.Mid(5);
1415 // if(wcsncmp(resultRef,L"heads/",6)==0)
1416 // resultRef = resultRef.Mid(6);
1418 //Find closest match of choice in combobox
1419 int ixFound = -1;
1420 int matchLength = 0;
1421 CString comboRefName;
1422 for(int i = 0; i < pComboBox->GetCount(); ++i)
1424 pComboBox->GetLBText(i, comboRefName);
1425 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1426 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1427 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1429 matchLength = comboRefName.GetLength();
1430 ixFound = i;
1433 if(ixFound >= 0)
1434 pComboBox->SetCurSel(ixFound);
1435 else
1436 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)
1438 return true;
1441 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1443 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1444 *pResult = FALSE;
1446 if (!pDispInfo->item.pszText)
1447 return; //User canceled changing
1449 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(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 CShadowTree* pTree2 = (CShadowTree*)m_RefTreeCtrl.GetItemData(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 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1503 if(!pTree->IsFrom(L"refs/heads/"))
1505 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1506 return;
1510 void CBrowseRefsDlg::OnEnChangeEditFilter()
1512 SetTimer(IDT_FILTER, 1000, nullptr);
1515 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1517 if (nIDEvent == IDT_FILTER)
1519 KillTimer(IDT_FILTER);
1520 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1523 CResizableStandAloneDialog::OnTimer(nIDEvent);
1526 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1528 // FIXME: x64 version would get this function called with unexpected parameters.
1529 if (!lParam)
1530 return 0;
1532 RECT * rect = (LPRECT)lParam;
1533 CPoint point;
1534 CString temp;
1535 point = CPoint(rect->left, rect->bottom);
1536 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1537 CMenu popup;
1538 if (popup.CreatePopupMenu())
1540 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1541 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1543 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1544 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1546 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1547 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1549 temp.LoadString(IDS_LOG_FILTER_REVS);
1550 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1552 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1553 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1555 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1556 if (selection != 0)
1558 if (selection == LOGFILTER_TOGGLE)
1559 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1560 else
1561 m_SelectedFilters ^= selection;
1562 SetFilterCueText();
1563 SetTimer(IDT_FILTER, 1000, nullptr);
1566 return 0L;
1569 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1571 KillTimer(LOGFILTER_TIMER);
1572 m_ctrlFilter.SetWindowText(_T(""));
1573 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1574 return 0L;
1577 void CBrowseRefsDlg::SetFilterCueText()
1579 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1580 temp += _T(" ");
1582 if (m_SelectedFilters & LOGFILTER_REFNAME)
1583 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1585 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1587 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1588 temp += _T(", ");
1589 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1592 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1594 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1595 temp += _T(", ");
1596 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1599 if (m_SelectedFilters & LOGFILTER_REVS)
1601 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1602 temp += _T(", ");
1603 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1606 // to make the cue banner text appear more to the right of the edit control
1607 temp = _T(" ") + temp;
1608 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1611 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1613 m_pickedRef = g_Git.GetCurrentBranch(true);
1614 m_bPickedRefSet = true;
1615 OnOK();
1618 void CBrowseRefsDlg::UpdateInfoLabel()
1620 CString temp;
1621 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1622 SetDlgItemText(IDC_INFOLABEL, temp);
1625 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1627 UpdateData(TRUE);
1628 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1629 Refresh();