Don't import ogdf namespace
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobbc921616896b2119aa7fce882a9fb71254c6999e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2018 - 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((int)wcslen(L"refs/remotes/"));
46 else if (CStringUtils::StartsWith(ref, L"remotes/"))
47 ref = ref.Mid((int)wcslen(L"remotes/"));
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)
170 , m_bWantPick(false)
172 // get short/long datetime setting from registry
173 DWORD RegUseShortDateFormat = CRegDWORD(L"Software\\TortoiseGit\\LogDateFormat", TRUE);
174 if (RegUseShortDateFormat)
175 m_DateFormat = DATE_SHORTDATE;
176 else
177 m_DateFormat = DATE_LONGDATE;
178 // get relative time display setting from registry
179 DWORD regRelativeTimes = CRegDWORD(L"Software\\TortoiseGit\\RelativeTimes", FALSE);
180 m_bRelativeTimes = (regRelativeTimes != 0);
182 m_regIncludeNestedRefs = CRegDWORD(L"Software\\TortoiseGit\\RefBrowserIncludeNestedRefs", TRUE);
184 m_currSortCol = m_regCurrSortCol;
185 m_currSortDesc = m_regCurrSortDesc == TRUE;
188 CBrowseRefsDlg::~CBrowseRefsDlg()
190 m_regCurrSortCol = m_currSortCol;
191 m_regCurrSortDesc = m_currSortDesc;
194 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
196 CDialog::DoDataExchange(pDX);
197 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
198 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
199 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
200 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
201 DDX_Control(pDX, IDC_BROWSE_REFS_BRANCHFILTER, m_cBranchFilter);
205 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
206 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
207 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
208 ON_WM_CONTEXTMENU()
209 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
210 ON_WM_DESTROY()
211 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
212 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
213 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
214 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
215 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
216 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
217 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
218 ON_WM_TIMER()
219 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
220 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
221 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter)
222 END_MESSAGE_MAP()
225 // CBrowseRefsDlg message handlers
227 void CBrowseRefsDlg::OnBnClickedOk()
229 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
231 OnOK();
232 return;
235 CIconMenu popupMenu;
236 popupMenu.CreatePopupMenu();
238 std::vector<CShadowTree*> selectedLeafs;
239 GetSelectedLeaves(selectedLeafs);
241 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
242 popupMenu.SetDefaultItem(1);
243 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."), IDI_LOG);
244 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."), IDI_LOG);
246 RECT rect;
247 GetDlgItem(IDOK)->GetWindowRect(&rect);
248 TPMPARAMS params;
249 params.cbSize = sizeof(TPMPARAMS);
250 params.rcExclude = rect;
251 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_VERTICAL, rect.left, rect.top, this, &params);
252 switch (selection)
254 case 1:
255 OnOK();
256 break;
257 case 2:
259 m_bPickedRefSet = true;
260 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..");
261 OnOK();
263 break;
264 case 3:
266 m_bPickedRefSet = true;
267 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"...");
268 OnOK();
270 break;
271 default:
272 break;
276 BOOL CBrowseRefsDlg::OnInitDialog()
278 CResizableStandAloneDialog::OnInitDialog();
279 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
281 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
282 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
283 m_ctrlFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
284 SetFilterCueText();
286 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
287 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
288 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
289 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_CENTER);
290 AddAnchor(IDC_BROWSE_REFS_BRANCHFILTER, TOP_CENTER, TOP_RIGHT);
291 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
292 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
293 AddAnchor(IDHELP, BOTTOM_RIGHT);
295 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER);
296 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
297 static int columnWidths[] = { 0, 0, 0, 300, 0, 0, 80 };
298 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
299 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
300 m_ListRefLeafs.m_bAllowHiding = false;
301 m_ListRefLeafs.Init();
302 m_ListRefLeafs.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
303 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
304 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, L"BrowseRefs", _countof(columnNames), columnWidths);
305 m_bPickedRefSet = false;
307 AddAnchor(IDOK,BOTTOM_RIGHT);
308 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
309 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
311 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
312 UpdateData(FALSE);
314 Refresh(m_initialRef);
316 EnableSaveRestore(L"BrowseRefs");
318 CString sWindowTitle;
319 GetWindowText(sWindowTitle);
320 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
322 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
324 if (m_bPickOne)
325 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
327 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_ALL)));
328 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED)));
329 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED)));
330 m_cBranchFilter.SetCurSel(0);
332 m_ListRefLeafs.SetFocus();
333 return FALSE;
336 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
338 int posSlash=nameLeft.Find('/');
339 CString nameSub;
340 if(posSlash<0)
342 nameSub=nameLeft;
343 nameLeft.Empty();//Nothing left
345 else
347 nameSub=nameLeft.Left(posSlash);
348 nameLeft=nameLeft.Mid(posSlash+1);
350 if(nameSub.IsEmpty())
351 return nullptr;
353 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
354 return nullptr;
356 CShadowTree& nextNode=m_ShadowTree[nameSub];
357 nextNode.m_csRefName=nameSub;
358 nextNode.m_pParent=this;
359 return &nextNode;
362 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
364 if(IsLeaf())
366 if (CStringUtils::EndsWith(partialRefName, m_csRefName))
368 //Match of leaf name. Try match on total name.
369 CString totalRefName = GetRefName();
370 if (CStringUtils::EndsWith(totalRefName, partialRefName))
371 return this; //Also match. Found.
374 else
376 //Not a leaf. Search all nodes.
377 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
379 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
380 if (pSubtree)
381 return pSubtree; //Found
384 return nullptr; //Not found
387 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
389 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
390 //List ctrl selection?
391 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
393 //A leaf is selected
394 return GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos))->GetRefName();
396 else if (pos && !pickFirstSelIfMultiSel)
398 // at least one leaf is selected
399 CString refs;
400 int index;
401 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
403 CString ref = GetListEntry(index)->GetRefName();
404 if (CStringUtils::StartsWith(ref, L"refs/"))
405 ref = ref.Mid((int)wcslen(L"refs/"));
406 if (CStringUtils::StartsWith(ref, L"heads/"))
407 ref = ref.Mid((int)wcslen(L"heads/"));
408 refs += ref + L' ';
410 return refs.Trim();
412 else if(!onlyIfLeaf)
414 //Tree ctrl selection?
415 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
416 if (hTree)
417 return GetTreeEntry(hTree)->GetRefName();
419 return CString();//None
422 void CBrowseRefsDlg::Refresh(CString selectRef)
424 remotes.clear();
425 if (g_Git.GetRemoteList(remotes))
426 MessageBox(CGit::GetLibGit2LastErr(L"Could not get a list of remotes."), L"TortoiseGit", MB_ICONERROR);
428 if(!selectRef.IsEmpty())
430 if (selectRef == L"HEAD")
432 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
433 selectRef.Empty();
434 else
435 selectRef = L"refs/heads/" + selectRef;
438 else
439 selectRef = GetSelectedRef(false, true);
441 m_RefTreeCtrl.DeleteAllItems();
442 m_ListRefLeafs.DeleteAllItems();
443 m_TreeRoot.m_ShadowTree.clear();
444 m_TreeRoot.m_csRefName = L"refs";
445 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs");
446 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
448 CString err;
449 MAP_REF_GITREVREFBROWSER refMap;
450 if (GitRevRefBrowser::GetGitRevRefMap(refMap, m_cBranchFilter.GetCurSel(), err, [&](const CString& refName)
452 //Use ref based on m_pickRef_Kind
453 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
454 return false; //Skip
455 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
456 return false; //Skip
457 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
458 return false; //Skip
459 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
460 return false;
461 return true;
464 MessageBox(L"Get refs failed:" + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
467 //Populate ref tree
468 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
470 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
471 GitRevRefBrowser ref = iterRefMap->second;
473 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
474 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
475 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
476 treeLeaf.m_csSubject = ref.GetSubject();
477 treeLeaf.m_csAuthor = ref.GetAuthorName();
478 treeLeaf.m_csDate = ref.GetAuthorDate();
479 treeLeaf.m_csDescription = ref.m_Description;
482 // always expand the tree first
483 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
485 // try exact match first
486 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
487 SelectRef(selectRef, false);
490 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
492 if(!bExactMatch)
494 CString newRefName = GetFullRefName(refName);
495 if(!newRefName.IsEmpty())
496 refName = newRefName;
497 //else refName is not a valid ref. Try to select as good as possible.
499 if (!CStringUtils::StartsWith(refName, L"refs"))
500 return false; // Not a ref name
502 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
503 if (treeLeafHead.m_hTree)
505 //Not a leaf. Select tree node and return
506 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
507 return true;
510 if (!treeLeafHead.m_pParent)
511 return false; //Weird... should not occur.
513 //This is the current head.
514 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
516 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
518 auto pCurrShadowTree = GetListEntry(indexPos);
519 if(pCurrShadowTree == &treeLeafHead)
521 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
522 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
526 return true;
529 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
531 if (!pTreePos)
533 if (CStringUtils::StartsWith(refName, L"refs/"))
534 refName = refName.Mid((int)wcslen(L"refs/"));
535 pTreePos=&m_TreeRoot;
537 if(refName.IsEmpty())
538 return *pTreePos;//Found leaf
540 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
541 if (!pNextTree)
543 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
544 ASSERT(!bCreateIfNotExist);
545 return *pTreePos;
548 if(!refName.IsEmpty())
550 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
551 //Leafs are for the list control.
552 if (!pNextTree->m_hTree)
554 //New tree. Create node in control.
555 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree);
556 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
560 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
564 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
566 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
567 *pResult = 0;
569 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
572 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
574 m_ListRefLeafs.DeleteAllItems();
576 auto pTree = GetTreeEntry(treeNode);
577 if (!pTree)
578 return;
580 FillListCtrlForShadowTree(pTree,L"",true);
581 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
582 m_ListRefLeafs.AdjustColumnWidths();
585 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
587 if(pTree->IsLeaf())
589 CString filter;
590 m_ctrlFilter.GetWindowText(filter);
591 filter.MakeLower();
592 bool positive = filter[0] != '!';
593 if (!positive)
594 filter = filter.Mid((int)wcslen(L"!"));
595 CString ref = refNamePrefix + pTree->m_csRefName;
596 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == L"refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
598 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
600 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
601 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
602 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
603 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : L"");
604 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
605 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
606 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
607 CString descrition = pTree->m_csDescription;
608 descrition.Replace(L'\n', L' ');
609 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
612 else
614 CString csThisName;
615 if (!isFirstLevel && !m_bIncludeNestedRefs)
616 return;
617 else if (!isFirstLevel)
618 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
619 else
620 m_pListCtrlRoot = pTree;
621 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
623 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
626 if (isFirstLevel)
628 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
629 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
631 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
635 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
637 if (m_SelectedFilters & LOGFILTER_REFNAME)
639 CString msg = ref;
640 msg = msg.MakeLower();
642 if (msg.Find(filter) >= 0)
643 return positive;
646 if (m_SelectedFilters & LOGFILTER_SUBJECT)
648 CString msg = pTree->m_csSubject;
649 msg = msg.MakeLower();
651 if (msg.Find(filter) >= 0)
652 return positive;
655 if (m_SelectedFilters & LOGFILTER_AUTHORS)
657 CString msg = pTree->m_csAuthor;
658 msg = msg.MakeLower();
660 if (msg.Find(filter) >= 0)
661 return positive;
664 if (m_SelectedFilters & LOGFILTER_REVS)
666 CString msg = pTree->m_csRefHash;
667 msg = msg.MakeLower();
669 if (msg.Find(filter) >= 0)
670 return positive;
672 return !positive;
675 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
677 ASSERT(!leafs.empty());
679 CString csMessage;
680 UINT mbIcon=MB_ICONQUESTION;
682 bool bIsRemoteBranch = false;
683 bool bIsBranch = false;
684 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
685 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
687 if(bIsBranch)
689 if(leafs.size() == 1)
691 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
692 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
694 //Check if branch is fully merged in HEAD
695 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), L"HEAD"))
697 csMessage += L"\r\n\r\n";
698 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
699 mbIcon = MB_ICONWARNING;
702 if(bIsRemoteBranch)
704 csMessage += L"\r\n\r\n";
705 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
706 mbIcon = MB_ICONWARNING;
709 else
711 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
713 csMessage += L"\r\n\r\n";
714 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
715 mbIcon = MB_ICONWARNING;
717 if(bIsRemoteBranch)
719 csMessage += L"\r\n\r\n";
720 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
721 mbIcon = MB_ICONWARNING;
726 else if(leafs[0]->IsFrom(L"refs/tags/"))
728 if(leafs.size() == 1)
730 CString tagToDelete = leafs[0]->GetRefName().Mid((int)wcslen(L"refs/tags/"));
731 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
733 else
734 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
737 return MessageBox(csMessage, L"TortoiseGit", MB_YESNO | mbIcon) == IDYES;
740 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
742 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
743 if(!DoDeleteRef((*i)->GetRefName()))
744 return false;
745 return true;
748 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
750 bool bIsRemoteBranch = false;
751 bool bIsBranch = false;
752 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
754 bIsBranch = true;
755 bIsRemoteBranch = true;
757 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
758 bIsBranch = true;
760 if (bIsRemoteBranch)
762 CString branchToDelete = completeRefName.Mid((int)wcslen(L"refs/remotes/"));
763 CString remoteName, remoteBranchToDelete;
764 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
765 return false;
767 if (CAppUtils::IsSSHPutty())
768 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName);
770 CSysProgressDlg sysProgressDlg;
771 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
772 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
773 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
774 sysProgressDlg.SetShowProgressBar(false);
775 sysProgressDlg.ShowModal(this, true);
777 STRING_VECTOR list;
778 list.push_back(L"refs/heads/" + remoteBranchToDelete);
779 if (g_Git.DeleteRemoteRefs(remoteName, list))
781 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
782 sysProgressDlg.Stop();
783 BringWindowToTop();
784 return false;
786 sysProgressDlg.Stop();
787 BringWindowToTop();
789 else if (bIsBranch)
791 if (g_Git.DeleteRef(completeRefName))
793 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
794 return false;
797 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
799 if (g_Git.DeleteRef(completeRefName))
801 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
802 return false;
805 return true;
808 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
810 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
811 if (!pLeaf)
812 return CString();
813 return pLeaf->GetRefName();
817 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
819 if (pWndFrom == &m_RefTreeCtrl)
820 OnContextMenu_RefTreeCtrl(point);
823 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
825 CPoint clientPoint=point;
826 m_RefTreeCtrl.ScreenToClient(&clientPoint);
828 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
829 if (hTreeItem)
830 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
832 VectorPShadowTree tree;
833 ShowContextMenu(point,hTreeItem,tree);
836 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
838 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
839 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
840 while (pos)
842 selectedLeafs.push_back(GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos)));
846 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
848 std::vector<CShadowTree*> selectedLeafs;
849 GetSelectedLeaves(selectedLeafs);
850 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
853 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
855 ASSERT(selectedLeafs.size() == 2);
857 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
858 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
859 else
860 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
863 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
865 int i = 0;
866 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
868 if (*it == entry)
869 return i;
871 return -1;
874 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
876 CIconMenu popupMenu;
877 popupMenu.CreatePopupMenu();
879 bool bAddSeparator = false;
880 CString remoteName;
882 if(selectedLeafs.size()==1)
884 bAddSeparator = true;
886 bool bShowReflogOption = false;
887 bool bShowFetchOption = false;
888 bool bShowRenameOption = false;
889 bool bShowCreateBranchOption = false;
890 bool bShowEditBranchDescriptionOption = false;
892 CString fetchFromCmd;
894 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
896 bShowReflogOption = true;
897 bShowRenameOption = true;
898 bShowEditBranchDescriptionOption = true;
900 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
902 bShowReflogOption = true;
903 bShowFetchOption = true;
904 bShowCreateBranchOption = true;
906 CString remoteBranch;
907 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
908 bShowFetchOption = false;
909 else
910 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
913 if (m_bWantPick)
915 popupMenu.AppendMenuIcon(eCmd_Select, IDS_SELECT);
916 popupMenu.AppendMenu(MF_SEPARATOR);
918 popupMenu.AppendMenuIcon(eCmd_ViewLog, IDS_MENULOG, IDI_LOG);
919 popupMenu.SetDefaultItem(0, TRUE);
920 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
921 if(bShowReflogOption)
922 popupMenu.AppendMenuIcon(eCmd_ShowReflog, IDS_MENUREFLOG, IDI_LOG);
924 if (m_bHasWC)
926 popupMenu.AppendMenu(MF_SEPARATOR);
927 popupMenu.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
930 popupMenu.AppendMenu(MF_SEPARATOR);
931 bAddSeparator = false;
933 if(bShowFetchOption)
935 bAddSeparator = true;
936 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_UPDATE);
939 if(bAddSeparator)
940 popupMenu.AppendMenu(MF_SEPARATOR);
942 bAddSeparator = false;
943 if (m_bHasWC)
945 CString str;
946 if (selectedLeafs[0]->GetRefName() != L"refs/heads/" + g_Git.GetCurrentBranch())
948 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
949 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
951 popupMenu.AppendMenuIcon(eCmd_Switch, IDS_SWITCH_TO_THIS, IDI_SWITCH);
952 popupMenu.AppendMenu(MF_SEPARATOR);
955 if(bShowCreateBranchOption)
957 bAddSeparator = true;
958 popupMenu.AppendMenuIcon(eCmd_CreateBranch, IDS_MENUBRANCH, IDI_COPY);
961 if (bShowEditBranchDescriptionOption)
963 bAddSeparator = true;
964 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, IDS_PROC_BROWSEREFS_EDITDESCRIPTION, IDI_RENAME);
966 if(bShowRenameOption)
968 bAddSeparator = true;
969 popupMenu.AppendMenuIcon(eCmd_Rename, IDS_PROC_BROWSEREFS_RENAME, IDI_RENAME);
972 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
974 if (bAddSeparator)
975 popupMenu.AppendMenu(MF_SEPARATOR);
976 bAddSeparator = true;
977 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
978 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH);
979 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH);
982 else if(selectedLeafs.size() == 2)
984 bAddSeparator = true;
985 popupMenu.AppendMenuIcon(eCmd_Diff, IDS_PROC_BROWSEREFS_COMPAREREFS, IDI_DIFF);
986 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
987 CString menu;
988 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."));
989 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
990 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."));
991 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
994 if(!selectedLeafs.empty())
996 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
998 if(bAddSeparator)
999 popupMenu.AppendMenu(MF_SEPARATOR);
1000 CString menuItemName;
1001 if(selectedLeafs.size() == 1)
1002 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1003 else
1004 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1006 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1007 bAddSeparator = true;
1009 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1011 if(bAddSeparator)
1012 popupMenu.AppendMenu(MF_SEPARATOR);
1013 CString menuItemName;
1014 if(selectedLeafs.size() == 1)
1015 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1016 else
1017 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1019 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1020 bAddSeparator = true;
1022 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1024 if(bAddSeparator)
1025 popupMenu.AppendMenu(MF_SEPARATOR);
1026 CString menuItemName;
1027 if(selectedLeafs.size() == 1)
1028 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1029 else
1030 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1032 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1033 bAddSeparator = true;
1038 if (hTreePos && selectedLeafs.empty())
1040 auto pTree = GetTreeEntry(hTreePos);
1041 if(pTree->IsFrom(L"refs/remotes"))
1043 if(bAddSeparator)
1044 popupMenu.AppendMenu(MF_SEPARATOR);
1045 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, IDS_PROC_BROWSEREFS_MANAGEREMOTES, IDI_SETTINGS);
1046 bAddSeparator = true;
1047 if(selectedLeafs.empty())
1049 CString remoteBranch;
1050 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1051 remoteName.Empty();
1052 int pos = findVectorPosition(remotes, remoteName);
1053 if (pos >= 0)
1055 CString temp;
1056 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1057 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_UPDATE);
1059 temp.LoadString(IDS_DELETEREMOTETAG);
1060 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1063 bAddSeparator = false;
1065 if(pTree->IsFrom(L"refs/heads"))
1067 if(bAddSeparator)
1068 popupMenu.AppendMenu(MF_SEPARATOR);
1069 CString temp;
1070 temp.LoadString(IDS_MENUBRANCH);
1071 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1072 bAddSeparator = false;
1074 if(pTree->IsFrom(L"refs/tags"))
1076 if(bAddSeparator)
1077 popupMenu.AppendMenu(MF_SEPARATOR);
1078 popupMenu.AppendMenuIcon(eCmd_CreateTag, IDS_MENUTAG, IDI_TAG);
1079 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, IDS_PROC_BROWSEREFS_DELETEALLTAGS, IDI_DELETE);
1080 if (!remotes.empty())
1082 popupMenu.AppendMenu(MF_SEPARATOR);
1083 int i = 0;
1084 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1086 CString temp;
1087 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1088 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1091 bAddSeparator = false;
1094 if (bAddSeparator)
1095 popupMenu.AppendMenu(MF_SEPARATOR);
1096 popupMenu.AppendMenuIcon(eCmd_Copy, IDS_COPY_REF_NAMES, IDI_COPYCLIP);
1098 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
1099 switch ((eCmd)(selection & 0xFFFF))
1101 case eCmd_Select:
1102 EndDialog(IDOK);
1103 break;
1104 case eCmd_ViewLog:
1106 CString sCmd;
1107 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1108 CAppUtils::RunTortoiseGitProc(sCmd);
1110 break;
1111 case eCmd_ViewLogRange:
1113 CString sCmd;
1114 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."));
1115 CAppUtils::RunTortoiseGitProc(sCmd);
1117 break;
1118 case eCmd_ViewLogRangeReachableFromOnlyOne:
1120 CString sCmd;
1121 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."));
1122 CAppUtils::RunTortoiseGitProc(sCmd);
1124 break;
1125 case eCmd_RepoBrowser:
1126 CAppUtils::RunTortoiseGitProc(L"/command:repobrowser /path:\"" + g_Git.m_CurrentDir + L"\" /rev:" + selectedLeafs[0]->GetRefName());
1127 break;
1128 case eCmd_DeleteBranch:
1129 case eCmd_DeleteRemoteBranch:
1131 if(ConfirmDeleteRef(selectedLeafs))
1132 DoDeleteRefs(selectedLeafs);
1133 Refresh();
1135 break;
1136 case eCmd_DeleteTag:
1138 if(ConfirmDeleteRef(selectedLeafs))
1139 DoDeleteRefs(selectedLeafs);
1140 Refresh();
1142 break;
1143 case eCmd_ShowReflog:
1145 CRefLogDlg refLogDlg(this);
1146 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1147 refLogDlg.DoModal();
1149 break;
1150 case eCmd_Fetch:
1152 CAppUtils::Fetch(GetSafeHwnd(), remoteName);
1153 Refresh();
1155 break;
1156 case eCmd_DeleteRemoteTag:
1158 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1159 int remoteInx = selection >> 16;
1160 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1161 return;
1162 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1163 deleteRemoteTagDlg.DoModal();
1165 break;
1166 case eCmd_Merge:
1168 CString ref = selectedLeafs[0]->GetRefName();
1169 CAppUtils::Merge(GetSafeHwnd(), &ref);
1171 break;
1172 case eCmd_Switch:
1174 CAppUtils::Switch(GetSafeHwnd(), selectedLeafs[0]->GetRefName());
1176 break;
1177 case eCmd_Rename:
1179 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1180 if (pos)
1181 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1183 break;
1184 case eCmd_AddRemote:
1186 CAddRemoteDlg(this).DoModal();
1187 Refresh();
1189 break;
1190 case eCmd_ManageRemotes:
1192 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1193 // CSettingGitRemote W_Remotes(m_cmdPath);
1194 // W_Remotes.DoModal();
1195 Refresh();
1197 break;
1198 case eCmd_CreateBranch:
1200 CString* commitHash = nullptr;
1201 if (selectedLeafs.size() == 1)
1202 commitHash = &(selectedLeafs[0]->m_csRefHash);
1203 CAppUtils::CreateBranchTag(GetSafeHwnd(), false, commitHash);
1204 Refresh();
1206 break;
1207 case eCmd_CreateTag:
1209 CAppUtils::CreateBranchTag(GetSafeHwnd(), true);
1210 Refresh();
1212 break;
1213 case eCmd_DeleteAllTags:
1215 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1217 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1218 selectedLeafs.push_back(GetListEntry(i));
1220 if (ConfirmDeleteRef(selectedLeafs))
1221 DoDeleteRefs(selectedLeafs);
1222 Refresh();
1224 break;
1225 case eCmd_Diff:
1227 CFileDiffDlg dlg;
1228 dlg.SetDiff(
1229 nullptr,
1230 selectedLeafs[0]->GetRefName() + L"^{}",
1231 selectedLeafs[1]->GetRefName() + L"^{}");
1232 dlg.DoModal();
1234 break;
1235 case eCmd_UnifiedDiff:
1237 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1239 break;
1240 case eCmd_DiffWC:
1242 CString sCmd;
1243 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s^{} /revision2:%s", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)selectedLeafs[0]->GetRefName(), (LPCTSTR)GitRev::GetWorkingCopy());
1244 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
1245 sCmd += L" /alternative";
1247 CAppUtils::RunTortoiseGitProc(sCmd);
1249 break;
1250 case eCmd_EditBranchDescription:
1252 CInputDlg dlg;
1253 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1254 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1255 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1256 dlg.m_bUseLogWidth = true;
1257 if(dlg.DoModal() == IDOK)
1259 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1260 Refresh();
1263 break;
1264 case eCmd_UpstreamDrop:
1266 CString key;
1267 key.Format(L"branch.%s.remote", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1268 g_Git.UnsetConfigValue(key);
1269 key.Format(L"branch.%s.merge", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1270 g_Git.UnsetConfigValue(key);
1272 Refresh();
1273 break;
1274 case eCmd_UpstreamSet:
1276 CString newRef = CBrowseRefsDlg::PickRef(false, L"", gPickRef_Remote, false);
1277 if (newRef.IsEmpty() || newRef.Find(L"refs/remotes/") != 0)
1278 return;
1279 CString remote, branch;
1280 if (SplitRemoteBranchName(newRef, remote, branch))
1281 return;
1282 CString key;
1283 key.Format(L"branch.%s.remote", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1284 g_Git.SetConfigValue(key, remote);
1285 key.Format(L"branch.%s.merge", (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1286 g_Git.SetConfigValue(key, L"refs/heads/" + branch);
1287 Refresh();
1289 break;
1290 case eCmd_Copy:
1292 CString sClipdata;
1293 bool first = true;
1294 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1295 while (pos)
1297 auto index = m_ListRefLeafs.GetNextSelectedItem(pos);
1298 if (!first)
1299 sClipdata += L"\r\n";
1300 sClipdata += m_ListRefLeafs.GetItemText(index, eCol_Name);
1301 first = false;
1303 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1305 break;
1309 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1311 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1312 if(!(*i)->IsFrom(from))
1313 return false;
1314 return true;
1317 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1319 if (pMsg->message == WM_KEYDOWN)
1321 switch (pMsg->wParam)
1323 /* case VK_RETURN:
1325 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1327 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1329 PostMessage(WM_COMMAND, IDOK);
1331 return TRUE;
1334 break;
1335 */ case VK_F2:
1337 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1339 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1340 if (pos)
1341 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1344 break;
1346 case VK_F5:
1348 Refresh();
1350 break;
1351 case L'E':
1353 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1355 m_ctrlFilter.SetSel(0, -1, FALSE);
1356 m_ctrlFilter.SetFocus();
1357 return TRUE;
1360 break;
1361 case VK_ESCAPE:
1362 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER) && m_ctrlFilter.GetWindowTextLength())
1364 OnClickedCancelFilter(NULL, NULL);
1365 return TRUE;
1367 break;
1372 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1375 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1377 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1378 *pResult = 0;
1380 if(m_currSortCol == pNMLV->iSubItem)
1381 m_currSortDesc = !m_currSortDesc;
1382 else
1384 m_currSortCol = pNMLV->iSubItem;
1385 m_currSortDesc = false;
1388 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1389 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1391 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1394 void CBrowseRefsDlg::OnDestroy()
1396 if (!m_bPickedRefSet)
1397 m_pickedRef = GetSelectedRef(true, false);
1399 CResizableStandAloneDialog::OnDestroy();
1402 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1404 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1405 *pResult = 0;
1407 auto item = GetListEntry(pNMListView->iItem);
1408 if (item && pNMListView->uNewState == LVIS_SELECTED)
1409 m_sLastSelected = item->GetRefName();
1411 UpdateInfoLabel();
1414 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1416 *pResult = 0;
1418 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1419 return;
1421 if (m_bWantPick)
1423 EndDialog(IDOK);
1424 return;
1427 CString sCmd;
1428 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(GetSelectedRef(true, false)));
1429 CAppUtils::RunTortoiseGitProc(sCmd);
1432 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1434 CBrowseRefsDlg dlg(CString(), nullptr);
1436 if(initialRef.IsEmpty())
1437 initialRef = L"HEAD";
1438 dlg.m_bWantPick = true;
1439 dlg.m_initialRef = initialRef;
1440 dlg.m_pickRef_Kind = pickRef_Kind;
1441 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1443 if(dlg.DoModal() != IDOK)
1444 return CString();
1446 return dlg.m_pickedRef;
1449 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo& refComboBox, int pickRef_Kind /* = gPickRef_All*/, int useShortName /* = gPickRef_Head*/)
1451 CString origRef;
1452 refComboBox.GetLBText(refComboBox.GetCurSel(), origRef);
1453 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1454 if(resultRef.IsEmpty())
1455 return false;
1457 if (useShortName)
1459 CGit::REF_TYPE refType;
1460 CString shortName = CGit::GetShortName(resultRef, &refType);
1461 switch (refType)
1463 case CGit::REF_TYPE::LOCAL_BRANCH:
1464 if (useShortName & gPickRef_Head)
1465 resultRef = shortName;
1466 break;
1467 case CGit::REF_TYPE::ANNOTATED_TAG:
1468 case CGit::REF_TYPE::TAG:
1469 if (useShortName & gPickRef_Tag)
1470 resultRef = shortName;
1471 break;
1472 case CGit::REMOTE_BRANCH:
1473 if (useShortName & gPickRef_Remote)
1474 resultRef = shortName;
1475 break;
1479 CGit::StripRefName(resultRef);
1481 refComboBox.AddString(resultRef);
1483 return true;
1486 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1488 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1489 *pResult = FALSE;
1491 if (!pDispInfo->item.pszText)
1492 return; //User canceled changing
1494 auto pTree = GetListEntry(pDispInfo->item.iItem);
1495 if(!pTree->IsFrom(L"refs/heads/"))
1497 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1498 return;
1501 CString selectedTreeRef;
1502 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1503 if (!hTree)
1505 auto pTree2 = GetTreeEntry(hTree);
1506 selectedTreeRef = pTree2->GetRefName();
1509 CString origName = pTree->GetRefName().Mid((int)wcslen(L"refs/heads/"));
1511 CString newName;
1512 if (m_pListCtrlRoot)
1513 newName = m_pListCtrlRoot->GetRefName() + L'/';
1514 newName += pDispInfo->item.pszText;
1516 if (!CStringUtils::StartsWith(newName, L"refs/heads/"))
1518 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1519 return;
1522 CString newNameTrunced = newName.Mid((int)wcslen(L"refs/heads/"));
1524 CString errorMsg;
1525 if (g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L'"', &errorMsg, CP_UTF8) != 0)
1527 MessageBox(errorMsg, L"TortoiseGit", MB_OK | MB_ICONERROR);
1528 return;
1530 //Do as if it failed to rename. Let Refresh() do the job.
1531 //*pResult = TRUE;
1533 Refresh(selectedTreeRef);
1535 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1537 // AfxMessageBox(W_csPopup);
1540 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1542 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1543 *pResult = FALSE;
1545 auto pTree = GetListEntry(pDispInfo->item.iItem);
1546 if(!pTree->IsFrom(L"refs/heads/"))
1548 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1549 return;
1553 void CBrowseRefsDlg::OnEnChangeEditFilter()
1555 SetTimer(IDT_FILTER, 1000, nullptr);
1558 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1560 if (nIDEvent == IDT_FILTER)
1562 KillTimer(IDT_FILTER);
1563 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1566 CResizableStandAloneDialog::OnTimer(nIDEvent);
1569 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1571 // FIXME: x64 version would get this function called with unexpected parameters.
1572 if (!lParam)
1573 return 0;
1575 RECT * rect = (LPRECT)lParam;
1576 CPoint point;
1577 CString temp;
1578 point = CPoint(rect->left, rect->bottom);
1579 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1580 CMenu popup;
1581 if (popup.CreatePopupMenu())
1583 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1584 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1586 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1587 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1589 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1590 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1592 temp.LoadString(IDS_LOG_FILTER_REVS);
1593 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1595 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1596 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1598 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1599 if (selection != 0)
1601 if (selection == LOGFILTER_TOGGLE)
1602 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1603 else
1604 m_SelectedFilters ^= selection;
1605 SetFilterCueText();
1606 SetTimer(IDT_FILTER, 1000, nullptr);
1609 return 0L;
1612 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1614 KillTimer(LOGFILTER_TIMER);
1615 m_ctrlFilter.SetWindowText(L"");
1616 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1617 return 0L;
1620 void CBrowseRefsDlg::SetFilterCueText()
1622 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1623 temp += L' ';
1625 if (m_SelectedFilters & LOGFILTER_REFNAME)
1626 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1628 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1630 if (!CStringUtils::EndsWith(temp, L' '))
1631 temp += L", ";
1632 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1635 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1637 if (!CStringUtils::EndsWith(temp, L' '))
1638 temp += L", ";
1639 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1642 if (m_SelectedFilters & LOGFILTER_REVS)
1644 if (!CStringUtils::EndsWith(temp, L' '))
1645 temp += L", ";
1646 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1649 // to make the cue banner text appear more to the right of the edit control
1650 temp = L" " + temp;
1651 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1654 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1656 m_pickedRef = g_Git.GetCurrentBranch(true);
1657 m_bPickedRefSet = true;
1658 OnOK();
1661 void CBrowseRefsDlg::UpdateInfoLabel()
1663 CString temp;
1664 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1665 SetDlgItemText(IDC_INFOLABEL, temp);
1668 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1670 UpdateData(TRUE);
1671 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1672 Refresh();
1675 CShadowTree* CBrowseRefsDlg::GetListEntry(int index)
1677 auto entry = reinterpret_cast<CShadowTree*>(m_ListRefLeafs.GetItemData(index));
1678 ASSERT(entry);
1679 return entry;
1682 CShadowTree* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem)
1684 auto entry = reinterpret_cast<CShadowTree*>(m_RefTreeCtrl.GetItemData(treeItem));
1685 ASSERT(entry);
1686 return entry;
1690 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()
1692 Refresh();