1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2018 - 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"
42 static int SplitRemoteBranchName(CString ref
, CString
&remote
, CString
&branch
)
44 if (CStringUtils::StartsWith(ref
, L
"refs/remotes/"))
45 ref
= ref
.Mid((int)wcslen(L
"refs/remotes/"));
46 else if (CStringUtils::StartsWith(ref
, L
"remotes/"))
47 ref
= ref
.Mid((int)wcslen(L
"remotes/"));
50 int result
= g_Git
.GetRemoteList(list
);
54 for (size_t i
= 0; i
< list
.size(); ++i
)
56 if (CStringUtils::StartsWith(ref
, list
[i
] + L
"/"))
59 branch
= ref
.Mid(list
[i
].GetLength() + 1);
73 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
78 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
79 HDITEM HeaderItem
= {0};
80 HeaderItem
.mask
= HDI_FORMAT
;
81 for (int i
=0; i
<pHeader
->GetItemCount(); ++i
)
83 pHeader
->GetItem(i
, &HeaderItem
);
84 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
85 pHeader
->SetItem(i
, &HeaderItem
);
89 pHeader
->GetItem(nColumn
, &HeaderItem
);
90 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
91 pHeader
->SetItem(nColumn
, &HeaderItem
);
95 class CRefLeafListCompareFunc
98 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){
99 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
101 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
104 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
106 return reinterpret_cast<CRefLeafListCompareFunc
*>(lParamSort
)->Compare(lParam1
, lParam2
);
109 int Compare(LPARAM lParam1
, LPARAM lParam2
)
112 reinterpret_cast<CShadowTree
*>(m_pList
->GetItemData((int)lParam1
)),
113 reinterpret_cast<CShadowTree
*>(m_pList
->GetItemData((int)lParam2
)));
116 int Compare(const CShadowTree
* pLeft
, const CShadowTree
* pRight
)
118 int result
=CompareNoDesc(pLeft
,pRight
);
124 int CompareNoDesc(const CShadowTree
* pLeft
, const CShadowTree
* pRight
)
128 case CBrowseRefsDlg::eCol_Name
: return SortStrCmp(pLeft
->GetRefName(), pRight
->GetRefName());
129 case CBrowseRefsDlg::eCol_Upstream
: return SortStrCmp(pLeft
->m_csUpstream
, pRight
->m_csUpstream
);
130 case CBrowseRefsDlg::eCol_Date
: return ((pLeft
->m_csDate
== pRight
->m_csDate
) ? 0 : ((pLeft
->m_csDate
> pRight
->m_csDate
) ? 1 : -1));
131 case CBrowseRefsDlg::eCol_Msg
: return SortStrCmp(pLeft
->m_csSubject
, pRight
->m_csSubject
);
132 case CBrowseRefsDlg::eCol_LastAuthor
: return SortStrCmp(pLeft
->m_csAuthor
, pRight
->m_csAuthor
);
133 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
134 case CBrowseRefsDlg::eCol_Description
: return SortStrCmp(pLeft
->m_csDescription
, pRight
->m_csDescription
);
138 int SortStrCmp(const CString
& left
, const CString
& right
)
141 return StrCmpLogicalW(left
, right
);
142 return StrCmpI(left
, right
);
151 // CBrowseRefsDlg dialog
153 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
155 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=nullptr*/)
156 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
159 m_currSortDesc(false),
160 m_regCurrSortCol(L
"Software\\TortoiseGit\\RefBrowserSortCol", 0),
161 m_regCurrSortDesc(L
"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE
),
162 m_initialRef(L
"HEAD"),
163 m_pickRef_Kind(gPickRef_All
),
164 m_pListCtrlRoot(nullptr),
166 m_SelectedFilters(LOGFILTER_ALL
),
168 m_bIncludeNestedRefs(TRUE
),
169 m_bPickedRefSet(false)
172 // get short/long datetime setting from registry
173 DWORD RegUseShortDateFormat
= CRegDWORD(L
"Software\\TortoiseGit\\LogDateFormat", TRUE
);
174 if (RegUseShortDateFormat
)
175 m_DateFormat
= DATE_SHORTDATE
;
177 m_DateFormat
= DATE_LONGDATE
;
178 // get relative time display setting from registry
179 DWORD regRelativeTimes
= CRegDWORD(L
"Software\\TortoiseGit\\RelativeTimes", FALSE
);
180 m_bRelativeTimes
= (regRelativeTimes
!= 0);
182 m_regIncludeNestedRefs
= CRegDWORD(L
"Software\\TortoiseGit\\RefBrowserIncludeNestedRefs", TRUE
);
184 m_currSortCol
= m_regCurrSortCol
;
185 m_currSortDesc
= m_regCurrSortDesc
== TRUE
;
188 CBrowseRefsDlg::~CBrowseRefsDlg()
190 m_regCurrSortCol
= m_currSortCol
;
191 m_regCurrSortDesc
= m_currSortDesc
;
194 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
196 CDialog::DoDataExchange(pDX
);
197 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
198 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
199 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
200 DDX_Check(pDX
, IDC_INCLUDENESTEDREFS
, m_bIncludeNestedRefs
);
201 DDX_Control(pDX
, IDC_BROWSE_REFS_BRANCHFILTER
, m_cBranchFilter
);
205 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
206 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
207 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
209 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
211 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
212 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnItemChangedListRefLeafs
)
213 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
214 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
215 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
216 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
217 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED
, OnClickedCancelFilter
)
219 ON_BN_CLICKED(IDC_CURRENTBRANCH
, OnBnClickedCurrentbranch
)
220 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS
, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs
)
221 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER
, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter
)
225 // CBrowseRefsDlg message handlers
227 void CBrowseRefsDlg::OnBnClickedOk()
229 if (m_bPickOne
|| m_ListRefLeafs
.GetSelectedCount() != 2)
236 popupMenu
.CreatePopupMenu();
238 std::vector
<CShadowTree
*> selectedLeafs
;
239 GetSelectedLeaves(selectedLeafs
);
241 popupMenu
.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG
);
242 popupMenu
.SetDefaultItem(1);
243 popupMenu
.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."), IDI_LOG
);
244 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."), IDI_LOG
);
247 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
249 params
.cbSize
= sizeof(TPMPARAMS
);
250 params
.rcExclude
= rect
;
251 int selection
= popupMenu
.TrackPopupMenuEx(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
| TPM_VERTICAL
, rect
.left
, rect
.top
, this, ¶ms
);
259 m_bPickedRefSet
= true;
260 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..");
266 m_bPickedRefSet
= true;
267 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...");
276 BOOL
CBrowseRefsDlg::OnInitDialog()
278 CResizableStandAloneDialog::OnInitDialog();
279 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
281 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
282 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
, 14, 14);
283 m_ctrlFilter
.SetInfoIcon(IDI_LOGFILTER
, 19, 19);
286 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
287 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
288 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, TOP_LEFT
);
289 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, TOP_LEFT
, TOP_CENTER
);
290 AddAnchor(IDC_BROWSE_REFS_BRANCHFILTER
, TOP_CENTER
, TOP_RIGHT
);
291 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
292 AddAnchor(IDC_INCLUDENESTEDREFS
, BOTTOM_LEFT
);
293 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
295 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle() | LVS_EX_INFOTIP
| LVS_EX_DOUBLEBUFFER
);
296 static UINT columnNames
[] = { IDS_BRANCHNAME
, IDS_TRACKEDBRANCH
, IDS_DATELASTCOMMIT
, IDS_LASTCOMMIT
, IDS_LASTAUTHOR
, IDS_HASH
, IDS_DESCRIPTION
};
297 static int columnWidths
[] = { 0, 0, 0, 300, 0, 0, 80 };
298 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Upstream
) | (1 << eCol_Date
) | (1 << eCol_Msg
) |
299 (1 << eCol_LastAuthor
) | (1 << eCol_Hash
) | (1 << eCol_Description
);
300 m_ListRefLeafs
.m_bAllowHiding
= false;
301 m_ListRefLeafs
.Init();
302 m_ListRefLeafs
.SetListContextMenuHandler([&](CPoint point
) {OnContextMenu_ListRefLeafs(point
); });
303 m_ListRefLeafs
.m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
304 m_ListRefLeafs
.m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, L
"BrowseRefs", _countof(columnNames
), columnWidths
);
305 m_bPickedRefSet
= false;
307 AddAnchor(IDOK
,BOTTOM_RIGHT
);
308 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
309 AddAnchor(IDC_CURRENTBRANCH
, BOTTOM_RIGHT
);
311 m_bIncludeNestedRefs
= !!m_regIncludeNestedRefs
;
314 Refresh(m_initialRef
);
316 EnableSaveRestore(L
"BrowseRefs");
318 CString sWindowTitle
;
319 GetWindowText(sWindowTitle
);
320 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
322 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
325 m_ListRefLeafs
.ModifyStyle(0, LVS_SINGLESEL
);
327 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_ALL
)));
328 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED
)));
329 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED
)));
330 m_cBranchFilter
.SetCurSel(0);
332 m_ListRefLeafs
.SetFocus();
336 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
338 int posSlash
=nameLeft
.Find('/');
343 nameLeft
.Empty();//Nothing left
347 nameSub
=nameLeft
.Left(posSlash
);
348 nameLeft
=nameLeft
.Mid(posSlash
+1);
350 if(nameSub
.IsEmpty())
353 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
356 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
357 nextNode
.m_csRefName
=nameSub
;
358 nextNode
.m_pParent
=this;
362 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
366 if (CStringUtils::EndsWith(partialRefName
, m_csRefName
))
368 //Match of leaf name. Try match on total name.
369 CString totalRefName
= GetRefName();
370 if (CStringUtils::EndsWith(totalRefName
, partialRefName
))
371 return this; //Also match. Found.
376 //Not a leaf. Search all nodes.
377 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
379 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
381 return pSubtree
; //Found
384 return nullptr; //Not found
387 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
389 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
390 //List ctrl selection?
391 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
394 return GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
))->GetRefName();
396 else if (pos
&& !pickFirstSelIfMultiSel
)
398 // at least one leaf is selected
401 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
403 CString ref
= GetListEntry(index
)->GetRefName();
404 if (CStringUtils::StartsWith(ref
, L
"refs/"))
405 ref
= ref
.Mid((int)wcslen(L
"refs/"));
406 if (CStringUtils::StartsWith(ref
, L
"heads/"))
407 ref
= ref
.Mid((int)wcslen(L
"heads/"));
414 //Tree ctrl selection?
415 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
417 return GetTreeEntry(hTree
)->GetRefName();
419 return CString();//None
422 void CBrowseRefsDlg::Refresh(CString selectRef
)
425 if (g_Git
.GetRemoteList(remotes
))
426 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get a list of remotes."), L
"TortoiseGit", MB_ICONERROR
);
428 if(!selectRef
.IsEmpty())
430 if (selectRef
== L
"HEAD")
432 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
435 selectRef
= L
"refs/heads/" + selectRef
;
439 selectRef
= GetSelectedRef(false, true);
441 m_RefTreeCtrl
.DeleteAllItems();
442 m_ListRefLeafs
.DeleteAllItems();
443 m_TreeRoot
.m_ShadowTree
.clear();
444 m_TreeRoot
.m_csRefName
= L
"refs";
445 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs");
446 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
449 MAP_REF_GITREVREFBROWSER refMap
;
450 if (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 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
470 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
471 GitRevRefBrowser ref
= iterRefMap
->second
;
473 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
474 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
475 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"refs/remotes/");
476 treeLeaf
.m_csSubject
= ref
.GetSubject();
477 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
478 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
479 treeLeaf
.m_csDescription
= ref
.m_Description
;
482 // always expand the tree first
483 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
485 // try exact match first
486 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
487 SelectRef(selectRef
, false);
490 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
494 CString newRefName
= GetFullRefName(refName
);
495 if(!newRefName
.IsEmpty())
496 refName
= newRefName
;
497 //else refName is not a valid ref. Try to select as good as possible.
499 if (!CStringUtils::StartsWith(refName
, L
"refs"))
500 return false; // Not a ref name
502 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
503 if (treeLeafHead
.m_hTree
)
505 //Not a leaf. Select tree node and return
506 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
510 if (!treeLeafHead
.m_pParent
)
511 return false; //Weird... should not occur.
513 //This is the current head.
514 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
516 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
518 auto pCurrShadowTree
= GetListEntry(indexPos
);
519 if(pCurrShadowTree
== &treeLeafHead
)
521 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
522 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
529 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
533 if (CStringUtils::StartsWith(refName
, L
"refs/"))
534 refName
= refName
.Mid((int)wcslen(L
"refs/"));
535 pTreePos
=&m_TreeRoot
;
537 if(refName
.IsEmpty())
538 return *pTreePos
;//Found leaf
540 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
543 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
544 ASSERT(!bCreateIfNotExist
);
548 if(!refName
.IsEmpty())
550 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
551 //Leafs are for the list control.
552 if (!pNextTree
->m_hTree
)
554 //New tree. Create node in control.
555 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
);
556 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
560 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
564 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
566 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
569 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
572 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
574 m_ListRefLeafs
.DeleteAllItems();
576 auto pTree
= GetTreeEntry(treeNode
);
580 FillListCtrlForShadowTree(pTree
,L
"",true);
581 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
582 m_ListRefLeafs
.AdjustColumnWidths();
585 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
590 m_ctrlFilter
.GetWindowText(filter
);
592 bool positive
= filter
[0] != '!';
594 filter
= filter
.Mid((int)wcslen(L
"!"));
595 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
596 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== L
"refs" && !pTree
->m_pParent
) && IsMatchFilter(pTree
, ref
, filter
, positive
))
598 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
600 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
601 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
602 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
603 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : L
"");
604 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
605 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
606 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
607 CString descrition
= pTree
->m_csDescription
;
608 descrition
.Replace(L
'\n', L
' ');
609 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
615 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
617 else if (!isFirstLevel
)
618 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
620 m_pListCtrlRoot
= pTree
;
621 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
623 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
628 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
629 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
631 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
635 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
, bool positive
)
637 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
640 msg
= msg
.MakeLower();
642 if (msg
.Find(filter
) >= 0)
646 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
648 CString msg
= pTree
->m_csSubject
;
649 msg
= msg
.MakeLower();
651 if (msg
.Find(filter
) >= 0)
655 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
657 CString msg
= pTree
->m_csAuthor
;
658 msg
= msg
.MakeLower();
660 if (msg
.Find(filter
) >= 0)
664 if (m_SelectedFilters
& LOGFILTER_REVS
)
666 CString msg
= pTree
->m_csRefHash
;
667 msg
= msg
.MakeLower();
669 if (msg
.Find(filter
) >= 0)
675 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
677 ASSERT(!leafs
.empty());
680 UINT mbIcon
=MB_ICONQUESTION
;
682 bool bIsRemoteBranch
= false;
683 bool bIsBranch
= false;
684 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
685 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
689 if(leafs
.size() == 1)
691 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
692 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)branchToDelete
);
694 //Check if branch is fully merged in HEAD
695 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), L
"HEAD"))
697 csMessage
+= L
"\r\n\r\n";
698 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
699 mbIcon
= MB_ICONWARNING
;
704 csMessage
+= L
"\r\n\r\n";
705 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
706 mbIcon
= MB_ICONWARNING
;
711 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
713 csMessage
+= L
"\r\n\r\n";
714 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
715 mbIcon
= MB_ICONWARNING
;
719 csMessage
+= L
"\r\n\r\n";
720 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
721 mbIcon
= MB_ICONWARNING
;
726 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
728 if(leafs
.size() == 1)
730 CString tagToDelete
= leafs
[0]->GetRefName().Mid((int)wcslen(L
"refs/tags/"));
731 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tagToDelete
);
734 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
737 return MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| mbIcon
) == IDYES
;
740 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
742 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
743 if(!DoDeleteRef((*i
)->GetRefName()))
748 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
750 bool bIsRemoteBranch
= false;
751 bool bIsBranch
= false;
752 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
755 bIsRemoteBranch
= true;
757 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
762 CString branchToDelete
= completeRefName
.Mid((int)wcslen(L
"refs/remotes/"));
763 CString remoteName
, remoteBranchToDelete
;
764 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
767 if (CAppUtils::IsSSHPutty())
768 CAppUtils::LaunchPAgent(this->GetSafeHwnd(), nullptr, &remoteName
);
770 CSysProgressDlg sysProgressDlg
;
771 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
772 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
773 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
774 sysProgressDlg
.SetShowProgressBar(false);
775 sysProgressDlg
.ShowModal(this, true);
778 list
.push_back(L
"refs/heads/" + remoteBranchToDelete
);
779 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
781 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete remote ref.", CGit::GIT_CMD_PUSH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
782 sysProgressDlg
.Stop();
786 sysProgressDlg
.Stop();
791 if (g_Git
.DeleteRef(completeRefName
))
793 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
797 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/tags/"))
799 if (g_Git
.DeleteRef(completeRefName
))
801 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
808 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
810 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
813 return pLeaf
->GetRefName();
817 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
819 if (pWndFrom
== &m_RefTreeCtrl
)
820 OnContextMenu_RefTreeCtrl(point
);
823 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
825 CPoint clientPoint
=point
;
826 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
828 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
830 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
832 VectorPShadowTree tree
;
833 ShowContextMenu(point
,hTreeItem
,tree
);
836 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
838 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
839 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
842 selectedLeafs
.push_back(GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
846 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
848 std::vector
<CShadowTree
*> selectedLeafs
;
849 GetSelectedLeaves(selectedLeafs
);
850 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
853 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
855 ASSERT(selectedLeafs
.size() == 2);
857 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
858 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
860 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
863 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
866 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
874 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
877 popupMenu
.CreatePopupMenu();
879 bool bAddSeparator
= false;
882 if(selectedLeafs
.size()==1)
884 bAddSeparator
= true;
886 bool bShowReflogOption
= false;
887 bool bShowFetchOption
= false;
888 bool bShowRenameOption
= false;
889 bool bShowCreateBranchOption
= false;
890 bool bShowEditBranchDescriptionOption
= false;
892 CString fetchFromCmd
;
894 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
896 bShowReflogOption
= true;
897 bShowRenameOption
= true;
898 bShowEditBranchDescriptionOption
= true;
900 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
902 bShowReflogOption
= true;
903 bShowFetchOption
= true;
904 bShowCreateBranchOption
= true;
906 CString remoteBranch
;
907 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
908 bShowFetchOption
= false;
910 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
915 popupMenu
.AppendMenuIcon(eCmd_Select
, IDS_SELECT
);
916 popupMenu
.AppendMenu(MF_SEPARATOR
);
918 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, IDS_MENULOG
, IDI_LOG
);
919 popupMenu
.SetDefaultItem(0, TRUE
);
920 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
921 if(bShowReflogOption
)
922 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, IDS_MENUREFLOG
, IDI_LOG
);
926 popupMenu
.AppendMenu(MF_SEPARATOR
);
927 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
930 popupMenu
.AppendMenu(MF_SEPARATOR
);
931 bAddSeparator
= false;
935 bAddSeparator
= true;
936 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_UPDATE
);
940 popupMenu
.AppendMenu(MF_SEPARATOR
);
942 bAddSeparator
= false;
946 if (selectedLeafs
[0]->GetRefName() != L
"refs/heads/" + g_Git
.GetCurrentBranch())
948 str
.Format(IDS_LOG_POPUP_MERGEREV
, (LPCTSTR
)g_Git
.GetCurrentBranch());
949 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
951 popupMenu
.AppendMenuIcon(eCmd_Switch
, IDS_SWITCH_TO_THIS
, IDI_SWITCH
);
952 popupMenu
.AppendMenu(MF_SEPARATOR
);
955 if(bShowCreateBranchOption
)
957 bAddSeparator
= true;
958 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, IDS_MENUBRANCH
, IDI_COPY
);
961 if (bShowEditBranchDescriptionOption
)
963 bAddSeparator
= true;
964 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, IDS_PROC_BROWSEREFS_EDITDESCRIPTION
, IDI_RENAME
);
966 if(bShowRenameOption
)
968 bAddSeparator
= true;
969 popupMenu
.AppendMenuIcon(eCmd_Rename
, IDS_PROC_BROWSEREFS_RENAME
, IDI_RENAME
);
972 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
975 popupMenu
.AppendMenu(MF_SEPARATOR
);
976 bAddSeparator
= true;
977 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
978 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
);
979 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
);
982 else if(selectedLeafs
.size() == 2)
984 bAddSeparator
= true;
985 popupMenu
.AppendMenuIcon(eCmd_Diff
, IDS_PROC_BROWSEREFS_COMPAREREFS
, IDI_DIFF
);
986 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
988 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."));
989 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
990 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."));
991 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
994 if(!selectedLeafs
.empty())
996 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
999 popupMenu
.AppendMenu(MF_SEPARATOR
);
1000 CString menuItemName
;
1001 if(selectedLeafs
.size() == 1)
1002 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
1004 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
1006 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
1007 bAddSeparator
= true;
1009 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1012 popupMenu
.AppendMenu(MF_SEPARATOR
);
1013 CString menuItemName
;
1014 if(selectedLeafs
.size() == 1)
1015 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1017 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1019 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1020 bAddSeparator
= true;
1022 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1025 popupMenu
.AppendMenu(MF_SEPARATOR
);
1026 CString menuItemName
;
1027 if(selectedLeafs
.size() == 1)
1028 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1030 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1032 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1033 bAddSeparator
= true;
1038 if (hTreePos
&& selectedLeafs
.empty())
1040 auto pTree
= GetTreeEntry(hTreePos
);
1041 if(pTree
->IsFrom(L
"refs/remotes"))
1044 popupMenu
.AppendMenu(MF_SEPARATOR
);
1045 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, IDS_PROC_BROWSEREFS_MANAGEREMOTES
, IDI_SETTINGS
);
1046 bAddSeparator
= true;
1047 if(selectedLeafs
.empty())
1049 CString remoteBranch
;
1050 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1052 int pos
= findVectorPosition(remotes
, remoteName
);
1056 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
1057 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_UPDATE
);
1059 temp
.LoadString(IDS_DELETEREMOTETAG
);
1060 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1063 bAddSeparator
= false;
1065 if(pTree
->IsFrom(L
"refs/heads"))
1068 popupMenu
.AppendMenu(MF_SEPARATOR
);
1070 temp
.LoadString(IDS_MENUBRANCH
);
1071 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1072 bAddSeparator
= false;
1074 if(pTree
->IsFrom(L
"refs/tags"))
1077 popupMenu
.AppendMenu(MF_SEPARATOR
);
1078 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, IDS_MENUTAG
, IDI_TAG
);
1079 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, IDS_PROC_BROWSEREFS_DELETEALLTAGS
, IDI_DELETE
);
1080 if (!remotes
.empty())
1082 popupMenu
.AppendMenu(MF_SEPARATOR
);
1084 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1087 temp
.Format(IDS_DELETEREMOTETAGON
, (LPCTSTR
)*it
);
1088 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1091 bAddSeparator
= false;
1095 popupMenu
.AppendMenu(MF_SEPARATOR
);
1096 popupMenu
.AppendMenuIcon(eCmd_Copy
, IDS_COPY_REF_NAMES
, IDI_COPYCLIP
);
1098 int selection
= popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
1099 switch ((eCmd
)(selection
& 0xFFFF))
1107 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)g_Git
.FixBranchName(selectedLeafs
[0]->GetRefName()));
1108 CAppUtils::RunTortoiseGitProc(sCmd
);
1111 case eCmd_ViewLogRange
:
1114 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."));
1115 CAppUtils::RunTortoiseGitProc(sCmd
);
1118 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1121 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."));
1122 CAppUtils::RunTortoiseGitProc(sCmd
);
1125 case eCmd_RepoBrowser
:
1126 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedLeafs
[0]->GetRefName());
1128 case eCmd_DeleteBranch
:
1129 case eCmd_DeleteRemoteBranch
:
1131 if(ConfirmDeleteRef(selectedLeafs
))
1132 DoDeleteRefs(selectedLeafs
);
1136 case eCmd_DeleteTag
:
1138 if(ConfirmDeleteRef(selectedLeafs
))
1139 DoDeleteRefs(selectedLeafs
);
1143 case eCmd_ShowReflog
:
1145 CRefLogDlg
refLogDlg(this);
1146 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1147 refLogDlg
.DoModal();
1152 CAppUtils::Fetch(GetSafeHwnd(), remoteName
);
1156 case eCmd_DeleteRemoteTag
:
1158 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1159 int remoteInx
= selection
>> 16;
1160 if (remoteInx
< 0 || (size_t)remoteInx
>= remotes
.size())
1162 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1163 deleteRemoteTagDlg
.DoModal();
1168 CString ref
= selectedLeafs
[0]->GetRefName();
1169 CAppUtils::Merge(GetSafeHwnd(), &ref
);
1174 CAppUtils::Switch(GetSafeHwnd(), selectedLeafs
[0]->GetRefName());
1179 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1181 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1184 case eCmd_AddRemote
:
1186 CAddRemoteDlg(this).DoModal();
1190 case eCmd_ManageRemotes
:
1192 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(), this).DoModal();
1193 // CSettingGitRemote W_Remotes(m_cmdPath);
1194 // W_Remotes.DoModal();
1198 case eCmd_CreateBranch
:
1200 CString
* commitHash
= nullptr;
1201 if (selectedLeafs
.size() == 1)
1202 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
1203 CAppUtils::CreateBranchTag(GetSafeHwnd(), false, commitHash
);
1207 case eCmd_CreateTag
:
1209 CAppUtils::CreateBranchTag(GetSafeHwnd(), true);
1213 case eCmd_DeleteAllTags
:
1215 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1217 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1218 selectedLeafs
.push_back(GetListEntry(i
));
1220 if (ConfirmDeleteRef(selectedLeafs
))
1221 DoDeleteRefs(selectedLeafs
);
1230 selectedLeafs
[0]->GetRefName() + L
"^{}",
1231 selectedLeafs
[1]->GetRefName() + L
"^{}");
1235 case eCmd_UnifiedDiff
:
1237 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1243 sCmd
.Format(L
"/command:showcompare /path:\"%s\" /revision1:%s^{} /revision2:%s", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)selectedLeafs
[0]->GetRefName(), (LPCTSTR
)GitRev::GetWorkingCopy());
1244 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1245 sCmd
+= L
" /alternative";
1247 CAppUtils::RunTortoiseGitProc(sCmd
);
1250 case eCmd_EditBranchDescription
:
1253 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1254 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1255 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1256 dlg
.m_bUseLogWidth
= true;
1257 if(dlg
.DoModal() == IDOK
)
1259 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1264 case eCmd_UpstreamDrop
:
1267 key
.Format(L
"branch.%s.remote", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1268 g_Git
.UnsetConfigValue(key
);
1269 key
.Format(L
"branch.%s.merge", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1270 g_Git
.UnsetConfigValue(key
);
1274 case eCmd_UpstreamSet
:
1276 CString newRef
= CBrowseRefsDlg::PickRef(false, L
"", gPickRef_Remote
, false);
1277 if (newRef
.IsEmpty() || newRef
.Find(L
"refs/remotes/") != 0)
1279 CString remote
, branch
;
1280 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1283 key
.Format(L
"branch.%s.remote", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1284 g_Git
.SetConfigValue(key
, remote
);
1285 key
.Format(L
"branch.%s.merge", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1286 g_Git
.SetConfigValue(key
, L
"refs/heads/" + branch
);
1294 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1297 auto index
= m_ListRefLeafs
.GetNextSelectedItem(pos
);
1299 sClipdata
+= L
"\r\n";
1300 sClipdata
+= m_ListRefLeafs
.GetItemText(index
, eCol_Name
);
1303 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1309 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1311 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1312 if(!(*i
)->IsFrom(from
))
1317 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1319 if (pMsg
->message
== WM_KEYDOWN
)
1321 switch (pMsg
->wParam
)
1325 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1327 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1329 PostMessage(WM_COMMAND, IDOK);
1337 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1339 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1341 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1353 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1355 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1356 m_ctrlFilter
.SetFocus();
1362 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1364 OnClickedCancelFilter(NULL
, NULL
);
1372 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1375 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1377 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1380 if(m_currSortCol
== pNMLV
->iSubItem
)
1381 m_currSortDesc
= !m_currSortDesc
;
1384 m_currSortCol
= pNMLV
->iSubItem
;
1385 m_currSortDesc
= false;
1388 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1389 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1391 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1394 void CBrowseRefsDlg::OnDestroy()
1396 if (!m_bPickedRefSet
)
1397 m_pickedRef
= GetSelectedRef(true, false);
1399 CResizableStandAloneDialog::OnDestroy();
1402 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1404 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1407 auto item
= GetListEntry(pNMListView
->iItem
);
1408 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1409 m_sLastSelected
= item
->GetRefName();
1414 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1418 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1428 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)g_Git
.FixBranchName(GetSelectedRef(true, false)));
1429 CAppUtils::RunTortoiseGitProc(sCmd
);
1432 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefsOrRange
)
1434 CBrowseRefsDlg
dlg(CString(), nullptr);
1436 if(initialRef
.IsEmpty())
1437 initialRef
= L
"HEAD";
1438 dlg
.m_bWantPick
= true;
1439 dlg
.m_initialRef
= initialRef
;
1440 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1441 dlg
.m_bPickOne
= !pickMultipleRefsOrRange
;
1443 if(dlg
.DoModal() != IDOK
)
1446 return dlg
.m_pickedRef
;
1449 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo
& refComboBox
, int pickRef_Kind
/* = gPickRef_All*/, int useShortName
/* = gPickRef_Head*/)
1452 refComboBox
.GetLBText(refComboBox
.GetCurSel(), origRef
);
1453 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1454 if(resultRef
.IsEmpty())
1459 CGit::REF_TYPE refType
;
1460 CString shortName
= CGit::GetShortName(resultRef
, &refType
);
1463 case CGit::REF_TYPE::LOCAL_BRANCH
:
1464 if (useShortName
& gPickRef_Head
)
1465 resultRef
= shortName
;
1467 case CGit::REF_TYPE::ANNOTATED_TAG
:
1468 case CGit::REF_TYPE::TAG
:
1469 if (useShortName
& gPickRef_Tag
)
1470 resultRef
= shortName
;
1472 case CGit::REMOTE_BRANCH
:
1473 if (useShortName
& gPickRef_Remote
)
1474 resultRef
= shortName
;
1479 CGit::StripRefName(resultRef
);
1481 refComboBox
.AddString(resultRef
);
1486 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1488 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1491 if (!pDispInfo
->item
.pszText
)
1492 return; //User canceled changing
1494 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1495 if(!pTree
->IsFrom(L
"refs/heads/"))
1497 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1501 CString selectedTreeRef
;
1502 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1505 auto pTree2
= GetTreeEntry(hTree
);
1506 selectedTreeRef
= pTree2
->GetRefName();
1509 CString origName
= pTree
->GetRefName().Mid((int)wcslen(L
"refs/heads/"));
1512 if (m_pListCtrlRoot
)
1513 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1514 newName
+= pDispInfo
->item
.pszText
;
1516 if (!CStringUtils::StartsWith(newName
, L
"refs/heads/"))
1518 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1522 CString newNameTrunced
= newName
.Mid((int)wcslen(L
"refs/heads/"));
1525 if (g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
'"', &errorMsg
, CP_UTF8
) != 0)
1527 MessageBox(errorMsg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1530 //Do as if it failed to rename. Let Refresh() do the job.
1533 Refresh(selectedTreeRef
);
1535 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1537 // AfxMessageBox(W_csPopup);
1540 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1542 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1545 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1546 if(!pTree
->IsFrom(L
"refs/heads/"))
1548 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1553 void CBrowseRefsDlg::OnEnChangeEditFilter()
1555 SetTimer(IDT_FILTER
, 1000, nullptr);
1558 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1560 if (nIDEvent
== IDT_FILTER
)
1562 KillTimer(IDT_FILTER
);
1563 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1566 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1569 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1571 // FIXME: x64 version would get this function called with unexpected parameters.
1575 RECT
* rect
= (LPRECT
)lParam
;
1578 point
= CPoint(rect
->left
, rect
->bottom
);
1579 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1581 if (popup
.CreatePopupMenu())
1583 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1584 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1586 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1587 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1589 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1590 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1592 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1593 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1595 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1596 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1598 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1601 if (selection
== LOGFILTER_TOGGLE
)
1602 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1604 m_SelectedFilters
^= selection
;
1606 SetTimer(IDT_FILTER
, 1000, nullptr);
1612 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1614 KillTimer(LOGFILTER_TIMER
);
1615 m_ctrlFilter
.SetWindowText(L
"");
1616 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1620 void CBrowseRefsDlg::SetFilterCueText()
1622 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1625 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1626 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1628 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1630 if (!CStringUtils::EndsWith(temp
, L
' '))
1632 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1635 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1637 if (!CStringUtils::EndsWith(temp
, L
' '))
1639 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1642 if (m_SelectedFilters
& LOGFILTER_REVS
)
1644 if (!CStringUtils::EndsWith(temp
, L
' '))
1646 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1649 // to make the cue banner text appear more to the right of the edit control
1651 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1654 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1656 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1657 m_bPickedRefSet
= true;
1661 void CBrowseRefsDlg::UpdateInfoLabel()
1664 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1665 SetDlgItemText(IDC_INFOLABEL
, temp
);
1668 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1671 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;
1675 CShadowTree
* CBrowseRefsDlg::GetListEntry(int index
)
1677 auto entry
= reinterpret_cast<CShadowTree
*>(m_ListRefLeafs
.GetItemData(index
));
1682 CShadowTree
* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem
)
1684 auto entry
= reinterpret_cast<CShadowTree
*>(m_RefTreeCtrl
.GetItemData(treeItem
));
1690 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()