1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2011 - 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
),
73 CBrowseRefsDlg::~CBrowseRefsDlg()
77 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
79 CDialog::DoDataExchange(pDX
);
80 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
81 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
85 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
86 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
87 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
89 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
91 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
92 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
93 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
97 // CBrowseRefsDlg message handlers
99 void CBrowseRefsDlg::OnBnClickedOk()
104 BOOL
CBrowseRefsDlg::OnInitDialog()
106 CResizableStandAloneDialog::OnInitDialog();
107 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
109 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
110 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
111 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
113 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
114 m_ListRefLeafs
.InsertColumn(eCol_Name
, L
"Name",0,150);
115 m_ListRefLeafs
.InsertColumn(eCol_Date
, L
"Date Last Commit",0,100);
116 m_ListRefLeafs
.InsertColumn(eCol_Msg
, L
"Last Commit",0,300);
117 m_ListRefLeafs
.InsertColumn(eCol_Hash
, L
"Hash",0,80);
119 AddAnchor(IDOK
,BOTTOM_RIGHT
);
120 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
122 Refresh(m_initialRef
);
124 EnableSaveRestore(L
"BrowseRefs");
126 CString sWindowTitle
;
127 GetWindowText(sWindowTitle
);
128 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
130 m_ListRefLeafs
.SetFocus();
134 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
136 int posSlash
=nameLeft
.Find('/');
141 nameLeft
.Empty();//Nothing left
145 nameSub
=nameLeft
.Left(posSlash
);
146 nameLeft
=nameLeft
.Mid(posSlash
+1);
148 if(nameSub
.IsEmpty())
151 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
154 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
155 nextNode
.m_csRefName
=nameSub
;
156 nextNode
.m_pParent
=this;
160 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
164 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
166 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
168 //Match of leaf name. Try match on total name.
169 CString totalRefName
= GetRefName();
170 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
171 return this; //Also match. Found.
176 //Not a leaf. Search all nodes.
177 for(TShadowTreeMap::iterator itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
179 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
181 return pSubtree
; //Found
184 return NULL
;//Not found
188 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
190 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
192 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
193 //List ctrl selection?
194 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
197 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
198 m_ListRefLeafs
.GetNextSelectedItem(pos
));
199 return pTree
->GetRefName();
203 //Tree ctrl selection?
204 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
207 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
208 return pTree
->GetRefName();
211 return CString();//None
214 void CBrowseRefsDlg::Refresh(CString selectRef
)
217 // g_Git.GetMapHashToFriendName(m_RefMap);
219 if(!selectRef
.IsEmpty())
221 if(selectRef
== "HEAD")
223 selectRef
= g_Git
.GetSymbolicRef(selectRef
, false);
228 selectRef
= GetSelectedRef(false, true);
231 m_RefTreeCtrl
.DeleteAllItems();
232 m_ListRefLeafs
.DeleteAllItems();
233 m_TreeRoot
.m_ShadowTree
.clear();
234 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
235 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
238 g_Git
.Run(L
"git for-each-ref --format="
241 L
"%(authordate:relative)%04"
244 L
"%(authordate:iso8601)%03",
245 &allRefs
, NULL
, CP_UTF8
);
250 MAP_STRING_STRING refMap
;
252 //First sort on ref name
253 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
255 singleRef
.TrimLeft(L
"\r\n");
257 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
258 if(refName
.IsEmpty())
260 CString refRest
=singleRef
.Mid(valuePos
);
263 //Use ref based on m_pickRef_Kind
264 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
266 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
268 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
271 refMap
[refName
] = refRest
; //Use
277 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
279 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
280 CString values
=iterRefMap
->second
;
281 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
284 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
285 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
286 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
287 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
288 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
292 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
293 //Probably not on a branch. Select root node.
294 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
298 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
302 CString newRefName
= GetFullRefName(refName
);
303 if(!newRefName
.IsEmpty())
304 refName
= newRefName
;
305 //else refName is not a valid ref. Try to select as good as possible.
307 if(wcsnicmp(refName
,L
"refs/",5)!=0)
308 return false; // Not a ref name
310 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
311 if(treeLeafHead
.m_hTree
!= NULL
)
313 //Not a leaf. Select tree node and return
314 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
318 if(treeLeafHead
.m_pParent
==NULL
)
319 return false; //Weird... should not occur.
321 //This is the current head.
322 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
324 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
326 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
327 if(pCurrShadowTree
== &treeLeafHead
)
329 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
330 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
337 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
341 if(wcsnicmp(refName
,L
"refs/",5)==0)
342 refName
=refName
.Mid(5);
343 pTreePos
=&m_TreeRoot
;
345 if(refName
.IsEmpty())
346 return *pTreePos
;//Found leaf
348 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
351 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
352 ASSERT(!bCreateIfNotExist
);
356 if(!refName
.IsEmpty())
358 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
359 //Leafs are for the list control.
360 if(pNextTree
->m_hTree
==NULL
)
362 //New tree. Create node in control.
363 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
364 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
368 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
372 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
374 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
377 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
380 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
382 m_ListRefLeafs
.DeleteAllItems();
384 m_currSortDesc
= false;
385 SetSortArrow(&m_ListRefLeafs
,-1,false);
387 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
393 FillListCtrlForShadowTree(pTree
,L
"",true);
396 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
400 if (!pTree
->m_csRefName
.IsEmpty())
402 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
404 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
405 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, refNamePrefix
+pTree
->m_csRefName
);
406 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
407 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
408 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
416 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
418 m_pListCtrlRoot
= pTree
;
419 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
421 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
426 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
428 ASSERT(!leafs
.empty());
433 UINT mbIcon
=MB_ICONQUESTION
;
434 csMessage
= L
"Are you sure you want to delete ";
436 bool bIsRemoteBranch
= false;
437 bool bIsBranch
= false;
438 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
439 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
443 if(leafs
.size() == 1)
445 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
446 csTitle
.Format(L
"Confirm deletion of %sbranch %s",
447 bIsRemoteBranch
? L
"remote ": L
"",
452 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
453 csMessage
+= L
"branch:\r\n\r\n<b>";
454 csMessage
+= branchToDelete
;
455 csMessage
+= L
"</b>";
457 //Check if branch is fully merged in HEAD
458 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
459 CGitHash commonAncestor
;
460 CString commonAncestorstr
;
462 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
463 g_Git
.Run(cmd
, &commonAncestorstr
, NULL
, CP_UTF8
);
465 commonAncestor
=commonAncestorstr
;
467 if(commonAncestor
!= branchHash
)
469 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
470 mbIcon
= MB_ICONWARNING
;
474 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
475 mbIcon
= MB_ICONWARNING
;
480 csTitle
.Format(L
"Confirm deletion of %d %sbranches",
482 bIsRemoteBranch
? L
"remote ": L
"");
484 CString csMoreMsgText
;
485 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
486 csMessage
+= csMoreMsgText
;
488 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
489 csMessage
+= L
"branches";
491 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
492 mbIcon
= MB_ICONWARNING
;
496 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
497 mbIcon
= MB_ICONWARNING
;
502 else if(leafs
[0]->IsFrom(L
"refs/tags"))
504 if(leafs
.size() == 1)
506 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
507 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
508 csMessage
+= "the tag:\r\n\r\n<b>";
509 csMessage
+= tagToDelete
;
514 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
515 csTitle
.Format(L
"Confirm deletion of %d tags", leafs
.size());
516 CString csMoreMsgText
;
517 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
518 csMessage
+= csMoreMsgText
;
519 csMessage
+= L
"tags";
523 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
527 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
529 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
530 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
535 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
537 bool bIsRemoteBranch
= false;
538 bool bIsBranch
= false;
539 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
540 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
544 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
548 int slash
= branchToDelete
.Find(L
'/');
551 CString remoteName
= branchToDelete
.Left(slash
);
552 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
554 if(CAppUtils::IsSSHPutty())
556 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
559 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
562 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
564 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
567 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
568 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
572 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
574 CString tagToDelete
= completeRefName
.Mid(10);
576 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
578 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
581 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
582 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
589 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
591 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
594 return pLeaf
->GetRefName();
598 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
600 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
601 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
604 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
606 CPoint clientPoint
=point
;
607 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
609 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
611 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
613 VectorPShadowTree tree
;
614 ShowContextMenu(point
,hTreeItem
,tree
);
618 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
620 std::vector
<CShadowTree
*> selectedLeafs
;
621 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
622 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
625 selectedLeafs
.push_back(
626 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
627 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
630 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
633 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
636 popupMenu
.CreatePopupMenu();
638 bool bAddSeparator
= false;
641 if(selectedLeafs
.size()==1)
643 bAddSeparator
= true;
645 bool bShowReflogOption
= false;
646 bool bShowFetchOption
= false;
647 bool bShowSwitchOption
= false;
648 bool bShowRenameOption
= false;
649 bool bShowCreateBranchOption
= false;
651 CString fetchFromCmd
;
653 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
655 bShowReflogOption
= true;
656 bShowSwitchOption
= true;
657 bShowRenameOption
= true;
659 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
661 bShowReflogOption
= true;
662 bShowFetchOption
= true;
663 bShowCreateBranchOption
= true;
665 int dummy
= 0;//Needed for tokenize
666 remoteName
= selectedLeafs
[0]->GetRefName();
667 remoteName
= remoteName
.Mid(13);
668 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
669 fetchFromCmd
.Format(L
"Fetch from \"%s\"", remoteName
);
671 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
675 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, L
"Show Log", IDI_LOG
);
676 if(bShowReflogOption
) popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, L
"Show Reflog", IDI_LOG
);
678 popupMenu
.AppendMenu(MF_SEPARATOR
);
679 bAddSeparator
= false;
683 bAddSeparator
= true;
684 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
688 popupMenu
.AppendMenu(MF_SEPARATOR
);
690 popupMenu
.AppendMenuIcon(eCmd_Switch
, L
"Switch to this Ref", IDI_SWITCH
);
691 bAddSeparator
= false;
692 popupMenu
.AppendMenu(MF_SEPARATOR
);
694 if(bShowCreateBranchOption
)
696 bAddSeparator
= true;
697 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
700 if(bShowRenameOption
)
702 bAddSeparator
= true;
703 popupMenu
.AppendMenuIcon(eCmd_Rename
, L
"Rename", IDI_RENAME
);
706 else if(selectedLeafs
.size() == 2)
708 bAddSeparator
= true;
709 popupMenu
.AppendMenuIcon(eCmd_Diff
, L
"Compare these Refs", IDI_DIFF
);
712 if(!selectedLeafs
.empty())
714 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
717 popupMenu
.AppendMenu(MF_SEPARATOR
);
718 CString menuItemName
;
719 if(selectedLeafs
.size() == 1)
720 menuItemName
= L
"Delete remote branch";
722 menuItemName
.Format(L
"Delete %d remote branches", selectedLeafs
.size());
724 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
725 bAddSeparator
= true;
727 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
730 popupMenu
.AppendMenu(MF_SEPARATOR
);
731 CString menuItemName
;
732 if(selectedLeafs
.size() == 1)
733 menuItemName
= L
"Delete branch";
735 menuItemName
.Format(L
"Delete %d branches", selectedLeafs
.size());
737 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
738 bAddSeparator
= true;
740 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
743 popupMenu
.AppendMenu(MF_SEPARATOR
);
744 CString menuItemName
;
745 if(selectedLeafs
.size() == 1)
746 menuItemName
= L
"Delete tag";
748 menuItemName
.Format(L
"Delete %d tags", selectedLeafs
.size());
750 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
751 bAddSeparator
= true;
756 if(hTreePos
!=NULL
&& selectedLeafs
.empty())
758 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
759 if(pTree
->IsFrom(L
"refs/remotes"))
762 popupMenu
.AppendMenu(MF_SEPARATOR
);
763 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, L
"Manage Remotes", IDI_SETTINGS
);
764 bAddSeparator
= true;
765 if(selectedLeafs
.empty())
767 int dummy
= 0;//Needed for tokenize
768 remoteName
= pTree
->GetRefName();
769 remoteName
= remoteName
.Mid(13);
770 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
771 if(!remoteName
.IsEmpty())
773 CString fetchFromCmd
;
774 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
775 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
779 if(pTree
->IsFrom(L
"refs/heads"))
782 popupMenu
.AppendMenu(MF_SEPARATOR
);
783 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
785 if(pTree
->IsFrom(L
"refs/tags"))
788 popupMenu
.AppendMenu(MF_SEPARATOR
);
789 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, L
"Create tag", IDI_TAG
);
794 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
800 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
804 case eCmd_DeleteBranch
:
805 case eCmd_DeleteRemoteBranch
:
807 if(ConfirmDeleteRef(selectedLeafs
))
808 DoDeleteRefs(selectedLeafs
, true);
814 if(ConfirmDeleteRef(selectedLeafs
))
815 DoDeleteRefs(selectedLeafs
, true);
819 case eCmd_ShowReflog
:
821 CRefLogDlg
refLogDlg(this);
822 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
828 CAppUtils::Fetch(remoteName
);
834 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
839 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
841 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
846 CAddRemoteDlg(this).DoModal();
850 case eCmd_ManageRemotes
:
852 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(g_Git
.m_CurrentDir
),this).DoModal();
853 // CSettingGitRemote W_Remotes(m_cmdPath);
854 // W_Remotes.DoModal();
858 case eCmd_CreateBranch
:
860 CString
*commitHash
= NULL
;
861 if (selectedLeafs
.size() == 1)
862 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
863 CAppUtils::CreateBranchTag(false, commitHash
);
869 CAppUtils::CreateBranchTag(true);
878 selectedLeafs
[0]->m_csRefHash
,
879 selectedLeafs
[1]->m_csRefHash
);
886 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
888 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
889 if(!(*i
)->IsFrom(from
))
894 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
896 if (pMsg
->message
== WM_KEYDOWN
)
898 switch (pMsg
->wParam
)
902 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
904 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
906 PostMessage(WM_COMMAND, IDOK);
914 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
916 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
918 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
932 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
935 class CRefLeafListCompareFunc
938 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
940 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
942 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
945 int Compare(LPARAM lParam1
, LPARAM lParam2
)
948 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
949 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
952 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
954 int result
=CompareNoDesc(pLeft
,pRight
);
960 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
964 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
965 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
966 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
967 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
980 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
982 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
985 if(m_currSortCol
== pNMLV
->iSubItem
)
986 m_currSortDesc
= !m_currSortDesc
;
989 m_currSortCol
= pNMLV
->iSubItem
;
990 m_currSortDesc
= false;
993 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
994 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
996 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
999 void CBrowseRefsDlg::OnDestroy()
1001 m_pickedRef
= GetSelectedRef(true, false);
1003 CResizableStandAloneDialog::OnDestroy();
1006 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1008 UNREFERENCED_PARAMETER(pNMHDR
);
1014 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
1016 CBrowseRefsDlg
dlg(CString(),NULL
);
1018 if(initialRef
.IsEmpty())
1019 initialRef
= L
"HEAD";
1020 dlg
.m_initialRef
= initialRef
;
1021 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1023 if(dlg
.DoModal() != IDOK
)
1026 return dlg
.m_pickedRef
;
1029 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1032 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1033 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1034 if(resultRef
.IsEmpty())
1036 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1037 resultRef
= resultRef
.Mid(5);
1038 // if(wcsncmp(resultRef,L"heads/",6)==0)
1039 // resultRef = resultRef.Mid(6);
1041 //Find closest match of choice in combobox
1043 int matchLength
= 0;
1044 CString comboRefName
;
1045 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1047 pComboBox
->GetLBText(i
, comboRefName
);
1048 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1049 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1050 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1052 matchLength
= comboRefName
.GetLength();
1057 pComboBox
->SetCurSel(ixFound
);
1059 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)
1064 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1066 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1069 if(pDispInfo
->item
.pszText
== NULL
)
1070 return; //User canceled changing
1072 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1074 if(!pTree
->IsFrom(L
"refs/heads"))
1076 CMessageBox::Show(m_hWnd
, L
"At the moment, you can only rename branches.", L
"Cannot Rename This Ref",MB_OK
|MB_ICONERROR
);
1080 CString origName
= pTree
->GetRefName().Mid(11);
1083 if(m_pListCtrlRoot
!= NULL
)
1084 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1085 newName
+= pDispInfo
->item
.pszText
;
1087 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1089 CMessageBox::Show(m_hWnd
, L
"You cannot change the type of this ref with a rename.", L
"Cannot Change Ref Type",MB_OK
|MB_ICONERROR
);
1093 CString newNameTrunced
= newName
.Mid(11);
1096 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &result
, CP_UTF8
) != 0)
1099 errorMsg
.Format(L
"Could not rename branch %s. Message from git:\r\n\r\n%s",origName
,result
);
1100 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error Renaming Branch",MB_OK
|MB_ICONERROR
);
1103 //Do as if it failed to rename. Let Refresh() do the job.
1108 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1110 // AfxMessageBox(W_csPopup);
1114 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1116 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1119 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1121 if(!pTree
->IsFrom(L
"refs/heads"))
1123 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.