1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2021, 2023-2024 - 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 const 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 const 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
),
160 m_regCurrSortCol(L
"Software\\TortoiseGit\\RefBrowserSortCol", 0),
161 m_regCurrSortDesc(L
"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE
),
162 m_initialRef(L
"HEAD"),
163 m_SelectedFilters(LOGFILTER_ALL
),
164 m_bIncludeNestedRefs(TRUE
)
166 // get short/long datetime setting from registry
167 DWORD RegUseShortDateFormat
= CRegDWORD(L
"Software\\TortoiseGit\\LogDateFormat", TRUE
);
168 if (RegUseShortDateFormat
)
169 m_DateFormat
= DATE_SHORTDATE
;
171 m_DateFormat
= DATE_LONGDATE
;
172 // get relative time display setting from registry
173 DWORD regRelativeTimes
= CRegDWORD(L
"Software\\TortoiseGit\\RelativeTimes", FALSE
);
174 m_bRelativeTimes
= (regRelativeTimes
!= 0);
176 m_regIncludeNestedRefs
= CRegDWORD(L
"Software\\TortoiseGit\\RefBrowserIncludeNestedRefs", TRUE
);
178 m_currSortCol
= m_regCurrSortCol
;
179 m_currSortDesc
= m_regCurrSortDesc
== TRUE
;
182 CBrowseRefsDlg::~CBrowseRefsDlg()
184 m_regCurrSortCol
= m_currSortCol
;
185 m_regCurrSortDesc
= m_currSortDesc
;
188 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
190 CDialog::DoDataExchange(pDX
);
191 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
192 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
193 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
194 DDX_Check(pDX
, IDC_INCLUDENESTEDREFS
, m_bIncludeNestedRefs
);
195 DDX_Control(pDX
, IDC_BROWSE_REFS_BRANCHFILTER
, m_cBranchFilter
);
199 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
200 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
201 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
203 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
205 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
206 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnItemChangedListRefLeafs
)
207 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
208 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
209 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
210 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
211 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED
, OnClickedCancelFilter
)
213 ON_BN_CLICKED(IDC_CURRENTBRANCH
, OnBnClickedCurrentbranch
)
214 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS
, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs
)
215 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER
, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter
)
219 // CBrowseRefsDlg message handlers
221 void CBrowseRefsDlg::OnBnClickedOk()
223 if (m_bPickOne
|| !m_bShowRangeOptionWithTwoRefs
|| m_ListRefLeafs
.GetSelectedCount() != 2)
230 popupMenu
.CreatePopupMenu();
232 std::vector
<CShadowTree
*> selectedLeafs
;
233 GetSelectedLeaves(selectedLeafs
);
235 popupMenu
.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG
);
236 popupMenu
.SetDefaultItem(1);
237 popupMenu
.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[0]->GetRefName(), L
".."), IDI_LOG
);
238 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[1]->GetRefName(), L
".."), IDI_LOG
);
239 popupMenu
.AppendMenuIcon(4, GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[1]->GetRefName(), L
"..."), IDI_LOG
);
242 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
244 params
.cbSize
= sizeof(TPMPARAMS
);
245 params
.rcExclude
= rect
;
246 const int selection
= popupMenu
.TrackPopupMenuEx(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
| TPM_VERTICAL
, rect
.left
, rect
.top
, this, ¶ms
);
254 m_bPickedRefSet
= true;
255 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[0]->GetRefName(), L
"..");
261 m_bPickedRefSet
= true;
262 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[1]->GetRefName(), L
"..");
268 m_bPickedRefSet
= true;
269 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, selectedLeafs
[1]->GetRefName(), 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(GetSafeHwnd(), 300), 0, 0, CDPIAware::Instance().ScaleX(GetSafeHwnd(), 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 CAppUtils::SetWindowTitle(*this, g_Git
.m_CurrentDir
);
323 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
326 m_ListRefLeafs
.ModifyStyle(0, LVS_SINGLESEL
);
328 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_ALL
)));
329 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED
)));
330 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED
)));
331 m_cBranchFilter
.SetCurSel(0);
333 m_ListRefLeafs
.SetFocus();
337 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
339 const int posSlash
= nameLeft
.Find('/');
344 nameLeft
.Empty();//Nothing left
348 nameSub
=nameLeft
.Left(posSlash
);
349 nameLeft
=nameLeft
.Mid(posSlash
+1);
351 if(nameSub
.IsEmpty())
354 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
357 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
358 nextNode
.m_csRefName
=nameSub
;
359 nextNode
.m_pParent
=this;
363 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
367 if (CStringUtils::EndsWith(partialRefName
, m_csRefName
))
369 //Match of leaf name. Try match on total name.
370 CString totalRefName
= GetRefName();
371 if (CStringUtils::EndsWith(totalRefName
, partialRefName
))
372 return this; //Also match. Found.
377 //Not a leaf. Search all nodes.
378 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
380 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
382 return pSubtree
; //Found
385 return nullptr; //Not found
388 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
390 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
391 //List ctrl selection?
392 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
395 return GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
))->GetRefName();
397 else if (pos
&& !pickFirstSelIfMultiSel
)
399 // at least one leaf is selected
402 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
404 CString ref
= GetListEntry(index
)->GetRefName();
405 if (CStringUtils::StartsWith(ref
, L
"refs/"))
406 ref
= ref
.Mid(static_cast<int>(wcslen(L
"refs/")));
407 if (CStringUtils::StartsWith(ref
, L
"heads/"))
408 ref
= ref
.Mid(static_cast<int>(wcslen(L
"heads/")));
415 //Tree ctrl selection?
416 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
418 return GetTreeEntry(hTree
)->GetRefName();
420 return CString();//None
423 void CBrowseRefsDlg::Refresh(CString selectRef
)
426 if (g_Git
.GetRemoteList(remotes
))
427 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get a list of remotes."), L
"TortoiseGit", MB_ICONERROR
);
429 if(!selectRef
.IsEmpty())
431 if (selectRef
== L
"HEAD")
433 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
434 selectRef
= L
"refs/heads";
436 selectRef
= L
"refs/heads/" + selectRef
;
440 selectRef
= GetSelectedRef(false, true);
442 m_RefTreeCtrl
.DeleteAllItems();
443 m_ListRefLeafs
.DeleteAllItems();
444 m_TreeRoot
.m_ShadowTree
.clear();
445 m_TreeRoot
.m_csRefName
= L
"refs";
446 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs");
447 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
, reinterpret_cast<DWORD_PTR
>(&m_TreeRoot
));
449 MAP_REF_GITREVREFBROWSER refMap
;
450 if (CString err
; GitRevRefBrowser::GetGitRevRefMap(refMap
, m_cBranchFilter
.GetCurSel(), err
, [&](const CString
& refName
)
452 //Use ref based on m_pickRef_Kind
453 if (CStringUtils::StartsWith(refName
, L
"refs/heads/") && !(m_pickRef_Kind
& gPickRef_Head
))
455 if (CStringUtils::StartsWith(refName
, L
"refs/tags/") && !(m_pickRef_Kind
& gPickRef_Tag
))
457 if (CStringUtils::StartsWith(refName
, L
"refs/remotes/") && !(m_pickRef_Kind
& gPickRef_Remote
))
459 if (m_pickRef_Kind
== gPickRef_Remote
&& !CStringUtils::StartsWith(refName
, L
"refs/remotes/")) // do not show refs/stash if only remote branches are requested
464 MessageBox(L
"Get refs failed:" + err
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
468 STRING_VECTOR remoteBranches
;
469 if (g_Git
.GetBranchList(remoteBranches
, nullptr, CGit::BRANCH_REMOTE
))
470 MessageBox(L
"Loading remote tracking branches failed.", L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
471 std::sort(remoteBranches
.begin(), remoteBranches
.end());
474 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
476 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
477 GitRevRefBrowser ref
= iterRefMap
->second
;
479 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
480 CGit::GetShortName(ref
.m_UpstreamRef
, ref
.m_UpstreamRef
, L
"refs/");
481 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
482 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"remotes/");
483 if (!ref
.m_UpstreamRef
.IsEmpty() && !std::binary_search(remoteBranches
.cbegin(), remoteBranches
.cend(), ref
.m_UpstreamRef
))
484 treeLeaf
.m_csUpstream
= L
"(gone: " + treeLeaf
.m_csUpstream
+ L
")";
485 treeLeaf
.m_csSubject
= ref
.GetSubject();
486 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
487 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
488 treeLeaf
.m_csDescription
= ref
.m_Description
;
491 // always expand the tree first
492 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
494 // try exact match first
495 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
496 SelectRef(selectRef
, false);
497 else if (refMap
.empty())
498 SelectRef(L
"refs", false);
501 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
505 CString newRefName
= GetFullRefName(refName
);
506 if(!newRefName
.IsEmpty())
507 refName
= newRefName
;
508 //else refName is not a valid ref. Try to select as good as possible.
510 if (!CStringUtils::StartsWith(refName
, L
"refs"))
511 return false; // Not a ref name
513 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
514 if (treeLeafHead
.m_hTree
)
516 //Not a leaf. Select tree node and return
517 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
521 if (!treeLeafHead
.m_pParent
)
522 return false; //Weird... should not occur.
524 //This is the current head.
525 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
527 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
529 auto pCurrShadowTree
= GetListEntry(indexPos
);
530 if(pCurrShadowTree
== &treeLeafHead
)
532 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
533 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
540 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
544 if (CStringUtils::StartsWith(refName
, L
"refs/"))
545 refName
= refName
.Mid(static_cast<int>(wcslen(L
"refs/")));
546 pTreePos
=&m_TreeRoot
;
548 if(refName
.IsEmpty())
549 return *pTreePos
;//Found leaf
551 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
554 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
555 ASSERT(!bCreateIfNotExist
);
559 if(!refName
.IsEmpty())
561 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
562 //Leafs are for the list control.
563 if (!pNextTree
->m_hTree
)
565 //New tree. Create node in control.
566 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
);
567 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
, reinterpret_cast<DWORD_PTR
>(pNextTree
));
571 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
575 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
577 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
580 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
583 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
585 m_ListRefLeafs
.DeleteAllItems();
587 auto pTree
= GetTreeEntry(treeNode
);
592 m_ctrlFilter
.GetWindowText(filterText
);
594 CBrowseRefsDlgFilter
filter(filterText
, false, m_SelectedFilters
, false);
596 FillListCtrlForShadowTree(pTree
, L
"", true, filter
);
597 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
598 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Description
, pTree
->IsFrom(L
"refs/heads"));
599 m_ListRefLeafs
.AdjustColumnWidths();
603 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
, const CBrowseRefsDlgFilter
& filter
)
607 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
608 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== L
"refs" && !pTree
->m_pParent
) && filter(pTree
, ref
))
610 const int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
612 m_ListRefLeafs
.SetItemData(indexItem
, reinterpret_cast<DWORD_PTR
>(pTree
));
613 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
614 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
615 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : CString());
616 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
617 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
618 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
619 CString descrition
= pTree
->m_csDescription
;
620 descrition
.Replace(L
'\n', L
' ');
621 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
627 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
629 else if (!isFirstLevel
)
630 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
632 m_pListCtrlRoot
= pTree
;
633 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
635 FillListCtrlForShadowTree(&itSubTree
->second
, csThisName
, false, filter
);
640 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
641 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, reinterpret_cast<DWORD_PTR
>(&compareFunc
));
643 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
647 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
649 ASSERT(!leafs
.empty());
652 UINT mbIcon
=MB_ICONQUESTION
;
654 bool bIsRemoteBranch
= false;
655 bool bIsBranch
= false;
656 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
657 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
661 if(leafs
.size() == 1)
663 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
664 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, static_cast<LPCWSTR
>(branchToDelete
));
666 //Check if branch is fully merged in HEAD
667 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), L
"HEAD"))
669 csMessage
+= L
"\r\n\r\n";
670 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
671 mbIcon
= MB_ICONWARNING
;
676 csMessage
+= L
"\r\n\r\n";
677 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
678 mbIcon
= MB_ICONWARNING
;
683 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
685 csMessage
+= L
"\r\n\r\n";
686 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
687 mbIcon
= MB_ICONWARNING
;
691 csMessage
+= L
"\r\n\r\n";
692 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
693 mbIcon
= MB_ICONWARNING
;
698 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
700 if(leafs
.size() == 1)
702 CString tagToDelete
= leafs
[0]->GetRefName().Mid(static_cast<int>(wcslen(L
"refs/tags/")));
703 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, static_cast<LPCWSTR
>(tagToDelete
));
706 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
709 return MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| mbIcon
) == IDYES
;
712 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
714 bool allRemoteBranch
= true;
715 std::map
<CString
, STRING_VECTOR
> remoteBranches
;
716 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
718 CString completeRefName
= (*i
)->GetRefName();
719 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
721 CString branchToDelete
= completeRefName
.Mid(static_cast<int>(wcslen(L
"refs/remotes/")));
722 CString remoteName
, remoteBranchToDelete
;
723 if (!SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
724 remoteBranches
[remoteName
].push_back(remoteBranchToDelete
);
728 allRemoteBranch
= false;
734 // delete multiple remote branches in batch, so it is faster, fewer password prompt
735 for (const auto& remotebranchlist
: remoteBranches
)
737 auto& remoteName
= remotebranchlist
.first
;
738 if (CAppUtils::IsSSHPutty())
739 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName
);
741 CSysProgressDlg sysProgressDlg
;
742 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
743 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
744 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
745 sysProgressDlg
.SetShowProgressBar(false);
746 sysProgressDlg
.ShowModal(this, true);
749 list
.reserve(remotebranchlist
.second
.size());
750 std::transform(remotebranchlist
.second
.cbegin(), remotebranchlist
.second
.cend(), std::back_inserter(list
), [](auto& branch
) { return L
"refs/heads/" + branch
; });
751 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
753 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete remote refs.", CGit::GIT_CMD_PUSH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
754 sysProgressDlg
.Stop();
758 sysProgressDlg
.Stop();
764 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
765 if(!DoDeleteRef((*i
)->GetRefName()))
770 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
772 bool bIsRemoteBranch
= false;
773 bool bIsBranch
= false;
774 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
777 bIsRemoteBranch
= true;
779 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
784 CString branchToDelete
= completeRefName
.Mid(static_cast<int>(wcslen(L
"refs/remotes/")));
785 CString remoteName
, remoteBranchToDelete
;
786 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
789 if (CAppUtils::IsSSHPutty())
790 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName
);
792 CSysProgressDlg sysProgressDlg
;
793 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
794 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
795 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
796 sysProgressDlg
.SetShowProgressBar(false);
797 sysProgressDlg
.ShowModal(this, true);
800 list
.push_back(L
"refs/heads/" + remoteBranchToDelete
);
801 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
803 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete remote ref.", CGit::GIT_CMD_PUSH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
804 sysProgressDlg
.Stop();
808 sysProgressDlg
.Stop();
813 if (g_Git
.DeleteRef(completeRefName
))
815 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
819 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/tags/"))
821 if (g_Git
.DeleteRef(completeRefName
))
823 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
830 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
832 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
835 return pLeaf
->GetRefName();
839 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
841 if (pWndFrom
== &m_RefTreeCtrl
)
842 OnContextMenu_RefTreeCtrl(point
);
845 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
847 CPoint clientPoint
=point
;
848 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
850 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
852 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
854 VectorPShadowTree tree
;
855 ShowContextMenu(point
,hTreeItem
,tree
);
858 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
860 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
861 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
864 selectedLeafs
.push_back(GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
868 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
870 std::vector
<CShadowTree
*> selectedLeafs
;
871 GetSelectedLeaves(selectedLeafs
);
872 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
875 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
877 ASSERT(selectedLeafs
.size() == 2);
879 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
880 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
882 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
885 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
888 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
896 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
899 popupMenu
.CreatePopupMenu();
901 bool bAddSeparator
= false;
904 if(selectedLeafs
.size()==1)
906 bAddSeparator
= true;
908 bool bShowReflogOption
= false;
909 bool bShowFetchOption
= false;
910 bool bShowRenameOption
= false;
911 bool bShowCreateBranchOption
= false;
912 bool bShowEditBranchDescriptionOption
= false;
914 CString fetchFromCmd
;
916 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
918 bShowReflogOption
= true;
919 bShowRenameOption
= true;
920 bShowEditBranchDescriptionOption
= true;
922 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
924 bShowReflogOption
= true;
925 bShowFetchOption
= true;
926 bShowCreateBranchOption
= true;
928 CString remoteBranch
;
929 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
930 bShowFetchOption
= false;
932 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, static_cast<LPCWSTR
>(remoteName
));
937 popupMenu
.AppendMenuIcon(eCmd_Select
, IDS_SELECT
);
938 popupMenu
.AppendMenu(MF_SEPARATOR
);
940 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, IDS_MENULOG
, IDI_LOG
);
941 popupMenu
.SetDefaultItem(0, TRUE
);
942 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
943 if(bShowReflogOption
)
944 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, IDS_MENUREFLOG
, IDI_LOG
);
948 popupMenu
.AppendMenu(MF_SEPARATOR
);
949 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
952 popupMenu
.AppendMenu(MF_SEPARATOR
);
953 bAddSeparator
= false;
957 bAddSeparator
= true;
958 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_UPDATE
);
962 popupMenu
.AppendMenu(MF_SEPARATOR
);
964 bAddSeparator
= false;
968 if (selectedLeafs
[0]->GetRefName() != L
"refs/heads/" + g_Git
.GetCurrentBranch())
970 str
.Format(IDS_LOG_POPUP_MERGEREV
, static_cast<LPCWSTR
>(g_Git
.GetCurrentBranch()));
971 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
973 popupMenu
.AppendMenuIcon(eCmd_Switch
, IDS_SWITCH_TO_THIS
, IDI_SWITCH
);
974 popupMenu
.AppendMenu(MF_SEPARATOR
);
977 if(bShowCreateBranchOption
)
979 bAddSeparator
= true;
980 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, IDS_MENUBRANCH
, IDI_COPY
);
983 if (bShowEditBranchDescriptionOption
)
985 bAddSeparator
= true;
986 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, IDS_PROC_BROWSEREFS_EDITDESCRIPTION
, IDI_RENAME
);
988 if(bShowRenameOption
)
990 bAddSeparator
= true;
991 popupMenu
.AppendMenuIcon(eCmd_Rename
, IDS_PROC_BROWSEREFS_RENAME
, IDI_RENAME
);
994 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
997 popupMenu
.AppendMenu(MF_SEPARATOR
);
998 bAddSeparator
= true;
999 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
1000 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
);
1001 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
);
1004 else if(selectedLeafs
.size() == 2)
1006 bAddSeparator
= true;
1007 popupMenu
.AppendMenuIcon(eCmd_Diff
, IDS_PROC_BROWSEREFS_COMPAREREFS
, IDI_DIFF
);
1008 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
1010 menu
.Format(IDS_SHOWLOG_OF
, static_cast<LPCWSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..")));
1011 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
1012 menu
.Format(IDS_SHOWLOG_OF
, static_cast<LPCWSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...")));
1013 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
1016 if(!selectedLeafs
.empty())
1018 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
1021 popupMenu
.AppendMenu(MF_SEPARATOR
);
1022 CString menuItemName
;
1023 if(selectedLeafs
.size() == 1)
1024 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
1026 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
1028 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
1029 bAddSeparator
= true;
1031 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1034 popupMenu
.AppendMenu(MF_SEPARATOR
);
1035 CString menuItemName
;
1036 if(selectedLeafs
.size() == 1)
1037 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1039 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1041 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1042 bAddSeparator
= true;
1044 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1047 popupMenu
.AppendMenu(MF_SEPARATOR
);
1048 CString menuItemName
;
1049 if(selectedLeafs
.size() == 1)
1050 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1052 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1054 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1055 bAddSeparator
= true;
1060 if (hTreePos
&& selectedLeafs
.empty())
1062 auto pTree
= GetTreeEntry(hTreePos
);
1063 if(pTree
->IsFrom(L
"refs/remotes"))
1066 popupMenu
.AppendMenu(MF_SEPARATOR
);
1067 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, IDS_PROC_BROWSEREFS_MANAGEREMOTES
, IDI_SETTINGS
);
1068 bAddSeparator
= true;
1069 if(selectedLeafs
.empty())
1071 CString remoteBranch
;
1072 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1074 const int pos
= findVectorPosition(remotes
, remoteName
);
1078 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, static_cast<LPCWSTR
>(remoteName
));
1079 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_UPDATE
);
1081 temp
.LoadString(IDS_DELETEREMOTETAG
);
1082 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1085 bAddSeparator
= false;
1087 if(pTree
->IsFrom(L
"refs/heads"))
1090 popupMenu
.AppendMenu(MF_SEPARATOR
);
1092 temp
.LoadString(IDS_MENUBRANCH
);
1093 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1094 bAddSeparator
= false;
1096 if(pTree
->IsFrom(L
"refs/tags"))
1099 popupMenu
.AppendMenu(MF_SEPARATOR
);
1100 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, IDS_MENUTAG
, IDI_TAG
);
1101 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, IDS_PROC_BROWSEREFS_DELETEALLTAGS
, IDI_DELETE
);
1102 if (!remotes
.empty())
1104 popupMenu
.AppendMenu(MF_SEPARATOR
);
1106 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1109 temp
.Format(IDS_DELETEREMOTETAGON
, static_cast<LPCWSTR
>(*it
));
1110 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1113 bAddSeparator
= false;
1117 popupMenu
.AppendMenu(MF_SEPARATOR
);
1118 popupMenu
.AppendMenuIcon(eCmd_Copy
, IDS_COPY_REF_NAMES
, IDI_COPYCLIP
);
1120 const int selection
= popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
1121 switch (static_cast<eCmd
>(selection
& 0xFFFF))
1129 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())));
1130 CAppUtils::RunTortoiseGitProc(sCmd
);
1133 case eCmd_ViewLogRange
:
1136 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCWSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..")));
1137 CAppUtils::RunTortoiseGitProc(sCmd
);
1140 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1143 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCWSTR
>(GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...")));
1144 CAppUtils::RunTortoiseGitProc(sCmd
);
1147 case eCmd_RepoBrowser
:
1148 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedLeafs
[0]->GetRefName());
1150 case eCmd_DeleteBranch
:
1151 case eCmd_DeleteRemoteBranch
:
1153 if(ConfirmDeleteRef(selectedLeafs
))
1154 DoDeleteRefs(selectedLeafs
);
1158 case eCmd_DeleteTag
:
1160 if(ConfirmDeleteRef(selectedLeafs
))
1161 DoDeleteRefs(selectedLeafs
);
1165 case eCmd_ShowReflog
:
1167 CRefLogDlg
refLogDlg(this);
1168 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1169 refLogDlg
.DoModal();
1174 CAppUtils::Fetch(GetSafeHwnd(), remoteName
);
1178 case eCmd_DeleteRemoteTag
:
1180 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1181 const int remoteInx
= selection
>> 16;
1182 if (remoteInx
< 0 || static_cast<size_t>(remoteInx
) >= remotes
.size())
1184 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1185 deleteRemoteTagDlg
.DoModal();
1190 CString ref
= selectedLeafs
[0]->GetRefName();
1191 CAppUtils::Merge(GetSafeHwnd(), &ref
);
1196 CAppUtils::Switch(GetSafeHwnd(), selectedLeafs
[0]->GetRefName());
1201 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1203 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1206 case eCmd_AddRemote
:
1208 CAddRemoteDlg(this).DoModal();
1212 case eCmd_ManageRemotes
:
1214 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(), this).DoModal();
1215 // CSettingGitRemote W_Remotes(m_cmdPath);
1216 // W_Remotes.DoModal();
1220 case eCmd_CreateBranch
:
1222 CString
* commitHash
= nullptr;
1223 if (selectedLeafs
.size() == 1)
1224 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
1225 CAppUtils::CreateBranchTag(GetSafeHwnd(), false, commitHash
);
1229 case eCmd_CreateTag
:
1231 CAppUtils::CreateBranchTag(GetSafeHwnd(), true);
1235 case eCmd_DeleteAllTags
:
1237 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1239 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1240 selectedLeafs
.push_back(GetListEntry(i
));
1242 if (ConfirmDeleteRef(selectedLeafs
))
1243 DoDeleteRefs(selectedLeafs
);
1252 selectedLeafs
[0]->GetRefName(),
1253 selectedLeafs
[1]->GetRefName());
1257 case eCmd_UnifiedDiff
:
1259 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1265 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()));
1266 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1267 sCmd
+= L
" /alternative";
1269 CAppUtils::RunTortoiseGitProc(sCmd
);
1272 case eCmd_EditBranchDescription
:
1275 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1276 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1277 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1278 dlg
.m_bUseLogWidth
= true;
1279 if(dlg
.DoModal() == IDOK
)
1281 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1286 case eCmd_UpstreamDrop
:
1289 key
.Format(L
"branch.%s.remote", static_cast<LPCWSTR
>(selectedLeafs
[0]->GetRefsHeadsName()));
1290 g_Git
.UnsetConfigValue(key
);
1291 key
.Format(L
"branch.%s.merge", static_cast<LPCWSTR
>(selectedLeafs
[0]->GetRefsHeadsName()));
1292 g_Git
.UnsetConfigValue(key
);
1296 case eCmd_UpstreamSet
:
1298 CString newRef
= CBrowseRefsDlg::PickRef(false, L
"", gPickRef_Remote
, false);
1299 if (newRef
.IsEmpty() || newRef
.Find(L
"refs/remotes/") != 0)
1301 CString remote
, branch
;
1302 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1304 // Setting the config keys directly might result in an invalid situation if the remote is not set to
1305 // fetch the desired upstream branch (in remote.x.fetch), cf. issue #3638
1306 if (CString errorMsg
; g_Git
.Run(L
"git.exe branch --set-upstream-to=\"" + remote
+ L
'/' + branch
+ L
"\" -- \"" + selectedLeafs
[0]->GetRefsHeadsName() + L
"\"", &errorMsg
, CP_UTF8
) != 0)
1308 MessageBox(errorMsg
+ "\r\n\r\nThis is generally caused when remote." + remote
+ ".fetch does not include the desired branch.", L
"TortoiseGit", MB_ICONERROR
);
1318 for (const auto& leaf
: selectedLeafs
)
1321 sClipdata
+= L
"\r\n";
1322 sClipdata
+= CGit::StripRefName(leaf
->GetRefName());
1325 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1331 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1333 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1334 if(!(*i
)->IsFrom(from
))
1339 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1341 if (pMsg
->message
== WM_KEYDOWN
)
1343 switch (pMsg
->wParam
)
1347 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1349 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1351 PostMessage(WM_COMMAND, IDOK);
1359 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1361 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1363 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1375 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1377 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1378 m_ctrlFilter
.SetFocus();
1384 if (pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
&& (GetAsyncKeyState(VK_CONTROL
) & 0x8000))
1386 // select all entries
1387 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1388 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1393 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1395 OnClickedCancelFilter(NULL
, NULL
);
1403 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1406 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1408 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1411 if(m_currSortCol
== pNMLV
->iSubItem
)
1412 m_currSortDesc
= !m_currSortDesc
;
1415 m_currSortCol
= pNMLV
->iSubItem
;
1416 m_currSortDesc
= false;
1419 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1420 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, reinterpret_cast<DWORD_PTR
>(&compareFunc
));
1422 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1425 void CBrowseRefsDlg::OnDestroy()
1427 if (!m_bPickedRefSet
)
1428 m_pickedRef
= GetSelectedRef(true, false);
1430 CResizableStandAloneDialog::OnDestroy();
1433 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1435 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1438 if (!(pNMListView
->uChanged
& LVIF_STATE
))
1441 auto item
= GetListEntry(pNMListView
->iItem
);
1442 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1443 m_sLastSelected
= item
->GetRefName();
1448 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1452 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1462 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))));
1463 CAppUtils::RunTortoiseGitProc(sCmd
);
1466 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefs
, bool showRangeOptionWithTwoRefs
)
1468 CBrowseRefsDlg
dlg(CString(), nullptr);
1470 if(initialRef
.IsEmpty())
1471 initialRef
= L
"HEAD";
1472 dlg
.m_bWantPick
= true;
1473 dlg
.m_initialRef
= initialRef
;
1474 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1475 dlg
.m_bPickOne
= !pickMultipleRefs
;
1476 dlg
.m_bShowRangeOptionWithTwoRefs
= showRangeOptionWithTwoRefs
;
1478 if(dlg
.DoModal() != IDOK
)
1481 return dlg
.m_pickedRef
;
1484 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo
& refComboBox
, int pickRef_Kind
/* = gPickRef_All*/, int useShortName
/* = gPickRef_Head*/)
1487 refComboBox
.GetLBText(refComboBox
.GetCurSel(), origRef
);
1488 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1489 if(resultRef
.IsEmpty())
1494 CGit::REF_TYPE refType
;
1495 CString shortName
= CGit::GetShortName(resultRef
, &refType
);
1498 case CGit::REF_TYPE::LOCAL_BRANCH
:
1499 if (useShortName
& gPickRef_Head
)
1500 resultRef
= shortName
;
1502 case CGit::REF_TYPE::ANNOTATED_TAG
:
1503 case CGit::REF_TYPE::TAG
:
1504 if (useShortName
& gPickRef_Tag
)
1505 resultRef
= shortName
;
1507 case CGit::REMOTE_BRANCH
:
1508 if (useShortName
& gPickRef_Remote
)
1509 resultRef
= shortName
;
1514 CGit::StripRefName(resultRef
);
1516 refComboBox
.AddString(resultRef
);
1521 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1523 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1526 if (!pDispInfo
->item
.pszText
)
1527 return; //User canceled changing
1529 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1530 if(!pTree
->IsFrom(L
"refs/heads/"))
1532 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1536 CString selectedTreeRef
;
1537 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1540 auto pTree2
= GetTreeEntry(hTree
);
1541 selectedTreeRef
= pTree2
->GetRefName();
1544 CString origName
= pTree
->GetRefName().Mid(static_cast<int>(wcslen(L
"refs/heads/")));
1547 if (m_pListCtrlRoot
)
1548 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1549 newName
+= pDispInfo
->item
.pszText
;
1551 if (!CStringUtils::StartsWith(newName
, L
"refs/heads/"))
1553 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1557 CString newNameTrunced
= newName
.Mid(static_cast<int>(wcslen(L
"refs/heads/")));
1559 if (CString errorMsg
; g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" -- \"" + newNameTrunced
+ L
'"', &errorMsg
, CP_UTF8
) != 0)
1561 MessageBox(errorMsg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1564 //Do as if it failed to rename. Let Refresh() do the job.
1567 Refresh(selectedTreeRef
);
1569 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1571 // AfxMessageBox(W_csPopup);
1574 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1576 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1579 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1580 if(!pTree
->IsFrom(L
"refs/heads/"))
1582 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1587 void CBrowseRefsDlg::OnEnChangeEditFilter()
1589 SetTimer(IDT_FILTER
, 1000, nullptr);
1592 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1594 if (nIDEvent
== IDT_FILTER
)
1596 KillTimer(IDT_FILTER
);
1597 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1600 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1603 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1605 // FIXME: x64 version would get this function called with unexpected parameters.
1609 auto rect
= reinterpret_cast<LPRECT
>(lParam
);
1610 CPoint point
= CPoint(rect
->left
, rect
->bottom
);
1611 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1613 if (popup
.CreatePopupMenu())
1616 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1617 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1619 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1620 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1622 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1623 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1625 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1626 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1628 popup
.AppendMenu(MF_SEPARATOR
);
1630 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1631 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1633 const int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1636 if (selection
== LOGFILTER_TOGGLE
)
1637 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1639 m_SelectedFilters
^= selection
;
1641 SetTimer(IDT_FILTER
, 1000, nullptr);
1647 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1649 KillTimer(LOGFILTER_TIMER
);
1650 m_ctrlFilter
.SetWindowText(L
"");
1651 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1655 void CBrowseRefsDlg::SetFilterCueText()
1657 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1660 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1661 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1663 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1665 if (!CStringUtils::EndsWith(temp
, L
' '))
1667 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1670 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1672 if (!CStringUtils::EndsWith(temp
, L
' '))
1674 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1677 if (m_SelectedFilters
& LOGFILTER_REVS
)
1679 if (!CStringUtils::EndsWith(temp
, L
' '))
1681 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1684 // to make the cue banner text appear more to the right of the edit control
1686 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1689 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1691 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1692 m_bPickedRefSet
= true;
1696 void CBrowseRefsDlg::UpdateInfoLabel()
1699 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1700 SetDlgItemText(IDC_INFOLABEL
, temp
);
1703 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1706 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;
1710 CShadowTree
* CBrowseRefsDlg::GetListEntry(int index
)
1712 auto entry
= reinterpret_cast<CShadowTree
*>(m_ListRefLeafs
.GetItemData(index
));
1717 CShadowTree
* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem
)
1719 auto entry
= reinterpret_cast<CShadowTree
*>(m_RefTreeCtrl
.GetItemData(treeItem
));
1725 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()