1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2016 - 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 (ref
.Left(13) == _T("refs/remotes/"))
46 else if (ref
.Left(8) == _T("remotes/"))
50 int result
= g_Git
.GetRemoteList(list
);
54 for (size_t i
= 0; i
< list
.size(); ++i
)
56 if (ref
.Left(list
[i
].GetLength() + 1) == list
[i
] + _T("/"))
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(_T("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(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE
);
179 m_bRelativeTimes
= (regRelativeTimes
!= 0);
181 m_regIncludeNestedRefs
= CRegDWORD(_T("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
, _T("..")), IDI_LOG
);
243 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")), 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
, _T(".."));
262 m_bPickedRefSet
= true;
263 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..."));
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
);
279 m_ctrlFilter
.SetInfoIcon(IDI_FILTEREDIT
);
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
);
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, _T("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);
328 m_ListRefLeafs
.SetFocus();
332 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
334 int posSlash
=nameLeft
.Find('/');
339 nameLeft
.Empty();//Nothing left
343 nameSub
=nameLeft
.Left(posSlash
);
344 nameLeft
=nameLeft
.Mid(posSlash
+1);
346 if(nameSub
.IsEmpty())
349 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
352 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
353 nextNode
.m_csRefName
=nameSub
;
354 nextNode
.m_pParent
=this;
358 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
362 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
364 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
366 //Match of leaf name. Try match on total name.
367 CString totalRefName
= GetRefName();
368 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
369 return this; //Also match. Found.
374 //Not a leaf. Search all nodes.
375 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
377 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
379 return pSubtree
; //Found
382 return nullptr; //Not found
385 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
387 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
388 //List ctrl selection?
389 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
392 return GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
))->GetRefName();
394 else if (pos
&& !pickFirstSelIfMultiSel
)
396 // at least one leaf is selected
399 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
401 CString ref
= GetListEntry(index
)->GetRefName();
402 if (CStringUtils::StartsWith(ref
, L
"refs/"))
404 if (CStringUtils::StartsWith(ref
, L
"heads/"))
406 refs
+= ref
+ _T(" ");
412 //Tree ctrl selection?
413 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
415 return GetTreeEntry(hTree
)->GetRefName();
417 return CString();//None
420 void CBrowseRefsDlg::Refresh(CString selectRef
)
423 if (g_Git
.GetRemoteList(remotes
))
424 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR
);
426 if(!selectRef
.IsEmpty())
428 if (selectRef
== L
"HEAD")
430 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
433 selectRef
= L
"refs/heads/" + selectRef
;
437 selectRef
= GetSelectedRef(false, true);
439 m_RefTreeCtrl
.DeleteAllItems();
440 m_ListRefLeafs
.DeleteAllItems();
441 m_TreeRoot
.m_ShadowTree
.clear();
442 m_TreeRoot
.m_csRefName
= L
"refs";
443 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs", nullptr, nullptr);
444 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
447 MAP_REF_GITREVREFBROWSER refMap
;
448 if (GitRevRefBrowser::GetGitRevRefMap(refMap
, m_cBranchFilter
.GetCurSel(), err
, [&](const CString
& refName
)
450 //Use ref based on m_pickRef_Kind
451 if (CStringUtils::StartsWith(refName
, L
"refs/heads/") && !(m_pickRef_Kind
& gPickRef_Head
))
453 if (CStringUtils::StartsWith(refName
, L
"refs/tags/") && !(m_pickRef_Kind
& gPickRef_Tag
))
455 if (CStringUtils::StartsWith(refName
, L
"refs/remotes/") && !(m_pickRef_Kind
& gPickRef_Remote
))
457 if (m_pickRef_Kind
== gPickRef_Remote
&& !CStringUtils::StartsWith(refName
, L
"refs/remotes/")) // do not show refs/stash if only remote branches are requested
462 MessageBox(_T("Get refs failed:") + err
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
466 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
468 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
469 GitRevRefBrowser ref
= iterRefMap
->second
;
471 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
472 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
473 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"refs/remotes/");
474 treeLeaf
.m_csSubject
= ref
.GetSubject();
475 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
476 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
477 treeLeaf
.m_csDescription
= ref
.m_Description
;
480 // always expand the tree first
481 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
483 // try exact match first
484 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
485 SelectRef(selectRef
, false);
488 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
492 CString newRefName
= GetFullRefName(refName
);
493 if(!newRefName
.IsEmpty())
494 refName
= newRefName
;
495 //else refName is not a valid ref. Try to select as good as possible.
497 if (!CStringUtils::StartsWith(refName
, L
"refs"))
498 return false; // Not a ref name
500 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
501 if (treeLeafHead
.m_hTree
)
503 //Not a leaf. Select tree node and return
504 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
508 if (!treeLeafHead
.m_pParent
)
509 return false; //Weird... should not occur.
511 //This is the current head.
512 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
514 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
516 auto pCurrShadowTree
= GetListEntry(indexPos
);
517 if(pCurrShadowTree
== &treeLeafHead
)
519 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
520 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
527 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
531 if (CStringUtils::StartsWith(refName
, L
"refs/"))
532 refName
=refName
.Mid(5);
533 pTreePos
=&m_TreeRoot
;
535 if(refName
.IsEmpty())
536 return *pTreePos
;//Found leaf
538 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
541 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
542 ASSERT(!bCreateIfNotExist
);
546 if(!refName
.IsEmpty())
548 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
549 //Leafs are for the list control.
550 if (!pNextTree
->m_hTree
)
552 //New tree. Create node in control.
553 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
, nullptr);
554 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
558 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
562 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
564 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
567 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
570 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
572 m_ListRefLeafs
.DeleteAllItems();
574 auto pTree
= GetTreeEntry(treeNode
);
578 FillListCtrlForShadowTree(pTree
,L
"",true);
579 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
580 m_ListRefLeafs
.AdjustColumnWidths();
583 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
588 m_ctrlFilter
.GetWindowText(filter
);
590 bool positive
= filter
[0] != '!';
592 filter
= filter
.Mid(1);
593 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
594 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== L
"refs" && !pTree
->m_pParent
) && IsMatchFilter(pTree
, ref
, filter
, positive
))
596 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
598 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
599 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
600 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
601 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : _T(""));
602 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
603 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
604 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
605 CString descrition
= pTree
->m_csDescription
;
606 descrition
.Replace(L
"\n", L
" ");
607 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
613 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
615 else if (!isFirstLevel
)
616 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
618 m_pListCtrlRoot
= pTree
;
619 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
621 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
626 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
627 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
629 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
633 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
, bool positive
)
635 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
638 msg
= msg
.MakeLower();
640 if (msg
.Find(filter
) >= 0)
644 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
646 CString msg
= pTree
->m_csSubject
;
647 msg
= msg
.MakeLower();
649 if (msg
.Find(filter
) >= 0)
653 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
655 CString msg
= pTree
->m_csAuthor
;
656 msg
= msg
.MakeLower();
658 if (msg
.Find(filter
) >= 0)
662 if (m_SelectedFilters
& LOGFILTER_REVS
)
664 CString msg
= pTree
->m_csRefHash
;
665 msg
= msg
.MakeLower();
667 if (msg
.Find(filter
) >= 0)
673 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
675 ASSERT(!leafs
.empty());
678 UINT mbIcon
=MB_ICONQUESTION
;
680 bool bIsRemoteBranch
= false;
681 bool bIsBranch
= false;
682 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
683 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
687 if(leafs
.size() == 1)
689 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
690 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)branchToDelete
);
692 //Check if branch is fully merged in HEAD
693 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), _T("HEAD")))
695 csMessage
+= L
"\r\n\r\n";
696 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
697 mbIcon
= MB_ICONWARNING
;
702 csMessage
+= L
"\r\n\r\n";
703 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
704 mbIcon
= MB_ICONWARNING
;
709 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
711 csMessage
+= L
"\r\n\r\n";
712 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
713 mbIcon
= MB_ICONWARNING
;
717 csMessage
+= L
"\r\n\r\n";
718 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
719 mbIcon
= MB_ICONWARNING
;
724 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
726 if(leafs
.size() == 1)
728 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
729 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tagToDelete
);
732 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
735 return CMessageBox::Show(m_hWnd
, csMessage
, _T("TortoiseGit"), MB_YESNO
| mbIcon
) == IDYES
;
738 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
740 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
741 if(!DoDeleteRef((*i
)->GetRefName()))
746 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
748 bool bIsRemoteBranch
= false;
749 bool bIsBranch
= false;
750 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
753 bIsRemoteBranch
= true;
755 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
760 CString branchToDelete
= completeRefName
.Mid(13);
761 CString remoteName
, remoteBranchToDelete
;
762 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
765 if (CAppUtils::IsSSHPutty())
766 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
768 CSysProgressDlg sysProgressDlg
;
769 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
770 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
771 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
772 sysProgressDlg
.SetShowProgressBar(false);
773 sysProgressDlg
.ShowModal(this, true);
776 list
.push_back(_T("refs/heads/") + remoteBranchToDelete
);
777 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
779 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
780 sysProgressDlg
.Stop();
784 sysProgressDlg
.Stop();
789 if (g_Git
.DeleteRef(completeRefName
))
791 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
795 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/tags/"))
797 if (g_Git
.DeleteRef(completeRefName
))
799 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
806 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
808 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
811 return pLeaf
->GetRefName();
815 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
817 if (pWndFrom
== &m_RefTreeCtrl
)
818 OnContextMenu_RefTreeCtrl(point
);
821 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
823 CPoint clientPoint
=point
;
824 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
826 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
828 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
830 VectorPShadowTree tree
;
831 ShowContextMenu(point
,hTreeItem
,tree
);
834 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
836 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
837 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
840 selectedLeafs
.push_back(GetListEntry(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
844 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
846 std::vector
<CShadowTree
*> selectedLeafs
;
847 GetSelectedLeaves(selectedLeafs
);
848 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
851 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
853 ASSERT(selectedLeafs
.size() == 2);
855 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
856 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
858 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
861 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
864 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
872 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
875 popupMenu
.CreatePopupMenu();
877 bool bAddSeparator
= false;
880 if(selectedLeafs
.size()==1)
882 bAddSeparator
= true;
884 bool bShowReflogOption
= false;
885 bool bShowFetchOption
= false;
886 bool bShowRenameOption
= false;
887 bool bShowCreateBranchOption
= false;
888 bool bShowEditBranchDescriptionOption
= false;
890 CString fetchFromCmd
;
892 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
894 bShowReflogOption
= true;
895 bShowRenameOption
= true;
896 bShowEditBranchDescriptionOption
= true;
898 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
900 bShowReflogOption
= true;
901 bShowFetchOption
= true;
902 bShowCreateBranchOption
= true;
904 CString remoteBranch
;
905 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
906 bShowFetchOption
= false;
908 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
911 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, IDS_MENULOG
, IDI_LOG
);
912 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
913 if(bShowReflogOption
)
914 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, IDS_MENUREFLOG
, IDI_LOG
);
918 popupMenu
.AppendMenu(MF_SEPARATOR
);
919 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
922 popupMenu
.AppendMenu(MF_SEPARATOR
);
923 bAddSeparator
= false;
927 bAddSeparator
= true;
928 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
932 popupMenu
.AppendMenu(MF_SEPARATOR
);
934 bAddSeparator
= false;
938 if (selectedLeafs
[0]->GetRefName() != _T("refs/heads/") + g_Git
.GetCurrentBranch())
940 str
.Format(IDS_LOG_POPUP_MERGEREV
, (LPCTSTR
)g_Git
.GetCurrentBranch());
941 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
943 popupMenu
.AppendMenuIcon(eCmd_Switch
, IDS_SWITCH_TO_THIS
, IDI_SWITCH
);
944 popupMenu
.AppendMenu(MF_SEPARATOR
);
947 if(bShowCreateBranchOption
)
949 bAddSeparator
= true;
950 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, IDS_MENUBRANCH
, IDI_COPY
);
953 if (bShowEditBranchDescriptionOption
)
955 bAddSeparator
= true;
956 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, IDS_PROC_BROWSEREFS_EDITDESCRIPTION
, IDI_RENAME
);
958 if(bShowRenameOption
)
960 bAddSeparator
= true;
961 popupMenu
.AppendMenuIcon(eCmd_Rename
, IDS_PROC_BROWSEREFS_RENAME
, IDI_RENAME
);
964 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
967 popupMenu
.AppendMenu(MF_SEPARATOR
);
968 bAddSeparator
= true;
969 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
970 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
);
971 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
);
974 else if(selectedLeafs
.size() == 2)
976 bAddSeparator
= true;
977 popupMenu
.AppendMenuIcon(eCmd_Diff
, IDS_PROC_BROWSEREFS_COMPAREREFS
, IDI_DIFF
);
978 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
980 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")));
981 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
982 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")));
983 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
986 if(!selectedLeafs
.empty())
988 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
991 popupMenu
.AppendMenu(MF_SEPARATOR
);
992 CString menuItemName
;
993 if(selectedLeafs
.size() == 1)
994 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
996 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
998 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
999 bAddSeparator
= true;
1001 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1004 popupMenu
.AppendMenu(MF_SEPARATOR
);
1005 CString menuItemName
;
1006 if(selectedLeafs
.size() == 1)
1007 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1009 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1011 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1012 bAddSeparator
= true;
1014 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1017 popupMenu
.AppendMenu(MF_SEPARATOR
);
1018 CString menuItemName
;
1019 if(selectedLeafs
.size() == 1)
1020 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1022 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1024 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1025 bAddSeparator
= true;
1030 if (hTreePos
&& selectedLeafs
.empty())
1032 auto pTree
= GetTreeEntry(hTreePos
);
1033 if(pTree
->IsFrom(L
"refs/remotes"))
1036 popupMenu
.AppendMenu(MF_SEPARATOR
);
1037 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, IDS_PROC_BROWSEREFS_MANAGEREMOTES
, IDI_SETTINGS
);
1038 bAddSeparator
= true;
1039 if(selectedLeafs
.empty())
1041 CString remoteBranch
;
1042 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1044 int pos
= findVectorPosition(remotes
, remoteName
);
1048 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
1049 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_PULL
);
1051 temp
.LoadString(IDS_DELETEREMOTETAG
);
1052 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1056 if(pTree
->IsFrom(L
"refs/heads"))
1059 popupMenu
.AppendMenu(MF_SEPARATOR
);
1061 temp
.LoadString(IDS_MENUBRANCH
);
1062 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1064 if(pTree
->IsFrom(L
"refs/tags"))
1067 popupMenu
.AppendMenu(MF_SEPARATOR
);
1068 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, IDS_MENUTAG
, IDI_TAG
);
1069 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, IDS_PROC_BROWSEREFS_DELETEALLTAGS
, IDI_DELETE
);
1070 if (!remotes
.empty())
1072 popupMenu
.AppendMenu(MF_SEPARATOR
);
1074 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1077 temp
.Format(IDS_DELETEREMOTETAGON
, (LPCTSTR
)*it
);
1078 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1085 int selection
= popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
1086 switch ((eCmd
)(selection
& 0xFFFF))
1091 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)g_Git
.FixBranchName(selectedLeafs
[0]->GetRefName()));
1092 CAppUtils::RunTortoiseGitProc(sCmd
);
1095 case eCmd_ViewLogRange
:
1098 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")));
1099 CAppUtils::RunTortoiseGitProc(sCmd
);
1102 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1105 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")));
1106 CAppUtils::RunTortoiseGitProc(sCmd
);
1109 case eCmd_RepoBrowser
:
1110 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git
.m_CurrentDir
+ _T("\" /rev:") + selectedLeafs
[0]->GetRefName());
1112 case eCmd_DeleteBranch
:
1113 case eCmd_DeleteRemoteBranch
:
1115 if(ConfirmDeleteRef(selectedLeafs
))
1116 DoDeleteRefs(selectedLeafs
);
1120 case eCmd_DeleteTag
:
1122 if(ConfirmDeleteRef(selectedLeafs
))
1123 DoDeleteRefs(selectedLeafs
);
1127 case eCmd_ShowReflog
:
1129 CRefLogDlg
refLogDlg(this);
1130 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1131 refLogDlg
.DoModal();
1136 CAppUtils::Fetch(remoteName
);
1140 case eCmd_DeleteRemoteTag
:
1142 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1143 int remoteInx
= selection
>> 16;
1144 if (remoteInx
< 0 || (size_t)remoteInx
>= remotes
.size())
1146 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1147 deleteRemoteTagDlg
.DoModal();
1152 CString ref
= selectedLeafs
[0]->GetRefName();
1153 CAppUtils::Merge(&ref
);
1158 CAppUtils::Switch(selectedLeafs
[0]->GetRefName());
1163 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1165 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1168 case eCmd_AddRemote
:
1170 CAddRemoteDlg(this).DoModal();
1174 case eCmd_ManageRemotes
:
1176 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(), this).DoModal();
1177 // CSettingGitRemote W_Remotes(m_cmdPath);
1178 // W_Remotes.DoModal();
1182 case eCmd_CreateBranch
:
1184 CString
* commitHash
= nullptr;
1185 if (selectedLeafs
.size() == 1)
1186 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
1187 CAppUtils::CreateBranchTag(false, commitHash
);
1191 case eCmd_CreateTag
:
1193 CAppUtils::CreateBranchTag(true);
1197 case eCmd_DeleteAllTags
:
1199 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1201 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1202 selectedLeafs
.push_back(GetListEntry(i
));
1204 if (ConfirmDeleteRef(selectedLeafs
))
1205 DoDeleteRefs(selectedLeafs
);
1214 selectedLeafs
[0]->GetRefName() + L
"^{}",
1215 selectedLeafs
[1]->GetRefName() + L
"^{}");
1219 case eCmd_UnifiedDiff
:
1221 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1227 sCmd
.Format(_T("/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s"), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)selectedLeafs
[0]->GetRefName(), (LPCTSTR
)GitRev::GetWorkingCopy());
1228 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1229 sCmd
+= L
" /alternative";
1231 CAppUtils::RunTortoiseGitProc(sCmd
);
1234 case eCmd_EditBranchDescription
:
1237 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1238 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1239 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1240 dlg
.m_bUseLogWidth
= true;
1241 if(dlg
.DoModal() == IDOK
)
1243 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1248 case eCmd_UpstreamDrop
:
1251 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1252 g_Git
.UnsetConfigValue(key
);
1253 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1254 g_Git
.UnsetConfigValue(key
);
1258 case eCmd_UpstreamSet
:
1260 CString newRef
= CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote
, false);
1261 if (newRef
.IsEmpty() || newRef
.Find(_T("refs/remotes/")) != 0)
1263 CString remote
, branch
;
1264 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1267 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1268 g_Git
.SetConfigValue(key
, remote
);
1269 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1270 g_Git
.SetConfigValue(key
, _T("refs/heads/") + branch
);
1277 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1279 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1280 if(!(*i
)->IsFrom(from
))
1285 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1287 if (pMsg
->message
== WM_KEYDOWN
)
1289 switch (pMsg
->wParam
)
1293 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1295 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1297 PostMessage(WM_COMMAND, IDOK);
1305 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1307 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1309 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1321 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1323 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1324 m_ctrlFilter
.SetFocus();
1330 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1332 OnClickedCancelFilter(NULL
, NULL
);
1340 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1343 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1345 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1348 if(m_currSortCol
== pNMLV
->iSubItem
)
1349 m_currSortDesc
= !m_currSortDesc
;
1352 m_currSortCol
= pNMLV
->iSubItem
;
1353 m_currSortDesc
= false;
1356 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1357 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1359 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1362 void CBrowseRefsDlg::OnDestroy()
1364 if (!m_bPickedRefSet
)
1365 m_pickedRef
= GetSelectedRef(true, false);
1367 CResizableStandAloneDialog::OnDestroy();
1370 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1372 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1375 auto item
= GetListEntry(pNMListView
->iItem
);
1376 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1377 m_sLastSelected
= item
->GetRefName();
1382 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1386 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1391 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefsOrRange
)
1393 CBrowseRefsDlg
dlg(CString(), nullptr);
1395 if(initialRef
.IsEmpty())
1396 initialRef
= L
"HEAD";
1397 dlg
.m_initialRef
= initialRef
;
1398 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1399 dlg
.m_bPickOne
= !pickMultipleRefsOrRange
;
1401 if(dlg
.DoModal() != IDOK
)
1404 return dlg
.m_pickedRef
;
1407 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1410 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1411 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1412 if(resultRef
.IsEmpty())
1414 if (CStringUtils::StartsWith(resultRef
, L
"refs/"))
1415 resultRef
= resultRef
.Mid(5);
1416 // if(wcsncmp(resultRef,L"heads/",6)==0)
1417 // resultRef = resultRef.Mid(6);
1419 //Find closest match of choice in combobox
1421 int matchLength
= 0;
1422 CString comboRefName
;
1423 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1425 pComboBox
->GetLBText(i
, comboRefName
);
1426 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1427 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1428 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1430 matchLength
= comboRefName
.GetLength();
1435 pComboBox
->SetCurSel(ixFound
);
1437 ASSERT(FALSE
);//No match found. So either pickRef_Kind is wrong or the combobox does not contain the ref specified in the picker (which it should unless the repo has changed before creating the CBrowseRef dialog)
1442 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1444 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1447 if (!pDispInfo
->item
.pszText
)
1448 return; //User canceled changing
1450 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1451 if(!pTree
->IsFrom(L
"refs/heads/"))
1453 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1457 CString selectedTreeRef
;
1458 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1461 auto pTree2
= GetTreeEntry(hTree
);
1462 selectedTreeRef
= pTree2
->GetRefName();
1465 CString origName
= pTree
->GetRefName().Mid(11);
1468 if (m_pListCtrlRoot
)
1469 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1470 newName
+= pDispInfo
->item
.pszText
;
1472 if (!CStringUtils::StartsWith(newName
, L
"refs/heads/"))
1474 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1478 CString newNameTrunced
= newName
.Mid(11);
1481 if(g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &errorMsg
, CP_UTF8
) != 0)
1483 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1486 //Do as if it failed to rename. Let Refresh() do the job.
1489 Refresh(selectedTreeRef
);
1491 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1493 // AfxMessageBox(W_csPopup);
1496 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1498 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1501 auto pTree
= GetListEntry(pDispInfo
->item
.iItem
);
1502 if(!pTree
->IsFrom(L
"refs/heads/"))
1504 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1509 void CBrowseRefsDlg::OnEnChangeEditFilter()
1511 SetTimer(IDT_FILTER
, 1000, nullptr);
1514 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1516 if (nIDEvent
== IDT_FILTER
)
1518 KillTimer(IDT_FILTER
);
1519 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1522 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1525 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1527 // FIXME: x64 version would get this function called with unexpected parameters.
1531 RECT
* rect
= (LPRECT
)lParam
;
1534 point
= CPoint(rect
->left
, rect
->bottom
);
1535 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1537 if (popup
.CreatePopupMenu())
1539 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1540 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1542 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1543 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1545 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1546 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1548 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1549 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1551 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1552 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1554 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1557 if (selection
== LOGFILTER_TOGGLE
)
1558 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1560 m_SelectedFilters
^= selection
;
1562 SetTimer(IDT_FILTER
, 1000, nullptr);
1568 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1570 KillTimer(LOGFILTER_TIMER
);
1571 m_ctrlFilter
.SetWindowText(_T(""));
1572 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1576 void CBrowseRefsDlg::SetFilterCueText()
1578 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1581 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1582 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1584 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1586 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1588 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1591 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1593 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1595 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1598 if (m_SelectedFilters
& LOGFILTER_REVS
)
1600 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1602 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1605 // to make the cue banner text appear more to the right of the edit control
1606 temp
= _T(" ") + temp
;
1607 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1610 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1612 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1613 m_bPickedRefSet
= true;
1617 void CBrowseRefsDlg::UpdateInfoLabel()
1620 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1621 SetDlgItemText(IDC_INFOLABEL
, temp
);
1624 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1627 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;
1631 CShadowTree
* CBrowseRefsDlg::GetListEntry(int index
)
1633 auto entry
= reinterpret_cast<CShadowTree
*>(m_ListRefLeafs
.GetItemData(index
));
1638 CShadowTree
* CBrowseRefsDlg::GetTreeEntry(HTREEITEM treeItem
)
1640 auto entry
= reinterpret_cast<CShadowTree
*>(m_RefTreeCtrl
.GetItemData(treeItem
));
1646 void CBrowseRefsDlg::OnCbnSelchangeBrowseRefsBranchfilter()