Fix typo in doc
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobb98a37e4ab13cdb206957951d499dca5046929b6
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2017 - 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 (CStringUtils::StartsWith(ref, L"refs/remotes/"))
45 ref = ref.Mid(13);
46 else if (CStringUtils::StartsWith(ref, L"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 (CStringUtils::StartsWith(ref, list[i] + L"/"))
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(L"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(L"Software\\TortoiseGit\\RelativeTimes", FALSE);
179 m_bRelativeTimes = (regRelativeTimes != 0);
181 m_regIncludeNestedRefs = CRegDWORD(L"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, L".."), IDI_LOG);
243 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."), 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, L"..");
257 OnOK();
259 break;
260 case 3:
262 m_bPickedRefSet = true;
263 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"...");
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, 14, 14);
279 m_ctrlFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
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 | LVS_EX_DOUBLEBUFFER);
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, L"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);
327 if (CAppUtils::GetMsysgitVersion() < 0x02070000)
329 m_cBranchFilter.EnableWindow(FALSE);
330 CString temp;
331 temp.Format(IDS_GITVER_REQUIRED, L"\"git for-each-ref --merged\"", L"2.7");
332 m_tooltips.AddTool(IDC_BROWSE_REFS_BRANCHFILTER, temp);
335 m_ListRefLeafs.SetFocus();
336 return FALSE;
339 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
341 int posSlash=nameLeft.Find('/');
342 CString nameSub;
343 if(posSlash<0)
345 nameSub=nameLeft;
346 nameLeft.Empty();//Nothing left
348 else
350 nameSub=nameLeft.Left(posSlash);
351 nameLeft=nameLeft.Mid(posSlash+1);
353 if(nameSub.IsEmpty())
354 return nullptr;
356 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
357 return nullptr;
359 CShadowTree& nextNode=m_ShadowTree[nameSub];
360 nextNode.m_csRefName=nameSub;
361 nextNode.m_pParent=this;
362 return &nextNode;
365 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
367 if(IsLeaf())
369 if (CStringUtils::EndsWith(partialRefName, m_csRefName))
371 //Match of leaf name. Try match on total name.
372 CString totalRefName = GetRefName();
373 if (CStringUtils::EndsWith(totalRefName, partialRefName))
374 return this; //Also match. Found.
377 else
379 //Not a leaf. Search all nodes.
380 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
382 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
383 if (pSubtree)
384 return pSubtree; //Found
387 return nullptr; //Not found
390 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
392 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
393 //List ctrl selection?
394 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
396 //A leaf is selected
397 return GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos))->GetRefName();
399 else if (pos && !pickFirstSelIfMultiSel)
401 // at least one leaf is selected
402 CString refs;
403 int index;
404 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
406 CString ref = GetListEntry(index)->GetRefName();
407 if (CStringUtils::StartsWith(ref, L"refs/"))
408 ref = ref.Mid(5);
409 if (CStringUtils::StartsWith(ref, L"heads/"))
410 ref = ref.Mid(6);
411 refs += ref + L' ';
413 return refs.Trim();
415 else if(!onlyIfLeaf)
417 //Tree ctrl selection?
418 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
419 if (hTree)
420 return GetTreeEntry(hTree)->GetRefName();
422 return CString();//None
425 void CBrowseRefsDlg::Refresh(CString selectRef)
427 remotes.clear();
428 if (g_Git.GetRemoteList(remotes))
429 MessageBox(CGit::GetLibGit2LastErr(L"Could not get a list of remotes."), L"TortoiseGit", MB_ICONERROR);
431 if(!selectRef.IsEmpty())
433 if (selectRef == L"HEAD")
435 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
436 selectRef.Empty();
437 else
438 selectRef = L"refs/heads/" + selectRef;
441 else
442 selectRef = GetSelectedRef(false, true);
444 m_RefTreeCtrl.DeleteAllItems();
445 m_ListRefLeafs.DeleteAllItems();
446 m_TreeRoot.m_ShadowTree.clear();
447 m_TreeRoot.m_csRefName = L"refs";
448 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs");
449 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
451 CString err;
452 MAP_REF_GITREVREFBROWSER refMap;
453 if (GitRevRefBrowser::GetGitRevRefMap(refMap, m_cBranchFilter.GetCurSel(), err, [&](const CString& refName)
455 //Use ref based on m_pickRef_Kind
456 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
457 return false; //Skip
458 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
459 return false; //Skip
460 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
461 return false; //Skip
462 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
463 return false;
464 return true;
467 MessageBox(L"Get refs failed:" + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
470 //Populate ref tree
471 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
473 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
474 GitRevRefBrowser ref = iterRefMap->second;
476 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
477 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
478 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
479 treeLeaf.m_csSubject = ref.GetSubject();
480 treeLeaf.m_csAuthor = ref.GetAuthorName();
481 treeLeaf.m_csDate = ref.GetAuthorDate();
482 treeLeaf.m_csDescription = ref.m_Description;
485 // always expand the tree first
486 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
488 // try exact match first
489 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
490 SelectRef(selectRef, false);
493 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
495 if(!bExactMatch)
497 CString newRefName = GetFullRefName(refName);
498 if(!newRefName.IsEmpty())
499 refName = newRefName;
500 //else refName is not a valid ref. Try to select as good as possible.
502 if (!CStringUtils::StartsWith(refName, L"refs"))
503 return false; // Not a ref name
505 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
506 if (treeLeafHead.m_hTree)
508 //Not a leaf. Select tree node and return
509 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
510 return true;
513 if (!treeLeafHead.m_pParent)
514 return false; //Weird... should not occur.
516 //This is the current head.
517 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
519 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
521 auto pCurrShadowTree = GetListEntry(indexPos);
522 if(pCurrShadowTree == &treeLeafHead)
524 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
525 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
529 return true;
532 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
534 if (!pTreePos)
536 if (CStringUtils::StartsWith(refName, L"refs/"))
537 refName=refName.Mid(5);
538 pTreePos=&m_TreeRoot;
540 if(refName.IsEmpty())
541 return *pTreePos;//Found leaf
543 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
544 if (!pNextTree)
546 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
547 ASSERT(!bCreateIfNotExist);
548 return *pTreePos;
551 if(!refName.IsEmpty())
553 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
554 //Leafs are for the list control.
555 if (!pNextTree->m_hTree)
557 //New tree. Create node in control.
558 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree);
559 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
563 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
567 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
569 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
570 *pResult = 0;
572 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
575 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
577 m_ListRefLeafs.DeleteAllItems();
579 auto pTree = GetTreeEntry(treeNode);
580 if (!pTree)
581 return;
583 FillListCtrlForShadowTree(pTree,L"",true);
584 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
585 m_ListRefLeafs.AdjustColumnWidths();
588 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
590 if(pTree->IsLeaf())
592 CString filter;
593 m_ctrlFilter.GetWindowText(filter);
594 filter.MakeLower();
595 bool positive = filter[0] != '!';
596 if (!positive)
597 filter = filter.Mid(1);
598 CString ref = refNamePrefix + pTree->m_csRefName;
599 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == L"refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
601 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
603 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
604 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
605 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
606 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : L"");
607 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
608 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
609 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
610 CString descrition = pTree->m_csDescription;
611 descrition.Replace(L'\n', L' ');
612 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
615 else
617 CString csThisName;
618 if (!isFirstLevel && !m_bIncludeNestedRefs)
619 return;
620 else if (!isFirstLevel)
621 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
622 else
623 m_pListCtrlRoot = pTree;
624 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
626 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
629 if (isFirstLevel)
631 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
632 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
634 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
638 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
640 if (m_SelectedFilters & LOGFILTER_REFNAME)
642 CString msg = ref;
643 msg = msg.MakeLower();
645 if (msg.Find(filter) >= 0)
646 return positive;
649 if (m_SelectedFilters & LOGFILTER_SUBJECT)
651 CString msg = pTree->m_csSubject;
652 msg = msg.MakeLower();
654 if (msg.Find(filter) >= 0)
655 return positive;
658 if (m_SelectedFilters & LOGFILTER_AUTHORS)
660 CString msg = pTree->m_csAuthor;
661 msg = msg.MakeLower();
663 if (msg.Find(filter) >= 0)
664 return positive;
667 if (m_SelectedFilters & LOGFILTER_REVS)
669 CString msg = pTree->m_csRefHash;
670 msg = msg.MakeLower();
672 if (msg.Find(filter) >= 0)
673 return positive;
675 return !positive;
678 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
680 ASSERT(!leafs.empty());
682 CString csMessage;
683 UINT mbIcon=MB_ICONQUESTION;
685 bool bIsRemoteBranch = false;
686 bool bIsBranch = false;
687 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
688 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
690 if(bIsBranch)
692 if(leafs.size() == 1)
694 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
695 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
697 //Check if branch is fully merged in HEAD
698 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), L"HEAD"))
700 csMessage += L"\r\n\r\n";
701 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
702 mbIcon = MB_ICONWARNING;
705 if(bIsRemoteBranch)
707 csMessage += L"\r\n\r\n";
708 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
709 mbIcon = MB_ICONWARNING;
712 else
714 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
716 csMessage += L"\r\n\r\n";
717 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
718 mbIcon = MB_ICONWARNING;
720 if(bIsRemoteBranch)
722 csMessage += L"\r\n\r\n";
723 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
724 mbIcon = MB_ICONWARNING;
729 else if(leafs[0]->IsFrom(L"refs/tags/"))
731 if(leafs.size() == 1)
733 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
734 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
736 else
737 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
740 return MessageBox(csMessage, L"TortoiseGit", MB_YESNO | mbIcon) == IDYES;
743 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
745 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
746 if(!DoDeleteRef((*i)->GetRefName()))
747 return false;
748 return true;
751 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
753 bool bIsRemoteBranch = false;
754 bool bIsBranch = false;
755 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
757 bIsBranch = true;
758 bIsRemoteBranch = true;
760 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
761 bIsBranch = true;
763 if (bIsRemoteBranch)
765 CString branchToDelete = completeRefName.Mid(13);
766 CString remoteName, remoteBranchToDelete;
767 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
768 return false;
770 if (CAppUtils::IsSSHPutty())
771 CAppUtils::LaunchPAgent(nullptr, &remoteName);
773 CSysProgressDlg sysProgressDlg;
774 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
775 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
776 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
777 sysProgressDlg.SetShowProgressBar(false);
778 sysProgressDlg.ShowModal(this, true);
780 STRING_VECTOR list;
781 list.push_back(L"refs/heads/" + remoteBranchToDelete);
782 if (g_Git.DeleteRemoteRefs(remoteName, list))
784 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
785 sysProgressDlg.Stop();
786 BringWindowToTop();
787 return false;
789 sysProgressDlg.Stop();
790 BringWindowToTop();
792 else if (bIsBranch)
794 if (g_Git.DeleteRef(completeRefName))
796 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
797 return false;
800 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
802 if (g_Git.DeleteRef(completeRefName))
804 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
805 return false;
808 return true;
811 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
813 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
814 if (!pLeaf)
815 return CString();
816 return pLeaf->GetRefName();
820 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
822 if (pWndFrom == &m_RefTreeCtrl)
823 OnContextMenu_RefTreeCtrl(point);
826 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
828 CPoint clientPoint=point;
829 m_RefTreeCtrl.ScreenToClient(&clientPoint);
831 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
832 if (hTreeItem)
833 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
835 VectorPShadowTree tree;
836 ShowContextMenu(point,hTreeItem,tree);
839 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
841 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
842 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
843 while (pos)
845 selectedLeafs.push_back(GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos)));
849 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
851 std::vector<CShadowTree*> selectedLeafs;
852 GetSelectedLeaves(selectedLeafs);
853 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
856 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
858 ASSERT(selectedLeafs.size() == 2);
860 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
861 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
862 else
863 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
866 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
868 int i = 0;
869 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
871 if (*it == entry)
872 return i;
874 return -1;
877 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
879 CIconMenu popupMenu;
880 popupMenu.CreatePopupMenu();
882 bool bAddSeparator = false;
883 CString remoteName;
885 if(selectedLeafs.size()==1)
887 bAddSeparator = true;
889 bool bShowReflogOption = false;
890 bool bShowFetchOption = false;
891 bool bShowRenameOption = false;
892 bool bShowCreateBranchOption = false;
893 bool bShowEditBranchDescriptionOption = false;
895 CString fetchFromCmd;
897 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
899 bShowReflogOption = true;
900 bShowRenameOption = true;
901 bShowEditBranchDescriptionOption = true;
903 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
905 bShowReflogOption = true;
906 bShowFetchOption = true;
907 bShowCreateBranchOption = true;
909 CString remoteBranch;
910 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
911 bShowFetchOption = false;
912 else
913 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
916 popupMenu.AppendMenuIcon(eCmd_ViewLog, IDS_MENULOG, IDI_LOG);
917 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
918 if(bShowReflogOption)
919 popupMenu.AppendMenuIcon(eCmd_ShowReflog, IDS_MENUREFLOG, IDI_LOG);
921 if (m_bHasWC)
923 popupMenu.AppendMenu(MF_SEPARATOR);
924 popupMenu.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
927 popupMenu.AppendMenu(MF_SEPARATOR);
928 bAddSeparator = false;
930 if(bShowFetchOption)
932 bAddSeparator = true;
933 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
936 if(bAddSeparator)
937 popupMenu.AppendMenu(MF_SEPARATOR);
939 bAddSeparator = false;
940 if (m_bHasWC)
942 CString str;
943 if (selectedLeafs[0]->GetRefName() != L"refs/heads/" + g_Git.GetCurrentBranch())
945 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
946 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
948 popupMenu.AppendMenuIcon(eCmd_Switch, IDS_SWITCH_TO_THIS, IDI_SWITCH);
949 popupMenu.AppendMenu(MF_SEPARATOR);
952 if(bShowCreateBranchOption)
954 bAddSeparator = true;
955 popupMenu.AppendMenuIcon(eCmd_CreateBranch, IDS_MENUBRANCH, IDI_COPY);
958 if (bShowEditBranchDescriptionOption)
960 bAddSeparator = true;
961 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, IDS_PROC_BROWSEREFS_EDITDESCRIPTION, IDI_RENAME);
963 if(bShowRenameOption)
965 bAddSeparator = true;
966 popupMenu.AppendMenuIcon(eCmd_Rename, IDS_PROC_BROWSEREFS_RENAME, IDI_RENAME);
969 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
971 if (bAddSeparator)
972 popupMenu.AppendMenu(MF_SEPARATOR);
973 bAddSeparator = true;
974 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
975 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH);
976 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH);
979 else if(selectedLeafs.size() == 2)
981 bAddSeparator = true;
982 popupMenu.AppendMenuIcon(eCmd_Diff, IDS_PROC_BROWSEREFS_COMPAREREFS, IDI_DIFF);
983 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
984 CString menu;
985 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."));
986 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
987 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."));
988 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
991 if(!selectedLeafs.empty())
993 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
995 if(bAddSeparator)
996 popupMenu.AppendMenu(MF_SEPARATOR);
997 CString menuItemName;
998 if(selectedLeafs.size() == 1)
999 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1000 else
1001 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1003 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1004 bAddSeparator = true;
1006 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1008 if(bAddSeparator)
1009 popupMenu.AppendMenu(MF_SEPARATOR);
1010 CString menuItemName;
1011 if(selectedLeafs.size() == 1)
1012 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1013 else
1014 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1016 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1017 bAddSeparator = true;
1019 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1021 if(bAddSeparator)
1022 popupMenu.AppendMenu(MF_SEPARATOR);
1023 CString menuItemName;
1024 if(selectedLeafs.size() == 1)
1025 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1026 else
1027 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1029 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1030 bAddSeparator = true;
1035 if (hTreePos && selectedLeafs.empty())
1037 auto pTree = GetTreeEntry(hTreePos);
1038 if(pTree->IsFrom(L"refs/remotes"))
1040 if(bAddSeparator)
1041 popupMenu.AppendMenu(MF_SEPARATOR);
1042 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, IDS_PROC_BROWSEREFS_MANAGEREMOTES, IDI_SETTINGS);
1043 bAddSeparator = true;
1044 if(selectedLeafs.empty())
1046 CString remoteBranch;
1047 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1048 remoteName.Empty();
1049 int pos = findVectorPosition(remotes, remoteName);
1050 if (pos >= 0)
1052 CString temp;
1053 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1054 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1056 temp.LoadString(IDS_DELETEREMOTETAG);
1057 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1060 bAddSeparator = false;
1062 if(pTree->IsFrom(L"refs/heads"))
1064 if(bAddSeparator)
1065 popupMenu.AppendMenu(MF_SEPARATOR);
1066 CString temp;
1067 temp.LoadString(IDS_MENUBRANCH);
1068 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1069 bAddSeparator = false;
1071 if(pTree->IsFrom(L"refs/tags"))
1073 if(bAddSeparator)
1074 popupMenu.AppendMenu(MF_SEPARATOR);
1075 popupMenu.AppendMenuIcon(eCmd_CreateTag, IDS_MENUTAG, IDI_TAG);
1076 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, IDS_PROC_BROWSEREFS_DELETEALLTAGS, IDI_DELETE);
1077 if (!remotes.empty())
1079 popupMenu.AppendMenu(MF_SEPARATOR);
1080 int i = 0;
1081 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1083 CString temp;
1084 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1085 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1088 bAddSeparator = false;
1091 if (bAddSeparator)
1092 popupMenu.AppendMenu(MF_SEPARATOR);
1093 popupMenu.AppendMenuIcon(eCmd_Copy, IDS_COPY_REF_NAMES, IDI_COPYCLIP);
1095 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
1096 switch ((eCmd)(selection & 0xFFFF))
1098 case eCmd_ViewLog:
1100 CString sCmd;
1101 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1102 CAppUtils::RunTortoiseGitProc(sCmd);
1104 break;
1105 case eCmd_ViewLogRange:
1107 CString sCmd;
1108 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."));
1109 CAppUtils::RunTortoiseGitProc(sCmd);
1111 break;
1112 case eCmd_ViewLogRangeReachableFromOnlyOne:
1114 CString sCmd;
1115 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."));
1116 CAppUtils::RunTortoiseGitProc(sCmd);
1118 break;
1119 case eCmd_RepoBrowser:
1120 CAppUtils::RunTortoiseGitProc(L"/command:repobrowser /path:\"" + g_Git.m_CurrentDir + L"\" /rev:" + selectedLeafs[0]->GetRefName());
1121 break;
1122 case eCmd_DeleteBranch:
1123 case eCmd_DeleteRemoteBranch:
1125 if(ConfirmDeleteRef(selectedLeafs))
1126 DoDeleteRefs(selectedLeafs);
1127 Refresh();
1129 break;
1130 case eCmd_DeleteTag:
1132 if(ConfirmDeleteRef(selectedLeafs))
1133 DoDeleteRefs(selectedLeafs);
1134 Refresh();
1136 break;
1137 case eCmd_ShowReflog:
1139 CRefLogDlg refLogDlg(this);
1140 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1141 refLogDlg.DoModal();
1143 break;
1144 case eCmd_Fetch:
1146 CAppUtils::Fetch(remoteName);
1147 Refresh();
1149 break;
1150 case eCmd_DeleteRemoteTag:
1152 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1153 int remoteInx = selection >> 16;
1154 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1155 return;
1156 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1157 deleteRemoteTagDlg.DoModal();
1159 break;
1160 case eCmd_Merge:
1162 CString ref = selectedLeafs[0]->GetRefName();
1163 CAppUtils::Merge(&ref);
1165 break;
1166 case eCmd_Switch:
1168 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1170 break;
1171 case eCmd_Rename:
1173 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1174 if (pos)
1175 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1177 break;
1178 case eCmd_AddRemote:
1180 CAddRemoteDlg(this).DoModal();
1181 Refresh();
1183 break;
1184 case eCmd_ManageRemotes:
1186 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1187 // CSettingGitRemote W_Remotes(m_cmdPath);
1188 // W_Remotes.DoModal();
1189 Refresh();
1191 break;
1192 case eCmd_CreateBranch:
1194 CString* commitHash = nullptr;
1195 if (selectedLeafs.size() == 1)
1196 commitHash = &(selectedLeafs[0]->m_csRefHash);
1197 CAppUtils::CreateBranchTag(false, commitHash);
1198 Refresh();
1200 break;
1201 case eCmd_CreateTag:
1203 CAppUtils::CreateBranchTag(true);
1204 Refresh();
1206 break;
1207 case eCmd_DeleteAllTags:
1209 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1211 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1212 selectedLeafs.push_back(GetListEntry(i));
1214 if (ConfirmDeleteRef(selectedLeafs))
1215 DoDeleteRefs(selectedLeafs);
1216 Refresh();
1218 break;
1219 case eCmd_Diff:
1221 CFileDiffDlg dlg;
1222 dlg.SetDiff(
1223 nullptr,
1224 selectedLeafs[0]->GetRefName() + L"^{}",
1225 selectedLeafs[1]->GetRefName() + L"^{}");
1226 dlg.DoModal();
1228 break;
1229 case eCmd_UnifiedDiff:
1231 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1233 break;
1234 case eCmd_DiffWC:
1236 CString sCmd;
1237 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)selectedLeafs[0]->GetRefName(), (LPCTSTR)GitRev::GetWorkingCopy());
1238 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
1239 sCmd += L" /alternative";
1241 CAppUtils::RunTortoiseGitProc(sCmd);
1243 break;
1244 case eCmd_EditBranchDescription:
1246 CInputDlg dlg;
1247 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1248 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1249 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1250 dlg.m_bUseLogWidth = true;
1251 if(dlg.DoModal() == IDOK)
1253 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1254 Refresh();
1257 break;
1258 case eCmd_UpstreamDrop:
1260 CString key;
1261 key.Format(L"branch.%s.remote", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1262 g_Git.UnsetConfigValue(key);
1263 key.Format(L"branch.%s.merge", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1264 g_Git.UnsetConfigValue(key);
1266 Refresh();
1267 break;
1268 case eCmd_UpstreamSet:
1270 CString newRef = CBrowseRefsDlg::PickRef(false, L"", gPickRef_Remote, false);
1271 if (newRef.IsEmpty() || newRef.Find(L"refs/remotes/") != 0)
1272 return;
1273 CString remote, branch;
1274 if (SplitRemoteBranchName(newRef, remote, branch))
1275 return;
1276 CString key;
1277 key.Format(L"branch.%s.remote", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1278 g_Git.SetConfigValue(key, remote);
1279 key.Format(L"branch.%s.merge", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1280 g_Git.SetConfigValue(key, L"refs/heads/" + branch);
1281 Refresh();
1283 break;
1284 case eCmd_Copy:
1286 CString sClipdata;
1287 bool first = true;
1288 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1289 while (pos)
1291 auto index = m_ListRefLeafs.GetNextSelectedItem(pos);
1292 if (!first)
1293 sClipdata += L"\r\n";
1294 sClipdata += m_ListRefLeafs.GetItemText(index, eCol_Name);
1295 first = false;
1297 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1299 break;
1303 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1305 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1306 if(!(*i)->IsFrom(from))
1307 return false;
1308 return true;
1311 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1313 if (pMsg->message == WM_KEYDOWN)
1315 switch (pMsg->wParam)
1317 /* case VK_RETURN:
1319 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1321 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1323 PostMessage(WM_COMMAND, IDOK);
1325 return TRUE;
1328 break;
1329 */ case VK_F2:
1331 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1333 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1334 if (pos)
1335 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1338 break;
1340 case VK_F5:
1342 Refresh();
1344 break;
1345 case L'E':
1347 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1349 m_ctrlFilter.SetSel(0, -1, FALSE);
1350 m_ctrlFilter.SetFocus();
1351 return TRUE;
1354 break;
1355 case VK_ESCAPE:
1356 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER) && m_ctrlFilter.GetWindowTextLength())
1358 OnClickedCancelFilter(NULL, NULL);
1359 return TRUE;
1361 break;
1366 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1369 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1371 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1372 *pResult = 0;
1374 if(m_currSortCol == pNMLV->iSubItem)
1375 m_currSortDesc = !m_currSortDesc;
1376 else
1378 m_currSortCol = pNMLV->iSubItem;
1379 m_currSortDesc = false;
1382 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1383 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1385 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1388 void CBrowseRefsDlg::OnDestroy()
1390 if (!m_bPickedRefSet)
1391 m_pickedRef = GetSelectedRef(true, false);
1393 CResizableStandAloneDialog::OnDestroy();
1396 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1398 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1399 *pResult = 0;
1401 auto item = GetListEntry(pNMListView->iItem);
1402 if (item && pNMListView->uNewState == LVIS_SELECTED)
1403 m_sLastSelected = item->GetRefName();
1405 UpdateInfoLabel();
1408 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1410 *pResult = 0;
1412 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1413 return;
1414 EndDialog(IDOK);
1417 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1419 CBrowseRefsDlg dlg(CString(), nullptr);
1421 if(initialRef.IsEmpty())
1422 initialRef = L"HEAD";
1423 dlg.m_initialRef = initialRef;
1424 dlg.m_pickRef_Kind = pickRef_Kind;
1425 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1427 if(dlg.DoModal() != IDOK)
1428 return CString();
1430 return dlg.m_pickedRef;
1433 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo& refComboBox, int pickRef_Kind /* = gPickRef_All*/, int useShortName /* = gPickRef_Head*/)
1435 CString origRef;
1436 refComboBox.GetLBText(refComboBox.GetCurSel(), origRef);
1437 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1438 if(resultRef.IsEmpty())
1439 return false;
1441 if (useShortName)
1443 CGit::REF_TYPE refType;
1444 CString shortName = CGit::GetShortName(resultRef, &refType);
1445 switch (refType)
1447 case CGit::REF_TYPE::LOCAL_BRANCH:
1448 if (useShortName & gPickRef_Head)
1449 resultRef = shortName;
1450 break;
1451 case CGit::REF_TYPE::ANNOTATED_TAG:
1452 case CGit::REF_TYPE::TAG:
1453 if (useShortName & gPickRef_Tag)
1454 resultRef = shortName;
1455 break;
1456 case CGit::REMOTE_BRANCH:
1457 if (useShortName & gPickRef_Remote)
1458 resultRef = shortName;
1459 break;
1463 CGit::StripRefName(resultRef);
1465 refComboBox.AddString(resultRef);
1467 return true;
1470 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1472 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1473 *pResult = FALSE;
1475 if (!pDispInfo->item.pszText)
1476 return; //User canceled changing
1478 auto pTree = GetListEntry(pDispInfo->item.iItem);
1479 if(!pTree->IsFrom(L"refs/heads/"))
1481 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1482 return;
1485 CString selectedTreeRef;
1486 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1487 if (!hTree)
1489 auto pTree2 = GetTreeEntry(hTree);
1490 selectedTreeRef = pTree2->GetRefName();
1493 CString origName = pTree->GetRefName().Mid(11);
1495 CString newName;
1496 if (m_pListCtrlRoot)
1497 newName = m_pListCtrlRoot->GetRefName() + L'/';
1498 newName += pDispInfo->item.pszText;
1500 if (!CStringUtils::StartsWith(newName, L"refs/heads/"))
1502 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1503 return;
1506 CString newNameTrunced = newName.Mid(11);
1508 CString errorMsg;
1509 if (g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L'"', &errorMsg, CP_UTF8) != 0)
1511 MessageBox(errorMsg, L"TortoiseGit", MB_OK | MB_ICONERROR);
1512 return;
1514 //Do as if it failed to rename. Let Refresh() do the job.
1515 //*pResult = TRUE;
1517 Refresh(selectedTreeRef);
1519 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1521 // AfxMessageBox(W_csPopup);
1524 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1526 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1527 *pResult = FALSE;
1529 auto pTree = GetListEntry(pDispInfo->item.iItem);
1530 if(!pTree->IsFrom(L"refs/heads/"))
1532 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1533 return;
1537 void CBrowseRefsDlg::OnEnChangeEditFilter()
1539 SetTimer(IDT_FILTER, 1000, nullptr);
1542 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1544 if (nIDEvent == IDT_FILTER)
1546 KillTimer(IDT_FILTER);
1547 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1550 CResizableStandAloneDialog::OnTimer(nIDEvent);
1553 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1555 // FIXME: x64 version would get this function called with unexpected parameters.
1556 if (!lParam)
1557 return 0;
1559 RECT * rect = (LPRECT)lParam;
1560 CPoint point;
1561 CString temp;
1562 point = CPoint(rect->left, rect->bottom);
1563 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1564 CMenu popup;
1565 if (popup.CreatePopupMenu())
1567 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1568 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1570 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1571 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1573 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1574 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1576 temp.LoadString(IDS_LOG_FILTER_REVS);
1577 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1579 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1580 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1582 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1583 if (selection != 0)
1585 if (selection == LOGFILTER_TOGGLE)
1586 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1587 else
1588 m_SelectedFilters ^= selection;
1589 SetFilterCueText();
1590 SetTimer(IDT_FILTER, 1000, nullptr);
1593 return 0L;
1596 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1598 KillTimer(LOGFILTER_TIMER);
1599 m_ctrlFilter.SetWindowText(L"");
1600 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1601 return 0L;
1604 void CBrowseRefsDlg::SetFilterCueText()
1606 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1607 temp += L' ';
1609 if (m_SelectedFilters & LOGFILTER_REFNAME)
1610 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1612 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1614 if (!CStringUtils::EndsWith(temp, L' '))
1615 temp += L", ";
1616 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1619 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1621 if (!CStringUtils::EndsWith(temp, L' '))
1622 temp += L", ";
1623 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1626 if (m_SelectedFilters & LOGFILTER_REVS)
1628 if (!CStringUtils::EndsWith(temp, L' '))
1629 temp += L", ";
1630 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1633 // to make the cue banner text appear more to the right of the edit control
1634 temp = L" " + temp;
1635 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1638 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1640 m_pickedRef = g_Git.GetCurrentBranch(true);
1641 m_bPickedRefSet = true;
1642 OnOK();
1645 void CBrowseRefsDlg::UpdateInfoLabel()
1647 CString temp;
1648 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1649 SetDlgItemText(IDC_INFOLABEL, temp);
1652 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1654 UpdateData(TRUE);
1655 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1656 Refresh();
1659 CShadowTree* CBrowseRefsDlg::GetListEntry(int index)
1661 auto entry = reinterpret_cast<CShadowTree*>(m_ListRefLeafs.GetItemData(index));
1662 ASSERT(entry);
1663 return entry;
1666 CShadowTree* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem)
1668 auto entry = reinterpret_cast<CShadowTree*>(m_RefTreeCtrl.GetItemData(treeItem));
1669 ASSERT(entry);
1670 return entry;
1674 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()
1676 Refresh();