1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2017 - 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/"))
46 else if (CStringUtils::StartsWith(ref
, 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)
171 // get short/long datetime setting from registry
172 DWORD RegUseShortDateFormat
= CRegDWORD(L
"Software\\TortoiseGit\\LogDateFormat", TRUE
);
173 if (RegUseShortDateFormat
)
174 m_DateFormat
= DATE_SHORTDATE
;
176 m_DateFormat
= DATE_LONGDATE
;
177 // get relative time display setting from registry
178 DWORD regRelativeTimes
= CRegDWORD(L
"Software\\TortoiseGit\\RelativeTimes", FALSE
);
179 m_bRelativeTimes
= (regRelativeTimes
!= 0);
181 m_regIncludeNestedRefs
= CRegDWORD(L
"Software\\TortoiseGit\\RefBrowserIncludeNestedRefs", TRUE
);
183 m_currSortCol
= m_regCurrSortCol
;
184 m_currSortDesc
= m_regCurrSortDesc
== TRUE
;
187 CBrowseRefsDlg::~CBrowseRefsDlg()
189 m_regCurrSortCol
= m_currSortCol
;
190 m_regCurrSortDesc
= m_currSortDesc
;
193 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
195 CDialog::DoDataExchange(pDX
);
196 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
197 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
198 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
199 DDX_Check(pDX
, IDC_INCLUDENESTEDREFS
, m_bIncludeNestedRefs
);
200 DDX_Control(pDX
, IDC_BROWSE_REFS_BRANCHFILTER
, m_cBranchFilter
);
204 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
205 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
206 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
208 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
210 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
211 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnItemChangedListRefLeafs
)
212 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
213 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
214 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
215 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
216 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED
, OnClickedCancelFilter
)
218 ON_BN_CLICKED(IDC_CURRENTBRANCH
, OnBnClickedCurrentbranch
)
219 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS
, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs
)
220 ON_CBN_SELCHANGE(IDC_BROWSE_REFS_BRANCHFILTER
, &CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter
)
224 // CBrowseRefsDlg message handlers
226 void CBrowseRefsDlg::OnBnClickedOk()
228 if (m_bPickOne
|| m_ListRefLeafs
.GetSelectedCount() != 2)
235 popupMenu
.CreatePopupMenu();
237 std::vector
<CShadowTree
*> selectedLeafs
;
238 GetSelectedLeaves(selectedLeafs
);
240 popupMenu
.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG
);
241 popupMenu
.SetDefaultItem(1);
242 popupMenu
.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."), IDI_LOG
);
243 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."), IDI_LOG
);
246 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
247 int selection
= popupMenu
.TrackPopupMenuEx(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, rect
.left
, rect
.top
, this, nullptr);
255 m_bPickedRefSet
= true;
256 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..");
262 m_bPickedRefSet
= true;
263 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"...");
272 BOOL
CBrowseRefsDlg::OnInitDialog()
274 CResizableStandAloneDialog::OnInitDialog();
275 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
277 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
278 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
, 14, 14);
279 m_ctrlFilter
.SetInfoIcon(IDI_LOGFILTER
, 19, 19);
282 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
283 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
284 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, TOP_LEFT
);
285 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, TOP_LEFT
, TOP_CENTER
);
286 AddAnchor(IDC_BROWSE_REFS_BRANCHFILTER
, TOP_CENTER
, TOP_RIGHT
);
287 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
288 AddAnchor(IDC_INCLUDENESTEDREFS
, BOTTOM_LEFT
);
289 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
291 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle() | LVS_EX_INFOTIP
| LVS_EX_DOUBLEBUFFER
);
292 static UINT columnNames
[] = { IDS_BRANCHNAME
, IDS_TRACKEDBRANCH
, IDS_DATELASTCOMMIT
, IDS_LASTCOMMIT
, IDS_LASTAUTHOR
, IDS_HASH
, IDS_DESCRIPTION
};
293 static int columnWidths
[] = { 0, 0, 0, 300, 0, 0, 80 };
294 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Upstream
) | (1 << eCol_Date
) | (1 << eCol_Msg
) |
295 (1 << eCol_LastAuthor
) | (1 << eCol_Hash
) | (1 << eCol_Description
);
296 m_ListRefLeafs
.m_bAllowHiding
= false;
297 m_ListRefLeafs
.Init();
298 m_ListRefLeafs
.SetListContextMenuHandler([&](CPoint point
) {OnContextMenu_ListRefLeafs(point
); });
299 m_ListRefLeafs
.m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
300 m_ListRefLeafs
.m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, L
"BrowseRefs", _countof(columnNames
), columnWidths
);
301 m_bPickedRefSet
= false;
303 AddAnchor(IDOK
,BOTTOM_RIGHT
);
304 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
305 AddAnchor(IDC_CURRENTBRANCH
, BOTTOM_RIGHT
);
307 m_bIncludeNestedRefs
= !!m_regIncludeNestedRefs
;
310 Refresh(m_initialRef
);
312 EnableSaveRestore(L
"BrowseRefs");
314 CString sWindowTitle
;
315 GetWindowText(sWindowTitle
);
316 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
318 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
321 m_ListRefLeafs
.ModifyStyle(0, LVS_SINGLESEL
);
323 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_ALL
)));
324 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYMERGED
)));
325 m_cBranchFilter
.AddString(CString(MAKEINTRESOURCE(IDS_BROWSE_REFS_ONLYUNMERGED
)));
326 m_cBranchFilter
.SetCurSel(0);
327 if (CAppUtils::GetMsysgitVersion() < 0x02070000)
329 m_cBranchFilter
.EnableWindow(FALSE
);
331 temp
.Format(IDS_GITVER_REQUIRED
, L
"\"git for-each-ref --merged\"", L
"2.7");
332 m_tooltips
.AddTool(IDC_BROWSE_REFS_BRANCHFILTER
, temp
);
335 m_ListRefLeafs
.SetFocus();
339 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
341 int posSlash
=nameLeft
.Find('/');
346 nameLeft
.Empty();//Nothing left
350 nameSub
=nameLeft
.Left(posSlash
);
351 nameLeft
=nameLeft
.Mid(posSlash
+1);
353 if(nameSub
.IsEmpty())
356 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
359 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
360 nextNode
.m_csRefName
=nameSub
;
361 nextNode
.m_pParent
=this;
365 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
369 if (CStringUtils::EndsWith(partialRefName
, m_csRefName
))
371 //Match of leaf name. Try match on total name.
372 CString totalRefName
= GetRefName();
373 if (CStringUtils::EndsWith(totalRefName
, partialRefName
))
374 return this; //Also match. Found.
379 //Not a leaf. Search all nodes.
380 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
382 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
384 return pSubtree
; //Found
387 return nullptr; //Not found
390 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
392 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
393 //List ctrl selection?
394 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
397 return GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
))->GetRefName();
399 else if (pos
&& !pickFirstSelIfMultiSel
)
401 // at least one leaf is selected
404 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
406 CString ref
= GetListEntry(index
)->GetRefName();
407 if (CStringUtils::StartsWith(ref
, L
"refs/"))
409 if (CStringUtils::StartsWith(ref
, L
"heads/"))
417 //Tree ctrl selection?
418 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
420 return GetTreeEntry(hTree
)->GetRefName();
422 return CString();//None
425 void CBrowseRefsDlg::Refresh(CString selectRef
)
428 if (g_Git
.GetRemoteList(remotes
))
429 MessageBox(CGit::GetLibGit2LastErr(L
"Could not get a list of remotes."), L
"TortoiseGit", MB_ICONERROR
);
431 if(!selectRef
.IsEmpty())
433 if (selectRef
== L
"HEAD")
435 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
438 selectRef
= L
"refs/heads/" + selectRef
;
442 selectRef
= GetSelectedRef(false, true);
444 m_RefTreeCtrl
.DeleteAllItems();
445 m_ListRefLeafs
.DeleteAllItems();
446 m_TreeRoot
.m_ShadowTree
.clear();
447 m_TreeRoot
.m_csRefName
= L
"refs";
448 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs");
449 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
452 MAP_REF_GITREVREFBROWSER refMap
;
453 if (GitRevRefBrowser::GetGitRevRefMap(refMap
, m_cBranchFilter
.GetCurSel(), err
, [&](const CString
& refName
)
455 //Use ref based on m_pickRef_Kind
456 if (CStringUtils::StartsWith(refName
, L
"refs/heads/") && !(m_pickRef_Kind
& gPickRef_Head
))
458 if (CStringUtils::StartsWith(refName
, L
"refs/tags/") && !(m_pickRef_Kind
& gPickRef_Tag
))
460 if (CStringUtils::StartsWith(refName
, L
"refs/remotes/") && !(m_pickRef_Kind
& gPickRef_Remote
))
462 if (m_pickRef_Kind
== gPickRef_Remote
&& !CStringUtils::StartsWith(refName
, L
"refs/remotes/")) // do not show refs/stash if only remote branches are requested
467 MessageBox(L
"Get refs failed:" + err
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
471 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
473 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
474 GitRevRefBrowser ref
= iterRefMap
->second
;
476 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
477 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
478 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"refs/remotes/");
479 treeLeaf
.m_csSubject
= ref
.GetSubject();
480 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
481 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
482 treeLeaf
.m_csDescription
= ref
.m_Description
;
485 // always expand the tree first
486 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
488 // try exact match first
489 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
490 SelectRef(selectRef
, false);
493 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
497 CString newRefName
= GetFullRefName(refName
);
498 if(!newRefName
.IsEmpty())
499 refName
= newRefName
;
500 //else refName is not a valid ref. Try to select as good as possible.
502 if (!CStringUtils::StartsWith(refName
, L
"refs"))
503 return false; // Not a ref name
505 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
506 if (treeLeafHead
.m_hTree
)
508 //Not a leaf. Select tree node and return
509 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
513 if (!treeLeafHead
.m_pParent
)
514 return false; //Weird... should not occur.
516 //This is the current head.
517 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
519 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
521 auto pCurrShadowTree
= GetListEntry(indexPos
);
522 if(pCurrShadowTree
== &treeLeafHead
)
524 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
525 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
532 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
536 if (CStringUtils::StartsWith(refName
, L
"refs/"))
537 refName
=refName
.Mid(5);
538 pTreePos
=&m_TreeRoot
;
540 if(refName
.IsEmpty())
541 return *pTreePos
;//Found leaf
543 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
546 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
547 ASSERT(!bCreateIfNotExist
);
551 if(!refName
.IsEmpty())
553 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
554 //Leafs are for the list control.
555 if (!pNextTree
->m_hTree
)
557 //New tree. Create node in control.
558 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
);
559 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
563 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
567 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
569 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
572 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
575 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
577 m_ListRefLeafs
.DeleteAllItems();
579 auto pTree
= GetTreeEntry(treeNode
);
583 FillListCtrlForShadowTree(pTree
,L
"",true);
584 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
585 m_ListRefLeafs
.AdjustColumnWidths();
588 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
593 m_ctrlFilter
.GetWindowText(filter
);
595 bool positive
= filter
[0] != '!';
597 filter
= filter
.Mid(1);
598 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
599 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== L
"refs" && !pTree
->m_pParent
) && IsMatchFilter(pTree
, ref
, filter
, positive
))
601 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
603 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
604 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
605 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
606 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : L
"");
607 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
608 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
609 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
610 CString descrition
= pTree
->m_csDescription
;
611 descrition
.Replace(L
'\n', L
' ');
612 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
618 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
620 else if (!isFirstLevel
)
621 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
623 m_pListCtrlRoot
= pTree
;
624 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
626 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
631 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
632 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
634 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
638 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
, bool positive
)
640 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
643 msg
= msg
.MakeLower();
645 if (msg
.Find(filter
) >= 0)
649 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
651 CString msg
= pTree
->m_csSubject
;
652 msg
= msg
.MakeLower();
654 if (msg
.Find(filter
) >= 0)
658 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
660 CString msg
= pTree
->m_csAuthor
;
661 msg
= msg
.MakeLower();
663 if (msg
.Find(filter
) >= 0)
667 if (m_SelectedFilters
& LOGFILTER_REVS
)
669 CString msg
= pTree
->m_csRefHash
;
670 msg
= msg
.MakeLower();
672 if (msg
.Find(filter
) >= 0)
678 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
680 ASSERT(!leafs
.empty());
683 UINT mbIcon
=MB_ICONQUESTION
;
685 bool bIsRemoteBranch
= false;
686 bool bIsBranch
= false;
687 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
688 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
692 if(leafs
.size() == 1)
694 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
695 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)branchToDelete
);
697 //Check if branch is fully merged in HEAD
698 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), L
"HEAD"))
700 csMessage
+= L
"\r\n\r\n";
701 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
702 mbIcon
= MB_ICONWARNING
;
707 csMessage
+= L
"\r\n\r\n";
708 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
709 mbIcon
= MB_ICONWARNING
;
714 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
716 csMessage
+= L
"\r\n\r\n";
717 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
718 mbIcon
= MB_ICONWARNING
;
722 csMessage
+= L
"\r\n\r\n";
723 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
724 mbIcon
= MB_ICONWARNING
;
729 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
731 if(leafs
.size() == 1)
733 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
734 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tagToDelete
);
737 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
740 return MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| mbIcon
) == IDYES
;
743 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
745 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
746 if(!DoDeleteRef((*i
)->GetRefName()))
751 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
753 bool bIsRemoteBranch
= false;
754 bool bIsBranch
= false;
755 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
758 bIsRemoteBranch
= true;
760 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
765 CString branchToDelete
= completeRefName
.Mid(13);
766 CString remoteName
, remoteBranchToDelete
;
767 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
770 if (CAppUtils::IsSSHPutty())
771 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
773 CSysProgressDlg sysProgressDlg
;
774 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
775 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
776 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
777 sysProgressDlg
.SetShowProgressBar(false);
778 sysProgressDlg
.ShowModal(this, true);
781 list
.push_back(L
"refs/heads/" + remoteBranchToDelete
);
782 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
784 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete remote ref.", CGit::GIT_CMD_PUSH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
785 sysProgressDlg
.Stop();
789 sysProgressDlg
.Stop();
794 if (g_Git
.DeleteRef(completeRefName
))
796 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
800 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/tags/"))
802 if (g_Git
.DeleteRef(completeRefName
))
804 MessageBox(g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
811 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
813 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
816 return pLeaf
->GetRefName();
820 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
822 if (pWndFrom
== &m_RefTreeCtrl
)
823 OnContextMenu_RefTreeCtrl(point
);
826 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
828 CPoint clientPoint
=point
;
829 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
831 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
833 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
835 VectorPShadowTree tree
;
836 ShowContextMenu(point
,hTreeItem
,tree
);
839 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
841 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
842 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
845 selectedLeafs
.push_back(GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
849 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
851 std::vector
<CShadowTree
*> selectedLeafs
;
852 GetSelectedLeaves(selectedLeafs
);
853 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
856 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
858 ASSERT(selectedLeafs
.size() == 2);
860 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
861 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
863 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
866 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
869 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
877 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
880 popupMenu
.CreatePopupMenu();
882 bool bAddSeparator
= false;
885 if(selectedLeafs
.size()==1)
887 bAddSeparator
= true;
889 bool bShowReflogOption
= false;
890 bool bShowFetchOption
= false;
891 bool bShowRenameOption
= false;
892 bool bShowCreateBranchOption
= false;
893 bool bShowEditBranchDescriptionOption
= false;
895 CString fetchFromCmd
;
897 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
899 bShowReflogOption
= true;
900 bShowRenameOption
= true;
901 bShowEditBranchDescriptionOption
= true;
903 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
905 bShowReflogOption
= true;
906 bShowFetchOption
= true;
907 bShowCreateBranchOption
= true;
909 CString remoteBranch
;
910 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
911 bShowFetchOption
= false;
913 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
916 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, IDS_MENULOG
, IDI_LOG
);
917 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
918 if(bShowReflogOption
)
919 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, IDS_MENUREFLOG
, IDI_LOG
);
923 popupMenu
.AppendMenu(MF_SEPARATOR
);
924 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
927 popupMenu
.AppendMenu(MF_SEPARATOR
);
928 bAddSeparator
= false;
932 bAddSeparator
= true;
933 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
937 popupMenu
.AppendMenu(MF_SEPARATOR
);
939 bAddSeparator
= false;
943 if (selectedLeafs
[0]->GetRefName() != L
"refs/heads/" + g_Git
.GetCurrentBranch())
945 str
.Format(IDS_LOG_POPUP_MERGEREV
, (LPCTSTR
)g_Git
.GetCurrentBranch());
946 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
948 popupMenu
.AppendMenuIcon(eCmd_Switch
, IDS_SWITCH_TO_THIS
, IDI_SWITCH
);
949 popupMenu
.AppendMenu(MF_SEPARATOR
);
952 if(bShowCreateBranchOption
)
954 bAddSeparator
= true;
955 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, IDS_MENUBRANCH
, IDI_COPY
);
958 if (bShowEditBranchDescriptionOption
)
960 bAddSeparator
= true;
961 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, IDS_PROC_BROWSEREFS_EDITDESCRIPTION
, IDI_RENAME
);
963 if(bShowRenameOption
)
965 bAddSeparator
= true;
966 popupMenu
.AppendMenuIcon(eCmd_Rename
, IDS_PROC_BROWSEREFS_RENAME
, IDI_RENAME
);
969 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
972 popupMenu
.AppendMenu(MF_SEPARATOR
);
973 bAddSeparator
= true;
974 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
975 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
);
976 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
);
979 else if(selectedLeafs
.size() == 2)
981 bAddSeparator
= true;
982 popupMenu
.AppendMenuIcon(eCmd_Diff
, IDS_PROC_BROWSEREFS_COMPAREREFS
, IDI_DIFF
);
983 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
985 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."));
986 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
987 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."));
988 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
991 if(!selectedLeafs
.empty())
993 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
996 popupMenu
.AppendMenu(MF_SEPARATOR
);
997 CString menuItemName
;
998 if(selectedLeafs
.size() == 1)
999 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
1001 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
1003 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
1004 bAddSeparator
= true;
1006 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1009 popupMenu
.AppendMenu(MF_SEPARATOR
);
1010 CString menuItemName
;
1011 if(selectedLeafs
.size() == 1)
1012 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1014 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1016 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1017 bAddSeparator
= true;
1019 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1022 popupMenu
.AppendMenu(MF_SEPARATOR
);
1023 CString menuItemName
;
1024 if(selectedLeafs
.size() == 1)
1025 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1027 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1029 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1030 bAddSeparator
= true;
1035 if (hTreePos
&& selectedLeafs
.empty())
1037 auto pTree
= GetTreeEntry(hTreePos
);
1038 if(pTree
->IsFrom(L
"refs/remotes"))
1041 popupMenu
.AppendMenu(MF_SEPARATOR
);
1042 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, IDS_PROC_BROWSEREFS_MANAGEREMOTES
, IDI_SETTINGS
);
1043 bAddSeparator
= true;
1044 if(selectedLeafs
.empty())
1046 CString remoteBranch
;
1047 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1049 int pos
= findVectorPosition(remotes
, remoteName
);
1053 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
1054 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_PULL
);
1056 temp
.LoadString(IDS_DELETEREMOTETAG
);
1057 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1060 bAddSeparator
= false;
1062 if(pTree
->IsFrom(L
"refs/heads"))
1065 popupMenu
.AppendMenu(MF_SEPARATOR
);
1067 temp
.LoadString(IDS_MENUBRANCH
);
1068 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1069 bAddSeparator
= false;
1071 if(pTree
->IsFrom(L
"refs/tags"))
1074 popupMenu
.AppendMenu(MF_SEPARATOR
);
1075 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, IDS_MENUTAG
, IDI_TAG
);
1076 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, IDS_PROC_BROWSEREFS_DELETEALLTAGS
, IDI_DELETE
);
1077 if (!remotes
.empty())
1079 popupMenu
.AppendMenu(MF_SEPARATOR
);
1081 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1084 temp
.Format(IDS_DELETEREMOTETAGON
, (LPCTSTR
)*it
);
1085 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1088 bAddSeparator
= false;
1092 popupMenu
.AppendMenu(MF_SEPARATOR
);
1093 popupMenu
.AppendMenuIcon(eCmd_Copy
, IDS_COPY_REF_NAMES
, IDI_COPYCLIP
);
1095 int selection
= popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
1096 switch ((eCmd
)(selection
& 0xFFFF))
1101 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)g_Git
.FixBranchName(selectedLeafs
[0]->GetRefName()));
1102 CAppUtils::RunTortoiseGitProc(sCmd
);
1105 case eCmd_ViewLogRange
:
1108 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
".."));
1109 CAppUtils::RunTortoiseGitProc(sCmd
);
1112 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1115 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, L
"..."));
1116 CAppUtils::RunTortoiseGitProc(sCmd
);
1119 case eCmd_RepoBrowser
:
1120 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedLeafs
[0]->GetRefName());
1122 case eCmd_DeleteBranch
:
1123 case eCmd_DeleteRemoteBranch
:
1125 if(ConfirmDeleteRef(selectedLeafs
))
1126 DoDeleteRefs(selectedLeafs
);
1130 case eCmd_DeleteTag
:
1132 if(ConfirmDeleteRef(selectedLeafs
))
1133 DoDeleteRefs(selectedLeafs
);
1137 case eCmd_ShowReflog
:
1139 CRefLogDlg
refLogDlg(this);
1140 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1141 refLogDlg
.DoModal();
1146 CAppUtils::Fetch(remoteName
);
1150 case eCmd_DeleteRemoteTag
:
1152 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1153 int remoteInx
= selection
>> 16;
1154 if (remoteInx
< 0 || (size_t)remoteInx
>= remotes
.size())
1156 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1157 deleteRemoteTagDlg
.DoModal();
1162 CString ref
= selectedLeafs
[0]->GetRefName();
1163 CAppUtils::Merge(&ref
);
1168 CAppUtils::Switch(selectedLeafs
[0]->GetRefName());
1173 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1175 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1178 case eCmd_AddRemote
:
1180 CAddRemoteDlg(this).DoModal();
1184 case eCmd_ManageRemotes
:
1186 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(), this).DoModal();
1187 // CSettingGitRemote W_Remotes(m_cmdPath);
1188 // W_Remotes.DoModal();
1192 case eCmd_CreateBranch
:
1194 CString
* commitHash
= nullptr;
1195 if (selectedLeafs
.size() == 1)
1196 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
1197 CAppUtils::CreateBranchTag(false, commitHash
);
1201 case eCmd_CreateTag
:
1203 CAppUtils::CreateBranchTag(true);
1207 case eCmd_DeleteAllTags
:
1209 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1211 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1212 selectedLeafs
.push_back(GetListEntry(i
));
1214 if (ConfirmDeleteRef(selectedLeafs
))
1215 DoDeleteRefs(selectedLeafs
);
1224 selectedLeafs
[0]->GetRefName() + L
"^{}",
1225 selectedLeafs
[1]->GetRefName() + L
"^{}");
1229 case eCmd_UnifiedDiff
:
1231 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1237 sCmd
.Format(L
"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)selectedLeafs
[0]->GetRefName(), (LPCTSTR
)GitRev::GetWorkingCopy());
1238 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1239 sCmd
+= L
" /alternative";
1241 CAppUtils::RunTortoiseGitProc(sCmd
);
1244 case eCmd_EditBranchDescription
:
1247 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1248 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1249 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1250 dlg
.m_bUseLogWidth
= true;
1251 if(dlg
.DoModal() == IDOK
)
1253 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1258 case eCmd_UpstreamDrop
:
1261 key
.Format(L
"branch.%s.remote", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1262 g_Git
.UnsetConfigValue(key
);
1263 key
.Format(L
"branch.%s.merge", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1264 g_Git
.UnsetConfigValue(key
);
1268 case eCmd_UpstreamSet
:
1270 CString newRef
= CBrowseRefsDlg::PickRef(false, L
"", gPickRef_Remote
, false);
1271 if (newRef
.IsEmpty() || newRef
.Find(L
"refs/remotes/") != 0)
1273 CString remote
, branch
;
1274 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1277 key
.Format(L
"branch.%s.remote", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1278 g_Git
.SetConfigValue(key
, remote
);
1279 key
.Format(L
"branch.%s.merge", (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1280 g_Git
.SetConfigValue(key
, L
"refs/heads/" + branch
);
1288 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1291 auto index
= m_ListRefLeafs
.GetNextSelectedItem(pos
);
1293 sClipdata
+= L
"\r\n";
1294 sClipdata
+= m_ListRefLeafs
.GetItemText(index
, eCol_Name
);
1297 CStringUtils::WriteAsciiStringToClipboard(sClipdata
, GetSafeHwnd());
1303 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1305 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1306 if(!(*i
)->IsFrom(from
))
1311 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1313 if (pMsg
->message
== WM_KEYDOWN
)
1315 switch (pMsg
->wParam
)
1319 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1321 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1323 PostMessage(WM_COMMAND, IDOK);
1331 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1333 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1335 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1347 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1349 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1350 m_ctrlFilter
.SetFocus();
1356 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1358 OnClickedCancelFilter(NULL
, NULL
);
1366 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1369 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1371 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1374 if(m_currSortCol
== pNMLV
->iSubItem
)
1375 m_currSortDesc
= !m_currSortDesc
;
1378 m_currSortCol
= pNMLV
->iSubItem
;
1379 m_currSortDesc
= false;
1382 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1383 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1385 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1388 void CBrowseRefsDlg::OnDestroy()
1390 if (!m_bPickedRefSet
)
1391 m_pickedRef
= GetSelectedRef(true, false);
1393 CResizableStandAloneDialog::OnDestroy();
1396 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1398 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1401 auto item
= GetListEntry(pNMListView
->iItem
);
1402 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1403 m_sLastSelected
= item
->GetRefName();
1408 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1412 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1417 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefsOrRange
)
1419 CBrowseRefsDlg
dlg(CString(), nullptr);
1421 if(initialRef
.IsEmpty())
1422 initialRef
= L
"HEAD";
1423 dlg
.m_initialRef
= initialRef
;
1424 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1425 dlg
.m_bPickOne
= !pickMultipleRefsOrRange
;
1427 if(dlg
.DoModal() != IDOK
)
1430 return dlg
.m_pickedRef
;
1433 bool CBrowseRefsDlg::PickRefForCombo(CHistoryCombo
& refComboBox
, int pickRef_Kind
/* = gPickRef_All*/, int useShortName
/* = gPickRef_Head*/)
1436 refComboBox
.GetLBText(refComboBox
.GetCurSel(), origRef
);
1437 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1438 if(resultRef
.IsEmpty())
1443 CGit::REF_TYPE refType
;
1444 CString shortName
= CGit::GetShortName(resultRef
, &refType
);
1447 case CGit::REF_TYPE::LOCAL_BRANCH
:
1448 if (useShortName
& gPickRef_Head
)
1449 resultRef
= shortName
;
1451 case CGit::REF_TYPE::ANNOTATED_TAG
:
1452 case CGit::REF_TYPE::TAG
:
1453 if (useShortName
& gPickRef_Tag
)
1454 resultRef
= shortName
;
1456 case CGit::REMOTE_BRANCH
:
1457 if (useShortName
& gPickRef_Remote
)
1458 resultRef
= shortName
;
1463 CGit::StripRefName(resultRef
);
1465 refComboBox
.AddString(resultRef
);
1470 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1472 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1475 if (!pDispInfo
->item
.pszText
)
1476 return; //User canceled changing
1478 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1479 if(!pTree
->IsFrom(L
"refs/heads/"))
1481 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1485 CString selectedTreeRef
;
1486 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1489 auto pTree2
= GetTreeEntry(hTree
);
1490 selectedTreeRef
= pTree2
->GetRefName();
1493 CString origName
= pTree
->GetRefName().Mid(11);
1496 if (m_pListCtrlRoot
)
1497 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1498 newName
+= pDispInfo
->item
.pszText
;
1500 if (!CStringUtils::StartsWith(newName
, L
"refs/heads/"))
1502 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1506 CString newNameTrunced
= newName
.Mid(11);
1509 if (g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
'"', &errorMsg
, CP_UTF8
) != 0)
1511 MessageBox(errorMsg
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1514 //Do as if it failed to rename. Let Refresh() do the job.
1517 Refresh(selectedTreeRef
);
1519 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1521 // AfxMessageBox(W_csPopup);
1524 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1526 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1529 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1530 if(!pTree
->IsFrom(L
"refs/heads/"))
1532 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1537 void CBrowseRefsDlg::OnEnChangeEditFilter()
1539 SetTimer(IDT_FILTER
, 1000, nullptr);
1542 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1544 if (nIDEvent
== IDT_FILTER
)
1546 KillTimer(IDT_FILTER
);
1547 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1550 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1553 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1555 // FIXME: x64 version would get this function called with unexpected parameters.
1559 RECT
* rect
= (LPRECT
)lParam
;
1562 point
= CPoint(rect
->left
, rect
->bottom
);
1563 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1565 if (popup
.CreatePopupMenu())
1567 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1568 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1570 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1571 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1573 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1574 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1576 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1577 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1579 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1580 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1582 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1585 if (selection
== LOGFILTER_TOGGLE
)
1586 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1588 m_SelectedFilters
^= selection
;
1590 SetTimer(IDT_FILTER
, 1000, nullptr);
1596 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1598 KillTimer(LOGFILTER_TIMER
);
1599 m_ctrlFilter
.SetWindowText(L
"");
1600 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1604 void CBrowseRefsDlg::SetFilterCueText()
1606 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1609 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1610 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1612 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1614 if (!CStringUtils::EndsWith(temp
, L
' '))
1616 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1619 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1621 if (!CStringUtils::EndsWith(temp
, L
' '))
1623 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1626 if (m_SelectedFilters
& LOGFILTER_REVS
)
1628 if (!CStringUtils::EndsWith(temp
, L
' '))
1630 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1633 // to make the cue banner text appear more to the right of the edit control
1635 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1638 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1640 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1641 m_bPickedRefSet
= true;
1645 void CBrowseRefsDlg::UpdateInfoLabel()
1648 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1649 SetDlgItemText(IDC_INFOLABEL
, temp
);
1652 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1655 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;
1659 CShadowTree
* CBrowseRefsDlg::GetListEntry(int index
)
1661 auto entry
= reinterpret_cast<CShadowTree
*>(m_ListRefLeafs
.GetItemData(index
));
1666 CShadowTree
* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem
)
1668 auto entry
= reinterpret_cast<CShadowTree
*>(m_RefTreeCtrl
.GetItemData(treeItem
));
1674 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()