Unbreak RefBroweser context menu
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob0687ad90e17cbb2453ebf4c17775e5e0793bf839
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.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
295 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
296 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
297 m_bPickedRefSet = false;
299 AddAnchor(IDOK,BOTTOM_RIGHT);
300 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
301 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
303 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
304 UpdateData();
306 Refresh(m_initialRef);
308 EnableSaveRestore(L"BrowseRefs");
310 CString sWindowTitle;
311 GetWindowText(sWindowTitle);
312 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
314 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
316 if (m_bPickOne)
317 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
319 m_ListRefLeafs.SetFocus();
320 return FALSE;
323 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
325 int posSlash=nameLeft.Find('/');
326 CString nameSub;
327 if(posSlash<0)
329 nameSub=nameLeft;
330 nameLeft.Empty();//Nothing left
332 else
334 nameSub=nameLeft.Left(posSlash);
335 nameLeft=nameLeft.Mid(posSlash+1);
337 if(nameSub.IsEmpty())
338 return nullptr;
340 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
341 return nullptr;
343 CShadowTree& nextNode=m_ShadowTree[nameSub];
344 nextNode.m_csRefName=nameSub;
345 nextNode.m_pParent=this;
346 return &nextNode;
349 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
351 if(IsLeaf())
353 if(m_csRefName.GetLength() > partialRefName.GetLength())
354 return nullptr;
355 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
357 //Match of leaf name. Try match on total name.
358 CString totalRefName = GetRefName();
359 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
360 return this; //Also match. Found.
363 else
365 //Not a leaf. Search all nodes.
366 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
368 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
369 if (pSubtree)
370 return pSubtree; //Found
373 return nullptr; //Not found
376 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
378 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
379 //List ctrl selection?
380 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
382 //A leaf is selected
383 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
384 m_ListRefLeafs.GetNextSelectedItem(pos));
385 return pTree->GetRefName();
387 else if (pos && !pickFirstSelIfMultiSel)
389 // at least one leaf is selected
390 CString refs;
391 int index;
392 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
394 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
395 if(wcsncmp(ref, L"refs/", 5) == 0)
396 ref = ref.Mid(5);
397 if(wcsncmp(ref, L"heads/", 6) == 0)
398 ref = ref.Mid(6);
399 refs += ref + _T(" ");
401 return refs.Trim();
403 else if(!onlyIfLeaf)
405 //Tree ctrl selection?
406 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
407 if (hTree)
409 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
410 return pTree->GetRefName();
413 return CString();//None
416 void CBrowseRefsDlg::Refresh(CString selectRef)
418 remotes.clear();
419 if (g_Git.GetRemoteList(remotes))
420 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
422 if(!selectRef.IsEmpty())
424 if(selectRef == "HEAD")
426 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
427 selectRef.Empty();
428 else
429 selectRef = L"refs/heads/" + selectRef;
432 else
433 selectRef = GetSelectedRef(false, true);
435 m_RefTreeCtrl.DeleteAllItems();
436 m_ListRefLeafs.DeleteAllItems();
437 m_TreeRoot.m_ShadowTree.clear();
438 m_TreeRoot.m_csRefName = "refs";
439 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs", nullptr, nullptr);
440 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
442 CString err;
443 MAP_REF_GITREVREFBROWSER refMap;
444 if (GitRevRefBrowser::GetGitRevRefMap(refMap, err, [&](const CString& refName)
446 //Use ref based on m_pickRef_Kind
447 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
448 return false; //Skip
449 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
450 return false; //Skip
451 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
452 return false; //Skip
453 if (m_pickRef_Kind == gPickRef_Remote && wcsncmp(refName, L"refs/remotes/", 13) != 0) // do not show refs/stash if only remote branches are requested
454 return false;
455 return true;
458 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
461 //Populate ref tree
462 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
464 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
465 GitRevRefBrowser ref = iterRefMap->second;
467 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
468 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
469 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
470 treeLeaf.m_csSubject = ref.GetSubject();
471 treeLeaf.m_csAuthor = ref.GetAuthorName();
472 treeLeaf.m_csDate = ref.GetAuthorDate();
473 treeLeaf.m_csDescription = ref.m_Description;
476 // always expand the tree first
477 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
479 // try exact match first
480 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
481 SelectRef(selectRef, false);
484 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
486 if(!bExactMatch)
488 CString newRefName = GetFullRefName(refName);
489 if(!newRefName.IsEmpty())
490 refName = newRefName;
491 //else refName is not a valid ref. Try to select as good as possible.
493 if (_wcsnicmp(refName, L"refs", 4) != 0)
494 return false; // Not a ref name
496 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
497 if (treeLeafHead.m_hTree)
499 //Not a leaf. Select tree node and return
500 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
501 return true;
504 if (!treeLeafHead.m_pParent)
505 return false; //Weird... should not occur.
507 //This is the current head.
508 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
510 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
512 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
513 if(pCurrShadowTree == &treeLeafHead)
515 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
516 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
520 return true;
523 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
525 if (!pTreePos)
527 if(_wcsnicmp(refName, L"refs/", 5) == 0)
528 refName=refName.Mid(5);
529 pTreePos=&m_TreeRoot;
531 if(refName.IsEmpty())
532 return *pTreePos;//Found leaf
534 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
535 if (!pNextTree)
537 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
538 ASSERT(!bCreateIfNotExist);
539 return *pTreePos;
542 if(!refName.IsEmpty())
544 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
545 //Leafs are for the list control.
546 if (!pNextTree->m_hTree)
548 //New tree. Create node in control.
549 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree, nullptr);
550 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
554 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
558 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
560 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
561 *pResult = 0;
563 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
566 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
568 m_ListRefLeafs.DeleteAllItems();
570 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
571 if (!pTree)
573 ASSERT(FALSE);
574 return;
576 FillListCtrlForShadowTree(pTree,L"",true);
577 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
578 m_ListRefLeafs.AdjustColumnWidths();
581 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
583 if(pTree->IsLeaf())
585 CString filter;
586 m_ctrlFilter.GetWindowText(filter);
587 filter.MakeLower();
588 bool positive = filter[0] != '!';
589 if (!positive)
590 filter = filter.Mid(1);
591 CString ref = refNamePrefix + pTree->m_csRefName;
592 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && !pTree->m_pParent) && IsMatchFilter(pTree, ref, filter, positive))
594 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
596 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
597 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
598 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
599 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
600 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
601 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
602 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
603 CString descrition = pTree->m_csDescription;
604 descrition.Replace(L"\n", L" ");
605 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
608 else
610 CString csThisName;
611 if (!isFirstLevel && !m_bIncludeNestedRefs)
612 return;
613 else if (!isFirstLevel)
614 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
615 else
616 m_pListCtrlRoot = pTree;
617 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
619 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
622 if (isFirstLevel)
624 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
625 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
627 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
631 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
633 if (m_SelectedFilters & LOGFILTER_REFNAME)
635 CString msg = ref;
636 msg = msg.MakeLower();
638 if (msg.Find(filter) >= 0)
639 return positive;
642 if (m_SelectedFilters & LOGFILTER_SUBJECT)
644 CString msg = pTree->m_csSubject;
645 msg = msg.MakeLower();
647 if (msg.Find(filter) >= 0)
648 return positive;
651 if (m_SelectedFilters & LOGFILTER_AUTHORS)
653 CString msg = pTree->m_csAuthor;
654 msg = msg.MakeLower();
656 if (msg.Find(filter) >= 0)
657 return positive;
660 if (m_SelectedFilters & LOGFILTER_REVS)
662 CString msg = pTree->m_csRefHash;
663 msg = msg.MakeLower();
665 if (msg.Find(filter) >= 0)
666 return positive;
668 return !positive;
671 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
673 ASSERT(!leafs.empty());
675 CString csMessage;
676 UINT mbIcon=MB_ICONQUESTION;
678 bool bIsRemoteBranch = false;
679 bool bIsBranch = false;
680 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
681 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
683 if(bIsBranch)
685 if(leafs.size() == 1)
687 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
688 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
690 //Check if branch is fully merged in HEAD
691 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
693 csMessage += L"\r\n\r\n";
694 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
695 mbIcon = MB_ICONWARNING;
698 if(bIsRemoteBranch)
700 csMessage += L"\r\n\r\n";
701 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
702 mbIcon = MB_ICONWARNING;
705 else
707 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
709 csMessage += L"\r\n\r\n";
710 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
711 mbIcon = MB_ICONWARNING;
713 if(bIsRemoteBranch)
715 csMessage += L"\r\n\r\n";
716 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
717 mbIcon = MB_ICONWARNING;
722 else if(leafs[0]->IsFrom(L"refs/tags/"))
724 if(leafs.size() == 1)
726 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
727 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
729 else
730 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
733 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
736 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
738 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
739 if(!DoDeleteRef((*i)->GetRefName()))
740 return false;
741 return true;
744 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
746 bool bIsRemoteBranch = false;
747 bool bIsBranch = false;
748 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
749 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
751 if (bIsRemoteBranch)
753 CString branchToDelete = completeRefName.Mid(13);
754 CString remoteName, remoteBranchToDelete;
755 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
756 return false;
758 if (CAppUtils::IsSSHPutty())
759 CAppUtils::LaunchPAgent(nullptr, &remoteName);
761 CSysProgressDlg sysProgressDlg;
762 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
763 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
764 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
765 sysProgressDlg.SetShowProgressBar(false);
766 sysProgressDlg.ShowModal(this, true);
768 STRING_VECTOR list;
769 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
770 if (g_Git.DeleteRemoteRefs(remoteName, list))
772 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
773 sysProgressDlg.Stop();
774 BringWindowToTop();
775 return false;
777 sysProgressDlg.Stop();
778 BringWindowToTop();
780 else if (bIsBranch)
782 if (g_Git.DeleteRef(completeRefName))
784 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
785 return false;
788 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
790 if (g_Git.DeleteRef(completeRefName))
792 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
793 return false;
796 return true;
799 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
801 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
802 if (!pLeaf)
803 return CString();
804 return pLeaf->GetRefName();
808 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
810 if (pWndFrom == &m_RefTreeCtrl)
811 OnContextMenu_RefTreeCtrl(point);
814 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
816 CPoint clientPoint=point;
817 m_RefTreeCtrl.ScreenToClient(&clientPoint);
819 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
820 if (hTreeItem)
821 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
823 VectorPShadowTree tree;
824 ShowContextMenu(point,hTreeItem,tree);
827 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
829 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
830 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
831 while (pos)
833 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
837 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
839 std::vector<CShadowTree*> selectedLeafs;
840 GetSelectedLeaves(selectedLeafs);
841 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
844 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
846 ASSERT(selectedLeafs.size() == 2);
848 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
849 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
850 else
851 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
854 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
856 int i = 0;
857 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
859 if (*it == entry)
860 return i;
862 return -1;
865 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
867 CIconMenu popupMenu;
868 popupMenu.CreatePopupMenu();
870 bool bAddSeparator = false;
871 CString remoteName;
873 if(selectedLeafs.size()==1)
875 bAddSeparator = true;
877 bool bShowReflogOption = false;
878 bool bShowFetchOption = false;
879 bool bShowRenameOption = false;
880 bool bShowCreateBranchOption = false;
881 bool bShowEditBranchDescriptionOption = false;
883 CString fetchFromCmd;
885 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
887 bShowReflogOption = true;
888 bShowRenameOption = true;
889 bShowEditBranchDescriptionOption = true;
891 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
893 bShowReflogOption = true;
894 bShowFetchOption = true;
895 bShowCreateBranchOption = true;
897 CString remoteBranch;
898 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
899 bShowFetchOption = false;
900 else
901 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
904 CString temp;
905 temp.LoadString(IDS_MENULOG);
906 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
907 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
908 if(bShowReflogOption)
910 temp.LoadString(IDS_MENUREFLOG);
911 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
914 popupMenu.AppendMenu(MF_SEPARATOR);
915 bAddSeparator = false;
917 if(bShowFetchOption)
919 bAddSeparator = true;
920 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
923 if(bAddSeparator)
924 popupMenu.AppendMenu(MF_SEPARATOR);
926 bAddSeparator = false;
927 if (m_bHasWC)
929 CString str;
930 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
932 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
933 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
935 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
936 popupMenu.AppendMenu(MF_SEPARATOR);
939 if(bShowCreateBranchOption)
941 bAddSeparator = true;
942 temp.LoadString(IDS_MENUBRANCH);
943 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
946 if (bShowEditBranchDescriptionOption)
948 bAddSeparator = true;
949 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
951 if(bShowRenameOption)
953 bAddSeparator = true;
954 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
957 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
959 if (bAddSeparator)
960 popupMenu.AppendMenu(MF_SEPARATOR);
961 bAddSeparator = true;
962 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
963 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH)));
964 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH)));
967 else if(selectedLeafs.size() == 2)
969 bAddSeparator = true;
970 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
971 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
972 CString menu;
973 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
974 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
975 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
976 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
979 if(!selectedLeafs.empty())
981 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
983 if(bAddSeparator)
984 popupMenu.AppendMenu(MF_SEPARATOR);
985 CString menuItemName;
986 if(selectedLeafs.size() == 1)
987 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
988 else
989 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
991 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
992 bAddSeparator = true;
994 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
996 if(bAddSeparator)
997 popupMenu.AppendMenu(MF_SEPARATOR);
998 CString menuItemName;
999 if(selectedLeafs.size() == 1)
1000 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1001 else
1002 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1004 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1005 bAddSeparator = true;
1007 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1009 if(bAddSeparator)
1010 popupMenu.AppendMenu(MF_SEPARATOR);
1011 CString menuItemName;
1012 if(selectedLeafs.size() == 1)
1013 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1014 else
1015 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1017 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1018 bAddSeparator = true;
1023 if (hTreePos && selectedLeafs.empty())
1025 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1026 if(pTree->IsFrom(L"refs/remotes"))
1028 if(bAddSeparator)
1029 popupMenu.AppendMenu(MF_SEPARATOR);
1030 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1031 bAddSeparator = true;
1032 if(selectedLeafs.empty())
1034 CString remoteBranch;
1035 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1036 remoteName.Empty();
1037 int pos = findVectorPosition(remotes, remoteName);
1038 if (pos >= 0)
1040 CString temp;
1041 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1042 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1044 temp.LoadString(IDS_DELETEREMOTETAG);
1045 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1049 if(pTree->IsFrom(L"refs/heads"))
1051 if(bAddSeparator)
1052 popupMenu.AppendMenu(MF_SEPARATOR);
1053 CString temp;
1054 temp.LoadString(IDS_MENUBRANCH);
1055 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1057 if(pTree->IsFrom(L"refs/tags"))
1059 if(bAddSeparator)
1060 popupMenu.AppendMenu(MF_SEPARATOR);
1061 CString temp;
1062 temp.LoadString(IDS_MENUTAG);
1063 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1064 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1065 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1066 if (!remotes.empty())
1068 popupMenu.AppendMenu(MF_SEPARATOR);
1069 int i = 0;
1070 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1072 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1073 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1080 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1081 switch ((eCmd)(selection & 0xFFFF))
1083 case eCmd_ViewLog:
1085 CString sCmd;
1086 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1087 CAppUtils::RunTortoiseGitProc(sCmd);
1089 break;
1090 case eCmd_ViewLogRange:
1092 CString sCmd;
1093 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1094 CAppUtils::RunTortoiseGitProc(sCmd);
1096 break;
1097 case eCmd_ViewLogRangeReachableFromOnlyOne:
1099 CString sCmd;
1100 sCmd.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1101 CAppUtils::RunTortoiseGitProc(sCmd);
1103 break;
1104 case eCmd_RepoBrowser:
1105 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1106 break;
1107 case eCmd_DeleteBranch:
1108 case eCmd_DeleteRemoteBranch:
1110 if(ConfirmDeleteRef(selectedLeafs))
1111 DoDeleteRefs(selectedLeafs);
1112 Refresh();
1114 break;
1115 case eCmd_DeleteTag:
1117 if(ConfirmDeleteRef(selectedLeafs))
1118 DoDeleteRefs(selectedLeafs);
1119 Refresh();
1121 break;
1122 case eCmd_ShowReflog:
1124 CRefLogDlg refLogDlg(this);
1125 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1126 refLogDlg.DoModal();
1128 break;
1129 case eCmd_Fetch:
1131 CAppUtils::Fetch(remoteName);
1132 Refresh();
1134 break;
1135 case eCmd_DeleteRemoteTag:
1137 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1138 int remoteInx = selection >> 16;
1139 if (remoteInx < 0 || (size_t)remoteInx >= remotes.size())
1140 return;
1141 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1142 deleteRemoteTagDlg.DoModal();
1144 break;
1145 case eCmd_Merge:
1147 CString ref = selectedLeafs[0]->GetRefName();
1148 CAppUtils::Merge(&ref);
1150 break;
1151 case eCmd_Switch:
1153 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1155 break;
1156 case eCmd_Rename:
1158 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1159 if (pos)
1160 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1162 break;
1163 case eCmd_AddRemote:
1165 CAddRemoteDlg(this).DoModal();
1166 Refresh();
1168 break;
1169 case eCmd_ManageRemotes:
1171 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1172 // CSettingGitRemote W_Remotes(m_cmdPath);
1173 // W_Remotes.DoModal();
1174 Refresh();
1176 break;
1177 case eCmd_CreateBranch:
1179 CString* commitHash = nullptr;
1180 if (selectedLeafs.size() == 1)
1181 commitHash = &(selectedLeafs[0]->m_csRefHash);
1182 CAppUtils::CreateBranchTag(false, commitHash);
1183 Refresh();
1185 break;
1186 case eCmd_CreateTag:
1188 CAppUtils::CreateBranchTag(true);
1189 Refresh();
1191 break;
1192 case eCmd_DeleteAllTags:
1194 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1196 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1197 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1199 if (ConfirmDeleteRef(selectedLeafs))
1200 DoDeleteRefs(selectedLeafs);
1201 Refresh();
1203 break;
1204 case eCmd_Diff:
1206 CFileDiffDlg dlg;
1207 dlg.SetDiff(
1208 nullptr,
1209 selectedLeafs[0]->GetRefName() + L"^{}",
1210 selectedLeafs[1]->GetRefName() + L"^{}");
1211 dlg.DoModal();
1213 break;
1214 case eCmd_UnifiedDiff:
1216 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1218 break;
1219 case eCmd_EditBranchDescription:
1221 CInputDlg dlg;
1222 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1223 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1224 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1225 dlg.m_bUseLogWidth = true;
1226 if(dlg.DoModal() == IDOK)
1228 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1229 Refresh();
1232 break;
1233 case eCmd_UpstreamDrop:
1235 CString key;
1236 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1237 g_Git.UnsetConfigValue(key);
1238 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1239 g_Git.UnsetConfigValue(key);
1241 Refresh();
1242 break;
1243 case eCmd_UpstreamSet:
1245 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1246 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1247 return;
1248 CString remote, branch;
1249 if (SplitRemoteBranchName(newRef, remote, branch))
1250 return;
1251 CString key;
1252 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1253 g_Git.SetConfigValue(key, remote);
1254 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1255 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1256 Refresh();
1258 break;
1262 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1264 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1265 if(!(*i)->IsFrom(from))
1266 return false;
1267 return true;
1270 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1272 if (pMsg->message == WM_KEYDOWN)
1274 switch (pMsg->wParam)
1276 /* case VK_RETURN:
1278 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1280 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1282 PostMessage(WM_COMMAND, IDOK);
1284 return TRUE;
1287 break;
1288 */ case VK_F2:
1290 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1292 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1293 if (pos)
1294 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1297 break;
1299 case VK_F5:
1301 Refresh();
1303 break;
1304 case L'E':
1306 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1308 m_ctrlFilter.SetSel(0, -1, FALSE);
1309 m_ctrlFilter.SetFocus();
1310 return TRUE;
1313 break;
1318 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1321 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1323 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1324 *pResult = 0;
1326 if(m_currSortCol == pNMLV->iSubItem)
1327 m_currSortDesc = !m_currSortDesc;
1328 else
1330 m_currSortCol = pNMLV->iSubItem;
1331 m_currSortDesc = false;
1334 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1335 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1337 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1340 void CBrowseRefsDlg::OnDestroy()
1342 if (!m_bPickedRefSet)
1343 m_pickedRef = GetSelectedRef(true, false);
1345 CResizableStandAloneDialog::OnDestroy();
1348 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1350 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1351 *pResult = 0;
1353 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1354 if (item && pNMListView->uNewState == LVIS_SELECTED)
1355 m_sLastSelected = item->GetRefName();
1357 UpdateInfoLabel();
1360 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1362 *pResult = 0;
1364 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1365 return;
1366 EndDialog(IDOK);
1369 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1371 CBrowseRefsDlg dlg(CString(), nullptr);
1373 if(initialRef.IsEmpty())
1374 initialRef = L"HEAD";
1375 dlg.m_initialRef = initialRef;
1376 dlg.m_pickRef_Kind = pickRef_Kind;
1377 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1379 if(dlg.DoModal() != IDOK)
1380 return CString();
1382 return dlg.m_pickedRef;
1385 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1387 CString origRef;
1388 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1389 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1390 if(resultRef.IsEmpty())
1391 return false;
1392 if(wcsncmp(resultRef,L"refs/",5)==0)
1393 resultRef = resultRef.Mid(5);
1394 // if(wcsncmp(resultRef,L"heads/",6)==0)
1395 // resultRef = resultRef.Mid(6);
1397 //Find closest match of choice in combobox
1398 int ixFound = -1;
1399 int matchLength = 0;
1400 CString comboRefName;
1401 for(int i = 0; i < pComboBox->GetCount(); ++i)
1403 pComboBox->GetLBText(i, comboRefName);
1404 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1405 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1406 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1408 matchLength = comboRefName.GetLength();
1409 ixFound = i;
1412 if(ixFound >= 0)
1413 pComboBox->SetCurSel(ixFound);
1414 else
1415 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)
1417 return true;
1420 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1422 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1423 *pResult = FALSE;
1425 if (!pDispInfo->item.pszText)
1426 return; //User canceled changing
1428 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1430 if(!pTree->IsFrom(L"refs/heads/"))
1432 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1433 return;
1436 CString selectedTreeRef;
1437 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1438 if (!hTree)
1440 CShadowTree* pTree2 = (CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
1441 selectedTreeRef = pTree2->GetRefName();
1444 CString origName = pTree->GetRefName().Mid(11);
1446 CString newName;
1447 if (m_pListCtrlRoot)
1448 newName = m_pListCtrlRoot->GetRefName() + L'/';
1449 newName += pDispInfo->item.pszText;
1451 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1453 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1454 return;
1457 CString newNameTrunced = newName.Mid(11);
1459 CString errorMsg;
1460 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1462 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1463 return;
1465 //Do as if it failed to rename. Let Refresh() do the job.
1466 //*pResult = TRUE;
1468 Refresh(selectedTreeRef);
1470 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1472 // AfxMessageBox(W_csPopup);
1475 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1477 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1478 *pResult = FALSE;
1480 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1482 if(!pTree->IsFrom(L"refs/heads/"))
1484 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1485 return;
1489 void CBrowseRefsDlg::OnEnChangeEditFilter()
1491 SetTimer(IDT_FILTER, 1000, nullptr);
1494 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1496 if (nIDEvent == IDT_FILTER)
1498 KillTimer(IDT_FILTER);
1499 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1502 CResizableStandAloneDialog::OnTimer(nIDEvent);
1505 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1507 // FIXME: x64 version would get this function called with unexpected parameters.
1508 if (!lParam)
1509 return 0;
1511 RECT * rect = (LPRECT)lParam;
1512 CPoint point;
1513 CString temp;
1514 point = CPoint(rect->left, rect->bottom);
1515 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1516 CMenu popup;
1517 if (popup.CreatePopupMenu())
1519 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1520 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1522 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1523 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1525 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1526 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1528 temp.LoadString(IDS_LOG_FILTER_REVS);
1529 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1531 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1532 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1534 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1535 if (selection != 0)
1537 if (selection == LOGFILTER_TOGGLE)
1538 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1539 else
1540 m_SelectedFilters ^= selection;
1541 SetFilterCueText();
1542 SetTimer(IDT_FILTER, 1000, nullptr);
1545 return 0L;
1548 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1550 KillTimer(LOGFILTER_TIMER);
1551 m_ctrlFilter.SetWindowText(_T(""));
1552 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1553 return 0L;
1556 void CBrowseRefsDlg::SetFilterCueText()
1558 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1559 temp += _T(" ");
1561 if (m_SelectedFilters & LOGFILTER_REFNAME)
1562 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1564 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1566 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1567 temp += _T(", ");
1568 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1571 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1573 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1574 temp += _T(", ");
1575 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1578 if (m_SelectedFilters & LOGFILTER_REVS)
1580 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1581 temp += _T(", ");
1582 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1585 // to make the cue banner text appear more to the right of the edit control
1586 temp = _T(" ") + temp;
1587 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1590 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1592 m_pickedRef = g_Git.GetCurrentBranch(true);
1593 m_bPickedRefSet = true;
1594 OnOK();
1597 void CBrowseRefsDlg::UpdateInfoLabel()
1599 CString temp;
1600 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1601 SetDlgItemText(IDC_INFOLABEL, temp);
1604 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1606 UpdateData(TRUE);
1607 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1608 Refresh();