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_csRefName
= "refs";
235 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
236 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
239 g_Git
.Run(L
"git for-each-ref --format="
242 L
"%(authordate:relative)%04"
245 L
"%(authordate:iso8601)%03",
246 &allRefs
, NULL
, CP_UTF8
);
251 MAP_STRING_STRING refMap
;
253 //First sort on ref name
254 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
256 singleRef
.TrimLeft(L
"\r\n");
258 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
259 if(refName
.IsEmpty())
261 CString refRest
=singleRef
.Mid(valuePos
);
264 //Use ref based on m_pickRef_Kind
265 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
267 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
269 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
272 refMap
[refName
] = refRest
; //Use
278 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
280 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
281 CString values
=iterRefMap
->second
;
282 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
285 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
286 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
287 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
288 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
289 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
293 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
294 //Probably not on a branch. Select root node.
295 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
299 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
303 CString newRefName
= GetFullRefName(refName
);
304 if(!newRefName
.IsEmpty())
305 refName
= newRefName
;
306 //else refName is not a valid ref. Try to select as good as possible.
308 if(wcsnicmp(refName
,L
"refs/",5)!=0)
309 return false; // Not a ref name
311 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
312 if(treeLeafHead
.m_hTree
!= NULL
)
314 //Not a leaf. Select tree node and return
315 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
319 if(treeLeafHead
.m_pParent
==NULL
)
320 return false; //Weird... should not occur.
322 //This is the current head.
323 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
325 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
327 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
328 if(pCurrShadowTree
== &treeLeafHead
)
330 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
331 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
338 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
342 if(wcsnicmp(refName
,L
"refs/",5)==0)
343 refName
=refName
.Mid(5);
344 pTreePos
=&m_TreeRoot
;
346 if(refName
.IsEmpty())
347 return *pTreePos
;//Found leaf
349 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
352 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
353 ASSERT(!bCreateIfNotExist
);
357 if(!refName
.IsEmpty())
359 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
360 //Leafs are for the list control.
361 if(pNextTree
->m_hTree
==NULL
)
363 //New tree. Create node in control.
364 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
365 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
369 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
373 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
375 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
378 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
381 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
383 m_ListRefLeafs
.DeleteAllItems();
385 m_currSortDesc
= false;
386 SetSortArrow(&m_ListRefLeafs
,-1,false);
388 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
394 FillListCtrlForShadowTree(pTree
,L
"",true);
397 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
401 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== "refs" && pTree
->m_pParent
== NULL
))
403 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
405 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
406 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, refNamePrefix
+pTree
->m_csRefName
);
407 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
408 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
409 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
417 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
419 m_pListCtrlRoot
= pTree
;
420 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
422 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
427 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
429 ASSERT(!leafs
.empty());
434 UINT mbIcon
=MB_ICONQUESTION
;
435 csMessage
= L
"Are you sure you want to delete ";
437 bool bIsRemoteBranch
= false;
438 bool bIsBranch
= false;
439 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
440 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
444 if(leafs
.size() == 1)
446 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
447 csTitle
.Format(L
"Confirm deletion of %sbranch %s",
448 bIsRemoteBranch
? L
"remote ": L
"",
453 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
454 csMessage
+= L
"branch:\r\n\r\n<b>";
455 csMessage
+= branchToDelete
;
456 csMessage
+= L
"</b>";
458 //Check if branch is fully merged in HEAD
459 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
460 CGitHash commonAncestor
;
461 CString commonAncestorstr
;
463 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
464 g_Git
.Run(cmd
, &commonAncestorstr
, NULL
, CP_UTF8
);
466 commonAncestor
=commonAncestorstr
;
468 if(commonAncestor
!= branchHash
)
470 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
471 mbIcon
= MB_ICONWARNING
;
475 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
476 mbIcon
= MB_ICONWARNING
;
481 csTitle
.Format(L
"Confirm deletion of %d %sbranches",
483 bIsRemoteBranch
? L
"remote ": L
"");
485 CString csMoreMsgText
;
486 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
487 csMessage
+= csMoreMsgText
;
489 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
490 csMessage
+= L
"branches";
492 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
493 mbIcon
= MB_ICONWARNING
;
497 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
498 mbIcon
= MB_ICONWARNING
;
503 else if(leafs
[0]->IsFrom(L
"refs/tags"))
505 if(leafs
.size() == 1)
507 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
508 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
509 csMessage
+= "the tag:\r\n\r\n<b>";
510 csMessage
+= tagToDelete
;
515 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
516 csTitle
.Format(L
"Confirm deletion of %d tags", leafs
.size());
517 CString csMoreMsgText
;
518 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
519 csMessage
+= csMoreMsgText
;
520 csMessage
+= L
"tags";
524 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
528 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
530 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
531 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
536 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
538 bool bIsRemoteBranch
= false;
539 bool bIsBranch
= false;
540 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
541 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
545 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
549 int slash
= branchToDelete
.Find(L
'/');
552 CString remoteName
= branchToDelete
.Left(slash
);
553 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
555 if(CAppUtils::IsSSHPutty())
557 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
560 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
563 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
565 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
568 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
569 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
573 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
575 CString tagToDelete
= completeRefName
.Mid(10);
577 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
579 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
582 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
583 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
590 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
592 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
595 return pLeaf
->GetRefName();
599 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
601 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
602 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
605 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
607 CPoint clientPoint
=point
;
608 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
610 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
612 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
614 VectorPShadowTree tree
;
615 ShowContextMenu(point
,hTreeItem
,tree
);
619 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
621 std::vector
<CShadowTree
*> selectedLeafs
;
622 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
623 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
626 selectedLeafs
.push_back(
627 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
628 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
631 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
634 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
637 popupMenu
.CreatePopupMenu();
639 bool bAddSeparator
= false;
642 if(selectedLeafs
.size()==1)
644 bAddSeparator
= true;
646 bool bShowReflogOption
= false;
647 bool bShowFetchOption
= false;
648 bool bShowSwitchOption
= false;
649 bool bShowRenameOption
= false;
650 bool bShowCreateBranchOption
= false;
652 CString fetchFromCmd
;
654 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
656 bShowReflogOption
= true;
657 bShowSwitchOption
= true;
658 bShowRenameOption
= true;
660 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
662 bShowReflogOption
= true;
663 bShowFetchOption
= true;
664 bShowCreateBranchOption
= true;
666 int dummy
= 0;//Needed for tokenize
667 remoteName
= selectedLeafs
[0]->GetRefName();
668 remoteName
= remoteName
.Mid(13);
669 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
670 fetchFromCmd
.Format(L
"Fetch from \"%s\"", remoteName
);
672 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
676 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, L
"Show Log", IDI_LOG
);
677 if(bShowReflogOption
) popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, L
"Show Reflog", IDI_LOG
);
679 popupMenu
.AppendMenu(MF_SEPARATOR
);
680 bAddSeparator
= false;
684 bAddSeparator
= true;
685 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
689 popupMenu
.AppendMenu(MF_SEPARATOR
);
691 popupMenu
.AppendMenuIcon(eCmd_Switch
, L
"Switch to this Ref", IDI_SWITCH
);
692 bAddSeparator
= false;
693 popupMenu
.AppendMenu(MF_SEPARATOR
);
695 if(bShowCreateBranchOption
)
697 bAddSeparator
= true;
698 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
701 if(bShowRenameOption
)
703 bAddSeparator
= true;
704 popupMenu
.AppendMenuIcon(eCmd_Rename
, L
"Rename", IDI_RENAME
);
707 else if(selectedLeafs
.size() == 2)
709 bAddSeparator
= true;
710 popupMenu
.AppendMenuIcon(eCmd_Diff
, L
"Compare these Refs", IDI_DIFF
);
713 if(!selectedLeafs
.empty())
715 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
718 popupMenu
.AppendMenu(MF_SEPARATOR
);
719 CString menuItemName
;
720 if(selectedLeafs
.size() == 1)
721 menuItemName
= L
"Delete remote branch";
723 menuItemName
.Format(L
"Delete %d remote branches", selectedLeafs
.size());
725 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
726 bAddSeparator
= true;
728 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
731 popupMenu
.AppendMenu(MF_SEPARATOR
);
732 CString menuItemName
;
733 if(selectedLeafs
.size() == 1)
734 menuItemName
= L
"Delete branch";
736 menuItemName
.Format(L
"Delete %d branches", selectedLeafs
.size());
738 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
739 bAddSeparator
= true;
741 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
744 popupMenu
.AppendMenu(MF_SEPARATOR
);
745 CString menuItemName
;
746 if(selectedLeafs
.size() == 1)
747 menuItemName
= L
"Delete tag";
749 menuItemName
.Format(L
"Delete %d tags", selectedLeafs
.size());
751 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
752 bAddSeparator
= true;
757 if(hTreePos
!=NULL
&& selectedLeafs
.empty())
759 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
760 if(pTree
->IsFrom(L
"refs/remotes"))
763 popupMenu
.AppendMenu(MF_SEPARATOR
);
764 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, L
"Manage Remotes", IDI_SETTINGS
);
765 bAddSeparator
= true;
766 if(selectedLeafs
.empty())
768 int dummy
= 0;//Needed for tokenize
769 remoteName
= pTree
->GetRefName();
770 remoteName
= remoteName
.Mid(13);
771 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
772 if(!remoteName
.IsEmpty())
774 CString fetchFromCmd
;
775 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
776 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
780 if(pTree
->IsFrom(L
"refs/heads"))
783 popupMenu
.AppendMenu(MF_SEPARATOR
);
784 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
786 if(pTree
->IsFrom(L
"refs/tags"))
789 popupMenu
.AppendMenu(MF_SEPARATOR
);
790 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, L
"Create tag", IDI_TAG
);
795 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
801 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
805 case eCmd_DeleteBranch
:
806 case eCmd_DeleteRemoteBranch
:
808 if(ConfirmDeleteRef(selectedLeafs
))
809 DoDeleteRefs(selectedLeafs
, true);
815 if(ConfirmDeleteRef(selectedLeafs
))
816 DoDeleteRefs(selectedLeafs
, true);
820 case eCmd_ShowReflog
:
822 CRefLogDlg
refLogDlg(this);
823 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
829 CAppUtils::Fetch(remoteName
);
835 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
840 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
842 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
847 CAddRemoteDlg(this).DoModal();
851 case eCmd_ManageRemotes
:
853 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(g_Git
.m_CurrentDir
),this).DoModal();
854 // CSettingGitRemote W_Remotes(m_cmdPath);
855 // W_Remotes.DoModal();
859 case eCmd_CreateBranch
:
861 CString
*commitHash
= NULL
;
862 if (selectedLeafs
.size() == 1)
863 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
864 CAppUtils::CreateBranchTag(false, commitHash
);
870 CAppUtils::CreateBranchTag(true);
879 selectedLeafs
[0]->m_csRefHash
,
880 selectedLeafs
[1]->m_csRefHash
);
887 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
889 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
890 if(!(*i
)->IsFrom(from
))
895 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
897 if (pMsg
->message
== WM_KEYDOWN
)
899 switch (pMsg
->wParam
)
903 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
905 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
907 PostMessage(WM_COMMAND, IDOK);
915 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
917 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
919 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
933 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
936 class CRefLeafListCompareFunc
939 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
941 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
943 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
946 int Compare(LPARAM lParam1
, LPARAM lParam2
)
949 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
950 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
953 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
955 int result
=CompareNoDesc(pLeft
,pRight
);
961 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
965 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
966 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
967 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
968 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
981 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
983 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
986 if(m_currSortCol
== pNMLV
->iSubItem
)
987 m_currSortDesc
= !m_currSortDesc
;
990 m_currSortCol
= pNMLV
->iSubItem
;
991 m_currSortDesc
= false;
994 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
995 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
997 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1000 void CBrowseRefsDlg::OnDestroy()
1002 m_pickedRef
= GetSelectedRef(true, false);
1004 CResizableStandAloneDialog::OnDestroy();
1007 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1009 UNREFERENCED_PARAMETER(pNMHDR
);
1015 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
1017 CBrowseRefsDlg
dlg(CString(),NULL
);
1019 if(initialRef
.IsEmpty())
1020 initialRef
= L
"HEAD";
1021 dlg
.m_initialRef
= initialRef
;
1022 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1024 if(dlg
.DoModal() != IDOK
)
1027 return dlg
.m_pickedRef
;
1030 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1033 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1034 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1035 if(resultRef
.IsEmpty())
1037 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1038 resultRef
= resultRef
.Mid(5);
1039 // if(wcsncmp(resultRef,L"heads/",6)==0)
1040 // resultRef = resultRef.Mid(6);
1042 //Find closest match of choice in combobox
1044 int matchLength
= 0;
1045 CString comboRefName
;
1046 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1048 pComboBox
->GetLBText(i
, comboRefName
);
1049 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1050 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1051 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1053 matchLength
= comboRefName
.GetLength();
1058 pComboBox
->SetCurSel(ixFound
);
1060 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)
1065 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1067 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1070 if(pDispInfo
->item
.pszText
== NULL
)
1071 return; //User canceled changing
1073 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1075 if(!pTree
->IsFrom(L
"refs/heads"))
1077 CMessageBox::Show(m_hWnd
, L
"At the moment, you can only rename branches.", L
"Cannot Rename This Ref",MB_OK
|MB_ICONERROR
);
1081 CString origName
= pTree
->GetRefName().Mid(11);
1084 if(m_pListCtrlRoot
!= NULL
)
1085 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1086 newName
+= pDispInfo
->item
.pszText
;
1088 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1090 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
);
1094 CString newNameTrunced
= newName
.Mid(11);
1097 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &result
, CP_UTF8
) != 0)
1100 errorMsg
.Format(L
"Could not rename branch %s. Message from git:\r\n\r\n%s",origName
,result
);
1101 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error Renaming Branch",MB_OK
|MB_ICONERROR
);
1104 //Do as if it failed to rename. Let Refresh() do the job.
1109 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1111 // AfxMessageBox(W_csPopup);
1115 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1117 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1120 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1122 if(!pTree
->IsFrom(L
"refs/heads"))
1124 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.