Add support for Ctrl+A in BrowseRefsDlg and RepositoryBrowser
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobf7d5d122f0854ced3096dd4ac87e67e52e1269cd
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2021 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // BrowseRefsDlg.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
25 #include "LogDlg.h"
26 #include "AddRemoteDlg.h"
27 #include "AppUtils.h"
28 #include "Settings/SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
32 #include "IconMenu.h"
33 #include "FileDiffDlg.h"
34 #include "DeleteRemoteTagDlg.h"
35 #include "UnicodeUtils.h"
36 #include "InputDlg.h"
37 #include "SysProgressDlg.h"
38 #include "LoglistUtils.h"
39 #include "GitRevRefBrowser.h"
40 #include "StringUtils.h"
41 #include "BrowseRefsDlgFilter.h"
42 #include "DPIAware.h"
44 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
46 if (CStringUtils::StartsWith(ref, L"refs/remotes/"))
47 ref = ref.Mid(static_cast<int>(wcslen(L"refs/remotes/")));
48 else if (CStringUtils::StartsWith(ref, L"remotes/"))
49 ref = ref.Mid(static_cast<int>(wcslen(L"remotes/")));
51 STRING_VECTOR list;
52 int result = g_Git.GetRemoteList(list);
53 if (result != 0)
54 return result;
56 for (size_t i = 0; i < list.size(); ++i)
58 if (CStringUtils::StartsWith(ref, list[i] + L"/"))
60 remote = list[i];
61 branch = ref.Mid(list[i].GetLength() + 1);
62 return 0;
64 if (ref == list[i])
66 remote = list[i];
67 branch.Empty();
68 return 0;
72 return -1;
75 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
77 if (!control)
78 return;
79 // set the sort arrow
80 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
81 HDITEM HeaderItem = {0};
82 HeaderItem.mask = HDI_FORMAT;
83 for (int i=0; i<pHeader->GetItemCount(); ++i)
85 pHeader->GetItem(i, &HeaderItem);
86 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
87 pHeader->SetItem(i, &HeaderItem);
89 if (nColumn >= 0)
91 pHeader->GetItem(nColumn, &HeaderItem);
92 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
93 pHeader->SetItem(nColumn, &HeaderItem);
97 class CRefLeafListCompareFunc
99 public:
100 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
101 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
102 if (m_bSortLogical)
103 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
106 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
108 return reinterpret_cast<CRefLeafListCompareFunc*>(lParamSort)->Compare(lParam1, lParam2);
111 int Compare(LPARAM lParam1, LPARAM lParam2)
113 return Compare(
114 reinterpret_cast<CShadowTree*>(m_pList->GetItemData(static_cast<int>(lParam1))),
115 reinterpret_cast<CShadowTree*>(m_pList->GetItemData(static_cast<int>(lParam2))));
118 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
120 int result=CompareNoDesc(pLeft,pRight);
121 if(m_desc)
122 return -result;
123 return result;
126 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
128 switch(m_col)
130 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
131 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
132 case CBrowseRefsDlg::eCol_Date: return ((pLeft->m_csDate == pRight->m_csDate) ? 0 : ((pLeft->m_csDate > pRight->m_csDate) ? 1 : -1));
133 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
134 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
135 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
136 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
138 return 0;
140 int SortStrCmp(const CString& left, const CString& right)
142 if (m_bSortLogical)
143 return StrCmpLogicalW(left, right);
144 return StrCmpI(left, right);
147 int m_col;
148 bool m_desc;
149 CListCtrl* m_pList;
150 bool m_bSortLogical;
153 // CBrowseRefsDlg dialog
155 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
157 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=nullptr*/)
158 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
159 m_cmdPath(cmdPath),
160 m_currSortCol(0),
161 m_currSortDesc(false),
162 m_regCurrSortCol(L"Software\\TortoiseGit\\RefBrowserSortCol", 0),
163 m_regCurrSortDesc(L"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE),
164 m_initialRef(L"HEAD"),
165 m_pickRef_Kind(gPickRef_All),
166 m_pListCtrlRoot(nullptr),
167 m_bHasWC(true),
168 m_SelectedFilters(LOGFILTER_ALL),
169 m_bPickOne(false),
170 m_bIncludeNestedRefs(TRUE),
171 m_bPickedRefSet(false)
172 , m_bWantPick(false)
174 // get short/long datetime setting from registry
175 DWORD RegUseShortDateFormat = CRegDWORD(L"Software\\TortoiseGit\\LogDateFormat", TRUE);
176 if (RegUseShortDateFormat)
177 m_DateFormat = DATE_SHORTDATE;
178 else
179 m_DateFormat = DATE_LONGDATE;
180 // get relative time display setting from registry
181 DWORD regRelativeTimes = CRegDWORD(L"Software\\TortoiseGit\\RelativeTimes", FALSE);
182 m_bRelativeTimes = (regRelativeTimes != 0);
184 m_regIncludeNestedRefs = CRegDWORD(L"Software\\TortoiseGit\\RefBrowserIncludeNestedRefs", TRUE);
186 m_currSortCol = m_regCurrSortCol;
187 m_currSortDesc = m_regCurrSortDesc == TRUE;
190 CBrowseRefsDlg::~CBrowseRefsDlg()
192 m_regCurrSortCol = m_currSortCol;
193 m_regCurrSortDesc = m_currSortDesc;
196 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
198 CDialog::DoDataExchange(pDX);
199 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
200 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
201 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
202 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
203 DDX_Control(pDX, IDC_BROWSE_REFS_BRANCHFILTER, m_cBranchFilter);
207 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
208 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
209 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
210 ON_WM_CONTEXTMENU()
211 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
212 ON_WM_DESTROY()
213 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
214 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
215 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
216 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
217 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
218 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
219 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
220 ON_WM_TIMER()
221 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
222 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
223 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter)
224 END_MESSAGE_MAP()
227 // CBrowseRefsDlg message handlers
229 void CBrowseRefsDlg::OnBnClickedOk()
231 if (m_bPickOne || !m_bShowRangeOptionWithTwoRefs || m_ListRefLeafs.GetSelectedCount() != 2)
233 OnOK();
234 return;
237 CIconMenu popupMenu;
238 popupMenu.CreatePopupMenu();
240 std::vector<CShadowTree*> selectedLeafs;
241 GetSelectedLeaves(selectedLeafs);
243 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
244 popupMenu.SetDefaultItem(1);
245 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L".."), IDI_LOG);
246 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..."), IDI_LOG);
248 RECT rect;
249 GetDlgItem(IDOK)->GetWindowRect(&rect);
250 TPMPARAMS params;
251 params.cbSize = sizeof(TPMPARAMS);
252 params.rcExclude = rect;
253 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY | TPM_VERTICAL, rect.left, rect.top, this, &params);
254 switch (selection)
256 case 1:
257 OnOK();
258 break;
259 case 2:
261 m_bPickedRefSet = true;
262 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..");
263 OnOK();
265 break;
266 case 3:
268 m_bPickedRefSet = true;
269 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"...");
270 OnOK();
272 break;
273 default:
274 break;
278 BOOL CBrowseRefsDlg::OnInitDialog()
280 CResizableStandAloneDialog::OnInitDialog();
281 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
283 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
284 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
285 m_ctrlFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
286 SetFilterCueText();
288 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
289 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
290 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
291 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_CENTER);
292 AddAnchor(IDC_BROWSE_REFS_BRANCHFILTER, TOP_CENTER, TOP_RIGHT);
293 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
294 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
295 AddAnchor(IDHELP, BOTTOM_RIGHT);
297 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | LVS_EX_INFOTIP | LVS_EX_DOUBLEBUFFER);
298 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
299 static int columnWidths[] = { 0, 0, 0, CDPIAware::Instance().ScaleX(300), 0, 0, CDPIAware::Instance().ScaleX(80) };
300 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
301 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
302 m_ListRefLeafs.m_bAllowHiding = false;
303 m_ListRefLeafs.Init();
304 m_ListRefLeafs.SetListContextMenuHandler([&](CPoint point) {OnContextMenu_ListRefLeafs(point); });
305 m_ListRefLeafs.m_ColumnManager.SetNames(columnNames, _countof(columnNames));
306 constexpr int columnVersion = 6; // adjust when changing number/names/etc. of columns
307 m_ListRefLeafs.m_ColumnManager.ReadSettings(dwDefaultColumns, 0, L"BrowseRefs", columnVersion, _countof(columnNames), columnWidths);
308 m_bPickedRefSet = false;
310 AddAnchor(IDOK,BOTTOM_RIGHT);
311 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
312 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
314 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
315 UpdateData(FALSE);
317 Refresh(m_initialRef);
319 EnableSaveRestore(L"BrowseRefs");
321 CString sWindowTitle;
322 GetWindowText(sWindowTitle);
323 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
325 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
327 if (m_bPickOne)
328 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
330 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_ALL)));
331 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED)));
332 m_cBranchFilter.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED)));
333 m_cBranchFilter.SetCurSel(0);
335 m_ListRefLeafs.SetFocus();
336 return FALSE;
339 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
341 int posSlash=nameLeft.Find('/');
342 CString nameSub;
343 if(posSlash<0)
345 nameSub=nameLeft;
346 nameLeft.Empty();//Nothing left
348 else
350 nameSub=nameLeft.Left(posSlash);
351 nameLeft=nameLeft.Mid(posSlash+1);
353 if(nameSub.IsEmpty())
354 return nullptr;
356 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
357 return nullptr;
359 CShadowTree& nextNode=m_ShadowTree[nameSub];
360 nextNode.m_csRefName=nameSub;
361 nextNode.m_pParent=this;
362 return &nextNode;
365 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
367 if(IsLeaf())
369 if (CStringUtils::EndsWith(partialRefName, m_csRefName))
371 //Match of leaf name. Try match on total name.
372 CString totalRefName = GetRefName();
373 if (CStringUtils::EndsWith(totalRefName, partialRefName))
374 return this; //Also match. Found.
377 else
379 //Not a leaf. Search all nodes.
380 for (auto itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
382 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
383 if (pSubtree)
384 return pSubtree; //Found
387 return nullptr; //Not found
390 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
392 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
393 //List ctrl selection?
394 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
396 //A leaf is selected
397 return GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos))->GetRefName();
399 else if (pos && !pickFirstSelIfMultiSel)
401 // at least one leaf is selected
402 CString refs;
403 int index;
404 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
406 CString ref = GetListEntry(index)->GetRefName();
407 if (CStringUtils::StartsWith(ref, L"refs/"))
408 ref = ref.Mid(static_cast<int>(wcslen(L"refs/")));
409 if (CStringUtils::StartsWith(ref, L"heads/"))
410 ref = ref.Mid(static_cast<int>(wcslen(L"heads/")));
411 refs += ref + L' ';
413 return refs.Trim();
415 else if(!onlyIfLeaf)
417 //Tree ctrl selection?
418 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
419 if (hTree)
420 return GetTreeEntry(hTree)->GetRefName();
422 return CString();//None
425 void CBrowseRefsDlg::Refresh(CString selectRef)
427 remotes.clear();
428 if (g_Git.GetRemoteList(remotes))
429 MessageBox(CGit::GetLibGit2LastErr(L"Could not get a list of remotes."), L"TortoiseGit", MB_ICONERROR);
431 if(!selectRef.IsEmpty())
433 if (selectRef == L"HEAD")
435 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
436 selectRef.Empty();
437 else
438 selectRef = L"refs/heads/" + selectRef;
441 else
442 selectRef = GetSelectedRef(false, true);
444 m_RefTreeCtrl.DeleteAllItems();
445 m_ListRefLeafs.DeleteAllItems();
446 m_TreeRoot.m_ShadowTree.clear();
447 m_TreeRoot.m_csRefName = L"refs";
448 m_TreeRoot.m_hTree = m_RefTreeCtrl.InsertItem(L"refs");
449 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree, reinterpret_cast<DWORD_PTR>(&m_TreeRoot));
451 MAP_REF_GITREVREFBROWSER refMap;
452 if (CString err; GitRevRefBrowser::GetGitRevRefMap(refMap, m_cBranchFilter.GetCurSel(), err, [&](const CString& refName)
454 //Use ref based on m_pickRef_Kind
455 if (CStringUtils::StartsWith(refName, L"refs/heads/") && !(m_pickRef_Kind & gPickRef_Head))
456 return false; //Skip
457 if (CStringUtils::StartsWith(refName, L"refs/tags/") && !(m_pickRef_Kind & gPickRef_Tag))
458 return false; //Skip
459 if (CStringUtils::StartsWith(refName, L"refs/remotes/") && !(m_pickRef_Kind & gPickRef_Remote))
460 return false; //Skip
461 if (m_pickRef_Kind == gPickRef_Remote && !CStringUtils::StartsWith(refName, L"refs/remotes/")) // do not show refs/stash if only remote branches are requested
462 return false;
463 return true;
466 MessageBox(L"Get refs failed:" + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
469 //Populate ref tree
470 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
472 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
473 GitRevRefBrowser ref = iterRefMap->second;
475 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
476 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
477 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
478 treeLeaf.m_csSubject = ref.GetSubject();
479 treeLeaf.m_csAuthor = ref.GetAuthorName();
480 treeLeaf.m_csDate = ref.GetAuthorDate();
481 treeLeaf.m_csDescription = ref.m_Description;
484 // always expand the tree first
485 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
487 // try exact match first
488 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
489 SelectRef(selectRef, false);
492 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
494 if(!bExactMatch)
496 CString newRefName = GetFullRefName(refName);
497 if(!newRefName.IsEmpty())
498 refName = newRefName;
499 //else refName is not a valid ref. Try to select as good as possible.
501 if (!CStringUtils::StartsWith(refName, L"refs"))
502 return false; // Not a ref name
504 CShadowTree& treeLeafHead = GetTreeNode(refName, nullptr, false);
505 if (treeLeafHead.m_hTree)
507 //Not a leaf. Select tree node and return
508 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
509 return true;
512 if (!treeLeafHead.m_pParent)
513 return false; //Weird... should not occur.
515 //This is the current head.
516 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
518 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
520 auto pCurrShadowTree = GetListEntry(indexPos);
521 if(pCurrShadowTree == &treeLeafHead)
523 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
524 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
528 return true;
531 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
533 if (!pTreePos)
535 if (CStringUtils::StartsWith(refName, L"refs/"))
536 refName = refName.Mid(static_cast<int>(wcslen(L"refs/")));
537 pTreePos=&m_TreeRoot;
539 if(refName.IsEmpty())
540 return *pTreePos;//Found leaf
542 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
543 if (!pNextTree)
545 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
546 ASSERT(!bCreateIfNotExist);
547 return *pTreePos;
550 if(!refName.IsEmpty())
552 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
553 //Leafs are for the list control.
554 if (!pNextTree->m_hTree)
556 //New tree. Create node in control.
557 pNextTree->m_hTree = m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName, pTreePos->m_hTree);
558 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree, reinterpret_cast<DWORD_PTR>(pNextTree));
562 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
566 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
568 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
569 *pResult = 0;
571 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
574 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
576 m_ListRefLeafs.DeleteAllItems();
578 auto pTree = GetTreeEntry(treeNode);
579 if (!pTree)
580 return;
582 CString filterText;
583 m_ctrlFilter.GetWindowText(filterText);
585 CBrowseRefsDlgFilter filter(filterText, false, m_SelectedFilters, false);
587 FillListCtrlForShadowTree(pTree, L"", true, filter);
588 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
589 m_ListRefLeafs.m_ColumnManager.SetVisible(eCol_Description, pTree->IsFrom(L"refs/heads"));
590 m_ListRefLeafs.AdjustColumnWidths();
591 UpdateInfoLabel();
594 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel, const CBrowseRefsDlgFilter& filter)
596 if(pTree->IsLeaf())
598 CString ref = refNamePrefix + pTree->m_csRefName;
599 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == L"refs" && !pTree->m_pParent) && filter(pTree, ref))
601 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
603 m_ListRefLeafs.SetItemData(indexItem, reinterpret_cast<DWORD_PTR>(pTree));
604 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
605 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
606 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : CString());
607 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
608 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
609 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
610 CString descrition = pTree->m_csDescription;
611 descrition.Replace(L'\n', L' ');
612 m_ListRefLeafs.SetItemText(indexItem, eCol_Description, descrition);
615 else
617 CString csThisName;
618 if (!isFirstLevel && !m_bIncludeNestedRefs)
619 return;
620 else if (!isFirstLevel)
621 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
622 else
623 m_pListCtrlRoot = pTree;
624 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
626 FillListCtrlForShadowTree(&itSubTree->second, csThisName, false, filter);
629 if (isFirstLevel)
631 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
632 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, reinterpret_cast<DWORD_PTR>(&compareFunc));
634 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
638 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
640 ASSERT(!leafs.empty());
642 CString csMessage;
643 UINT mbIcon=MB_ICONQUESTION;
645 bool bIsRemoteBranch = false;
646 bool bIsBranch = false;
647 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
648 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
650 if(bIsBranch)
652 if(leafs.size() == 1)
654 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
655 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, static_cast<LPCWSTR>(branchToDelete));
657 //Check if branch is fully merged in HEAD
658 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), L"HEAD"))
660 csMessage += L"\r\n\r\n";
661 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
662 mbIcon = MB_ICONWARNING;
665 if(bIsRemoteBranch)
667 csMessage += L"\r\n\r\n";
668 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
669 mbIcon = MB_ICONWARNING;
672 else
674 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
676 csMessage += L"\r\n\r\n";
677 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
678 mbIcon = MB_ICONWARNING;
680 if(bIsRemoteBranch)
682 csMessage += L"\r\n\r\n";
683 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
684 mbIcon = MB_ICONWARNING;
689 else if(leafs[0]->IsFrom(L"refs/tags/"))
691 if(leafs.size() == 1)
693 CString tagToDelete = leafs[0]->GetRefName().Mid(static_cast<int>(wcslen(L"refs/tags/")));
694 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, static_cast<LPCWSTR>(tagToDelete));
696 else
697 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
700 return MessageBox(csMessage, L"TortoiseGit", MB_YESNO | mbIcon) == IDYES;
703 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
705 bool allRemoteBranch = true;
706 std::map<CString, STRING_VECTOR> remoteBranches;
707 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
709 CString completeRefName = (*i)->GetRefName();
710 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
712 CString branchToDelete = completeRefName.Mid(static_cast<int>(wcslen(L"refs/remotes/")));
713 CString remoteName, remoteBranchToDelete;
714 if (!SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
715 remoteBranches[remoteName].push_back(remoteBranchToDelete);
717 else
719 allRemoteBranch = false;
720 break;
723 if (allRemoteBranch)
725 // delete multiple remote branches in batch, so it is faster, fewer password prompt
726 for (const auto& remotebranchlist : remoteBranches)
728 auto& remoteName = remotebranchlist.first;
729 if (CAppUtils::IsSSHPutty())
730 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName);
732 CSysProgressDlg sysProgressDlg;
733 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
734 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
735 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
736 sysProgressDlg.SetShowProgressBar(false);
737 sysProgressDlg.ShowModal(this, true);
739 STRING_VECTOR list;
740 list.reserve(remotebranchlist.second.size());
741 std::transform(remotebranchlist.second.cbegin(), remotebranchlist.second.cend(), std::back_inserter(list), [](auto& branch) { return L"refs/heads/" + branch; });
742 if (g_Git.DeleteRemoteRefs(remoteName, list))
744 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote refs.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
745 sysProgressDlg.Stop();
746 BringWindowToTop();
747 return false;
749 sysProgressDlg.Stop();
751 BringWindowToTop();
752 return true;
755 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
756 if(!DoDeleteRef((*i)->GetRefName()))
757 return false;
758 return true;
761 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
763 bool bIsRemoteBranch = false;
764 bool bIsBranch = false;
765 if (CStringUtils::StartsWith(completeRefName, L"refs/remotes/"))
767 bIsBranch = true;
768 bIsRemoteBranch = true;
770 else if (CStringUtils::StartsWith(completeRefName, L"refs/heads/"))
771 bIsBranch = true;
773 if (bIsRemoteBranch)
775 CString branchToDelete = completeRefName.Mid(static_cast<int>(wcslen(L"refs/remotes/")));
776 CString remoteName, remoteBranchToDelete;
777 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
778 return false;
780 if (CAppUtils::IsSSHPutty())
781 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName);
783 CSysProgressDlg sysProgressDlg;
784 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
785 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
786 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
787 sysProgressDlg.SetShowProgressBar(false);
788 sysProgressDlg.ShowModal(this, true);
790 STRING_VECTOR list;
791 list.push_back(L"refs/heads/" + remoteBranchToDelete);
792 if (g_Git.DeleteRemoteRefs(remoteName, list))
794 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote ref.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
795 sysProgressDlg.Stop();
796 BringWindowToTop();
797 return false;
799 sysProgressDlg.Stop();
800 BringWindowToTop();
802 else if (bIsBranch)
804 if (g_Git.DeleteRef(completeRefName))
806 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
807 return false;
810 else if (CStringUtils::StartsWith(completeRefName, L"refs/tags/"))
812 if (g_Git.DeleteRef(completeRefName))
814 MessageBox(g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), L"TortoiseGit", MB_OK | MB_ICONERROR);
815 return false;
818 return true;
821 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
823 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
824 if (!pLeaf)
825 return CString();
826 return pLeaf->GetRefName();
830 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
832 if (pWndFrom == &m_RefTreeCtrl)
833 OnContextMenu_RefTreeCtrl(point);
836 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
838 CPoint clientPoint=point;
839 m_RefTreeCtrl.ScreenToClient(&clientPoint);
841 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
842 if (hTreeItem)
843 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
845 VectorPShadowTree tree;
846 ShowContextMenu(point,hTreeItem,tree);
849 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
851 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
852 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
853 while (pos)
855 selectedLeafs.push_back(GetListEntry(m_ListRefLeafs.GetNextSelectedItem(pos)));
859 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
861 std::vector<CShadowTree*> selectedLeafs;
862 GetSelectedLeaves(selectedLeafs);
863 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
866 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
868 ASSERT(selectedLeafs.size() == 2);
870 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
871 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
872 else
873 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
876 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
878 int i = 0;
879 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
881 if (*it == entry)
882 return i;
884 return -1;
887 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
889 CIconMenu popupMenu;
890 popupMenu.CreatePopupMenu();
892 bool bAddSeparator = false;
893 CString remoteName;
895 if(selectedLeafs.size()==1)
897 bAddSeparator = true;
899 bool bShowReflogOption = false;
900 bool bShowFetchOption = false;
901 bool bShowRenameOption = false;
902 bool bShowCreateBranchOption = false;
903 bool bShowEditBranchDescriptionOption = false;
905 CString fetchFromCmd;
907 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
909 bShowReflogOption = true;
910 bShowRenameOption = true;
911 bShowEditBranchDescriptionOption = true;
913 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
915 bShowReflogOption = true;
916 bShowFetchOption = true;
917 bShowCreateBranchOption = true;
919 CString remoteBranch;
920 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
921 bShowFetchOption = false;
922 else
923 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, static_cast<LPCWSTR>(remoteName));
926 if (m_bWantPick)
928 popupMenu.AppendMenuIcon(eCmd_Select, IDS_SELECT);
929 popupMenu.AppendMenu(MF_SEPARATOR);
931 popupMenu.AppendMenuIcon(eCmd_ViewLog, IDS_MENULOG, IDI_LOG);
932 popupMenu.SetDefaultItem(0, TRUE);
933 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
934 if(bShowReflogOption)
935 popupMenu.AppendMenuIcon(eCmd_ShowReflog, IDS_MENUREFLOG, IDI_LOG);
937 if (m_bHasWC)
939 popupMenu.AppendMenu(MF_SEPARATOR);
940 popupMenu.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
943 popupMenu.AppendMenu(MF_SEPARATOR);
944 bAddSeparator = false;
946 if(bShowFetchOption)
948 bAddSeparator = true;
949 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_UPDATE);
952 if(bAddSeparator)
953 popupMenu.AppendMenu(MF_SEPARATOR);
955 bAddSeparator = false;
956 if (m_bHasWC)
958 CString str;
959 if (selectedLeafs[0]->GetRefName() != L"refs/heads/" + g_Git.GetCurrentBranch())
961 str.Format(IDS_LOG_POPUP_MERGEREV, static_cast<LPCWSTR>(g_Git.GetCurrentBranch()));
962 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
964 popupMenu.AppendMenuIcon(eCmd_Switch, IDS_SWITCH_TO_THIS, IDI_SWITCH);
965 popupMenu.AppendMenu(MF_SEPARATOR);
968 if(bShowCreateBranchOption)
970 bAddSeparator = true;
971 popupMenu.AppendMenuIcon(eCmd_CreateBranch, IDS_MENUBRANCH, IDI_COPY);
974 if (bShowEditBranchDescriptionOption)
976 bAddSeparator = true;
977 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, IDS_PROC_BROWSEREFS_EDITDESCRIPTION, IDI_RENAME);
979 if(bShowRenameOption)
981 bAddSeparator = true;
982 popupMenu.AppendMenuIcon(eCmd_Rename, IDS_PROC_BROWSEREFS_RENAME, IDI_RENAME);
985 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
987 if (bAddSeparator)
988 popupMenu.AppendMenu(MF_SEPARATOR);
989 bAddSeparator = true;
990 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
991 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH);
992 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH);
995 else if(selectedLeafs.size() == 2)
997 bAddSeparator = true;
998 popupMenu.AppendMenuIcon(eCmd_Diff, IDS_PROC_BROWSEREFS_COMPAREREFS, IDI_DIFF);
999 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1000 CString menu;
1001 menu.Format(IDS_SHOWLOG_OF, static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..")));
1002 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
1003 menu.Format(IDS_SHOWLOG_OF, static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"...")));
1004 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
1007 if(!selectedLeafs.empty())
1009 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
1011 if(bAddSeparator)
1012 popupMenu.AppendMenu(MF_SEPARATOR);
1013 CString menuItemName;
1014 if(selectedLeafs.size() == 1)
1015 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1016 else
1017 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1019 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1020 bAddSeparator = true;
1022 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1024 if(bAddSeparator)
1025 popupMenu.AppendMenu(MF_SEPARATOR);
1026 CString menuItemName;
1027 if(selectedLeafs.size() == 1)
1028 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1029 else
1030 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1032 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1033 bAddSeparator = true;
1035 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1037 if(bAddSeparator)
1038 popupMenu.AppendMenu(MF_SEPARATOR);
1039 CString menuItemName;
1040 if(selectedLeafs.size() == 1)
1041 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1042 else
1043 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1045 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1046 bAddSeparator = true;
1051 if (hTreePos && selectedLeafs.empty())
1053 auto pTree = GetTreeEntry(hTreePos);
1054 if(pTree->IsFrom(L"refs/remotes"))
1056 if(bAddSeparator)
1057 popupMenu.AppendMenu(MF_SEPARATOR);
1058 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, IDS_PROC_BROWSEREFS_MANAGEREMOTES, IDI_SETTINGS);
1059 bAddSeparator = true;
1060 if(selectedLeafs.empty())
1062 CString remoteBranch;
1063 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1064 remoteName.Empty();
1065 int pos = findVectorPosition(remotes, remoteName);
1066 if (pos >= 0)
1068 CString temp;
1069 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, static_cast<LPCWSTR>(remoteName));
1070 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_UPDATE);
1072 temp.LoadString(IDS_DELETEREMOTETAG);
1073 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1076 bAddSeparator = false;
1078 if(pTree->IsFrom(L"refs/heads"))
1080 if(bAddSeparator)
1081 popupMenu.AppendMenu(MF_SEPARATOR);
1082 CString temp;
1083 temp.LoadString(IDS_MENUBRANCH);
1084 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1085 bAddSeparator = false;
1087 if(pTree->IsFrom(L"refs/tags"))
1089 if(bAddSeparator)
1090 popupMenu.AppendMenu(MF_SEPARATOR);
1091 popupMenu.AppendMenuIcon(eCmd_CreateTag, IDS_MENUTAG, IDI_TAG);
1092 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, IDS_PROC_BROWSEREFS_DELETEALLTAGS, IDI_DELETE);
1093 if (!remotes.empty())
1095 popupMenu.AppendMenu(MF_SEPARATOR);
1096 int i = 0;
1097 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1099 CString temp;
1100 temp.Format(IDS_DELETEREMOTETAGON, static_cast<LPCWSTR>(*it));
1101 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1104 bAddSeparator = false;
1107 if (bAddSeparator)
1108 popupMenu.AppendMenu(MF_SEPARATOR);
1109 popupMenu.AppendMenuIcon(eCmd_Copy, IDS_COPY_REF_NAMES, IDI_COPYCLIP);
1111 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, nullptr);
1112 switch (static_cast<eCmd>(selection & 0xFFFF))
1114 case eCmd_Select:
1115 EndDialog(IDOK);
1116 break;
1117 case eCmd_ViewLog:
1119 CString sCmd;
1120 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(g_Git.FixBranchName(selectedLeafs[0]->GetRefName())));
1121 CAppUtils::RunTortoiseGitProc(sCmd);
1123 break;
1124 case eCmd_ViewLogRange:
1126 CString sCmd;
1127 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"..")));
1128 CAppUtils::RunTortoiseGitProc(sCmd);
1130 break;
1131 case eCmd_ViewLogRangeReachableFromOnlyOne:
1133 CString sCmd;
1134 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, L"...")));
1135 CAppUtils::RunTortoiseGitProc(sCmd);
1137 break;
1138 case eCmd_RepoBrowser:
1139 CAppUtils::RunTortoiseGitProc(L"/command:repobrowser /path:\"" + g_Git.m_CurrentDir + L"\" /rev:" + selectedLeafs[0]->GetRefName());
1140 break;
1141 case eCmd_DeleteBranch:
1142 case eCmd_DeleteRemoteBranch:
1144 if(ConfirmDeleteRef(selectedLeafs))
1145 DoDeleteRefs(selectedLeafs);
1146 Refresh();
1148 break;
1149 case eCmd_DeleteTag:
1151 if(ConfirmDeleteRef(selectedLeafs))
1152 DoDeleteRefs(selectedLeafs);
1153 Refresh();
1155 break;
1156 case eCmd_ShowReflog:
1158 CRefLogDlg refLogDlg(this);
1159 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1160 refLogDlg.DoModal();
1162 break;
1163 case eCmd_Fetch:
1165 CAppUtils::Fetch(GetSafeHwnd(), remoteName);
1166 Refresh();
1168 break;
1169 case eCmd_DeleteRemoteTag:
1171 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1172 int remoteInx = selection >> 16;
1173 if (remoteInx < 0 || static_cast<size_t>(remoteInx) >= remotes.size())
1174 return;
1175 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1176 deleteRemoteTagDlg.DoModal();
1178 break;
1179 case eCmd_Merge:
1181 CString ref = selectedLeafs[0]->GetRefName();
1182 CAppUtils::Merge(GetSafeHwnd(), &ref);
1184 break;
1185 case eCmd_Switch:
1187 CAppUtils::Switch(GetSafeHwnd(), selectedLeafs[0]->GetRefName());
1189 break;
1190 case eCmd_Rename:
1192 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1193 if (pos)
1194 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1196 break;
1197 case eCmd_AddRemote:
1199 CAddRemoteDlg(this).DoModal();
1200 Refresh();
1202 break;
1203 case eCmd_ManageRemotes:
1205 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1206 // CSettingGitRemote W_Remotes(m_cmdPath);
1207 // W_Remotes.DoModal();
1208 Refresh();
1210 break;
1211 case eCmd_CreateBranch:
1213 CString* commitHash = nullptr;
1214 if (selectedLeafs.size() == 1)
1215 commitHash = &(selectedLeafs[0]->m_csRefHash);
1216 CAppUtils::CreateBranchTag(GetSafeHwnd(), false, commitHash);
1217 Refresh();
1219 break;
1220 case eCmd_CreateTag:
1222 CAppUtils::CreateBranchTag(GetSafeHwnd(), true);
1223 Refresh();
1225 break;
1226 case eCmd_DeleteAllTags:
1228 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1230 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1231 selectedLeafs.push_back(GetListEntry(i));
1233 if (ConfirmDeleteRef(selectedLeafs))
1234 DoDeleteRefs(selectedLeafs);
1235 Refresh();
1237 break;
1238 case eCmd_Diff:
1240 CFileDiffDlg dlg;
1241 dlg.SetDiff(
1242 nullptr,
1243 selectedLeafs[0]->GetRefName(),
1244 selectedLeafs[1]->GetRefName());
1245 dlg.DoModal();
1247 break;
1248 case eCmd_UnifiedDiff:
1250 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
1252 break;
1253 case eCmd_DiffWC:
1255 CString sCmd;
1256 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(selectedLeafs[0]->GetRefName()), static_cast<LPCWSTR>(GitRev::GetWorkingCopy()));
1257 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
1258 sCmd += L" /alternative";
1260 CAppUtils::RunTortoiseGitProc(sCmd);
1262 break;
1263 case eCmd_EditBranchDescription:
1265 CInputDlg dlg;
1266 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1267 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1268 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1269 dlg.m_bUseLogWidth = true;
1270 if(dlg.DoModal() == IDOK)
1272 CAppUtils::UpdateBranchDescription(selectedLeafs[0]->GetRefsHeadsName(), dlg.m_sInputText);
1273 Refresh();
1276 break;
1277 case eCmd_UpstreamDrop:
1279 CString key;
1280 key.Format(L"branch.%s.remote", static_cast<LPCWSTR>(selectedLeafs[0]->GetRefsHeadsName()));
1281 g_Git.UnsetConfigValue(key);
1282 key.Format(L"branch.%s.merge", static_cast<LPCWSTR>(selectedLeafs[0]->GetRefsHeadsName()));
1283 g_Git.UnsetConfigValue(key);
1285 Refresh();
1286 break;
1287 case eCmd_UpstreamSet:
1289 CString newRef = CBrowseRefsDlg::PickRef(false, L"", gPickRef_Remote, false);
1290 if (newRef.IsEmpty() || newRef.Find(L"refs/remotes/") != 0)
1291 return;
1292 CString remote, branch;
1293 if (SplitRemoteBranchName(newRef, remote, branch))
1294 return;
1295 // Setting the config keys directly might result in an invalid situation if the remote is not set to
1296 // fetch the desired upstream branch (in remote.x.fetch), cf. issue #3638
1297 if (CString errorMsg; g_Git.Run(L"git.exe branch \"" + selectedLeafs[0]->GetRefsHeadsName() + L"\" --set-upstream-to=\"" + remote + L'/' + branch + L'"', &errorMsg, CP_UTF8) != 0)
1299 MessageBox(errorMsg + "\r\n\r\nThis is generally caused when remote." + remote + ".fetch does not include the desired branch.", L"TortoiseGit", MB_ICONERROR);
1300 return;
1302 Refresh();
1304 break;
1305 case eCmd_Copy:
1307 CString sClipdata;
1308 bool first = true;
1309 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1310 while (pos)
1312 auto index = m_ListRefLeafs.GetNextSelectedItem(pos);
1313 if (!first)
1314 sClipdata += L"\r\n";
1315 sClipdata += m_ListRefLeafs.GetItemText(index, eCol_Name);
1316 first = false;
1318 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
1320 break;
1324 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1326 for (auto i = leafs.cbegin(); i != leafs.cend(); ++i)
1327 if(!(*i)->IsFrom(from))
1328 return false;
1329 return true;
1332 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1334 if (pMsg->message == WM_KEYDOWN)
1336 switch (pMsg->wParam)
1338 /* case VK_RETURN:
1340 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1342 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1344 PostMessage(WM_COMMAND, IDOK);
1346 return TRUE;
1349 break;
1350 */ case VK_F2:
1352 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1354 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1355 if (pos)
1356 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1359 break;
1361 case VK_F5:
1363 Refresh();
1365 break;
1366 case L'E':
1368 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1370 m_ctrlFilter.SetSel(0, -1, FALSE);
1371 m_ctrlFilter.SetFocus();
1372 return TRUE;
1375 break;
1376 case L'A':
1377 if (pMsg->hwnd == m_ListRefLeafs.m_hWnd && (GetAsyncKeyState(VK_CONTROL) & 0x8000))
1379 // select all entries
1380 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1381 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1382 return TRUE;
1384 break;
1385 case VK_ESCAPE:
1386 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER) && m_ctrlFilter.GetWindowTextLength())
1388 OnClickedCancelFilter(NULL, NULL);
1389 return TRUE;
1391 break;
1396 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1399 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1401 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1402 *pResult = 0;
1404 if(m_currSortCol == pNMLV->iSubItem)
1405 m_currSortDesc = !m_currSortDesc;
1406 else
1408 m_currSortCol = pNMLV->iSubItem;
1409 m_currSortDesc = false;
1412 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1413 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, reinterpret_cast<DWORD_PTR>(&compareFunc));
1415 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1418 void CBrowseRefsDlg::OnDestroy()
1420 if (!m_bPickedRefSet)
1421 m_pickedRef = GetSelectedRef(true, false);
1423 CResizableStandAloneDialog::OnDestroy();
1426 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1428 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1429 *pResult = 0;
1431 if (!(pNMListView->uChanged & LVIF_STATE))
1432 return;
1434 auto item = GetListEntry(pNMListView->iItem);
1435 if (item && pNMListView->uNewState == LVIS_SELECTED)
1436 m_sLastSelected = item->GetRefName();
1438 UpdateInfoLabel();
1441 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1443 *pResult = 0;
1445 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1446 return;
1448 if (m_bWantPick)
1450 EndDialog(IDOK);
1451 return;
1454 CString sCmd;
1455 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(g_Git.FixBranchName(GetSelectedRef(true, false))));
1456 CAppUtils::RunTortoiseGitProc(sCmd);
1459 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefs, bool showRangeOptionWithTwoRefs)
1461 CBrowseRefsDlg dlg(CString(), nullptr);
1463 if(initialRef.IsEmpty())
1464 initialRef = L"HEAD";
1465 dlg.m_bWantPick = true;
1466 dlg.m_initialRef = initialRef;
1467 dlg.m_pickRef_Kind = pickRef_Kind;
1468 dlg.m_bPickOne = !pickMultipleRefs;
1469 dlg.m_bShowRangeOptionWithTwoRefs = showRangeOptionWithTwoRefs;
1471 if(dlg.DoModal() != IDOK)
1472 return CString();
1474 return dlg.m_pickedRef;
1477 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo& refComboBox, int pickRef_Kind /* = gPickRef_All*/, int useShortName /* = gPickRef_Head*/)
1479 CString origRef;
1480 refComboBox.GetLBText(refComboBox.GetCurSel(), origRef);
1481 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1482 if(resultRef.IsEmpty())
1483 return false;
1485 if (useShortName)
1487 CGit::REF_TYPE refType;
1488 CString shortName = CGit::GetShortName(resultRef, &refType);
1489 switch (refType)
1491 case CGit::REF_TYPE::LOCAL_BRANCH:
1492 if (useShortName & gPickRef_Head)
1493 resultRef = shortName;
1494 break;
1495 case CGit::REF_TYPE::ANNOTATED_TAG:
1496 case CGit::REF_TYPE::TAG:
1497 if (useShortName & gPickRef_Tag)
1498 resultRef = shortName;
1499 break;
1500 case CGit::REMOTE_BRANCH:
1501 if (useShortName & gPickRef_Remote)
1502 resultRef = shortName;
1503 break;
1507 CGit::StripRefName(resultRef);
1509 refComboBox.AddString(resultRef);
1511 return true;
1514 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1516 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1517 *pResult = FALSE;
1519 if (!pDispInfo->item.pszText)
1520 return; //User canceled changing
1522 auto pTree = GetListEntry(pDispInfo->item.iItem);
1523 if(!pTree->IsFrom(L"refs/heads/"))
1525 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1526 return;
1529 CString selectedTreeRef;
1530 HTREEITEM hTree = m_RefTreeCtrl.GetSelectedItem();
1531 if (!hTree)
1533 auto pTree2 = GetTreeEntry(hTree);
1534 selectedTreeRef = pTree2->GetRefName();
1537 CString origName = pTree->GetRefName().Mid(static_cast<int>(wcslen(L"refs/heads/")));
1539 CString newName;
1540 if (m_pListCtrlRoot)
1541 newName = m_pListCtrlRoot->GetRefName() + L'/';
1542 newName += pDispInfo->item.pszText;
1544 if (!CStringUtils::StartsWith(newName, L"refs/heads/"))
1546 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1547 return;
1550 CString newNameTrunced = newName.Mid(static_cast<int>(wcslen(L"refs/heads/")));
1552 if (CString errorMsg; g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L'"', &errorMsg, CP_UTF8) != 0)
1554 MessageBox(errorMsg, L"TortoiseGit", MB_OK | MB_ICONERROR);
1555 return;
1557 //Do as if it failed to rename. Let Refresh() do the job.
1558 //*pResult = TRUE;
1560 Refresh(selectedTreeRef);
1562 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1564 // AfxMessageBox(W_csPopup);
1567 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1569 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1570 *pResult = FALSE;
1572 auto pTree = GetListEntry(pDispInfo->item.iItem);
1573 if(!pTree->IsFrom(L"refs/heads/"))
1575 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1576 return;
1580 void CBrowseRefsDlg::OnEnChangeEditFilter()
1582 SetTimer(IDT_FILTER, 1000, nullptr);
1585 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1587 if (nIDEvent == IDT_FILTER)
1589 KillTimer(IDT_FILTER);
1590 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1593 CResizableStandAloneDialog::OnTimer(nIDEvent);
1596 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1598 // FIXME: x64 version would get this function called with unexpected parameters.
1599 if (!lParam)
1600 return 0;
1602 auto rect = reinterpret_cast<LPRECT>(lParam);
1603 CPoint point = CPoint(rect->left, rect->bottom);
1604 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1605 CMenu popup;
1606 if (popup.CreatePopupMenu())
1608 CString temp;
1609 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1610 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1612 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1613 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1615 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1616 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1618 temp.LoadString(IDS_LOG_FILTER_REVS);
1619 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1621 popup.AppendMenu(MF_SEPARATOR);
1623 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1624 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1626 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1627 if (selection != 0)
1629 if (selection == LOGFILTER_TOGGLE)
1630 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1631 else
1632 m_SelectedFilters ^= selection;
1633 SetFilterCueText();
1634 SetTimer(IDT_FILTER, 1000, nullptr);
1637 return 0L;
1640 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1642 KillTimer(LOGFILTER_TIMER);
1643 m_ctrlFilter.SetWindowText(L"");
1644 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1645 return 0L;
1648 void CBrowseRefsDlg::SetFilterCueText()
1650 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1651 temp += L' ';
1653 if (m_SelectedFilters & LOGFILTER_REFNAME)
1654 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1656 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1658 if (!CStringUtils::EndsWith(temp, L' '))
1659 temp += L", ";
1660 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1663 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1665 if (!CStringUtils::EndsWith(temp, L' '))
1666 temp += L", ";
1667 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1670 if (m_SelectedFilters & LOGFILTER_REVS)
1672 if (!CStringUtils::EndsWith(temp, L' '))
1673 temp += L", ";
1674 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1677 // to make the cue banner text appear more to the right of the edit control
1678 temp = L" " + temp;
1679 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1682 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1684 m_pickedRef = g_Git.GetCurrentBranch(true);
1685 m_bPickedRefSet = true;
1686 OnOK();
1689 void CBrowseRefsDlg::UpdateInfoLabel()
1691 CString temp;
1692 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1693 SetDlgItemText(IDC_INFOLABEL, temp);
1696 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1698 UpdateData(TRUE);
1699 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1700 Refresh();
1703 CShadowTree* CBrowseRefsDlg::GetListEntry(int index)
1705 auto entry = reinterpret_cast<CShadowTree*>(m_ListRefLeafs.GetItemData(index));
1706 ASSERT(entry);
1707 return entry;
1710 CShadowTree* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem)
1712 auto entry = reinterpret_cast<CShadowTree*>(m_RefTreeCtrl.GetItemData(treeItem));
1713 ASSERT(entry);
1714 return entry;
1718 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()
1720 Refresh();