Drop some magic numbers
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobbc1aa07bf67c0a6429aa932675b65855a417c445
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2016 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // BrowseRefsDlg.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
25 #include "LogDlg.h"
26 #include "AddRemoteDlg.h"
27 #include "AppUtils.h"
28 #include "Settings\SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
32 #include "IconMenu.h"
33 #include "FileDiffDlg.h"
34 #include "DeleteRemoteTagDlg.h"
35 #include "UnicodeUtils.h"
36 #include "InputDlg.h"
37 #include "SysProgressDlg.h"
38 #include "LoglistUtils.h"
39 #include "GitRevRefBrowser.h"
40 #include "StringUtils.h"
42 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
44 if (ref.Left(13) == _T("refs/remotes/"))
45 ref = ref.Mid(13);
46 else if (ref.Left(8) == _T("remotes/"))
47 ref = ref.Mid(8);
49 STRING_VECTOR list;
50 int result = g_Git.GetRemoteList(list);
51 if (result != 0)
52 return result;
54 for (size_t i = 0; i < list.size(); ++i)
56 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
58 remote = list[i];
59 branch = ref.Mid(list[i].GetLength() + 1);
60 return 0;
62 if (ref == list[i])
64 remote = list[i];
65 branch.Empty();
66 return 0;
70 return -1;
73 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
75 if (!control)
76 return;
77 // set the sort arrow
78 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
79 HDITEM HeaderItem = {0};
80 HeaderItem.mask = HDI_FORMAT;
81 for (int i=0; i<pHeader->GetItemCount(); ++i)
83 pHeader->GetItem(i, &HeaderItem);
84 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
85 pHeader->SetItem(i, &HeaderItem);
87 if (nColumn >= 0)
89 pHeader->GetItem(nColumn, &HeaderItem);
90 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
91 pHeader->SetItem(nColumn, &HeaderItem);
95 class CRefLeafListCompareFunc
97 public:
98 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
99 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
100 if (m_bSortLogical)
101 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
104 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
106 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
109 int Compare(LPARAM lParam1, LPARAM lParam2)
111 return Compare(
112 (CShadowTree*)m_pList->GetItemData((int)lParam1),
113 (CShadowTree*)m_pList->GetItemData((int)lParam2));
116 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
118 int result=CompareNoDesc(pLeft,pRight);
119 if(m_desc)
120 return -result;
121 return result;
124 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
126 switch(m_col)
128 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
129 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
130 case CBrowseRefsDlg::eCol_Date: return ((pLeft->m_csDate == pRight->m_csDate) ? 0 : ((pLeft->m_csDate > pRight->m_csDate) ? 1 : -1));
131 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
132 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
133 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
134 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
136 return 0;
138 int SortStrCmp(const CString& left, const CString& right)
140 if (m_bSortLogical)
141 return StrCmpLogicalW(left, right);
142 return StrCmpI(left, right);
145 int m_col;
146 bool m_desc;
147 CListCtrl* m_pList;
148 bool m_bSortLogical;
151 // CBrowseRefsDlg dialog
153 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
155 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=nullptr*/)
156 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
157 m_cmdPath(cmdPath),
158 m_currSortCol(0),
159 m_currSortDesc(false),
160 m_regCurrSortCol(L"Software\\TortoiseGit\\RefBrowserSortCol", 0),
161 m_regCurrSortDesc(L"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE),
162 m_initialRef(L"HEAD"),
163 m_pickRef_Kind(gPickRef_All),
164 m_pListCtrlRoot(nullptr),
165 m_bHasWC(true),
166 m_SelectedFilters(LOGFILTER_ALL),
167 m_bPickOne(false),
168 m_bIncludeNestedRefs(TRUE),
169 m_bPickedRefSet(false)
171 // get short/long datetime setting from registry
172 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
173 if (RegUseShortDateFormat)
174 m_DateFormat = DATE_SHORTDATE;
175 else
176 m_DateFormat = DATE_LONGDATE;
177 // get relative time display setting from registry
178 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
179 m_bRelativeTimes = (regRelativeTimes != 0);
181 m_regIncludeNestedRefs = CRegDWORD(_T("Software\\TortoiseGit\\RefBrowserIncludeNestedRefs"), TRUE);
183 m_currSortCol = m_regCurrSortCol;
184 m_currSortDesc = m_regCurrSortDesc == TRUE;
187 CBrowseRefsDlg::~CBrowseRefsDlg()
189 m_regCurrSortCol = m_currSortCol;
190 m_regCurrSortDesc = m_currSortDesc;
193 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
195 CDialog::DoDataExchange(pDX);
196 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
197 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
198 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
199 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
203 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
204 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
205 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
206 ON_WM_CONTEXTMENU()
207 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
208 ON_WM_DESTROY()
209 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
210 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
211 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
212 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
213 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
214 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
215 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
216 ON_WM_TIMER()
217 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
218 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
219 END_MESSAGE_MAP()
222 // CBrowseRefsDlg message handlers
224 void CBrowseRefsDlg::OnBnClickedOk()
226 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
228 OnOK();
229 return;
232 CIconMenu popupMenu;
233 popupMenu.CreatePopupMenu();
235 std::vector<CShadowTree*> selectedLeafs;
236 GetSelectedLeaves(selectedLeafs);
238 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
239 popupMenu.SetDefaultItem(1);
240 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
241 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
243 RECT rect;
244 GetDlgItem(IDOK)->GetWindowRect(&rect);
245 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
246 switch (selection)
248 case 1:
249 OnOK();
250 break;
251 case 2:
253 m_bPickedRefSet = true;
254 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
255 OnOK();
257 break;
258 case 3:
260 m_bPickedRefSet = true;
261 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
262 OnOK();
264 break;
265 default:
266 break;
270 BOOL CBrowseRefsDlg::OnInitDialog()
272 CResizableStandAloneDialog::OnInitDialog();
273 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
275 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
276 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
277 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
278 SetFilterCueText();
280 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
281 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
282 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
283 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
284 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
285 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
286 AddAnchor(IDHELP, BOTTOM_RIGHT);
288 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP);
289 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
290 static int columnWidths[] = { 0, 0, 0, 300, 0, 0, 80 };
291 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
292 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
293 m_ListRefLeafs.m_bAllowHiding = false;
294 m_ListRefLeafs.Init();
295 m_ListRefLeafs.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
296 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
297 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
298 m_bPickedRefSet = false;
300 AddAnchor(IDOK,BOTTOM_RIGHT);
301 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
302 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
304 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
305 UpdateData();
307 Refresh(m_initialRef);
309 EnableSaveRestore(L"BrowseRefs");
311 CString sWindowTitle;
312 GetWindowText(sWindowTitle);
313 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
315 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
317 if (m_bPickOne)
318 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
320 m_ListRefLeafs.SetFocus();
321 return FALSE;
324 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
326 int posSlash=nameLeft.Find('/');
327 CString nameSub;
328 if(posSlash<0)
330 nameSub=nameLeft;
331 nameLeft.Empty();//Nothing left
333 else
335 nameSub=nameLeft.Left(posSlash);
336 nameLeft=nameLeft.Mid(posSlash+1);
338 if(nameSub.IsEmpty())
339 return nullptr;
341 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
342 return nullptr;
344 CShadowTree& nextNode=m_ShadowTree[nameSub];
345 nextNode.m_csRefName=nameSub;
346 nextNode.m_pParent=this;
347 return &nextNode;
350 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
352 if(IsLeaf())
354 if(m_csRefName.GetLength() > partialRefName.GetLength())
355 return nullptr;
356 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
358 //Match of leaf name. Try match on total name.
359 CString totalRefName = GetRefName();
360 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
361 return this; //Also match. Found.
364 else
366 //Not a leaf. Search all nodes.
367 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
369 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
370 if (pSubtree)
371 return pSubtree; //Found
374 return nullptr; //Not found
377 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
379 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
380 //List ctrl selection?
381 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
383 //A leaf is selected
384 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
385 m_ListRefLeafs.GetNextSelectedItem(pos));
386 return pTree->GetRefName();
388 else if (pos && !pickFirstSelIfMultiSel)
390 // at least one leaf is selected
391 CString refs;
392 int index;
393 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
395 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
396 if (CStringUtils::StartsWith(ref, L"refs/"))
397 ref = ref.Mid(5);
398 if (CStringUtils::StartsWith(ref, L"heads/"))
399 ref = ref.Mid(6);
400 refs += ref + _T(" ");
402 return refs.Trim();
404 else if(!onlyIfLeaf)
406 //Tree ctrl selection?
407 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
408 if (hTree)
410 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
411 return pTree->GetRefName();
414 return CString();//None
417 void CBrowseRefsDlg::Refresh(CString selectRef)
419 remotes.clear();
420 if (g_Git.GetRemoteList(remotes))
421 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
423 if(!selectRef.IsEmpty())
425 if(selectRef == "HEAD")
427 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
428 selectRef.Empty();
429 else
430 selectRef = L"refs/heads/" + selectRef;
433 else
434 selectRef = GetSelectedRef(false, true);
436 m_RefTreeCtrl.DeleteAllItems();
437 m_ListRefLeafs.DeleteAllItems();
438 m_TreeRoot.m_ShadowTree.clear();
439 m_TreeRoot.m_csRefName = "refs";
440 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs", nullptr, nullptr);
441 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
443 CString err;
444 MAP_REF_GITREVREFBROWSER refMap;
445 if (GitRevRefBrowser::GetGitRevRefMap(refMap, err, [&](const CString& refName)
447 //Use ref based on m_pickRef_Kind
448 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
449 return false; //Skip
450 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
451 return false; //Skip
452 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
453 return false; //Skip
454 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
455 return false;
456 return true;
459 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
462 //Populate ref tree
463 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
465 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
466 GitRevRefBrowser ref = iterRefMap->second;
468 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
469 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
470 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
471 treeLeaf.m_csSubject = ref.GetSubject();
472 treeLeaf.m_csAuthor = ref.GetAuthorName();
473 treeLeaf.m_csDate = ref.GetAuthorDate();
474 treeLeaf.m_csDescription = ref.m_Description;
477 // always expand the tree first
478 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
480 // try exact match first
481 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
482 SelectRef(selectRef, false);
485 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
487 if(!bExactMatch)
489 CString newRefName = GetFullRefName(refName);
490 if(!newRefName.IsEmpty())
491 refName = newRefName;
492 //else refName is not a valid ref. Try to select as good as possible.
494 if (!CStringUtils::StartsWith(refName, L"refs"))
495 return false; // Not a ref name
497 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
498 if (treeLeafHead.m_hTree)
500 //Not a leaf. Select tree node and return
501 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
502 return true;
505 if (!treeLeafHead.m_pParent)
506 return false; //Weird... should not occur.
508 //This is the current head.
509 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
511 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
513 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
514 if(pCurrShadowTree == &treeLeafHead)
516 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
517 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
521 return true;
524 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
526 if (!pTreePos)
528 if (CStringUtils::StartsWith(refName, L"refs/"))
529 refName=refName.Mid(5);
530 pTreePos=&m_TreeRoot;
532 if(refName.IsEmpty())
533 return *pTreePos;//Found leaf
535 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
536 if (!pNextTree)
538 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
539 ASSERT(!bCreateIfNotExist);
540 return *pTreePos;
543 if(!refName.IsEmpty())
545 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
546 //Leafs are for the list control.
547 if (!pNextTree->m_hTree)
549 //New tree. Create node in control.
550 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree, nullptr);
551 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
555 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
559 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
561 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
562 *pResult = 0;
564 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
567 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
569 m_ListRefLeafs.DeleteAllItems();
571 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
572 if (!pTree)
574 ASSERT(FALSE);
575 return;
577 FillListCtrlForShadowTree(pTree,L"",true);
578 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
579 m_ListRefLeafs.AdjustColumnWidths();
582 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
584 if(pTree->IsLeaf())
586 CString filter;
587 m_ctrlFilter.GetWindowText(filter);
588 filter.MakeLower();
589 bool positive = filter[0] != '!';
590 if (!positive)
591 filter = filter.Mid(1);
592 CString ref = refNamePrefix + pTree->m_csRefName;
593 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
595 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
597 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
598 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
599 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
600 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
601 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
602 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
603 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
604 CString descrition = pTree->m_csDescription;
605 descrition.Replace(L"\n", L" ");
606 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
609 else
611 CString csThisName;
612 if (!isFirstLevel && !m_bIncludeNestedRefs)
613 return;
614 else if (!isFirstLevel)
615 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
616 else
617 m_pListCtrlRoot = pTree;
618 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
620 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
623 if (isFirstLevel)
625 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
626 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
628 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
632 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
634 if (m_SelectedFilters & LOGFILTER_REFNAME)
636 CString msg = ref;
637 msg = msg.MakeLower();
639 if (msg.Find(filter) >= 0)
640 return positive;
643 if (m_SelectedFilters & LOGFILTER_SUBJECT)
645 CString msg = pTree->m_csSubject;
646 msg = msg.MakeLower();
648 if (msg.Find(filter) >= 0)
649 return positive;
652 if (m_SelectedFilters & LOGFILTER_AUTHORS)
654 CString msg = pTree->m_csAuthor;
655 msg = msg.MakeLower();
657 if (msg.Find(filter) >= 0)
658 return positive;
661 if (m_SelectedFilters & LOGFILTER_REVS)
663 CString msg = pTree->m_csRefHash;
664 msg = msg.MakeLower();
666 if (msg.Find(filter) >= 0)
667 return positive;
669 return !positive;
672 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
674 ASSERT(!leafs.empty());
676 CString csMessage;
677 UINT mbIcon=MB_ICONQUESTION;
679 bool bIsRemoteBranch = false;
680 bool bIsBranch = false;
681 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
682 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
684 if(bIsBranch)
686 if(leafs.size() == 1)
688 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
689 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
691 //Check if branch is fully merged in HEAD
692 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
694 csMessage += L"\r\n\r\n";
695 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
696 mbIcon = MB_ICONWARNING;
699 if(bIsRemoteBranch)
701 csMessage += L"\r\n\r\n";
702 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
703 mbIcon = MB_ICONWARNING;
706 else
708 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
710 csMessage += L"\r\n\r\n";
711 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
712 mbIcon = MB_ICONWARNING;
714 if(bIsRemoteBranch)
716 csMessage += L"\r\n\r\n";
717 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
718 mbIcon = MB_ICONWARNING;
723 else if(leafs[0]->IsFrom(L"refs/tags/"))
725 if(leafs.size() == 1)
727 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
728 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
730 else
731 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
734 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
737 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
739 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
740 if(!DoDeleteRef((*i)->GetRefName()))
741 return false;
742 return true;
745 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
747 bool bIsRemoteBranch = false;
748 bool bIsBranch = false;
749 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
751 bIsBranch = true;
752 bIsRemoteBranch = true;
754 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
755 bIsBranch = true;
757 if (bIsRemoteBranch)
759 CString branchToDelete = completeRefName.Mid(13);
760 CString remoteName, remoteBranchToDelete;
761 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
762 return false;
764 if (CAppUtils::IsSSHPutty())
765 CAppUtils::LaunchPAgent(nullptr, &remoteName);
767 CSysProgressDlg sysProgressDlg;
768 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
769 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
770 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
771 sysProgressDlg.SetShowProgressBar(false);
772 sysProgressDlg.ShowModal(this, true);
774 STRING_VECTOR list;
775 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
776 if (g_Git.DeleteRemoteRefs(remoteName, list))
778 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
779 sysProgressDlg.Stop();
780 BringWindowToTop();
781 return false;
783 sysProgressDlg.Stop();
784 BringWindowToTop();
786 else if (bIsBranch)
788 if (g_Git.DeleteRef(completeRefName))
790 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
791 return false;
794 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
796 if (g_Git.DeleteRef(completeRefName))
798 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
799 return false;
802 return true;
805 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
807 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
808 if (!pLeaf)
809 return CString();
810 return pLeaf->GetRefName();
814 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
816 if (pWndFrom == &m_RefTreeCtrl)
817 OnContextMenu_RefTreeCtrl(point);
820 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
822 CPoint clientPoint=point;
823 m_RefTreeCtrl.ScreenToClient(&clientPoint);
825 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
826 if (hTreeItem)
827 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
829 VectorPShadowTree tree;
830 ShowContextMenu(point,hTreeItem,tree);
833 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
835 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
836 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
837 while (pos)
839 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
843 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
845 std::vector<CShadowTree*> selectedLeafs;
846 GetSelectedLeaves(selectedLeafs);
847 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
850 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
852 ASSERT(selectedLeafs.size() == 2);
854 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
855 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
856 else
857 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
860 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
862 int i = 0;
863 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
865 if (*it == entry)
866 return i;
868 return -1;
871 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
873 CIconMenu popupMenu;
874 popupMenu.CreatePopupMenu();
876 bool bAddSeparator = false;
877 CString remoteName;
879 if(selectedLeafs.size()==1)
881 bAddSeparator = true;
883 bool bShowReflogOption = false;
884 bool bShowFetchOption = false;
885 bool bShowRenameOption = false;
886 bool bShowCreateBranchOption = false;
887 bool bShowEditBranchDescriptionOption = false;
889 CString fetchFromCmd;
891 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
893 bShowReflogOption = true;
894 bShowRenameOption = true;
895 bShowEditBranchDescriptionOption = true;
897 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
899 bShowReflogOption = true;
900 bShowFetchOption = true;
901 bShowCreateBranchOption = true;
903 CString remoteBranch;
904 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
905 bShowFetchOption = false;
906 else
907 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
910 CString temp;
911 temp.LoadString(IDS_MENULOG);
912 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
913 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
914 if(bShowReflogOption)
916 temp.LoadString(IDS_MENUREFLOG);
917 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
920 popupMenu.AppendMenu(MF_SEPARATOR);
921 bAddSeparator = false;
923 if(bShowFetchOption)
925 bAddSeparator = true;
926 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
929 if(bAddSeparator)
930 popupMenu.AppendMenu(MF_SEPARATOR);
932 bAddSeparator = false;
933 if (m_bHasWC)
935 CString str;
936 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
938 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
939 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
941 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
942 popupMenu.AppendMenu(MF_SEPARATOR);
945 if(bShowCreateBranchOption)
947 bAddSeparator = true;
948 temp.LoadString(IDS_MENUBRANCH);
949 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
952 if (bShowEditBranchDescriptionOption)
954 bAddSeparator = true;
955 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
957 if(bShowRenameOption)
959 bAddSeparator = true;
960 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
963 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
965 if (bAddSeparator)
966 popupMenu.AppendMenu(MF_SEPARATOR);
967 bAddSeparator = true;
968 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
969 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH)));
970 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH)));
973 else if(selectedLeafs.size() == 2)
975 bAddSeparator = true;
976 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
977 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
978 CString menu;
979 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
980 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
981 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
982 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
985 if(!selectedLeafs.empty())
987 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
989 if(bAddSeparator)
990 popupMenu.AppendMenu(MF_SEPARATOR);
991 CString menuItemName;
992 if(selectedLeafs.size() == 1)
993 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
994 else
995 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
997 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
998 bAddSeparator = true;
1000 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1002 if(bAddSeparator)
1003 popupMenu.AppendMenu(MF_SEPARATOR);
1004 CString menuItemName;
1005 if(selectedLeafs.size() == 1)
1006 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1007 else
1008 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1010 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1011 bAddSeparator = true;
1013 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1015 if(bAddSeparator)
1016 popupMenu.AppendMenu(MF_SEPARATOR);
1017 CString menuItemName;
1018 if(selectedLeafs.size() == 1)
1019 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1020 else
1021 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1023 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1024 bAddSeparator = true;
1029 if (hTreePos && selectedLeafs.empty())
1031 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1032 if(pTree->IsFrom(L"refs/remotes"))
1034 if(bAddSeparator)
1035 popupMenu.AppendMenu(MF_SEPARATOR);
1036 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1037 bAddSeparator = true;
1038 if(selectedLeafs.empty())
1040 CString remoteBranch;
1041 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1042 remoteName.Empty();
1043 int pos = findVectorPosition(remotes, remoteName);
1044 if (pos >= 0)
1046 CString temp;
1047 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1048 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1050 temp.LoadString(IDS_DELETEREMOTETAG);
1051 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1055 if(pTree->IsFrom(L"refs/heads"))
1057 if(bAddSeparator)
1058 popupMenu.AppendMenu(MF_SEPARATOR);
1059 CString temp;
1060 temp.LoadString(IDS_MENUBRANCH);
1061 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1063 if(pTree->IsFrom(L"refs/tags"))
1065 if(bAddSeparator)
1066 popupMenu.AppendMenu(MF_SEPARATOR);
1067 CString temp;
1068 temp.LoadString(IDS_MENUTAG);
1069 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1070 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1071 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1072 if (!remotes.empty())
1074 popupMenu.AppendMenu(MF_SEPARATOR);
1075 int i = 0;
1076 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1078 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1079 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1086 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1087 switch ((eCmd)(selection & 0xFFFF))
1089 case eCmd_ViewLog:
1091 CString sCmd;
1092 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1093 CAppUtils::RunTortoiseGitProc(sCmd);
1095 break;
1096 case eCmd_ViewLogRange:
1098 CString sCmd;
1099 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1100 CAppUtils::RunTortoiseGitProc(sCmd);
1102 break;
1103 case eCmd_ViewLogRangeReachableFromOnlyOne:
1105 CString sCmd;
1106 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1107 CAppUtils::RunTortoiseGitProc(sCmd);
1109 break;
1110 case eCmd_RepoBrowser:
1111 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1112 break;
1113 case eCmd_DeleteBranch:
1114 case eCmd_DeleteRemoteBranch:
1116 if(ConfirmDeleteRef(selectedLeafs))
1117 DoDeleteRefs(selectedLeafs);
1118 Refresh();
1120 break;
1121 case eCmd_DeleteTag:
1123 if(ConfirmDeleteRef(selectedLeafs))
1124 DoDeleteRefs(selectedLeafs);
1125 Refresh();
1127 break;
1128 case eCmd_ShowReflog:
1130 CRefLogDlg refLogDlg(this);
1131 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1132 refLogDlg.DoModal();
1134 break;
1135 case eCmd_Fetch:
1137 CAppUtils::Fetch(remoteName);
1138 Refresh();
1140 break;
1141 case eCmd_DeleteRemoteTag:
1143 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1144 int remoteInx = selection >> 16;
1145 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1146 return;
1147 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1148 deleteRemoteTagDlg.DoModal();
1150 break;
1151 case eCmd_Merge:
1153 CString ref = selectedLeafs[0]->GetRefName();
1154 CAppUtils::Merge(&ref);
1156 break;
1157 case eCmd_Switch:
1159 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1161 break;
1162 case eCmd_Rename:
1164 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1165 if (pos)
1166 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1168 break;
1169 case eCmd_AddRemote:
1171 CAddRemoteDlg(this).DoModal();
1172 Refresh();
1174 break;
1175 case eCmd_ManageRemotes:
1177 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1178 // CSettingGitRemote W_Remotes(m_cmdPath);
1179 // W_Remotes.DoModal();
1180 Refresh();
1182 break;
1183 case eCmd_CreateBranch:
1185 CString* commitHash = nullptr;
1186 if (selectedLeafs.size() == 1)
1187 commitHash = &(selectedLeafs[0]->m_csRefHash);
1188 CAppUtils::CreateBranchTag(false, commitHash);
1189 Refresh();
1191 break;
1192 case eCmd_CreateTag:
1194 CAppUtils::CreateBranchTag(true);
1195 Refresh();
1197 break;
1198 case eCmd_DeleteAllTags:
1200 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1202 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1203 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1205 if (ConfirmDeleteRef(selectedLeafs))
1206 DoDeleteRefs(selectedLeafs);
1207 Refresh();
1209 break;
1210 case eCmd_Diff:
1212 CFileDiffDlg dlg;
1213 dlg.SetDiff(
1214 nullptr,
1215 selectedLeafs[0]->GetRefName() + L"^{}",
1216 selectedLeafs[1]->GetRefName() + L"^{}");
1217 dlg.DoModal();
1219 break;
1220 case eCmd_UnifiedDiff:
1222 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1224 break;
1225 case eCmd_EditBranchDescription:
1227 CInputDlg dlg;
1228 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1229 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1230 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1231 dlg.m_bUseLogWidth = true;
1232 if(dlg.DoModal() == IDOK)
1234 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1235 Refresh();
1238 break;
1239 case eCmd_UpstreamDrop:
1241 CString key;
1242 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1243 g_Git.UnsetConfigValue(key);
1244 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1245 g_Git.UnsetConfigValue(key);
1247 Refresh();
1248 break;
1249 case eCmd_UpstreamSet:
1251 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1252 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1253 return;
1254 CString remote, branch;
1255 if (SplitRemoteBranchName(newRef, remote, branch))
1256 return;
1257 CString key;
1258 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1259 g_Git.SetConfigValue(key, remote);
1260 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1261 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1262 Refresh();
1264 break;
1268 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1270 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1271 if(!(*i)->IsFrom(from))
1272 return false;
1273 return true;
1276 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1278 if (pMsg->message == WM_KEYDOWN)
1280 switch (pMsg->wParam)
1282 /* case VK_RETURN:
1284 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1286 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1288 PostMessage(WM_COMMAND, IDOK);
1290 return TRUE;
1293 break;
1294 */ case VK_F2:
1296 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1298 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1299 if (pos)
1300 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1303 break;
1305 case VK_F5:
1307 Refresh();
1309 break;
1310 case L'E':
1312 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1314 m_ctrlFilter.SetSel(0, -1, FALSE);
1315 m_ctrlFilter.SetFocus();
1316 return TRUE;
1319 break;
1324 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1327 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1329 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1330 *pResult = 0;
1332 if(m_currSortCol == pNMLV->iSubItem)
1333 m_currSortDesc = !m_currSortDesc;
1334 else
1336 m_currSortCol = pNMLV->iSubItem;
1337 m_currSortDesc = false;
1340 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1341 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1343 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1346 void CBrowseRefsDlg::OnDestroy()
1348 if (!m_bPickedRefSet)
1349 m_pickedRef = GetSelectedRef(true, false);
1351 CResizableStandAloneDialog::OnDestroy();
1354 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1356 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1357 *pResult = 0;
1359 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1360 if (item && pNMListView->uNewState == LVIS_SELECTED)
1361 m_sLastSelected = item->GetRefName();
1363 UpdateInfoLabel();
1366 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1368 *pResult = 0;
1370 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1371 return;
1372 EndDialog(IDOK);
1375 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1377 CBrowseRefsDlg dlg(CString(), nullptr);
1379 if(initialRef.IsEmpty())
1380 initialRef = L"HEAD";
1381 dlg.m_initialRef = initialRef;
1382 dlg.m_pickRef_Kind = pickRef_Kind;
1383 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1385 if(dlg.DoModal() != IDOK)
1386 return CString();
1388 return dlg.m_pickedRef;
1391 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1393 CString origRef;
1394 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1395 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1396 if(resultRef.IsEmpty())
1397 return false;
1398 if (CStringUtils::StartsWith(resultRef, L"refs/"))
1399 resultRef = resultRef.Mid(5);
1400 // if(wcsncmp(resultRef,L"heads/",6)==0)
1401 // resultRef = resultRef.Mid(6);
1403 //Find closest match of choice in combobox
1404 int ixFound = -1;
1405 int matchLength = 0;
1406 CString comboRefName;
1407 for(int i = 0; i < pComboBox->GetCount(); ++i)
1409 pComboBox->GetLBText(i, comboRefName);
1410 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1411 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1412 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1414 matchLength = comboRefName.GetLength();
1415 ixFound = i;
1418 if(ixFound >= 0)
1419 pComboBox->SetCurSel(ixFound);
1420 else
1421 ASSERT(FALSE);//No match found. So either pickRef_Kind is wrong or the combobox does not contain the ref specified in the picker (which it should unless the repo has changed before creating the CBrowseRef dialog)
1423 return true;
1426 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1428 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1429 *pResult = FALSE;
1431 if (!pDispInfo->item.pszText)
1432 return; //User canceled changing
1434 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1436 if(!pTree->IsFrom(L"refs/heads/"))
1438 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1439 return;
1442 CString selectedTreeRef;
1443 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1444 if (!hTree)
1446 CShadowTree* pTree2 = (CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
1447 selectedTreeRef = pTree2->GetRefName();
1450 CString origName = pTree->GetRefName().Mid(11);
1452 CString newName;
1453 if (m_pListCtrlRoot)
1454 newName = m_pListCtrlRoot->GetRefName() + L'/';
1455 newName += pDispInfo->item.pszText;
1457 if (!CStringUtils::StartsWith(newName, L"refs/heads/"))
1459 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1460 return;
1463 CString newNameTrunced = newName.Mid(11);
1465 CString errorMsg;
1466 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1468 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1469 return;
1471 //Do as if it failed to rename. Let Refresh() do the job.
1472 //*pResult = TRUE;
1474 Refresh(selectedTreeRef);
1476 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1478 // AfxMessageBox(W_csPopup);
1481 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1483 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1484 *pResult = FALSE;
1486 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1488 if(!pTree->IsFrom(L"refs/heads/"))
1490 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1491 return;
1495 void CBrowseRefsDlg::OnEnChangeEditFilter()
1497 SetTimer(IDT_FILTER, 1000, nullptr);
1500 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1502 if (nIDEvent == IDT_FILTER)
1504 KillTimer(IDT_FILTER);
1505 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1508 CResizableStandAloneDialog::OnTimer(nIDEvent);
1511 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1513 // FIXME: x64 version would get this function called with unexpected parameters.
1514 if (!lParam)
1515 return 0;
1517 RECT * rect = (LPRECT)lParam;
1518 CPoint point;
1519 CString temp;
1520 point = CPoint(rect->left, rect->bottom);
1521 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1522 CMenu popup;
1523 if (popup.CreatePopupMenu())
1525 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1526 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1528 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1529 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1531 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1532 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1534 temp.LoadString(IDS_LOG_FILTER_REVS);
1535 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1537 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1538 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1540 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1541 if (selection != 0)
1543 if (selection == LOGFILTER_TOGGLE)
1544 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1545 else
1546 m_SelectedFilters ^= selection;
1547 SetFilterCueText();
1548 SetTimer(IDT_FILTER, 1000, nullptr);
1551 return 0L;
1554 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1556 KillTimer(LOGFILTER_TIMER);
1557 m_ctrlFilter.SetWindowText(_T(""));
1558 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1559 return 0L;
1562 void CBrowseRefsDlg::SetFilterCueText()
1564 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1565 temp += _T(" ");
1567 if (m_SelectedFilters & LOGFILTER_REFNAME)
1568 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1570 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1572 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1573 temp += _T(", ");
1574 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1577 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1579 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1580 temp += _T(", ");
1581 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1584 if (m_SelectedFilters & LOGFILTER_REVS)
1586 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1587 temp += _T(", ");
1588 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1591 // to make the cue banner text appear more to the right of the edit control
1592 temp = _T(" ") + temp;
1593 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1596 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1598 m_pickedRef = g_Git.GetCurrentBranch(true);
1599 m_bPickedRefSet = true;
1600 OnOK();
1603 void CBrowseRefsDlg::UpdateInfoLabel()
1605 CString temp;
1606 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1607 SetDlgItemText(IDC_INFOLABEL, temp);
1610 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1612 UpdateData(TRUE);
1613 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1614 Refresh();