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 ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
109 int Compare(LPARAM lParam1
, LPARAM lParam2
)
112 (CShadowTree
*)m_pList
->GetItemData((int)lParam1
),
113 (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
);
203 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
204 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
205 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
207 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
209 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
210 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnItemChangedListRefLeafs
)
211 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
212 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
213 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
214 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
215 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED
, OnClickedCancelFilter
)
217 ON_BN_CLICKED(IDC_CURRENTBRANCH
, OnBnClickedCurrentbranch
)
218 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS
, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs
)
222 // CBrowseRefsDlg message handlers
224 void CBrowseRefsDlg::OnBnClickedOk()
226 if (m_bPickOne
|| m_ListRefLeafs
.GetSelectedCount() != 2)
233 popupMenu
.CreatePopupMenu();
235 std::vector
<CShadowTree
*> selectedLeafs
;
236 GetSelectedLeaves(selectedLeafs
);
238 popupMenu
.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG
);
239 popupMenu
.SetDefaultItem(1);
240 popupMenu
.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")), IDI_LOG
);
241 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")), IDI_LOG
);
244 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
245 int selection
= popupMenu
.TrackPopupMenuEx(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, rect
.left
, rect
.top
, this, nullptr);
253 m_bPickedRefSet
= true;
254 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T(".."));
260 m_bPickedRefSet
= true;
261 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..."));
270 BOOL
CBrowseRefsDlg::OnInitDialog()
272 CResizableStandAloneDialog::OnInitDialog();
273 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
275 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
276 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
);
277 m_ctrlFilter
.SetInfoIcon(IDI_FILTEREDIT
);
280 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
281 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
282 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, TOP_LEFT
);
283 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, TOP_LEFT
, TOP_RIGHT
);
284 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
285 AddAnchor(IDC_INCLUDENESTEDREFS
, BOTTOM_LEFT
);
286 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
288 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle() | LVS_EX_INFOTIP
);
289 static UINT columnNames
[] = { IDS_BRANCHNAME
, IDS_TRACKEDBRANCH
, IDS_DATELASTCOMMIT
, IDS_LASTCOMMIT
, IDS_LASTAUTHOR
, IDS_HASH
, IDS_DESCRIPTION
};
290 static int columnWidths
[] = { 0, 0, 0, 300, 0, 0, 80 };
291 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Upstream
) | (1 << eCol_Date
) | (1 << eCol_Msg
) |
292 (1 << eCol_LastAuthor
) | (1 << eCol_Hash
) | (1 << eCol_Description
);
293 m_ListRefLeafs
.m_bAllowHiding
= false;
294 m_ListRefLeafs
.Init();
295 m_ListRefLeafs
.SetListContextMenuHandler([&](CPoint point
) {OnContextMenu_ListRefLeafs(point
); });
296 m_ListRefLeafs
.m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
297 m_ListRefLeafs
.m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, _T("BrowseRefs"), _countof(columnNames
), columnWidths
);
298 m_bPickedRefSet
= false;
300 AddAnchor(IDOK
,BOTTOM_RIGHT
);
301 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
302 AddAnchor(IDC_CURRENTBRANCH
, BOTTOM_RIGHT
);
304 m_bIncludeNestedRefs
= !!m_regIncludeNestedRefs
;
307 Refresh(m_initialRef
);
309 EnableSaveRestore(L
"BrowseRefs");
311 CString sWindowTitle
;
312 GetWindowText(sWindowTitle
);
313 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
315 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
318 m_ListRefLeafs
.ModifyStyle(0, LVS_SINGLESEL
);
320 m_ListRefLeafs
.SetFocus();
324 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
326 int posSlash
=nameLeft
.Find('/');
331 nameLeft
.Empty();//Nothing left
335 nameSub
=nameLeft
.Left(posSlash
);
336 nameLeft
=nameLeft
.Mid(posSlash
+1);
338 if(nameSub
.IsEmpty())
341 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
344 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
345 nextNode
.m_csRefName
=nameSub
;
346 nextNode
.m_pParent
=this;
350 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
354 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
356 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
358 //Match of leaf name. Try match on total name.
359 CString totalRefName
= GetRefName();
360 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
361 return this; //Also match. Found.
366 //Not a leaf. Search all nodes.
367 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
369 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
371 return pSubtree
; //Found
374 return nullptr; //Not found
377 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
379 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
380 //List ctrl selection?
381 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
384 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
385 m_ListRefLeafs
.GetNextSelectedItem(pos
));
386 return pTree
->GetRefName();
388 else if (pos
&& !pickFirstSelIfMultiSel
)
390 // at least one leaf is selected
393 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
395 CString ref
= ((CShadowTree
*)m_ListRefLeafs
.GetItemData(index
))->GetRefName();
396 if (CStringUtils::StartsWith(ref
, L
"refs/"))
398 if (CStringUtils::StartsWith(ref
, L
"heads/"))
400 refs
+= ref
+ _T(" ");
406 //Tree ctrl selection?
407 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
410 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
411 return pTree
->GetRefName();
414 return CString();//None
417 void CBrowseRefsDlg::Refresh(CString selectRef
)
420 if (g_Git
.GetRemoteList(remotes
))
421 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR
);
423 if(!selectRef
.IsEmpty())
425 if(selectRef
== "HEAD")
427 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
430 selectRef
= L
"refs/heads/" + selectRef
;
434 selectRef
= GetSelectedRef(false, true);
436 m_RefTreeCtrl
.DeleteAllItems();
437 m_ListRefLeafs
.DeleteAllItems();
438 m_TreeRoot
.m_ShadowTree
.clear();
439 m_TreeRoot
.m_csRefName
= "refs";
440 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs", nullptr, nullptr);
441 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
444 MAP_REF_GITREVREFBROWSER refMap
;
445 if (GitRevRefBrowser::GetGitRevRefMap(refMap
, err
, [&](const CString
& refName
)
447 //Use ref based on m_pickRef_Kind
448 if (CStringUtils::StartsWith(refName
, L
"refs/heads/") && !(m_pickRef_Kind
& gPickRef_Head
))
450 if (CStringUtils::StartsWith(refName
, L
"refs/tags/") && !(m_pickRef_Kind
& gPickRef_Tag
))
452 if (CStringUtils::StartsWith(refName
, L
"refs/remotes/") && !(m_pickRef_Kind
& gPickRef_Remote
))
454 if (m_pickRef_Kind
== gPickRef_Remote
&& !CStringUtils::StartsWith(refName
, L
"refs/remotes/")) // do not show refs/stash if only remote branches are requested
459 MessageBox(_T("Get refs failed:") + err
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
463 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
465 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
466 GitRevRefBrowser ref
= iterRefMap
->second
;
468 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
469 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
470 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"refs/remotes/");
471 treeLeaf
.m_csSubject
= ref
.GetSubject();
472 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
473 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
474 treeLeaf
.m_csDescription
= ref
.m_Description
;
477 // always expand the tree first
478 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
480 // try exact match first
481 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
482 SelectRef(selectRef
, false);
485 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
489 CString newRefName
= GetFullRefName(refName
);
490 if(!newRefName
.IsEmpty())
491 refName
= newRefName
;
492 //else refName is not a valid ref. Try to select as good as possible.
494 if (!CStringUtils::StartsWith(refName
, L
"refs"))
495 return false; // Not a ref name
497 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
498 if (treeLeafHead
.m_hTree
)
500 //Not a leaf. Select tree node and return
501 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
505 if (!treeLeafHead
.m_pParent
)
506 return false; //Weird... should not occur.
508 //This is the current head.
509 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
511 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
513 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
514 if(pCurrShadowTree
== &treeLeafHead
)
516 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
517 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
524 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
528 if (CStringUtils::StartsWith(refName
, L
"refs/"))
529 refName
=refName
.Mid(5);
530 pTreePos
=&m_TreeRoot
;
532 if(refName
.IsEmpty())
533 return *pTreePos
;//Found leaf
535 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
538 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
539 ASSERT(!bCreateIfNotExist
);
543 if(!refName
.IsEmpty())
545 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
546 //Leafs are for the list control.
547 if (!pNextTree
->m_hTree
)
549 //New tree. Create node in control.
550 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
, nullptr);
551 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
555 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
559 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
561 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
564 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
567 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
569 m_ListRefLeafs
.DeleteAllItems();
571 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
577 FillListCtrlForShadowTree(pTree
,L
"",true);
578 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
579 m_ListRefLeafs
.AdjustColumnWidths();
582 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
587 m_ctrlFilter
.GetWindowText(filter
);
589 bool positive
= filter
[0] != '!';
591 filter
= filter
.Mid(1);
592 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
593 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== "refs" && !pTree
->m_pParent
) && IsMatchFilter(pTree
, ref
, filter
, positive
))
595 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
597 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
598 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
599 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
600 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : _T(""));
601 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
602 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
603 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
604 CString descrition
= pTree
->m_csDescription
;
605 descrition
.Replace(L
"\n", L
" ");
606 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
612 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
614 else if (!isFirstLevel
)
615 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
617 m_pListCtrlRoot
= pTree
;
618 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
620 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
625 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
626 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
628 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
632 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
, bool positive
)
634 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
637 msg
= msg
.MakeLower();
639 if (msg
.Find(filter
) >= 0)
643 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
645 CString msg
= pTree
->m_csSubject
;
646 msg
= msg
.MakeLower();
648 if (msg
.Find(filter
) >= 0)
652 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
654 CString msg
= pTree
->m_csAuthor
;
655 msg
= msg
.MakeLower();
657 if (msg
.Find(filter
) >= 0)
661 if (m_SelectedFilters
& LOGFILTER_REVS
)
663 CString msg
= pTree
->m_csRefHash
;
664 msg
= msg
.MakeLower();
666 if (msg
.Find(filter
) >= 0)
672 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
674 ASSERT(!leafs
.empty());
677 UINT mbIcon
=MB_ICONQUESTION
;
679 bool bIsRemoteBranch
= false;
680 bool bIsBranch
= false;
681 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
682 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
686 if(leafs
.size() == 1)
688 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
689 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)branchToDelete
);
691 //Check if branch is fully merged in HEAD
692 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), _T("HEAD")))
694 csMessage
+= L
"\r\n\r\n";
695 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
696 mbIcon
= MB_ICONWARNING
;
701 csMessage
+= L
"\r\n\r\n";
702 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
703 mbIcon
= MB_ICONWARNING
;
708 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
710 csMessage
+= L
"\r\n\r\n";
711 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
712 mbIcon
= MB_ICONWARNING
;
716 csMessage
+= L
"\r\n\r\n";
717 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
718 mbIcon
= MB_ICONWARNING
;
723 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
725 if(leafs
.size() == 1)
727 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
728 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tagToDelete
);
731 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
734 return CMessageBox::Show(m_hWnd
, csMessage
, _T("TortoiseGit"), MB_YESNO
| mbIcon
) == IDYES
;
737 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
739 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
740 if(!DoDeleteRef((*i
)->GetRefName()))
745 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
747 bool bIsRemoteBranch
= false;
748 bool bIsBranch
= false;
749 if (CStringUtils::StartsWith(completeRefName
, L
"refs/remotes/"))
752 bIsRemoteBranch
= true;
754 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/heads/"))
759 CString branchToDelete
= completeRefName
.Mid(13);
760 CString remoteName
, remoteBranchToDelete
;
761 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
764 if (CAppUtils::IsSSHPutty())
765 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
767 CSysProgressDlg sysProgressDlg
;
768 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
769 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
770 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
771 sysProgressDlg
.SetShowProgressBar(false);
772 sysProgressDlg
.ShowModal(this, true);
775 list
.push_back(_T("refs/heads/") + remoteBranchToDelete
);
776 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
778 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
779 sysProgressDlg
.Stop();
783 sysProgressDlg
.Stop();
788 if (g_Git
.DeleteRef(completeRefName
))
790 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
794 else if (CStringUtils::StartsWith(completeRefName
, L
"refs/tags/"))
796 if (g_Git
.DeleteRef(completeRefName
))
798 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
805 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
807 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
810 return pLeaf
->GetRefName();
814 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
816 if (pWndFrom
== &m_RefTreeCtrl
)
817 OnContextMenu_RefTreeCtrl(point
);
820 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
822 CPoint clientPoint
=point
;
823 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
825 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
827 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
829 VectorPShadowTree tree
;
830 ShowContextMenu(point
,hTreeItem
,tree
);
833 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
835 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
836 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
839 selectedLeafs
.push_back((CShadowTree
*)m_ListRefLeafs
.GetItemData(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
843 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
845 std::vector
<CShadowTree
*> selectedLeafs
;
846 GetSelectedLeaves(selectedLeafs
);
847 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
850 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
852 ASSERT(selectedLeafs
.size() == 2);
854 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
855 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
857 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
860 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
863 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
871 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
874 popupMenu
.CreatePopupMenu();
876 bool bAddSeparator
= false;
879 if(selectedLeafs
.size()==1)
881 bAddSeparator
= true;
883 bool bShowReflogOption
= false;
884 bool bShowFetchOption
= false;
885 bool bShowRenameOption
= false;
886 bool bShowCreateBranchOption
= false;
887 bool bShowEditBranchDescriptionOption
= false;
889 CString fetchFromCmd
;
891 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
893 bShowReflogOption
= true;
894 bShowRenameOption
= true;
895 bShowEditBranchDescriptionOption
= true;
897 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
899 bShowReflogOption
= true;
900 bShowFetchOption
= true;
901 bShowCreateBranchOption
= true;
903 CString remoteBranch
;
904 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
905 bShowFetchOption
= false;
907 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
910 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, IDS_MENULOG
, IDI_LOG
);
911 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
912 if(bShowReflogOption
)
913 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, IDS_MENUREFLOG
, IDI_LOG
);
917 popupMenu
.AppendMenu(MF_SEPARATOR
);
918 popupMenu
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
921 popupMenu
.AppendMenu(MF_SEPARATOR
);
922 bAddSeparator
= false;
926 bAddSeparator
= true;
927 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
931 popupMenu
.AppendMenu(MF_SEPARATOR
);
933 bAddSeparator
= false;
937 if (selectedLeafs
[0]->GetRefName() != _T("refs/heads/") + g_Git
.GetCurrentBranch())
939 str
.Format(IDS_LOG_POPUP_MERGEREV
, (LPCTSTR
)g_Git
.GetCurrentBranch());
940 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
942 popupMenu
.AppendMenuIcon(eCmd_Switch
, IDS_SWITCH_TO_THIS
, IDI_SWITCH
);
943 popupMenu
.AppendMenu(MF_SEPARATOR
);
946 if(bShowCreateBranchOption
)
948 bAddSeparator
= true;
949 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, IDS_MENUBRANCH
, IDI_COPY
);
952 if (bShowEditBranchDescriptionOption
)
954 bAddSeparator
= true;
955 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, IDS_PROC_BROWSEREFS_EDITDESCRIPTION
, IDI_RENAME
);
957 if(bShowRenameOption
)
959 bAddSeparator
= true;
960 popupMenu
.AppendMenuIcon(eCmd_Rename
, IDS_PROC_BROWSEREFS_RENAME
, IDI_RENAME
);
963 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
966 popupMenu
.AppendMenu(MF_SEPARATOR
);
967 bAddSeparator
= true;
968 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
969 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
);
970 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
);
973 else if(selectedLeafs
.size() == 2)
975 bAddSeparator
= true;
976 popupMenu
.AppendMenuIcon(eCmd_Diff
, IDS_PROC_BROWSEREFS_COMPAREREFS
, IDI_DIFF
);
977 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
979 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")));
980 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
981 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")));
982 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
985 if(!selectedLeafs
.empty())
987 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
990 popupMenu
.AppendMenu(MF_SEPARATOR
);
991 CString menuItemName
;
992 if(selectedLeafs
.size() == 1)
993 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
995 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
997 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
998 bAddSeparator
= true;
1000 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1003 popupMenu
.AppendMenu(MF_SEPARATOR
);
1004 CString menuItemName
;
1005 if(selectedLeafs
.size() == 1)
1006 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1008 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1010 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1011 bAddSeparator
= true;
1013 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1016 popupMenu
.AppendMenu(MF_SEPARATOR
);
1017 CString menuItemName
;
1018 if(selectedLeafs
.size() == 1)
1019 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1021 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1023 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1024 bAddSeparator
= true;
1029 if (hTreePos
&& selectedLeafs
.empty())
1031 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
1032 if(pTree
->IsFrom(L
"refs/remotes"))
1035 popupMenu
.AppendMenu(MF_SEPARATOR
);
1036 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, IDS_PROC_BROWSEREFS_MANAGEREMOTES
, IDI_SETTINGS
);
1037 bAddSeparator
= true;
1038 if(selectedLeafs
.empty())
1040 CString remoteBranch
;
1041 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1043 int pos
= findVectorPosition(remotes
, remoteName
);
1047 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
1048 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_PULL
);
1050 temp
.LoadString(IDS_DELETEREMOTETAG
);
1051 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1055 if(pTree
->IsFrom(L
"refs/heads"))
1058 popupMenu
.AppendMenu(MF_SEPARATOR
);
1060 temp
.LoadString(IDS_MENUBRANCH
);
1061 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1063 if(pTree
->IsFrom(L
"refs/tags"))
1066 popupMenu
.AppendMenu(MF_SEPARATOR
);
1067 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, IDS_MENUTAG
, IDI_TAG
);
1068 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, IDS_PROC_BROWSEREFS_DELETEALLTAGS
, IDI_DELETE
);
1069 if (!remotes
.empty())
1071 popupMenu
.AppendMenu(MF_SEPARATOR
);
1073 for (auto it
= remotes
.cbegin(); it
!= remotes
.cend(); ++it
, ++i
)
1076 temp
.Format(IDS_DELETEREMOTETAGON
, (LPCTSTR
)*it
);
1077 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (i
<< 16), temp
, IDI_DELETE
);
1084 int selection
= popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
| TPM_RETURNCMD
, point
.x
, point
.y
, this, nullptr);
1085 switch ((eCmd
)(selection
& 0xFFFF))
1090 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)g_Git
.FixBranchName(selectedLeafs
[0]->GetRefName()));
1091 CAppUtils::RunTortoiseGitProc(sCmd
);
1094 case eCmd_ViewLogRange
:
1097 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")));
1098 CAppUtils::RunTortoiseGitProc(sCmd
);
1101 case eCmd_ViewLogRangeReachableFromOnlyOne
:
1104 sCmd
.Format(_T("/command:log /path:\"%s\" /range:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")));
1105 CAppUtils::RunTortoiseGitProc(sCmd
);
1108 case eCmd_RepoBrowser
:
1109 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git
.m_CurrentDir
+ _T("\" /rev:") + selectedLeafs
[0]->GetRefName());
1111 case eCmd_DeleteBranch
:
1112 case eCmd_DeleteRemoteBranch
:
1114 if(ConfirmDeleteRef(selectedLeafs
))
1115 DoDeleteRefs(selectedLeafs
);
1119 case eCmd_DeleteTag
:
1121 if(ConfirmDeleteRef(selectedLeafs
))
1122 DoDeleteRefs(selectedLeafs
);
1126 case eCmd_ShowReflog
:
1128 CRefLogDlg
refLogDlg(this);
1129 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
1130 refLogDlg
.DoModal();
1135 CAppUtils::Fetch(remoteName
);
1139 case eCmd_DeleteRemoteTag
:
1141 CDeleteRemoteTagDlg deleteRemoteTagDlg
;
1142 int remoteInx
= selection
>> 16;
1143 if (remoteInx
< 0 || (size_t)remoteInx
>= remotes
.size())
1145 deleteRemoteTagDlg
.m_sRemote
= remotes
[remoteInx
];
1146 deleteRemoteTagDlg
.DoModal();
1151 CString ref
= selectedLeafs
[0]->GetRefName();
1152 CAppUtils::Merge(&ref
);
1157 CAppUtils::Switch(selectedLeafs
[0]->GetRefName());
1162 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1164 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1167 case eCmd_AddRemote
:
1169 CAddRemoteDlg(this).DoModal();
1173 case eCmd_ManageRemotes
:
1175 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(), this).DoModal();
1176 // CSettingGitRemote W_Remotes(m_cmdPath);
1177 // W_Remotes.DoModal();
1181 case eCmd_CreateBranch
:
1183 CString
* commitHash
= nullptr;
1184 if (selectedLeafs
.size() == 1)
1185 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
1186 CAppUtils::CreateBranchTag(false, commitHash
);
1190 case eCmd_CreateTag
:
1192 CAppUtils::CreateBranchTag(true);
1196 case eCmd_DeleteAllTags
:
1198 for (int i
= 0; i
< m_ListRefLeafs
.GetItemCount(); ++i
)
1200 m_ListRefLeafs
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
1201 selectedLeafs
.push_back((CShadowTree
*)m_ListRefLeafs
.GetItemData(i
));
1203 if (ConfirmDeleteRef(selectedLeafs
))
1204 DoDeleteRefs(selectedLeafs
);
1213 selectedLeafs
[0]->GetRefName() + L
"^{}",
1214 selectedLeafs
[1]->GetRefName() + L
"^{}");
1218 case eCmd_UnifiedDiff
:
1220 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs
[0]->m_csRefHash
, CTGitPath(), selectedLeafs
[1]->m_csRefHash
, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
1226 sCmd
.Format(_T("/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s"), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)selectedLeafs
[0]->GetRefName(), (LPCTSTR
)GitRev::GetWorkingCopy());
1227 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
1228 sCmd
+= L
" /alternative";
1230 CAppUtils::RunTortoiseGitProc(sCmd
);
1233 case eCmd_EditBranchDescription
:
1236 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1237 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1238 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1239 dlg
.m_bUseLogWidth
= true;
1240 if(dlg
.DoModal() == IDOK
)
1242 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1247 case eCmd_UpstreamDrop
:
1250 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1251 g_Git
.UnsetConfigValue(key
);
1252 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1253 g_Git
.UnsetConfigValue(key
);
1257 case eCmd_UpstreamSet
:
1259 CString newRef
= CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote
, false);
1260 if (newRef
.IsEmpty() || newRef
.Find(_T("refs/remotes/")) != 0)
1262 CString remote
, branch
;
1263 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1266 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1267 g_Git
.SetConfigValue(key
, remote
);
1268 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1269 g_Git
.SetConfigValue(key
, _T("refs/heads/") + branch
);
1276 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1278 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1279 if(!(*i
)->IsFrom(from
))
1284 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1286 if (pMsg
->message
== WM_KEYDOWN
)
1288 switch (pMsg
->wParam
)
1292 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1294 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1296 PostMessage(WM_COMMAND, IDOK);
1304 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1306 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1308 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1320 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1322 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1323 m_ctrlFilter
.SetFocus();
1329 if (GetFocus() == GetDlgItem(IDC_BROWSEREFS_EDIT_FILTER
) && m_ctrlFilter
.GetWindowTextLength())
1331 OnClickedCancelFilter(NULL
, NULL
);
1339 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1342 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1344 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1347 if(m_currSortCol
== pNMLV
->iSubItem
)
1348 m_currSortDesc
= !m_currSortDesc
;
1351 m_currSortCol
= pNMLV
->iSubItem
;
1352 m_currSortDesc
= false;
1355 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1356 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1358 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1361 void CBrowseRefsDlg::OnDestroy()
1363 if (!m_bPickedRefSet
)
1364 m_pickedRef
= GetSelectedRef(true, false);
1366 CResizableStandAloneDialog::OnDestroy();
1369 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1371 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1374 CShadowTree
*item
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(pNMListView
->iItem
);
1375 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1376 m_sLastSelected
= item
->GetRefName();
1381 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1385 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1390 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefsOrRange
)
1392 CBrowseRefsDlg
dlg(CString(), nullptr);
1394 if(initialRef
.IsEmpty())
1395 initialRef
= L
"HEAD";
1396 dlg
.m_initialRef
= initialRef
;
1397 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1398 dlg
.m_bPickOne
= !pickMultipleRefsOrRange
;
1400 if(dlg
.DoModal() != IDOK
)
1403 return dlg
.m_pickedRef
;
1406 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1409 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1410 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1411 if(resultRef
.IsEmpty())
1413 if (CStringUtils::StartsWith(resultRef
, L
"refs/"))
1414 resultRef
= resultRef
.Mid(5);
1415 // if(wcsncmp(resultRef,L"heads/",6)==0)
1416 // resultRef = resultRef.Mid(6);
1418 //Find closest match of choice in combobox
1420 int matchLength
= 0;
1421 CString comboRefName
;
1422 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1424 pComboBox
->GetLBText(i
, comboRefName
);
1425 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1426 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1427 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1429 matchLength
= comboRefName
.GetLength();
1434 pComboBox
->SetCurSel(ixFound
);
1436 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)
1441 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1443 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1446 if (!pDispInfo
->item
.pszText
)
1447 return; //User canceled changing
1449 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(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 CShadowTree
* pTree2
= (CShadowTree
*)m_RefTreeCtrl
.GetItemData(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 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1503 if(!pTree
->IsFrom(L
"refs/heads/"))
1505 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1510 void CBrowseRefsDlg::OnEnChangeEditFilter()
1512 SetTimer(IDT_FILTER
, 1000, nullptr);
1515 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1517 if (nIDEvent
== IDT_FILTER
)
1519 KillTimer(IDT_FILTER
);
1520 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1523 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1526 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1528 // FIXME: x64 version would get this function called with unexpected parameters.
1532 RECT
* rect
= (LPRECT
)lParam
;
1535 point
= CPoint(rect
->left
, rect
->bottom
);
1536 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | ((m_SelectedFilters & x) ? MF_CHECKED : MF_UNCHECKED))
1538 if (popup
.CreatePopupMenu())
1540 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1541 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1543 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1544 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1546 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1547 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1549 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1550 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1552 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1553 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1555 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1558 if (selection
== LOGFILTER_TOGGLE
)
1559 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1561 m_SelectedFilters
^= selection
;
1563 SetTimer(IDT_FILTER
, 1000, nullptr);
1569 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1571 KillTimer(LOGFILTER_TIMER
);
1572 m_ctrlFilter
.SetWindowText(_T(""));
1573 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1577 void CBrowseRefsDlg::SetFilterCueText()
1579 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1582 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1583 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1585 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1587 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1589 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1592 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1594 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1596 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1599 if (m_SelectedFilters
& LOGFILTER_REVS
)
1601 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1603 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1606 // to make the cue banner text appear more to the right of the edit control
1607 temp
= _T(" ") + temp
;
1608 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1611 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1613 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1614 m_bPickedRefSet
= true;
1618 void CBrowseRefsDlg::UpdateInfoLabel()
1621 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1622 SetDlgItemText(IDC_INFOLABEL
, temp
);
1625 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1628 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;