1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2020 - 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
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
26 #include "AddRemoteDlg.h"
28 #include "Settings/SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
33 #include "FileDiffDlg.h"
34 #include "DeleteRemoteTagDlg.h"
35 #include "UnicodeUtils.h"
37 #include "SysProgressDlg.h"
38 #include "LoglistUtils.h"
39 #include "GitRevRefBrowser.h"
40 #include "StringUtils.h"
41 #include "BrowseRefsDlgFilter.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/")));
52 int result
= g_Git
.GetRemoteList(list
);
56 for (size_t i
= 0; i
< list
.size(); ++i
)
58 if (CStringUtils::StartsWith(ref
, list
[i
] + L
"/"))
61 branch
= ref
.Mid(list
[i
].GetLength() + 1);
75 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
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
);
91 pHeader
->GetItem(nColumn
, &HeaderItem
);
92 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
93 pHeader
->SetItem(nColumn
, &HeaderItem
);
97 class CRefLeafListCompareFunc
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
);
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
)
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
);
126 int CompareNoDesc(const CShadowTree
* pLeft
, const CShadowTree
* pRight
)
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
);
140 int SortStrCmp(const CString
& left
, const CString
& right
)
143 return StrCmpLogicalW(left
, right
);
144 return StrCmpI(left
, right
);
153 // CBrowseRefsDlg dialog
155 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
157 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=nullptr*/)
158 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
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),
168 m_SelectedFilters(LOGFILTER_ALL
),
170 m_bIncludeNestedRefs(TRUE
),
171 m_bPickedRefSet(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
;
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
)
211 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
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
)
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
)
227 // CBrowseRefsDlg message handlers
229 void CBrowseRefsDlg::OnBnClickedOk()
231 if (m_bPickOne
|| !m_bShowRangeOptionWithTwoRefs
|| m_ListRefLeafs
.GetSelectedCount() != 2)
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
);
249 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
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, ¶ms
);
261 m_bPickedRefSet
= true;
262 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..");
268 m_bPickedRefSet
= true;
269 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...");
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);
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
;
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
);
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();
339 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
341 int posSlash
=nameLeft
.Find('/');
346 nameLeft
.Empty();//Nothing left
350 nameSub
=nameLeft
.Left(posSlash
);
351 nameLeft
=nameLeft
.Mid(posSlash
+1);
353 if(nameSub
.IsEmpty())
356 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
359 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
360 nextNode
.m_csRefName
=nameSub
;
361 nextNode
.m_pParent
=this;
365 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
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.
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
);
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))
397 return GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
))->GetRefName();
399 else if (pos
&& !pickFirstSelIfMultiSel
)
401 // at least one leaf is selected
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/")));
417 //Tree ctrl selection?
418 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
420 return GetTreeEntry(hTree
)->GetRefName();
422 return CString();//None
425 void CBrowseRefsDlg::Refresh(CString selectRef
)
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
))
438 selectRef
= L
"refs/heads/" + selectRef
;
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
))
457 if (CStringUtils::StartsWith(refName
, L
"refs/tags/") && !(m_pickRef_Kind
& gPickRef_Tag
))
459 if (CStringUtils::StartsWith(refName
, L
"refs/remotes/") && !(m_pickRef_Kind
& gPickRef_Remote
))
461 if (m_pickRef_Kind
== gPickRef_Remote
&& !CStringUtils::StartsWith(refName
, L
"refs/remotes/")) // do not show refs/stash if only remote branches are requested
466 MessageBox(L
"Get refs failed:" + err
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
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
)
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
);
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
);
531 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
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
);
545 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
546 ASSERT(!bCreateIfNotExist
);
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
);
571 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
574 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
576 m_ListRefLeafs
.DeleteAllItems();
578 auto pTree
= GetTreeEntry(treeNode
);
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();
594 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
, const CBrowseRefsDlgFilter
& filter
)
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
) : L
"");
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
);
618 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
620 else if (!isFirstLevel
)
621 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
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
);
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());
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;}
652 if(leafs
.size() == 1)
654 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
655 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, static_cast<LPCTSTR
>(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
;
667 csMessage
+= L
"\r\n\r\n";
668 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
669 mbIcon
= MB_ICONWARNING
;
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
;
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<LPCTSTR
>(tagToDelete
));
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
);
719 allRemoteBranch
= false;
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);
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();
749 sysProgressDlg
.Stop();
755 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
756 if(!DoDeleteRef((*i
)->GetRefName()))
761 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
763 bool bIsRemoteBranch
= false;
764 bool bIsBranch
= false;
765 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
768 bIsRemoteBranch
= true;
770 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
775 CString branchToDelete
= completeRefName
.Mid(static_cast<int>(wcslen(L
"refs/remotes/")));
776 CString remoteName
, remoteBranchToDelete
;
777 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
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);
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();
799 sysProgressDlg
.Stop();
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
);
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
);
821 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
823 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
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
);
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();
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
);
873 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
876 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
879 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
887 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
890 popupMenu
.CreatePopupMenu();
892 bool bAddSeparator
= false;
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;
923 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, static_cast<LPCTSTR
>(remoteName
));
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
);
939 popupMenu
.AppendMenu(MF_SEPARATOR
);
940 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
943 popupMenu
.AppendMenu(MF_SEPARATOR
);
944 bAddSeparator
= false;
948 bAddSeparator
= true;
949 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_UPDATE
);
953 popupMenu
.AppendMenu(MF_SEPARATOR
);
955 bAddSeparator
= false;
959 if (selectedLeafs
[0]->GetRefName() != L
"refs/heads/" + g_Git
.GetCurrentBranch())
961 str
.Format(IDS_LOG_POPUP_MERGEREV
, static_cast<LPCTSTR
>(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/"))
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
);
1001 menu
.Format(IDS_SHOWLOG_OF
, static_cast<LPCTSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..")));
1002 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
1003 menu
.Format(IDS_SHOWLOG_OF
, static_cast<LPCTSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...")));
1004 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
1007 if(!selectedLeafs
.empty())
1009 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
1012 popupMenu
.AppendMenu(MF_SEPARATOR
);
1013 CString menuItemName
;
1014 if(selectedLeafs
.size() == 1)
1015 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
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/"))
1025 popupMenu
.AppendMenu(MF_SEPARATOR
);
1026 CString menuItemName
;
1027 if(selectedLeafs
.size() == 1)
1028 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
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/"))
1038 popupMenu
.AppendMenu(MF_SEPARATOR
);
1039 CString menuItemName
;
1040 if(selectedLeafs
.size() == 1)
1041 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
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"))
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
))
1065 int pos
= findVectorPosition(remotes
, remoteName
);
1069 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, static_cast<LPCTSTR
>(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"))
1081 popupMenu
.AppendMenu(MF_SEPARATOR
);
1083 temp
.LoadString(IDS_MENUBRANCH
);
1084 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1085 bAddSeparator
= false;
1087 if(pTree
->IsFrom(L
"refs/tags"))
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
);
1097 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1100 temp
.Format(IDS_DELETEREMOTETAGON
, static_cast<LPCTSTR
>(*it
));
1101 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1104 bAddSeparator
= false;
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))
1120 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(g_Git
.FixBranchName(selectedLeafs
[0]->GetRefName())));
1121 CAppUtils::RunTortoiseGitProc(sCmd
);
1124 case eCmd_ViewLogRange
:
1127 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..")));
1128 CAppUtils::RunTortoiseGitProc(sCmd
);
1131 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1134 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...")));
1135 CAppUtils::RunTortoiseGitProc(sCmd
);
1138 case eCmd_RepoBrowser
:
1139 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedLeafs
[0]->GetRefName());
1141 case eCmd_DeleteBranch
:
1142 case eCmd_DeleteRemoteBranch
:
1144 if(ConfirmDeleteRef(selectedLeafs
))
1145 DoDeleteRefs(selectedLeafs
);
1149 case eCmd_DeleteTag
:
1151 if(ConfirmDeleteRef(selectedLeafs
))
1152 DoDeleteRefs(selectedLeafs
);
1156 case eCmd_ShowReflog
:
1158 CRefLogDlg
refLogDlg(this);
1159 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1160 refLogDlg
.DoModal();
1165 CAppUtils::Fetch(GetSafeHwnd(), remoteName
);
1169 case eCmd_DeleteRemoteTag
:
1171 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1172 int remoteInx
= selection
>> 16;
1173 if (remoteInx
< 0 || static_cast<size_t>(remoteInx
) >= remotes
.size())
1175 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1176 deleteRemoteTagDlg
.DoModal();
1181 CString ref
= selectedLeafs
[0]->GetRefName();
1182 CAppUtils::Merge(GetSafeHwnd(), &ref
);
1187 CAppUtils::Switch(GetSafeHwnd(), selectedLeafs
[0]->GetRefName());
1192 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1194 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1197 case eCmd_AddRemote
:
1199 CAddRemoteDlg(this).DoModal();
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();
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
);
1220 case eCmd_CreateTag
:
1222 CAppUtils::CreateBranchTag(GetSafeHwnd(), true);
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
);
1243 selectedLeafs
[0]->GetRefName(),
1244 selectedLeafs
[1]->GetRefName());
1248 case eCmd_UnifiedDiff
:
1250 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1256 sCmd
.Format(L
"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(selectedLeafs
[0]->GetRefName()), static_cast<LPCTSTR
>(GitRev::GetWorkingCopy()));
1257 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1258 sCmd
+= L
" /alternative";
1260 CAppUtils::RunTortoiseGitProc(sCmd
);
1263 case eCmd_EditBranchDescription
:
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
);
1277 case eCmd_UpstreamDrop
:
1280 key
.Format(L
"branch.%s.remote", static_cast<LPCTSTR
>(selectedLeafs
[0]->GetRefsHeadsName()));
1281 g_Git
.UnsetConfigValue(key
);
1282 key
.Format(L
"branch.%s.merge", static_cast<LPCTSTR
>(selectedLeafs
[0]->GetRefsHeadsName()));
1283 g_Git
.UnsetConfigValue(key
);
1287 case eCmd_UpstreamSet
:
1289 CString newRef
= CBrowseRefsDlg::PickRef(false, L
"", gPickRef_Remote
, false);
1290 if (newRef
.IsEmpty() || newRef
.Find(L
"refs/remotes/") != 0)
1292 CString remote
, branch
;
1293 if (SplitRemoteBranchName(newRef
, remote
, branch
))
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
);
1309 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1312 auto index
= m_ListRefLeafs
.GetNextSelectedItem(pos
);
1314 sClipdata
+= L
"\r\n";
1315 sClipdata
+= m_ListRefLeafs
.GetItemText(index
, eCol_Name
);
1318 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
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
))
1332 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1334 if (pMsg
->message
== WM_KEYDOWN
)
1336 switch (pMsg
->wParam
)
1340 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1342 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1344 PostMessage(WM_COMMAND, IDOK);
1352 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1354 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1356 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1368 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1370 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1371 m_ctrlFilter
.SetFocus();
1377 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1379 OnClickedCancelFilter(NULL
, NULL
);
1387 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1390 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1392 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1395 if(m_currSortCol
== pNMLV
->iSubItem
)
1396 m_currSortDesc
= !m_currSortDesc
;
1399 m_currSortCol
= pNMLV
->iSubItem
;
1400 m_currSortDesc
= false;
1403 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1404 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, reinterpret_cast<DWORD_PTR
>(&compareFunc
));
1406 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1409 void CBrowseRefsDlg::OnDestroy()
1411 if (!m_bPickedRefSet
)
1412 m_pickedRef
= GetSelectedRef(true, false);
1414 CResizableStandAloneDialog::OnDestroy();
1417 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1419 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1422 if (!(pNMListView
->uChanged
& LVIF_STATE
))
1425 auto item
= GetListEntry(pNMListView
->iItem
);
1426 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1427 m_sLastSelected
= item
->GetRefName();
1432 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1436 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1446 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(g_Git
.FixBranchName(GetSelectedRef(true, false))));
1447 CAppUtils::RunTortoiseGitProc(sCmd
);
1450 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefs
, bool showRangeOptionWithTwoRefs
)
1452 CBrowseRefsDlg
dlg(CString(), nullptr);
1454 if(initialRef
.IsEmpty())
1455 initialRef
= L
"HEAD";
1456 dlg
.m_bWantPick
= true;
1457 dlg
.m_initialRef
= initialRef
;
1458 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1459 dlg
.m_bPickOne
= !pickMultipleRefs
;
1460 dlg
.m_bShowRangeOptionWithTwoRefs
= showRangeOptionWithTwoRefs
;
1462 if(dlg
.DoModal() != IDOK
)
1465 return dlg
.m_pickedRef
;
1468 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo
& refComboBox
, int pickRef_Kind
/* = gPickRef_All*/, int useShortName
/* = gPickRef_Head*/)
1471 refComboBox
.GetLBText(refComboBox
.GetCurSel(), origRef
);
1472 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1473 if(resultRef
.IsEmpty())
1478 CGit::REF_TYPE refType
;
1479 CString shortName
= CGit::GetShortName(resultRef
, &refType
);
1482 case CGit::REF_TYPE::LOCAL_BRANCH
:
1483 if (useShortName
& gPickRef_Head
)
1484 resultRef
= shortName
;
1486 case CGit::REF_TYPE::ANNOTATED_TAG
:
1487 case CGit::REF_TYPE::TAG
:
1488 if (useShortName
& gPickRef_Tag
)
1489 resultRef
= shortName
;
1491 case CGit::REMOTE_BRANCH
:
1492 if (useShortName
& gPickRef_Remote
)
1493 resultRef
= shortName
;
1498 CGit::StripRefName(resultRef
);
1500 refComboBox
.AddString(resultRef
);
1505 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1507 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1510 if (!pDispInfo
->item
.pszText
)
1511 return; //User canceled changing
1513 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1514 if(!pTree
->IsFrom(L
"refs/heads/"))
1516 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1520 CString selectedTreeRef
;
1521 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1524 auto pTree2
= GetTreeEntry(hTree
);
1525 selectedTreeRef
= pTree2
->GetRefName();
1528 CString origName
= pTree
->GetRefName().Mid(static_cast<int>(wcslen(L
"refs/heads/")));
1531 if (m_pListCtrlRoot
)
1532 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1533 newName
+= pDispInfo
->item
.pszText
;
1535 if (!CStringUtils::StartsWith(newName
, L
"refs/heads/"))
1537 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1541 CString newNameTrunced
= newName
.Mid(static_cast<int>(wcslen(L
"refs/heads/")));
1543 if (CString errorMsg
; g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
'"', &errorMsg
, CP_UTF8
) != 0)
1545 MessageBox(errorMsg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1548 //Do as if it failed to rename. Let Refresh() do the job.
1551 Refresh(selectedTreeRef
);
1553 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1555 // AfxMessageBox(W_csPopup);
1558 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1560 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1563 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1564 if(!pTree
->IsFrom(L
"refs/heads/"))
1566 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1571 void CBrowseRefsDlg::OnEnChangeEditFilter()
1573 SetTimer(IDT_FILTER
, 1000, nullptr);
1576 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1578 if (nIDEvent
== IDT_FILTER
)
1580 KillTimer(IDT_FILTER
);
1581 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1584 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1587 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1589 // FIXME: x64 version would get this function called with unexpected parameters.
1593 auto rect
= reinterpret_cast<LPRECT
>(lParam
);
1594 CPoint point
= CPoint(rect
->left
, rect
->bottom
);
1595 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1597 if (popup
.CreatePopupMenu())
1600 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1601 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1603 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1604 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1606 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1607 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1609 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1610 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1612 popup
.AppendMenu(MF_SEPARATOR
);
1614 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1615 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1617 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1620 if (selection
== LOGFILTER_TOGGLE
)
1621 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1623 m_SelectedFilters
^= selection
;
1625 SetTimer(IDT_FILTER
, 1000, nullptr);
1631 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1633 KillTimer(LOGFILTER_TIMER
);
1634 m_ctrlFilter
.SetWindowText(L
"");
1635 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1639 void CBrowseRefsDlg::SetFilterCueText()
1641 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1644 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1645 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1647 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1649 if (!CStringUtils::EndsWith(temp
, L
' '))
1651 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1654 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1656 if (!CStringUtils::EndsWith(temp
, L
' '))
1658 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1661 if (m_SelectedFilters
& LOGFILTER_REVS
)
1663 if (!CStringUtils::EndsWith(temp
, L
' '))
1665 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1668 // to make the cue banner text appear more to the right of the edit control
1670 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1673 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1675 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1676 m_bPickedRefSet
= true;
1680 void CBrowseRefsDlg::UpdateInfoLabel()
1683 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1684 SetDlgItemText(IDC_INFOLABEL
, temp
);
1687 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1690 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;
1694 CShadowTree
* CBrowseRefsDlg::GetListEntry(int index
)
1696 auto entry
= reinterpret_cast<CShadowTree
*>(m_ListRefLeafs
.GetItemData(index
));
1701 CShadowTree
* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem
)
1703 auto entry
= reinterpret_cast<CShadowTree
*>(m_RefTreeCtrl
.GetItemData(treeItem
));
1709 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()