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"
41 static int SplitRemoteBranchName(CString ref
, CString
&remote
, CString
&branch
)
43 if (ref
.Left(13) == _T("refs/remotes/"))
45 else if (ref
.Left(8) == _T("remotes/"))
49 int result
= g_Git
.GetRemoteList(list
);
53 for (size_t i
= 0; i
< list
.size(); ++i
)
55 if (ref
.Left(list
[i
].GetLength() + 1) == list
[i
] + _T("/"))
58 branch
= ref
.Mid(list
[i
].GetLength() + 1);
72 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
77 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
78 HDITEM HeaderItem
= {0};
79 HeaderItem
.mask
= HDI_FORMAT
;
80 for (int i
=0; i
<pHeader
->GetItemCount(); ++i
)
82 pHeader
->GetItem(i
, &HeaderItem
);
83 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
84 pHeader
->SetItem(i
, &HeaderItem
);
88 pHeader
->GetItem(nColumn
, &HeaderItem
);
89 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
90 pHeader
->SetItem(nColumn
, &HeaderItem
);
94 class CRefLeafListCompareFunc
97 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){
98 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
100 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
103 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
105 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
108 int Compare(LPARAM lParam1
, LPARAM lParam2
)
111 (CShadowTree
*)m_pList
->GetItemData((int)lParam1
),
112 (CShadowTree
*)m_pList
->GetItemData((int)lParam2
));
115 int Compare(const CShadowTree
* pLeft
, const CShadowTree
* pRight
)
117 int result
=CompareNoDesc(pLeft
,pRight
);
123 int CompareNoDesc(const CShadowTree
* pLeft
, const CShadowTree
* pRight
)
127 case CBrowseRefsDlg::eCol_Name
: return SortStrCmp(pLeft
->GetRefName(), pRight
->GetRefName());
128 case CBrowseRefsDlg::eCol_Upstream
: return SortStrCmp(pLeft
->m_csUpstream
, pRight
->m_csUpstream
);
129 case CBrowseRefsDlg::eCol_Date
: return ((pLeft
->m_csDate
== pRight
->m_csDate
) ? 0 : ((pLeft
->m_csDate
> pRight
->m_csDate
) ? 1 : -1));
130 case CBrowseRefsDlg::eCol_Msg
: return SortStrCmp(pLeft
->m_csSubject
, pRight
->m_csSubject
);
131 case CBrowseRefsDlg::eCol_LastAuthor
: return SortStrCmp(pLeft
->m_csAuthor
, pRight
->m_csAuthor
);
132 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
133 case CBrowseRefsDlg::eCol_Description
: return SortStrCmp(pLeft
->m_csDescription
, pRight
->m_csDescription
);
137 int SortStrCmp(const CString
& left
, const CString
& right
)
140 return StrCmpLogicalW(left
, right
);
141 return StrCmpI(left
, right
);
150 // CBrowseRefsDlg dialog
152 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
154 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=nullptr*/)
155 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
158 m_currSortDesc(false),
159 m_regCurrSortCol(L
"Software\\TortoiseGit\\RefBrowserSortCol", 0),
160 m_regCurrSortDesc(L
"Software\\TortoiseGit\\RefBrowserSortDesc", FALSE
),
161 m_initialRef(L
"HEAD"),
162 m_pickRef_Kind(gPickRef_All
),
163 m_pListCtrlRoot(nullptr),
165 m_SelectedFilters(LOGFILTER_ALL
),
167 m_bIncludeNestedRefs(TRUE
),
168 m_bPickedRefSet(false)
170 // get short/long datetime setting from registry
171 DWORD RegUseShortDateFormat
= CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE
);
172 if (RegUseShortDateFormat
)
173 m_DateFormat
= DATE_SHORTDATE
;
175 m_DateFormat
= DATE_LONGDATE
;
176 // get relative time display setting from registry
177 DWORD regRelativeTimes
= CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE
);
178 m_bRelativeTimes
= (regRelativeTimes
!= 0);
180 m_regIncludeNestedRefs
= CRegDWORD(_T("Software\\TortoiseGit\\RefBrowserIncludeNestedRefs"), TRUE
);
182 m_currSortCol
= m_regCurrSortCol
;
183 m_currSortDesc
= m_regCurrSortDesc
== TRUE
;
186 CBrowseRefsDlg::~CBrowseRefsDlg()
188 m_regCurrSortCol
= m_currSortCol
;
189 m_regCurrSortDesc
= m_currSortDesc
;
192 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
194 CDialog::DoDataExchange(pDX
);
195 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
196 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
197 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
198 DDX_Check(pDX
, IDC_INCLUDENESTEDREFS
, m_bIncludeNestedRefs
);
202 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
203 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
204 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
206 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
208 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
209 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnItemChangedListRefLeafs
)
210 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
211 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
212 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
213 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
214 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED
, OnClickedCancelFilter
)
216 ON_BN_CLICKED(IDC_CURRENTBRANCH
, OnBnClickedCurrentbranch
)
217 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS
, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs
)
221 // CBrowseRefsDlg message handlers
223 void CBrowseRefsDlg::OnBnClickedOk()
225 if (m_bPickOne
|| m_ListRefLeafs
.GetSelectedCount() != 2)
232 popupMenu
.CreatePopupMenu();
234 std::vector
<CShadowTree
*> selectedLeafs
;
235 GetSelectedLeaves(selectedLeafs
);
237 popupMenu
.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG
);
238 popupMenu
.SetDefaultItem(1);
239 popupMenu
.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")), IDI_LOG
);
240 popupMenu
.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")), IDI_LOG
);
243 GetDlgItem(IDOK
)->GetWindowRect(&rect
);
244 int selection
= popupMenu
.TrackPopupMenuEx(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, rect
.left
, rect
.top
, this, 0);
252 m_bPickedRefSet
= true;
253 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T(".."));
259 m_bPickedRefSet
= true;
260 m_pickedRef
= GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..."));
269 BOOL
CBrowseRefsDlg::OnInitDialog()
271 CResizableStandAloneDialog::OnInitDialog();
272 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
274 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
275 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
);
276 m_ctrlFilter
.SetInfoIcon(IDI_FILTEREDIT
);
279 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
280 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
281 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, TOP_LEFT
);
282 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, TOP_LEFT
, TOP_RIGHT
);
283 AddAnchor(IDC_INFOLABEL
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
284 AddAnchor(IDC_INCLUDENESTEDREFS
, BOTTOM_LEFT
);
285 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
287 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle() | LVS_EX_INFOTIP
);
288 static UINT columnNames
[] = { IDS_BRANCHNAME
, IDS_TRACKEDBRANCH
, IDS_DATELASTCOMMIT
, IDS_LASTCOMMIT
, IDS_LASTAUTHOR
, IDS_HASH
, IDS_DESCRIPTION
};
289 static int columnWidths
[] = { 0, 0, 0, 300, 0, 0, 80 };
290 DWORD dwDefaultColumns
= (1 << eCol_Name
) | (1 << eCol_Upstream
) | (1 << eCol_Date
) | (1 << eCol_Msg
) |
291 (1 << eCol_LastAuthor
) | (1 << eCol_Hash
) | (1 << eCol_Description
);
292 m_ListRefLeafs
.m_bAllowHiding
= false;
293 m_ListRefLeafs
.Init();
294 m_ListRefLeafs
.m_ColumnManager
.SetNames(columnNames
, _countof(columnNames
));
295 m_ListRefLeafs
.m_ColumnManager
.ReadSettings(dwDefaultColumns
, 0, _T("BrowseRefs"), _countof(columnNames
), columnWidths
);
296 m_bPickedRefSet
= false;
298 AddAnchor(IDOK
,BOTTOM_RIGHT
);
299 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
300 AddAnchor(IDC_CURRENTBRANCH
, BOTTOM_RIGHT
);
302 m_bIncludeNestedRefs
= !!m_regIncludeNestedRefs
;
305 Refresh(m_initialRef
);
307 EnableSaveRestore(L
"BrowseRefs");
309 CString sWindowTitle
;
310 GetWindowText(sWindowTitle
);
311 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
313 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
316 m_ListRefLeafs
.ModifyStyle(0, LVS_SINGLESEL
);
318 m_ListRefLeafs
.SetFocus();
322 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
324 int posSlash
=nameLeft
.Find('/');
329 nameLeft
.Empty();//Nothing left
333 nameSub
=nameLeft
.Left(posSlash
);
334 nameLeft
=nameLeft
.Mid(posSlash
+1);
336 if(nameSub
.IsEmpty())
339 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
342 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
343 nextNode
.m_csRefName
=nameSub
;
344 nextNode
.m_pParent
=this;
348 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
352 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
354 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
356 //Match of leaf name. Try match on total name.
357 CString totalRefName
= GetRefName();
358 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
359 return this; //Also match. Found.
364 //Not a leaf. Search all nodes.
365 for (auto itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
367 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
369 return pSubtree
; //Found
372 return nullptr; //Not found
375 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
377 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
378 //List ctrl selection?
379 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
382 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
383 m_ListRefLeafs
.GetNextSelectedItem(pos
));
384 return pTree
->GetRefName();
386 else if (pos
&& !pickFirstSelIfMultiSel
)
388 // at least one leaf is selected
391 while ((index
= m_ListRefLeafs
.GetNextSelectedItem(pos
)) >= 0)
393 CString ref
= ((CShadowTree
*)m_ListRefLeafs
.GetItemData(index
))->GetRefName();
394 if(wcsncmp(ref
, L
"refs/", 5) == 0)
396 if(wcsncmp(ref
, L
"heads/", 6) == 0)
398 refs
+= ref
+ _T(" ");
404 //Tree ctrl selection?
405 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
408 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
409 return pTree
->GetRefName();
412 return CString();//None
415 void CBrowseRefsDlg::Refresh(CString selectRef
)
418 if (g_Git
.GetRemoteList(remotes
))
419 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR
);
421 if(!selectRef
.IsEmpty())
423 if(selectRef
== "HEAD")
425 if (g_Git
.GetCurrentBranchFromFile(g_Git
.m_CurrentDir
, selectRef
))
428 selectRef
= L
"refs/heads/" + selectRef
;
432 selectRef
= GetSelectedRef(false, true);
434 m_RefTreeCtrl
.DeleteAllItems();
435 m_ListRefLeafs
.DeleteAllItems();
436 m_TreeRoot
.m_ShadowTree
.clear();
437 m_TreeRoot
.m_csRefName
= "refs";
438 m_TreeRoot
.m_hTree
= m_RefTreeCtrl
.InsertItem(L
"refs", nullptr, nullptr);
439 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
442 MAP_REF_GITREVREFBROWSER refMap
;
443 if (GitRevRefBrowser::GetGitRevRefMap(refMap
, err
, [&](const CString
& refName
)
445 //Use ref based on m_pickRef_Kind
446 if (wcsncmp(refName
, L
"refs/heads/", 11) == 0 && !(m_pickRef_Kind
& gPickRef_Head
))
448 if (wcsncmp(refName
, L
"refs/tags/", 10) == 0 && !(m_pickRef_Kind
& gPickRef_Tag
))
450 if (wcsncmp(refName
, L
"refs/remotes/", 13) == 0 && !(m_pickRef_Kind
& gPickRef_Remote
))
452 if (m_pickRef_Kind
== gPickRef_Remote
&& wcsncmp(refName
, L
"refs/remotes/", 13) != 0) // do not show refs/stash if only remote branches are requested
457 MessageBox(_T("Get refs failed:") + err
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
461 for (auto iterRefMap
= refMap
.cbegin(); iterRefMap
!= refMap
.cend(); ++iterRefMap
)
463 CShadowTree
& treeLeaf
= GetTreeNode(iterRefMap
->first
, nullptr, true);
464 GitRevRefBrowser ref
= iterRefMap
->second
;
466 treeLeaf
.m_csRefHash
= ref
.m_CommitHash
.ToString();
467 treeLeaf
.m_csUpstream
= ref
.m_UpstreamRef
;
468 CGit::GetShortName(treeLeaf
.m_csUpstream
, treeLeaf
.m_csUpstream
, L
"refs/remotes/");
469 treeLeaf
.m_csSubject
= ref
.GetSubject();
470 treeLeaf
.m_csAuthor
= ref
.GetAuthorName();
471 treeLeaf
.m_csDate
= ref
.GetAuthorDate();
472 treeLeaf
.m_csDescription
= ref
.m_Description
;
475 // always expand the tree first
476 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
, TVE_EXPAND
);
478 // try exact match first
479 if (!selectRef
.IsEmpty() && !SelectRef(selectRef
, true))
480 SelectRef(selectRef
, false);
483 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
487 CString newRefName
= GetFullRefName(refName
);
488 if(!newRefName
.IsEmpty())
489 refName
= newRefName
;
490 //else refName is not a valid ref. Try to select as good as possible.
492 if (_wcsnicmp(refName
, L
"refs", 4) != 0)
493 return false; // Not a ref name
495 CShadowTree
& treeLeafHead
= GetTreeNode(refName
, nullptr, false);
496 if (treeLeafHead
.m_hTree
)
498 //Not a leaf. Select tree node and return
499 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
503 if (!treeLeafHead
.m_pParent
)
504 return false; //Weird... should not occur.
506 //This is the current head.
507 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
509 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
511 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
512 if(pCurrShadowTree
== &treeLeafHead
)
514 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
515 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
522 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
526 if(_wcsnicmp(refName
, L
"refs/", 5) == 0)
527 refName
=refName
.Mid(5);
528 pTreePos
=&m_TreeRoot
;
530 if(refName
.IsEmpty())
531 return *pTreePos
;//Found leaf
533 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
536 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
537 ASSERT(!bCreateIfNotExist
);
541 if(!refName
.IsEmpty())
543 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
544 //Leafs are for the list control.
545 if (!pNextTree
->m_hTree
)
547 //New tree. Create node in control.
548 pNextTree
->m_hTree
= m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
, pTreePos
->m_hTree
, nullptr);
549 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
553 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
557 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
559 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
562 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
565 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
567 m_ListRefLeafs
.DeleteAllItems();
569 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
575 FillListCtrlForShadowTree(pTree
,L
"",true);
576 m_ListRefLeafs
.m_ColumnManager
.SetVisible(eCol_Upstream
, pTree
->IsFrom(L
"refs/heads"));
577 m_ListRefLeafs
.AdjustColumnWidths();
580 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
585 m_ctrlFilter
.GetWindowText(filter
);
587 bool positive
= filter
[0] != '!';
589 filter
= filter
.Mid(1);
590 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
591 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== "refs" && !pTree
->m_pParent
) && IsMatchFilter(pTree
, ref
, filter
, positive
))
593 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
595 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
596 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
597 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Upstream
, pTree
->m_csUpstream
);
598 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Date
, pTree
->m_csDate
!= 0 ? CLoglistUtils::FormatDateAndTime(pTree
->m_csDate
, m_DateFormat
, true, m_bRelativeTimes
) : _T(""));
599 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
600 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
601 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
602 CString descrition
= pTree
->m_csDescription
;
603 descrition
.Replace(L
"\n", L
" ");
604 m_ListRefLeafs
.SetItemText(indexItem
, eCol_Description
, descrition
);
610 if (!isFirstLevel
&& !m_bIncludeNestedRefs
)
612 else if (!isFirstLevel
)
613 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
615 m_pListCtrlRoot
= pTree
;
616 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
618 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
623 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
624 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
626 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
630 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
, bool positive
)
632 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
635 msg
= msg
.MakeLower();
637 if (msg
.Find(filter
) >= 0)
641 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
643 CString msg
= pTree
->m_csSubject
;
644 msg
= msg
.MakeLower();
646 if (msg
.Find(filter
) >= 0)
650 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
652 CString msg
= pTree
->m_csAuthor
;
653 msg
= msg
.MakeLower();
655 if (msg
.Find(filter
) >= 0)
659 if (m_SelectedFilters
& LOGFILTER_REVS
)
661 CString msg
= pTree
->m_csRefHash
;
662 msg
= msg
.MakeLower();
664 if (msg
.Find(filter
) >= 0)
670 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
672 ASSERT(!leafs
.empty());
675 UINT mbIcon
=MB_ICONQUESTION
;
677 bool bIsRemoteBranch
= false;
678 bool bIsBranch
= false;
679 if (leafs
[0]->IsFrom(L
"refs/remotes/")) {bIsBranch
= true; bIsRemoteBranch
= true;}
680 else if (leafs
[0]->IsFrom(L
"refs/heads/")) {bIsBranch
= true;}
684 if(leafs
.size() == 1)
686 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
687 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)branchToDelete
);
689 //Check if branch is fully merged in HEAD
690 if (!g_Git
.IsFastForward(leafs
[0]->GetRefName(), _T("HEAD")))
692 csMessage
+= L
"\r\n\r\n";
693 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
694 mbIcon
= MB_ICONWARNING
;
699 csMessage
+= L
"\r\n\r\n";
700 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
701 mbIcon
= MB_ICONWARNING
;
706 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
708 csMessage
+= L
"\r\n\r\n";
709 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
710 mbIcon
= MB_ICONWARNING
;
714 csMessage
+= L
"\r\n\r\n";
715 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
716 mbIcon
= MB_ICONWARNING
;
721 else if(leafs
[0]->IsFrom(L
"refs/tags/"))
723 if(leafs
.size() == 1)
725 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
726 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tagToDelete
);
729 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
732 return CMessageBox::Show(m_hWnd
, csMessage
, _T("TortoiseGit"), MB_YESNO
| mbIcon
) == IDYES
;
735 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
)
737 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
738 if(!DoDeleteRef((*i
)->GetRefName()))
743 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
)
745 bool bIsRemoteBranch
= false;
746 bool bIsBranch
= false;
747 if (wcsncmp(completeRefName
, L
"refs/remotes/",13) == 0) {bIsBranch
= true; bIsRemoteBranch
= true;}
748 else if (wcsncmp(completeRefName
, L
"refs/heads/",11) == 0) {bIsBranch
= true;}
752 CString branchToDelete
= completeRefName
.Mid(13);
753 CString remoteName
, remoteBranchToDelete
;
754 if (SplitRemoteBranchName(branchToDelete
, remoteName
, remoteBranchToDelete
))
757 if (CAppUtils::IsSSHPutty())
758 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
760 CSysProgressDlg sysProgressDlg
;
761 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
762 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
763 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
764 sysProgressDlg
.SetShowProgressBar(false);
765 sysProgressDlg
.ShowModal(this, true);
768 list
.push_back(_T("refs/heads/") + remoteBranchToDelete
);
769 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
771 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
772 sysProgressDlg
.Stop();
776 sysProgressDlg
.Stop();
781 if (g_Git
.DeleteRef(completeRefName
))
783 CMessageBox::Show(m_hWnd
, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
787 else if (wcsncmp(completeRefName
, L
"refs/tags/", 10) == 0)
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
);
798 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
800 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
803 return pLeaf
->GetRefName();
807 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
809 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
810 else if (pWndFrom
== &m_ListRefLeafs
)
812 CRect headerPosition
;
813 m_ListRefLeafs
.GetHeaderCtrl()->GetWindowRect(headerPosition
);
814 if (!headerPosition
.PtInRect(point
))
815 OnContextMenu_ListRefLeafs(point
);
819 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
821 CPoint clientPoint
=point
;
822 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
824 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
826 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
828 VectorPShadowTree tree
;
829 ShowContextMenu(point
,hTreeItem
,tree
);
832 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree
& selectedLeafs
)
834 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
835 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
838 selectedLeafs
.push_back((CShadowTree
*)m_ListRefLeafs
.GetItemData(m_ListRefLeafs
.GetNextSelectedItem(pos
)));
842 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
844 std::vector
<CShadowTree
*> selectedLeafs
;
845 GetSelectedLeaves(selectedLeafs
);
846 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
849 CString
CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree
& selectedLeafs
, const CString
&lastSelected
, const CString
&separator
)
851 ASSERT(selectedLeafs
.size() == 2);
853 if (selectedLeafs
.at(0)->GetRefName() == lastSelected
)
854 return g_Git
.StripRefName(selectedLeafs
.at(1)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
856 return g_Git
.StripRefName(selectedLeafs
.at(0)->GetRefName()) + separator
+ g_Git
.StripRefName(lastSelected
);
859 int findVectorPosition(const STRING_VECTOR
& vector
, const CString
& entry
)
862 for (auto it
= vector
.cbegin(); it
!= vector
.cend(); ++it
, ++i
)
870 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
873 popupMenu
.CreatePopupMenu();
875 bool bAddSeparator
= false;
878 if(selectedLeafs
.size()==1)
880 bAddSeparator
= true;
882 bool bShowReflogOption
= false;
883 bool bShowFetchOption
= false;
884 bool bShowRenameOption
= false;
885 bool bShowCreateBranchOption
= false;
886 bool bShowEditBranchDescriptionOption
= false;
888 CString fetchFromCmd
;
890 if(selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
892 bShowReflogOption
= true;
893 bShowRenameOption
= true;
894 bShowEditBranchDescriptionOption
= true;
896 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes/"))
898 bShowReflogOption
= true;
899 bShowFetchOption
= true;
900 bShowCreateBranchOption
= true;
902 CString remoteBranch
;
903 if (SplitRemoteBranchName(selectedLeafs
[0]->GetRefName(), remoteName
, remoteBranch
))
904 bShowFetchOption
= false;
906 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
910 temp
.LoadString(IDS_MENULOG
);
911 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
912 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
913 if(bShowReflogOption
)
915 temp
.LoadString(IDS_MENUREFLOG
);
916 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, temp
, IDI_LOG
);
919 popupMenu
.AppendMenu(MF_SEPARATOR
);
920 bAddSeparator
= false;
924 bAddSeparator
= true;
925 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
929 popupMenu
.AppendMenu(MF_SEPARATOR
);
931 bAddSeparator
= false;
935 if (selectedLeafs
[0]->GetRefName() != _T("refs/heads/") + g_Git
.GetCurrentBranch())
937 str
.Format(IDS_LOG_POPUP_MERGEREV
, (LPCTSTR
)g_Git
.GetCurrentBranch());
938 popupMenu
.AppendMenuIcon(eCmd_Merge
, str
, IDI_MERGE
);
940 popupMenu
.AppendMenuIcon(eCmd_Switch
, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS
)), IDI_SWITCH
);
941 popupMenu
.AppendMenu(MF_SEPARATOR
);
944 if(bShowCreateBranchOption
)
946 bAddSeparator
= true;
947 temp
.LoadString(IDS_MENUBRANCH
);
948 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
951 if (bShowEditBranchDescriptionOption
)
953 bAddSeparator
= true;
954 popupMenu
.AppendMenuIcon(eCmd_EditBranchDescription
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
)), IDI_RENAME
);
956 if(bShowRenameOption
)
958 bAddSeparator
= true;
959 popupMenu
.AppendMenuIcon(eCmd_Rename
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME
)), IDI_RENAME
);
962 if (m_bHasWC
&& selectedLeafs
[0]->IsFrom(L
"refs/heads/"))
965 popupMenu
.AppendMenu(MF_SEPARATOR
);
966 bAddSeparator
= true;
967 if (!selectedLeafs
[0]->m_csUpstream
.IsEmpty())
968 popupMenu
.AppendMenuIcon(eCmd_UpstreamDrop
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH
)));
969 popupMenu
.AppendMenuIcon(eCmd_UpstreamSet
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH
)));
972 else if(selectedLeafs
.size() == 2)
974 bAddSeparator
= true;
975 popupMenu
.AppendMenuIcon(eCmd_Diff
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS
)), IDI_DIFF
);
976 popupMenu
.AppendMenuIcon(eCmd_UnifiedDiff
, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF
)), IDI_DIFF
);
978 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("..")));
979 popupMenu
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
980 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedLeafs
, m_sLastSelected
, _T("...")));
981 popupMenu
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
984 if(!selectedLeafs
.empty())
986 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
989 popupMenu
.AppendMenu(MF_SEPARATOR
);
990 CString menuItemName
;
991 if(selectedLeafs
.size() == 1)
992 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
994 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
996 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
997 bAddSeparator
= true;
999 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
1002 popupMenu
.AppendMenu(MF_SEPARATOR
);
1003 CString menuItemName
;
1004 if(selectedLeafs
.size() == 1)
1005 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
1007 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
1009 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
1010 bAddSeparator
= true;
1012 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
1015 popupMenu
.AppendMenu(MF_SEPARATOR
);
1016 CString menuItemName
;
1017 if(selectedLeafs
.size() == 1)
1018 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
1020 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
1022 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
1023 bAddSeparator
= true;
1028 if (hTreePos
&& selectedLeafs
.empty())
1030 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
1031 if(pTree
->IsFrom(L
"refs/remotes"))
1034 popupMenu
.AppendMenu(MF_SEPARATOR
);
1035 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES
)), IDI_SETTINGS
);
1036 bAddSeparator
= true;
1037 if(selectedLeafs
.empty())
1039 CString remoteBranch
;
1040 if (SplitRemoteBranchName(pTree
->GetRefName(), remoteName
, remoteBranch
))
1042 int pos
= findVectorPosition(remotes
, remoteName
);
1046 temp
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, (LPCTSTR
)remoteName
);
1047 popupMenu
.AppendMenuIcon(eCmd_Fetch
, temp
, IDI_PULL
);
1049 temp
.LoadString(IDS_DELETEREMOTETAG
);
1050 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteTag
| (pos
<< 16), temp
, IDI_DELETE
);
1054 if(pTree
->IsFrom(L
"refs/heads"))
1057 popupMenu
.AppendMenu(MF_SEPARATOR
);
1059 temp
.LoadString(IDS_MENUBRANCH
);
1060 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
1062 if(pTree
->IsFrom(L
"refs/tags"))
1065 popupMenu
.AppendMenu(MF_SEPARATOR
);
1067 temp
.LoadString(IDS_MENUTAG
);
1068 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, temp
, IDI_TAG
);
1069 temp
.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS
);
1070 popupMenu
.AppendMenuIcon(eCmd_DeleteAllTags
, temp
, IDI_DELETE
);
1071 if (!remotes
.empty())
1073 popupMenu
.AppendMenu(MF_SEPARATOR
);
1075 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, 0);
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((CShadowTree
*)m_ListRefLeafs
.GetItemData(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
);
1224 case eCmd_EditBranchDescription
:
1227 dlg
.m_sHintText
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1228 dlg
.m_sInputText
= selectedLeafs
[0]->m_csDescription
;
1229 dlg
.m_sTitle
.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION
);
1230 dlg
.m_bUseLogWidth
= true;
1231 if(dlg
.DoModal() == IDOK
)
1233 CAppUtils::UpdateBranchDescription(selectedLeafs
[0]->GetRefsHeadsName(), dlg
.m_sInputText
);
1238 case eCmd_UpstreamDrop
:
1241 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1242 g_Git
.UnsetConfigValue(key
);
1243 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1244 g_Git
.UnsetConfigValue(key
);
1248 case eCmd_UpstreamSet
:
1250 CString newRef
= CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote
, false);
1251 if (newRef
.IsEmpty() || newRef
.Find(_T("refs/remotes/")) != 0)
1253 CString remote
, branch
;
1254 if (SplitRemoteBranchName(newRef
, remote
, branch
))
1257 key
.Format(_T("branch.%s.remote"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1258 g_Git
.SetConfigValue(key
, remote
);
1259 key
.Format(_T("branch.%s.merge"), (LPCTSTR
)selectedLeafs
[0]->GetRefsHeadsName());
1260 g_Git
.SetConfigValue(key
, _T("refs/heads/") + branch
);
1267 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
1269 for (auto i
= leafs
.cbegin(); i
!= leafs
.cend(); ++i
)
1270 if(!(*i
)->IsFrom(from
))
1275 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
1277 if (pMsg
->message
== WM_KEYDOWN
)
1279 switch (pMsg
->wParam
)
1283 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1285 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1287 PostMessage(WM_COMMAND, IDOK);
1295 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
1297 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
1299 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
1311 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1313 m_ctrlFilter
.SetSel(0, -1, FALSE
);
1314 m_ctrlFilter
.SetFocus();
1323 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
1326 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1328 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1331 if(m_currSortCol
== pNMLV
->iSubItem
)
1332 m_currSortDesc
= !m_currSortDesc
;
1335 m_currSortCol
= pNMLV
->iSubItem
;
1336 m_currSortDesc
= false;
1339 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1340 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1342 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1345 void CBrowseRefsDlg::OnDestroy()
1347 if (!m_bPickedRefSet
)
1348 m_pickedRef
= GetSelectedRef(true, false);
1350 CResizableStandAloneDialog::OnDestroy();
1353 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1355 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1358 CShadowTree
*item
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(pNMListView
->iItem
);
1359 if (item
&& pNMListView
->uNewState
== LVIS_SELECTED
)
1360 m_sLastSelected
= item
->GetRefName();
1365 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
)
1369 if (!m_ListRefLeafs
.GetFirstSelectedItemPosition())
1374 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
, bool pickMultipleRefsOrRange
)
1376 CBrowseRefsDlg
dlg(CString(), nullptr);
1378 if(initialRef
.IsEmpty())
1379 initialRef
= L
"HEAD";
1380 dlg
.m_initialRef
= initialRef
;
1381 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1382 dlg
.m_bPickOne
= !pickMultipleRefsOrRange
;
1384 if(dlg
.DoModal() != IDOK
)
1387 return dlg
.m_pickedRef
;
1390 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1393 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1394 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1395 if(resultRef
.IsEmpty())
1397 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1398 resultRef
= resultRef
.Mid(5);
1399 // if(wcsncmp(resultRef,L"heads/",6)==0)
1400 // resultRef = resultRef.Mid(6);
1402 //Find closest match of choice in combobox
1404 int matchLength
= 0;
1405 CString comboRefName
;
1406 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1408 pComboBox
->GetLBText(i
, comboRefName
);
1409 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1410 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1411 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1413 matchLength
= comboRefName
.GetLength();
1418 pComboBox
->SetCurSel(ixFound
);
1420 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)
1425 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1427 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1430 if (!pDispInfo
->item
.pszText
)
1431 return; //User canceled changing
1433 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1435 if(!pTree
->IsFrom(L
"refs/heads/"))
1437 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1441 CString selectedTreeRef
;
1442 HTREEITEM hTree
= m_RefTreeCtrl
.GetSelectedItem();
1445 CShadowTree
* pTree2
= (CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
1446 selectedTreeRef
= pTree2
->GetRefName();
1449 CString origName
= pTree
->GetRefName().Mid(11);
1452 if (m_pListCtrlRoot
)
1453 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1454 newName
+= pDispInfo
->item
.pszText
;
1456 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1458 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1462 CString newNameTrunced
= newName
.Mid(11);
1465 if(g_Git
.Run(L
"git.exe branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &errorMsg
, CP_UTF8
) != 0)
1467 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1470 //Do as if it failed to rename. Let Refresh() do the job.
1473 Refresh(selectedTreeRef
);
1475 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1477 // AfxMessageBox(W_csPopup);
1480 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1482 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1485 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1487 if(!pTree
->IsFrom(L
"refs/heads/"))
1489 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1494 void CBrowseRefsDlg::OnEnChangeEditFilter()
1496 SetTimer(IDT_FILTER
, 1000, nullptr);
1499 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1501 if (nIDEvent
== IDT_FILTER
)
1503 KillTimer(IDT_FILTER
);
1504 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1507 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1510 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1512 // FIXME: x64 version would get this function called with unexpected parameters.
1516 RECT
* rect
= (LPRECT
)lParam
;
1519 point
= CPoint(rect
->left
, rect
->bottom
);
1520 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1522 if (popup
.CreatePopupMenu())
1524 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1525 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1527 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1528 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1530 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1531 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1533 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1534 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1536 temp
.LoadString(IDS_LOG_FILTER_TOGGLE
);
1537 popup
.AppendMenu(MF_STRING
| MF_ENABLED
, LOGFILTER_TOGGLE
, temp
);
1539 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this, 0);
1542 if (selection
== LOGFILTER_TOGGLE
)
1543 m_SelectedFilters
= (~m_SelectedFilters
) & LOGFILTER_ALL
;
1545 m_SelectedFilters
^= selection
;
1547 SetTimer(IDT_FILTER
, 1000, nullptr);
1553 LRESULT
CBrowseRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
1555 KillTimer(LOGFILTER_TIMER
);
1556 m_ctrlFilter
.SetWindowText(_T(""));
1557 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1561 void CBrowseRefsDlg::SetFilterCueText()
1563 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1566 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1567 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1569 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1571 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1573 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1576 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1578 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1580 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1583 if (m_SelectedFilters
& LOGFILTER_REVS
)
1585 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1587 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1590 // to make the cue banner text appear more to the right of the edit control
1591 temp
= _T(" ") + temp
;
1592 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());
1595 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1597 m_pickedRef
= g_Git
.GetCurrentBranch(true);
1598 m_bPickedRefSet
= true;
1602 void CBrowseRefsDlg::UpdateInfoLabel()
1605 temp
.FormatMessage(IDS_REFBROWSE_INFO
, m_ListRefLeafs
.GetItemCount(), m_ListRefLeafs
.GetSelectedCount());
1606 SetDlgItemText(IDC_INFOLABEL
, temp
);
1609 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1612 m_regIncludeNestedRefs
= m_bIncludeNestedRefs
;