1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2012 - 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"
35 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
40 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
41 HDITEM HeaderItem
= {0};
42 HeaderItem
.mask
= HDI_FORMAT
;
43 for (int i
=0; i
<pHeader
->GetItemCount(); ++i
)
45 pHeader
->GetItem(i
, &HeaderItem
);
46 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
47 pHeader
->SetItem(i
, &HeaderItem
);
51 pHeader
->GetItem(nColumn
, &HeaderItem
);
52 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
53 pHeader
->SetItem(nColumn
, &HeaderItem
);
57 // CBrowseRefsDlg dialog
59 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
61 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=NULL*/)
62 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
65 m_currSortDesc(false),
66 m_initialRef(L
"HEAD"),
67 m_pickRef_Kind(gPickRef_All
),
68 m_pListCtrlRoot(NULL
),
70 m_SelectedFilters(LOGFILTER_ALL
)
75 CBrowseRefsDlg::~CBrowseRefsDlg()
79 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
81 CDialog::DoDataExchange(pDX
);
82 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
83 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
84 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
88 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
89 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
90 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
92 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
94 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
95 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
96 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
97 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
98 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
103 // CBrowseRefsDlg message handlers
105 void CBrowseRefsDlg::OnBnClickedOk()
110 BOOL
CBrowseRefsDlg::OnInitDialog()
112 CResizableStandAloneDialog::OnInitDialog();
113 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
115 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
116 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
);
117 m_ctrlFilter
.SetInfoIcon(IDI_FILTEREDIT
);
120 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
121 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
122 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, TOP_LEFT
);
123 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, TOP_LEFT
, TOP_RIGHT
);
124 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
126 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
128 temp
.LoadString(IDS_BRANCHNAME
);
129 m_ListRefLeafs
.InsertColumn(eCol_Name
, temp
, 0, 150);
130 temp
.LoadString(IDS_DATELASTCOMMIT
);
131 m_ListRefLeafs
.InsertColumn(eCol_Date
, temp
, 0, 100);
132 temp
.LoadString(IDS_LASTCOMMIT
);
133 m_ListRefLeafs
.InsertColumn(eCol_Msg
, temp
, 0, 300);
134 temp
.LoadString(IDS_LASTAUTHOR
);
135 m_ListRefLeafs
.InsertColumn(eCol_LastAuthor
, temp
, 0, 100);
136 temp
.LoadString(IDS_HASH
);
137 m_ListRefLeafs
.InsertColumn(eCol_Hash
, temp
, 0, 80);
139 AddAnchor(IDOK
,BOTTOM_RIGHT
);
140 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
142 Refresh(m_initialRef
);
144 EnableSaveRestore(L
"BrowseRefs");
146 CString sWindowTitle
;
147 GetWindowText(sWindowTitle
);
148 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
150 m_bHasWC
= !g_GitAdminDir
.IsBareRepo(g_Git
.m_CurrentDir
);
152 m_ListRefLeafs
.SetFocus();
156 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
158 int posSlash
=nameLeft
.Find('/');
163 nameLeft
.Empty();//Nothing left
167 nameSub
=nameLeft
.Left(posSlash
);
168 nameLeft
=nameLeft
.Mid(posSlash
+1);
170 if(nameSub
.IsEmpty())
173 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
176 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
177 nextNode
.m_csRefName
=nameSub
;
178 nextNode
.m_pParent
=this;
182 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
186 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
188 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
190 //Match of leaf name. Try match on total name.
191 CString totalRefName
= GetRefName();
192 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
193 return this; //Also match. Found.
198 //Not a leaf. Search all nodes.
199 for(TShadowTreeMap::iterator itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
201 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
203 return pSubtree
; //Found
206 return NULL
;//Not found
210 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
212 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
214 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
215 //List ctrl selection?
216 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
219 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
220 m_ListRefLeafs
.GetNextSelectedItem(pos
));
221 return pTree
->GetRefName();
225 //Tree ctrl selection?
226 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
229 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
230 return pTree
->GetRefName();
233 return CString();//None
236 void CBrowseRefsDlg::Refresh(CString selectRef
)
239 // g_Git.GetMapHashToFriendName(m_RefMap);
241 if(!selectRef
.IsEmpty())
243 if(selectRef
== "HEAD")
245 selectRef
= g_Git
.GetSymbolicRef(selectRef
, false);
250 selectRef
= GetSelectedRef(false, true);
253 m_RefTreeCtrl
.DeleteAllItems();
254 m_ListRefLeafs
.DeleteAllItems();
255 m_TreeRoot
.m_ShadowTree
.clear();
256 m_TreeRoot
.m_csRefName
= "refs";
257 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
258 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
261 g_Git
.Run(L
"git for-each-ref --format="
264 L
"%(authordate:relative)%04"
267 L
"%(authordate:iso8601)%03",
268 &allRefs
, NULL
, CP_UTF8
);
273 MAP_STRING_STRING refMap
;
275 //First sort on ref name
276 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
278 singleRef
.TrimLeft(L
"\r\n");
280 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
281 if(refName
.IsEmpty())
283 CString refRest
=singleRef
.Mid(valuePos
);
286 //Use ref based on m_pickRef_Kind
287 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
289 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
291 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
294 refMap
[refName
] = refRest
; //Use
300 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
302 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
303 CString values
=iterRefMap
->second
;
304 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
307 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
308 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
309 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
310 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
311 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
315 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
316 //Probably not on a branch. Select root node.
317 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
321 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
325 CString newRefName
= GetFullRefName(refName
);
326 if(!newRefName
.IsEmpty())
327 refName
= newRefName
;
328 //else refName is not a valid ref. Try to select as good as possible.
330 if(_wcsnicmp(refName
, L
"refs/", 5) != 0)
331 return false; // Not a ref name
333 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
334 if(treeLeafHead
.m_hTree
!= NULL
)
336 //Not a leaf. Select tree node and return
337 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
341 if(treeLeafHead
.m_pParent
==NULL
)
342 return false; //Weird... should not occur.
344 //This is the current head.
345 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
347 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
349 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
350 if(pCurrShadowTree
== &treeLeafHead
)
352 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
353 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
360 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
364 if(_wcsnicmp(refName
, L
"refs/", 5) == 0)
365 refName
=refName
.Mid(5);
366 pTreePos
=&m_TreeRoot
;
368 if(refName
.IsEmpty())
369 return *pTreePos
;//Found leaf
371 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
374 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
375 ASSERT(!bCreateIfNotExist
);
379 if(!refName
.IsEmpty())
381 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
382 //Leafs are for the list control.
383 if(pNextTree
->m_hTree
==NULL
)
385 //New tree. Create node in control.
386 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
387 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
391 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
395 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
397 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
400 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
403 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
405 m_ListRefLeafs
.DeleteAllItems();
407 m_currSortDesc
= false;
408 SetSortArrow(&m_ListRefLeafs
,-1,false);
410 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
416 FillListCtrlForShadowTree(pTree
,L
"",true);
419 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
424 m_ctrlFilter
.GetWindowText(filter
);
426 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
427 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== "refs" && pTree
->m_pParent
== NULL
) && IsMatchFilter(pTree
, ref
, filter
))
429 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
431 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
432 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
433 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
434 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
435 m_ListRefLeafs
.SetItemText(indexItem
,eCol_LastAuthor
, pTree
->m_csAuthor
);
436 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
444 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
446 m_pListCtrlRoot
= pTree
;
447 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
449 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
454 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
)
456 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
459 msg
= msg
.MakeLower();
461 if (msg
.Find(filter
) >= 0)
465 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
467 CString msg
= pTree
->m_csSubject
;
468 msg
= msg
.MakeLower();
470 if (msg
.Find(filter
) >= 0)
474 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
476 CString msg
= pTree
->m_csAuthor
;
477 msg
= msg
.MakeLower();
479 if (msg
.Find(filter
) >= 0)
483 if (m_SelectedFilters
& LOGFILTER_REVS
)
485 CString msg
= pTree
->m_csRefHash
;
486 msg
= msg
.MakeLower();
488 if (msg
.Find(filter
) >= 0)
494 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
496 ASSERT(!leafs
.empty());
499 UINT mbIcon
=MB_ICONQUESTION
;
501 bool bIsRemoteBranch
= false;
502 bool bIsBranch
= false;
503 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
504 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
508 if(leafs
.size() == 1)
510 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
511 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, branchToDelete
);
513 //Check if branch is fully merged in HEAD
514 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
515 CGitHash commonAncestor
;
516 CString commonAncestorstr
;
518 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
519 g_Git
.Run(cmd
, &commonAncestorstr
, NULL
, CP_UTF8
);
521 commonAncestor
=commonAncestorstr
;
523 if(commonAncestor
!= branchHash
)
525 csMessage
+= L
"\r\n\r\n";
526 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
527 mbIcon
= MB_ICONWARNING
;
532 csMessage
+= L
"\r\n\r\n";
533 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
534 mbIcon
= MB_ICONWARNING
;
539 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
541 csMessage
+= L
"\r\n\r\n";
542 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
543 mbIcon
= MB_ICONWARNING
;
547 csMessage
+= L
"\r\n\r\n";
548 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
549 mbIcon
= MB_ICONWARNING
;
554 else if(leafs
[0]->IsFrom(L
"refs/tags"))
556 if(leafs
.size() == 1)
558 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
559 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, tagToDelete
);
563 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
567 return CMessageBox::Show(m_hWnd
, csMessage
, _T("TortoiseGit"), MB_YESNO
| mbIcon
) == IDYES
;
571 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
573 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
574 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
579 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
581 bool bIsRemoteBranch
= false;
582 bool bIsBranch
= false;
583 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
584 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
588 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
592 int slash
= branchToDelete
.Find(L
'/');
595 CString remoteName
= branchToDelete
.Left(slash
);
596 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
598 if(CAppUtils::IsSSHPutty())
600 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
603 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
606 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
608 if(g_Git
.Run(cmd
,&errorMsg
,CP_UTF8
)!=0)
610 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
614 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
616 CString tagToDelete
= completeRefName
.Mid(10);
618 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
620 if(g_Git
.Run(cmd
,&errorMsg
,CP_UTF8
)!=0)
622 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
629 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
631 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
634 return pLeaf
->GetRefName();
638 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
640 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
641 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
644 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
646 CPoint clientPoint
=point
;
647 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
649 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
651 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
653 VectorPShadowTree tree
;
654 ShowContextMenu(point
,hTreeItem
,tree
);
658 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
660 std::vector
<CShadowTree
*> selectedLeafs
;
661 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
662 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
665 selectedLeafs
.push_back(
666 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
667 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
670 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
673 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
676 popupMenu
.CreatePopupMenu();
678 bool bAddSeparator
= false;
681 if(selectedLeafs
.size()==1)
683 bAddSeparator
= true;
685 bool bShowReflogOption
= false;
686 bool bShowFetchOption
= false;
687 bool bShowSwitchOption
= false;
688 bool bShowRenameOption
= false;
689 bool bShowCreateBranchOption
= false;
691 CString fetchFromCmd
;
693 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
695 bShowReflogOption
= true;
696 bShowSwitchOption
= true;
697 bShowRenameOption
= true;
699 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
701 bShowReflogOption
= true;
702 bShowFetchOption
= true;
703 bShowCreateBranchOption
= true;
705 int dummy
= 0;//Needed for tokenize
706 remoteName
= selectedLeafs
[0]->GetRefName();
707 remoteName
= remoteName
.Mid(13);
708 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
709 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, remoteName
);
711 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
716 temp
.LoadString(IDS_MENULOG
);
717 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
718 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
719 if(bShowReflogOption
)
721 temp
.LoadString(IDS_MENUREFLOG
);
722 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, temp
, IDI_LOG
);
725 popupMenu
.AppendMenu(MF_SEPARATOR
);
726 bAddSeparator
= false;
730 bAddSeparator
= true;
731 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
735 popupMenu
.AppendMenu(MF_SEPARATOR
);
737 bAddSeparator
= false;
740 popupMenu
.AppendMenuIcon(eCmd_Switch
, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS
)), IDI_SWITCH
);
741 popupMenu
.AppendMenu(MF_SEPARATOR
);
744 if(bShowCreateBranchOption
)
746 bAddSeparator
= true;
747 temp
.LoadString(IDS_MENUBRANCH
);
748 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
751 if(bShowRenameOption
)
753 bAddSeparator
= true;
754 popupMenu
.AppendMenuIcon(eCmd_Rename
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME
)), IDI_RENAME
);
757 else if(selectedLeafs
.size() == 2)
759 bAddSeparator
= true;
760 popupMenu
.AppendMenuIcon(eCmd_Diff
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS
)), IDI_DIFF
);
763 if(!selectedLeafs
.empty())
765 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
768 popupMenu
.AppendMenu(MF_SEPARATOR
);
769 CString menuItemName
;
770 if(selectedLeafs
.size() == 1)
771 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
773 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
775 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
776 bAddSeparator
= true;
778 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
781 popupMenu
.AppendMenu(MF_SEPARATOR
);
782 CString menuItemName
;
783 if(selectedLeafs
.size() == 1)
784 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
786 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
788 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
789 bAddSeparator
= true;
791 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
794 popupMenu
.AppendMenu(MF_SEPARATOR
);
795 CString menuItemName
;
796 if(selectedLeafs
.size() == 1)
797 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
799 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
801 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
802 bAddSeparator
= true;
807 if(hTreePos
!=NULL
&& selectedLeafs
.empty())
809 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
810 if(pTree
->IsFrom(L
"refs/remotes"))
813 popupMenu
.AppendMenu(MF_SEPARATOR
);
814 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES
)), IDI_SETTINGS
);
815 bAddSeparator
= true;
816 if(selectedLeafs
.empty())
818 int dummy
= 0;//Needed for tokenize
819 remoteName
= pTree
->GetRefName();
820 remoteName
= remoteName
.Mid(13);
821 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
822 if(!remoteName
.IsEmpty())
824 CString fetchFromCmd
;
825 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, remoteName
);
826 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
830 if(pTree
->IsFrom(L
"refs/heads"))
833 popupMenu
.AppendMenu(MF_SEPARATOR
);
835 temp
.LoadString(IDS_MENUBRANCH
);
836 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
838 if(pTree
->IsFrom(L
"refs/tags"))
841 popupMenu
.AppendMenu(MF_SEPARATOR
);
843 temp
.LoadString(IDS_MENUTAG
);
844 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, temp
, IDI_TAG
);
849 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
855 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
859 case eCmd_RepoBrowser
:
860 CAppUtils::RunTortoiseProc(_T("/command:repobrowser /path:\"") + g_Git
.m_CurrentDir
+ _T("\" /rev:") + selectedLeafs
[0]->GetRefName());
862 case eCmd_DeleteBranch
:
863 case eCmd_DeleteRemoteBranch
:
865 if(ConfirmDeleteRef(selectedLeafs
))
866 DoDeleteRefs(selectedLeafs
, true);
872 if(ConfirmDeleteRef(selectedLeafs
))
873 DoDeleteRefs(selectedLeafs
, true);
877 case eCmd_ShowReflog
:
879 CRefLogDlg
refLogDlg(this);
880 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
886 CAppUtils::Fetch(remoteName
);
892 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
897 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
899 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
904 CAddRemoteDlg(this).DoModal();
908 case eCmd_ManageRemotes
:
910 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(g_Git
.m_CurrentDir
), this).DoModal();
911 // CSettingGitRemote W_Remotes(m_cmdPath);
912 // W_Remotes.DoModal();
916 case eCmd_CreateBranch
:
918 CString
*commitHash
= NULL
;
919 if (selectedLeafs
.size() == 1)
920 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
921 CAppUtils::CreateBranchTag(false, commitHash
);
927 CAppUtils::CreateBranchTag(true);
936 selectedLeafs
[0]->m_csRefHash
,
937 selectedLeafs
[1]->m_csRefHash
);
944 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
946 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
947 if(!(*i
)->IsFrom(from
))
952 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
954 if (pMsg
->message
== WM_KEYDOWN
)
956 switch (pMsg
->wParam
)
960 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
962 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
964 PostMessage(WM_COMMAND, IDOK);
972 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
974 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
976 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
990 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
993 class CRefLeafListCompareFunc
996 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
998 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
1000 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
1003 int Compare(LPARAM lParam1
, LPARAM lParam2
)
1006 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
1007 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
1010 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
1012 int result
=CompareNoDesc(pLeft
,pRight
);
1018 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
1022 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
1023 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
1024 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
1025 case CBrowseRefsDlg::eCol_LastAuthor
: return pLeft
->m_csAuthor
.CompareNoCase(pRight
->m_csAuthor
);
1026 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
1039 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1041 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1044 if(m_currSortCol
== pNMLV
->iSubItem
)
1045 m_currSortDesc
= !m_currSortDesc
;
1048 m_currSortCol
= pNMLV
->iSubItem
;
1049 m_currSortDesc
= false;
1052 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1053 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1055 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1058 void CBrowseRefsDlg::OnDestroy()
1060 m_pickedRef
= GetSelectedRef(true, false);
1062 CResizableStandAloneDialog::OnDestroy();
1065 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1067 UNREFERENCED_PARAMETER(pNMHDR
);
1073 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
1075 CBrowseRefsDlg
dlg(CString(),NULL
);
1077 if(initialRef
.IsEmpty())
1078 initialRef
= L
"HEAD";
1079 dlg
.m_initialRef
= initialRef
;
1080 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1082 if(dlg
.DoModal() != IDOK
)
1085 return dlg
.m_pickedRef
;
1088 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1091 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1092 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1093 if(resultRef
.IsEmpty())
1095 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1096 resultRef
= resultRef
.Mid(5);
1097 // if(wcsncmp(resultRef,L"heads/",6)==0)
1098 // resultRef = resultRef.Mid(6);
1100 //Find closest match of choice in combobox
1102 int matchLength
= 0;
1103 CString comboRefName
;
1104 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1106 pComboBox
->GetLBText(i
, comboRefName
);
1107 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1108 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1109 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1111 matchLength
= comboRefName
.GetLength();
1116 pComboBox
->SetCurSel(ixFound
);
1118 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)
1123 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1125 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1128 if(pDispInfo
->item
.pszText
== NULL
)
1129 return; //User canceled changing
1131 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1133 if(!pTree
->IsFrom(L
"refs/heads"))
1135 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1139 CString origName
= pTree
->GetRefName().Mid(11);
1142 if(m_pListCtrlRoot
!= NULL
)
1143 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1144 newName
+= pDispInfo
->item
.pszText
;
1146 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1148 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1152 CString newNameTrunced
= newName
.Mid(11);
1155 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &errorMsg
, CP_UTF8
) != 0)
1157 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1160 //Do as if it failed to rename. Let Refresh() do the job.
1165 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1167 // AfxMessageBox(W_csPopup);
1171 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1173 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1176 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1178 if(!pTree
->IsFrom(L
"refs/heads"))
1180 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1185 void CBrowseRefsDlg::OnEnChangeEditFilter()
1187 SetTimer(IDT_FILTER
, 1000, NULL
);
1190 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1192 if (nIDEvent
== IDT_FILTER
)
1194 KillTimer(IDT_FILTER
);
1195 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1198 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1201 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1203 // FIXME: x64 version would get this function called with unexpected parameters.
1207 RECT
* rect
= (LPRECT
)lParam
;
1210 point
= CPoint(rect
->left
, rect
->bottom
);
1211 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1213 if (popup
.CreatePopupMenu())
1215 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1216 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1218 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1219 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1221 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1222 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1224 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1225 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1227 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this, 0);
1230 m_SelectedFilters
^= selection
;
1232 SetTimer(IDT_FILTER
, 1000, NULL
);
1238 void CBrowseRefsDlg::SetFilterCueText()
1240 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1243 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1244 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1246 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1248 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1250 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1253 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1255 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1257 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1260 if (m_SelectedFilters
& LOGFILTER_REVS
)
1262 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1264 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1267 // to make the cue banner text appear more to the right of the edit control
1268 temp
= _T(" ") + temp
;
1269 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());