Refactor out CResizableColumnsListCtrl
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob7edb8f2e8787c55a78232bcd5f58cbffe198d5a5
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"
41 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
43 if (ref.Left(13) == _T("refs/remotes/"))
44 ref = ref.Mid(13);
45 else if (ref.Left(8) == _T("remotes/"))
46 ref = ref.Mid(8);
48 STRING_VECTOR list;
49 int result = g_Git.GetRemoteList(list);
50 if (result != 0)
51 return result;
53 for (size_t i = 0; i < list.size(); ++i)
55 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
57 remote = list[i];
58 branch = ref.Mid(list[i].GetLength() + 1);
59 return 0;
61 if (ref == list[i])
63 remote = list[i];
64 branch.Empty();
65 return 0;
69 return -1;
72 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
74 if (!control)
75 return;
76 // set the sort arrow
77 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
78 HDITEM HeaderItem = {0};
79 HeaderItem.mask = HDI_FORMAT;
80 for (int i=0; i<pHeader->GetItemCount(); ++i)
82 pHeader->GetItem(i, &HeaderItem);
83 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
84 pHeader->SetItem(i, &HeaderItem);
86 if (nColumn >= 0)
88 pHeader->GetItem(nColumn, &HeaderItem);
89 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
90 pHeader->SetItem(nColumn, &HeaderItem);
94 class CRefLeafListCompareFunc
96 public:
97 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
98 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
99 if (m_bSortLogical)
100 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
103 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
105 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
108 int Compare(LPARAM lParam1, LPARAM lParam2)
110 return Compare(
111 (CShadowTree*)m_pList->GetItemData((int)lParam1),
112 (CShadowTree*)m_pList->GetItemData((int)lParam2));
115 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
117 int result=CompareNoDesc(pLeft,pRight);
118 if(m_desc)
119 return -result;
120 return result;
123 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
125 switch(m_col)
127 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
128 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
129 case CBrowseRefsDlg::eCol_Date: return ((pLeft->m_csDate == pRight->m_csDate) ? 0 : ((pLeft->m_csDate > pRight->m_csDate) ? 1 : -1));
130 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
131 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
132 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
133 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
135 return 0;
137 int SortStrCmp(const CString& left, const CString& right)
139 if (m_bSortLogical)
140 return StrCmpLogicalW(left, right);
141 return StrCmpI(left, right);
144 int m_col;
145 bool m_desc;
146 CListCtrl* m_pList;
147 bool m_bSortLogical;
150 // CBrowseRefsDlg dialog
152 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
154 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=nullptr*/)
155 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
156 m_cmdPath(cmdPath),
157 m_currSortCol(0),
158 m_currSortDesc(false),
159 m_regCurrSortCol(L"Software\\TortoiseGit\\RefBrowserSortCol", 0),
160 m_regCurrSortDesc(L"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE),
161 m_initialRef(L"HEAD"),
162 m_pickRef_Kind(gPickRef_All),
163 m_pListCtrlRoot(nullptr),
164 m_bHasWC(true),
165 m_SelectedFilters(LOGFILTER_ALL),
166 m_bPickOne(false),
167 m_bIncludeNestedRefs(TRUE),
168 m_bPickedRefSet(false)
170 // get short/long datetime setting from registry
171 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
172 if (RegUseShortDateFormat)
173 m_DateFormat = DATE_SHORTDATE;
174 else
175 m_DateFormat = DATE_LONGDATE;
176 // get relative time display setting from registry
177 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
178 m_bRelativeTimes = (regRelativeTimes != 0);
180 m_regIncludeNestedRefs = CRegDWORD(_T("Software\\TortoiseGit\\RefBrowserIncludeNestedRefs"), TRUE);
182 m_currSortCol = m_regCurrSortCol;
183 m_currSortDesc = m_regCurrSortDesc == TRUE;
186 CBrowseRefsDlg::~CBrowseRefsDlg()
188 m_regCurrSortCol = m_currSortCol;
189 m_regCurrSortDesc = m_currSortDesc;
192 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
194 CDialog::DoDataExchange(pDX);
195 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
196 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
197 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
198 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
202 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
203 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
204 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
205 ON_WM_CONTEXTMENU()
206 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
207 ON_WM_DESTROY()
208 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
209 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
210 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
211 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
212 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
213 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
214 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
215 ON_WM_TIMER()
216 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
217 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
218 END_MESSAGE_MAP()
221 // CBrowseRefsDlg message handlers
223 void CBrowseRefsDlg::OnBnClickedOk()
225 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
227 OnOK();
228 return;
231 CIconMenu popupMenu;
232 popupMenu.CreatePopupMenu();
234 std::vector<CShadowTree*> selectedLeafs;
235 GetSelectedLeaves(selectedLeafs);
237 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
238 popupMenu.SetDefaultItem(1);
239 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
240 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
242 RECT rect;
243 GetDlgItem(IDOK)->GetWindowRect(&rect);
244 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
245 switch (selection)
247 case 1:
248 OnOK();
249 break;
250 case 2:
252 m_bPickedRefSet = true;
253 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
254 OnOK();
256 break;
257 case 3:
259 m_bPickedRefSet = true;
260 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
261 OnOK();
263 break;
264 default:
265 break;
269 BOOL CBrowseRefsDlg::OnInitDialog()
271 CResizableStandAloneDialog::OnInitDialog();
272 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
274 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
275 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
276 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
277 SetFilterCueText();
279 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
280 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
281 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
282 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
283 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
284 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
285 AddAnchor(IDHELP, BOTTOM_RIGHT);
287 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP);
288 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
289 static int columnWidths[] = { 0, 0, 0, 300, 0, 0, 80 };
290 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
291 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
292 m_ListRefLeafs.m_bAllowHiding = false;
293 m_ListRefLeafs.Init();
294 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
295 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
296 m_bPickedRefSet = false;
298 AddAnchor(IDOK,BOTTOM_RIGHT);
299 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
300 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
302 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
303 UpdateData();
305 Refresh(m_initialRef);
307 EnableSaveRestore(L"BrowseRefs");
309 CString sWindowTitle;
310 GetWindowText(sWindowTitle);
311 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
313 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
315 if (m_bPickOne)
316 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
318 m_ListRefLeafs.SetFocus();
319 return FALSE;
322 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
324 int posSlash=nameLeft.Find('/');
325 CString nameSub;
326 if(posSlash<0)
328 nameSub=nameLeft;
329 nameLeft.Empty();//Nothing left
331 else
333 nameSub=nameLeft.Left(posSlash);
334 nameLeft=nameLeft.Mid(posSlash+1);
336 if(nameSub.IsEmpty())
337 return nullptr;
339 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
340 return nullptr;
342 CShadowTree& nextNode=m_ShadowTree[nameSub];
343 nextNode.m_csRefName=nameSub;
344 nextNode.m_pParent=this;
345 return &nextNode;
348 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
350 if(IsLeaf())
352 if(m_csRefName.GetLength() > partialRefName.GetLength())
353 return nullptr;
354 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
356 //Match of leaf name. Try match on total name.
357 CString totalRefName = GetRefName();
358 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
359 return this; //Also match. Found.
362 else
364 //Not a leaf. Search all nodes.
365 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
367 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
368 if (pSubtree)
369 return pSubtree; //Found
372 return nullptr; //Not found
375 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
377 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
378 //List ctrl selection?
379 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
381 //A leaf is selected
382 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
383 m_ListRefLeafs.GetNextSelectedItem(pos));
384 return pTree->GetRefName();
386 else if (pos && !pickFirstSelIfMultiSel)
388 // at least one leaf is selected
389 CString refs;
390 int index;
391 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
393 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
394 if(wcsncmp(ref, L"refs/", 5) == 0)
395 ref = ref.Mid(5);
396 if(wcsncmp(ref, L"heads/", 6) == 0)
397 ref = ref.Mid(6);
398 refs += ref + _T(" ");
400 return refs.Trim();
402 else if(!onlyIfLeaf)
404 //Tree ctrl selection?
405 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
406 if (hTree)
408 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
409 return pTree->GetRefName();
412 return CString();//None
415 void CBrowseRefsDlg::Refresh(CString selectRef)
417 remotes.clear();
418 if (g_Git.GetRemoteList(remotes))
419 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
421 if(!selectRef.IsEmpty())
423 if(selectRef == "HEAD")
425 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
426 selectRef.Empty();
427 else
428 selectRef = L"refs/heads/" + selectRef;
431 else
432 selectRef = GetSelectedRef(false, true);
434 m_RefTreeCtrl.DeleteAllItems();
435 m_ListRefLeafs.DeleteAllItems();
436 m_TreeRoot.m_ShadowTree.clear();
437 m_TreeRoot.m_csRefName = "refs";
438 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs", nullptr, nullptr);
439 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
441 CString err;
442 MAP_REF_GITREVREFBROWSER refMap;
443 if (GitRevRefBrowser::GetGitRevRefMap(refMap, err, [&](const CString& refName)
445 //Use ref based on m_pickRef_Kind
446 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
447 return false; //Skip
448 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
449 return false; //Skip
450 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
451 return false; //Skip
452 if (m_pickRef_Kind == gPickRef_Remote && wcsncmp(refName, L"refs/remotes/", 13) != 0) // do not show refs/stash if only remote branches are requested
453 return false;
454 return true;
457 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
460 //Populate ref tree
461 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
463 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
464 GitRevRefBrowser ref = iterRefMap->second;
466 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
467 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
468 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
469 treeLeaf.m_csSubject = ref.GetSubject();
470 treeLeaf.m_csAuthor = ref.GetAuthorName();
471 treeLeaf.m_csDate = ref.GetAuthorDate();
472 treeLeaf.m_csDescription = ref.m_Description;
475 // always expand the tree first
476 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
478 // try exact match first
479 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
480 SelectRef(selectRef, false);
483 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
485 if(!bExactMatch)
487 CString newRefName = GetFullRefName(refName);
488 if(!newRefName.IsEmpty())
489 refName = newRefName;
490 //else refName is not a valid ref. Try to select as good as possible.
492 if (_wcsnicmp(refName, L"refs", 4) != 0)
493 return false; // Not a ref name
495 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
496 if (treeLeafHead.m_hTree)
498 //Not a leaf. Select tree node and return
499 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
500 return true;
503 if (!treeLeafHead.m_pParent)
504 return false; //Weird... should not occur.
506 //This is the current head.
507 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
509 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
511 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
512 if(pCurrShadowTree == &treeLeafHead)
514 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
515 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
519 return true;
522 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
524 if (!pTreePos)
526 if(_wcsnicmp(refName, L"refs/", 5) == 0)
527 refName=refName.Mid(5);
528 pTreePos=&m_TreeRoot;
530 if(refName.IsEmpty())
531 return *pTreePos;//Found leaf
533 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
534 if (!pNextTree)
536 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
537 ASSERT(!bCreateIfNotExist);
538 return *pTreePos;
541 if(!refName.IsEmpty())
543 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
544 //Leafs are for the list control.
545 if (!pNextTree->m_hTree)
547 //New tree. Create node in control.
548 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree, nullptr);
549 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
553 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
557 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
559 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
560 *pResult = 0;
562 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
565 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
567 m_ListRefLeafs.DeleteAllItems();
569 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
570 if (!pTree)
572 ASSERT(FALSE);
573 return;
575 FillListCtrlForShadowTree(pTree,L"",true);
576 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
577 m_ListRefLeafs.AdjustColumnWidths();
580 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
582 if(pTree->IsLeaf())
584 CString filter;
585 m_ctrlFilter.GetWindowText(filter);
586 filter.MakeLower();
587 bool positive = filter[0] != '!';
588 if (!positive)
589 filter = filter.Mid(1);
590 CString ref = refNamePrefix + pTree->m_csRefName;
591 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
593 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
595 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
596 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
597 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
598 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
599 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
600 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
601 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
602 CString descrition = pTree->m_csDescription;
603 descrition.Replace(L"\n", L" ");
604 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
607 else
609 CString csThisName;
610 if (!isFirstLevel && !m_bIncludeNestedRefs)
611 return;
612 else if (!isFirstLevel)
613 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
614 else
615 m_pListCtrlRoot = pTree;
616 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
618 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
621 if (isFirstLevel)
623 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
624 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
626 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
630 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
632 if (m_SelectedFilters & LOGFILTER_REFNAME)
634 CString msg = ref;
635 msg = msg.MakeLower();
637 if (msg.Find(filter) >= 0)
638 return positive;
641 if (m_SelectedFilters & LOGFILTER_SUBJECT)
643 CString msg = pTree->m_csSubject;
644 msg = msg.MakeLower();
646 if (msg.Find(filter) >= 0)
647 return positive;
650 if (m_SelectedFilters & LOGFILTER_AUTHORS)
652 CString msg = pTree->m_csAuthor;
653 msg = msg.MakeLower();
655 if (msg.Find(filter) >= 0)
656 return positive;
659 if (m_SelectedFilters & LOGFILTER_REVS)
661 CString msg = pTree->m_csRefHash;
662 msg = msg.MakeLower();
664 if (msg.Find(filter) >= 0)
665 return positive;
667 return !positive;
670 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
672 ASSERT(!leafs.empty());
674 CString csMessage;
675 UINT mbIcon=MB_ICONQUESTION;
677 bool bIsRemoteBranch = false;
678 bool bIsBranch = false;
679 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
680 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
682 if(bIsBranch)
684 if(leafs.size() == 1)
686 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
687 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
689 //Check if branch is fully merged in HEAD
690 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
692 csMessage += L"\r\n\r\n";
693 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
694 mbIcon = MB_ICONWARNING;
697 if(bIsRemoteBranch)
699 csMessage += L"\r\n\r\n";
700 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
701 mbIcon = MB_ICONWARNING;
704 else
706 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
708 csMessage += L"\r\n\r\n";
709 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
710 mbIcon = MB_ICONWARNING;
712 if(bIsRemoteBranch)
714 csMessage += L"\r\n\r\n";
715 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
716 mbIcon = MB_ICONWARNING;
721 else if(leafs[0]->IsFrom(L"refs/tags/"))
723 if(leafs.size() == 1)
725 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
726 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
728 else
729 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
732 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
735 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
737 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
738 if(!DoDeleteRef((*i)->GetRefName()))
739 return false;
740 return true;
743 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
745 bool bIsRemoteBranch = false;
746 bool bIsBranch = false;
747 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
748 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
750 if (bIsRemoteBranch)
752 CString branchToDelete = completeRefName.Mid(13);
753 CString remoteName, remoteBranchToDelete;
754 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
755 return false;
757 if (CAppUtils::IsSSHPutty())
758 CAppUtils::LaunchPAgent(nullptr, &remoteName);
760 CSysProgressDlg sysProgressDlg;
761 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
762 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
763 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
764 sysProgressDlg.SetShowProgressBar(false);
765 sysProgressDlg.ShowModal(this, true);
767 STRING_VECTOR list;
768 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
769 if (g_Git.DeleteRemoteRefs(remoteName, list))
771 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
772 sysProgressDlg.Stop();
773 BringWindowToTop();
774 return false;
776 sysProgressDlg.Stop();
777 BringWindowToTop();
779 else if (bIsBranch)
781 if (g_Git.DeleteRef(completeRefName))
783 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
784 return false;
787 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
789 if (g_Git.DeleteRef(completeRefName))
791 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
792 return false;
795 return true;
798 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
800 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
801 if (!pLeaf)
802 return CString();
803 return pLeaf->GetRefName();
807 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
809 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
810 else if (pWndFrom == &m_ListRefLeafs)
812 CRect headerPosition;
813 m_ListRefLeafs.GetHeaderCtrl()->GetWindowRect(headerPosition);
814 if (!headerPosition.PtInRect(point))
815 OnContextMenu_ListRefLeafs(point);
819 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
821 CPoint clientPoint=point;
822 m_RefTreeCtrl.ScreenToClient(&clientPoint);
824 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
825 if (hTreeItem)
826 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
828 VectorPShadowTree tree;
829 ShowContextMenu(point,hTreeItem,tree);
832 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
834 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
835 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
836 while (pos)
838 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
842 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
844 std::vector<CShadowTree*> selectedLeafs;
845 GetSelectedLeaves(selectedLeafs);
846 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
849 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
851 ASSERT(selectedLeafs.size() == 2);
853 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
854 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
855 else
856 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
859 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
861 int i = 0;
862 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
864 if (*it == entry)
865 return i;
867 return -1;
870 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
872 CIconMenu popupMenu;
873 popupMenu.CreatePopupMenu();
875 bool bAddSeparator = false;
876 CString remoteName;
878 if(selectedLeafs.size()==1)
880 bAddSeparator = true;
882 bool bShowReflogOption = false;
883 bool bShowFetchOption = false;
884 bool bShowRenameOption = false;
885 bool bShowCreateBranchOption = false;
886 bool bShowEditBranchDescriptionOption = false;
888 CString fetchFromCmd;
890 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
892 bShowReflogOption = true;
893 bShowRenameOption = true;
894 bShowEditBranchDescriptionOption = true;
896 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
898 bShowReflogOption = true;
899 bShowFetchOption = true;
900 bShowCreateBranchOption = true;
902 CString remoteBranch;
903 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
904 bShowFetchOption = false;
905 else
906 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
909 CString temp;
910 temp.LoadString(IDS_MENULOG);
911 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
912 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
913 if(bShowReflogOption)
915 temp.LoadString(IDS_MENUREFLOG);
916 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
919 popupMenu.AppendMenu(MF_SEPARATOR);
920 bAddSeparator = false;
922 if(bShowFetchOption)
924 bAddSeparator = true;
925 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
928 if(bAddSeparator)
929 popupMenu.AppendMenu(MF_SEPARATOR);
931 bAddSeparator = false;
932 if (m_bHasWC)
934 CString str;
935 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
937 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
938 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
940 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
941 popupMenu.AppendMenu(MF_SEPARATOR);
944 if(bShowCreateBranchOption)
946 bAddSeparator = true;
947 temp.LoadString(IDS_MENUBRANCH);
948 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
951 if (bShowEditBranchDescriptionOption)
953 bAddSeparator = true;
954 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
956 if(bShowRenameOption)
958 bAddSeparator = true;
959 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
962 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
964 if (bAddSeparator)
965 popupMenu.AppendMenu(MF_SEPARATOR);
966 bAddSeparator = true;
967 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
968 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH)));
969 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH)));
972 else if(selectedLeafs.size() == 2)
974 bAddSeparator = true;
975 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
976 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
977 CString menu;
978 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
979 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
980 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
981 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
984 if(!selectedLeafs.empty())
986 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
988 if(bAddSeparator)
989 popupMenu.AppendMenu(MF_SEPARATOR);
990 CString menuItemName;
991 if(selectedLeafs.size() == 1)
992 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
993 else
994 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
996 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
997 bAddSeparator = true;
999 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1001 if(bAddSeparator)
1002 popupMenu.AppendMenu(MF_SEPARATOR);
1003 CString menuItemName;
1004 if(selectedLeafs.size() == 1)
1005 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1006 else
1007 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1009 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1010 bAddSeparator = true;
1012 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1014 if(bAddSeparator)
1015 popupMenu.AppendMenu(MF_SEPARATOR);
1016 CString menuItemName;
1017 if(selectedLeafs.size() == 1)
1018 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1019 else
1020 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1022 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1023 bAddSeparator = true;
1028 if (hTreePos && selectedLeafs.empty())
1030 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1031 if(pTree->IsFrom(L"refs/remotes"))
1033 if(bAddSeparator)
1034 popupMenu.AppendMenu(MF_SEPARATOR);
1035 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1036 bAddSeparator = true;
1037 if(selectedLeafs.empty())
1039 CString remoteBranch;
1040 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1041 remoteName.Empty();
1042 int pos = findVectorPosition(remotes, remoteName);
1043 if (pos >= 0)
1045 CString temp;
1046 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1047 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1049 temp.LoadString(IDS_DELETEREMOTETAG);
1050 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1054 if(pTree->IsFrom(L"refs/heads"))
1056 if(bAddSeparator)
1057 popupMenu.AppendMenu(MF_SEPARATOR);
1058 CString temp;
1059 temp.LoadString(IDS_MENUBRANCH);
1060 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1062 if(pTree->IsFrom(L"refs/tags"))
1064 if(bAddSeparator)
1065 popupMenu.AppendMenu(MF_SEPARATOR);
1066 CString temp;
1067 temp.LoadString(IDS_MENUTAG);
1068 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1069 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1070 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1071 if (!remotes.empty())
1073 popupMenu.AppendMenu(MF_SEPARATOR);
1074 int i = 0;
1075 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1077 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1078 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1085 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1086 switch ((eCmd)(selection & 0xFFFF))
1088 case eCmd_ViewLog:
1090 CString sCmd;
1091 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1092 CAppUtils::RunTortoiseGitProc(sCmd);
1094 break;
1095 case eCmd_ViewLogRange:
1097 CString sCmd;
1098 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1099 CAppUtils::RunTortoiseGitProc(sCmd);
1101 break;
1102 case eCmd_ViewLogRangeReachableFromOnlyOne:
1104 CString sCmd;
1105 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1106 CAppUtils::RunTortoiseGitProc(sCmd);
1108 break;
1109 case eCmd_RepoBrowser:
1110 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1111 break;
1112 case eCmd_DeleteBranch:
1113 case eCmd_DeleteRemoteBranch:
1115 if(ConfirmDeleteRef(selectedLeafs))
1116 DoDeleteRefs(selectedLeafs);
1117 Refresh();
1119 break;
1120 case eCmd_DeleteTag:
1122 if(ConfirmDeleteRef(selectedLeafs))
1123 DoDeleteRefs(selectedLeafs);
1124 Refresh();
1126 break;
1127 case eCmd_ShowReflog:
1129 CRefLogDlg refLogDlg(this);
1130 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1131 refLogDlg.DoModal();
1133 break;
1134 case eCmd_Fetch:
1136 CAppUtils::Fetch(remoteName);
1137 Refresh();
1139 break;
1140 case eCmd_DeleteRemoteTag:
1142 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1143 int remoteInx = selection >> 16;
1144 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1145 return;
1146 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1147 deleteRemoteTagDlg.DoModal();
1149 break;
1150 case eCmd_Merge:
1152 CString ref = selectedLeafs[0]->GetRefName();
1153 CAppUtils::Merge(&ref);
1155 break;
1156 case eCmd_Switch:
1158 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1160 break;
1161 case eCmd_Rename:
1163 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1164 if (pos)
1165 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1167 break;
1168 case eCmd_AddRemote:
1170 CAddRemoteDlg(this).DoModal();
1171 Refresh();
1173 break;
1174 case eCmd_ManageRemotes:
1176 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1177 // CSettingGitRemote W_Remotes(m_cmdPath);
1178 // W_Remotes.DoModal();
1179 Refresh();
1181 break;
1182 case eCmd_CreateBranch:
1184 CString* commitHash = nullptr;
1185 if (selectedLeafs.size() == 1)
1186 commitHash = &(selectedLeafs[0]->m_csRefHash);
1187 CAppUtils::CreateBranchTag(false, commitHash);
1188 Refresh();
1190 break;
1191 case eCmd_CreateTag:
1193 CAppUtils::CreateBranchTag(true);
1194 Refresh();
1196 break;
1197 case eCmd_DeleteAllTags:
1199 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1201 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1202 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1204 if (ConfirmDeleteRef(selectedLeafs))
1205 DoDeleteRefs(selectedLeafs);
1206 Refresh();
1208 break;
1209 case eCmd_Diff:
1211 CFileDiffDlg dlg;
1212 dlg.SetDiff(
1213 nullptr,
1214 selectedLeafs[0]->GetRefName() + L"^{}",
1215 selectedLeafs[1]->GetRefName() + L"^{}");
1216 dlg.DoModal();
1218 break;
1219 case eCmd_UnifiedDiff:
1221 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1223 break;
1224 case eCmd_EditBranchDescription:
1226 CInputDlg dlg;
1227 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1228 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1229 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1230 dlg.m_bUseLogWidth = true;
1231 if(dlg.DoModal() == IDOK)
1233 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1234 Refresh();
1237 break;
1238 case eCmd_UpstreamDrop:
1240 CString key;
1241 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1242 g_Git.UnsetConfigValue(key);
1243 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1244 g_Git.UnsetConfigValue(key);
1246 Refresh();
1247 break;
1248 case eCmd_UpstreamSet:
1250 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1251 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1252 return;
1253 CString remote, branch;
1254 if (SplitRemoteBranchName(newRef, remote, branch))
1255 return;
1256 CString key;
1257 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1258 g_Git.SetConfigValue(key, remote);
1259 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1260 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1261 Refresh();
1263 break;
1267 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1269 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1270 if(!(*i)->IsFrom(from))
1271 return false;
1272 return true;
1275 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1277 if (pMsg->message == WM_KEYDOWN)
1279 switch (pMsg->wParam)
1281 /* case VK_RETURN:
1283 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1285 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1287 PostMessage(WM_COMMAND, IDOK);
1289 return TRUE;
1292 break;
1293 */ case VK_F2:
1295 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1297 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1298 if (pos)
1299 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1302 break;
1304 case VK_F5:
1306 Refresh();
1308 break;
1309 case L'E':
1311 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1313 m_ctrlFilter.SetSel(0, -1, FALSE);
1314 m_ctrlFilter.SetFocus();
1315 return TRUE;
1318 break;
1323 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1326 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1328 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1329 *pResult = 0;
1331 if(m_currSortCol == pNMLV->iSubItem)
1332 m_currSortDesc = !m_currSortDesc;
1333 else
1335 m_currSortCol = pNMLV->iSubItem;
1336 m_currSortDesc = false;
1339 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1340 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1342 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1345 void CBrowseRefsDlg::OnDestroy()
1347 if (!m_bPickedRefSet)
1348 m_pickedRef = GetSelectedRef(true, false);
1350 CResizableStandAloneDialog::OnDestroy();
1353 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1355 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1356 *pResult = 0;
1358 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1359 if (item && pNMListView->uNewState == LVIS_SELECTED)
1360 m_sLastSelected = item->GetRefName();
1362 UpdateInfoLabel();
1365 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1367 *pResult = 0;
1369 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1370 return;
1371 EndDialog(IDOK);
1374 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1376 CBrowseRefsDlg dlg(CString(), nullptr);
1378 if(initialRef.IsEmpty())
1379 initialRef = L"HEAD";
1380 dlg.m_initialRef = initialRef;
1381 dlg.m_pickRef_Kind = pickRef_Kind;
1382 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1384 if(dlg.DoModal() != IDOK)
1385 return CString();
1387 return dlg.m_pickedRef;
1390 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1392 CString origRef;
1393 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1394 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1395 if(resultRef.IsEmpty())
1396 return false;
1397 if(wcsncmp(resultRef,L"refs/",5)==0)
1398 resultRef = resultRef.Mid(5);
1399 // if(wcsncmp(resultRef,L"heads/",6)==0)
1400 // resultRef = resultRef.Mid(6);
1402 //Find closest match of choice in combobox
1403 int ixFound = -1;
1404 int matchLength = 0;
1405 CString comboRefName;
1406 for(int i = 0; i < pComboBox->GetCount(); ++i)
1408 pComboBox->GetLBText(i, comboRefName);
1409 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1410 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1411 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1413 matchLength = comboRefName.GetLength();
1414 ixFound = i;
1417 if(ixFound >= 0)
1418 pComboBox->SetCurSel(ixFound);
1419 else
1420 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)
1422 return true;
1425 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1427 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1428 *pResult = FALSE;
1430 if (!pDispInfo->item.pszText)
1431 return; //User canceled changing
1433 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1435 if(!pTree->IsFrom(L"refs/heads/"))
1437 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1438 return;
1441 CString selectedTreeRef;
1442 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1443 if (!hTree)
1445 CShadowTree* pTree2 = (CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
1446 selectedTreeRef = pTree2->GetRefName();
1449 CString origName = pTree->GetRefName().Mid(11);
1451 CString newName;
1452 if (m_pListCtrlRoot)
1453 newName = m_pListCtrlRoot->GetRefName() + L'/';
1454 newName += pDispInfo->item.pszText;
1456 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1458 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1459 return;
1462 CString newNameTrunced = newName.Mid(11);
1464 CString errorMsg;
1465 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1467 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1468 return;
1470 //Do as if it failed to rename. Let Refresh() do the job.
1471 //*pResult = TRUE;
1473 Refresh(selectedTreeRef);
1475 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1477 // AfxMessageBox(W_csPopup);
1480 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1482 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1483 *pResult = FALSE;
1485 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1487 if(!pTree->IsFrom(L"refs/heads/"))
1489 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1490 return;
1494 void CBrowseRefsDlg::OnEnChangeEditFilter()
1496 SetTimer(IDT_FILTER, 1000, nullptr);
1499 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1501 if (nIDEvent == IDT_FILTER)
1503 KillTimer(IDT_FILTER);
1504 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1507 CResizableStandAloneDialog::OnTimer(nIDEvent);
1510 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1512 // FIXME: x64 version would get this function called with unexpected parameters.
1513 if (!lParam)
1514 return 0;
1516 RECT * rect = (LPRECT)lParam;
1517 CPoint point;
1518 CString temp;
1519 point = CPoint(rect->left, rect->bottom);
1520 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1521 CMenu popup;
1522 if (popup.CreatePopupMenu())
1524 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1525 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1527 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1528 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1530 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1531 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1533 temp.LoadString(IDS_LOG_FILTER_REVS);
1534 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1536 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1537 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1539 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1540 if (selection != 0)
1542 if (selection == LOGFILTER_TOGGLE)
1543 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1544 else
1545 m_SelectedFilters ^= selection;
1546 SetFilterCueText();
1547 SetTimer(IDT_FILTER, 1000, nullptr);
1550 return 0L;
1553 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1555 KillTimer(LOGFILTER_TIMER);
1556 m_ctrlFilter.SetWindowText(_T(""));
1557 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1558 return 0L;
1561 void CBrowseRefsDlg::SetFilterCueText()
1563 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1564 temp += _T(" ");
1566 if (m_SelectedFilters & LOGFILTER_REFNAME)
1567 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1569 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1571 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1572 temp += _T(", ");
1573 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1576 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1578 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1579 temp += _T(", ");
1580 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1583 if (m_SelectedFilters & LOGFILTER_REVS)
1585 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1586 temp += _T(", ");
1587 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1590 // to make the cue banner text appear more to the right of the edit control
1591 temp = _T(" ") + temp;
1592 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1595 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1597 m_pickedRef = g_Git.GetCurrentBranch(true);
1598 m_bPickedRefSet = true;
1599 OnOK();
1602 void CBrowseRefsDlg::UpdateInfoLabel()
1604 CString temp;
1605 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1606 SetDlgItemText(IDC_INFOLABEL, temp);
1609 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1611 UpdateData(TRUE);
1612 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1613 Refresh();