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_csShowName="Refs";
236 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
237 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
240 g_Git
.Run(L
"git for-each-ref --format="
243 L
"%(authordate:relative)%04"
246 L
"%(authordate:iso8601)%03",
252 MAP_STRING_STRING refMap
;
254 //First sort on ref name
255 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
257 singleRef
.TrimLeft(L
"\r\n");
259 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
260 if(refName
.IsEmpty())
262 CString refRest
=singleRef
.Mid(valuePos
);
265 //Use ref based on m_pickRef_Kind
266 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
268 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
270 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
273 refMap
[refName
] = refRest
; //Use
279 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
281 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
282 CString values
=iterRefMap
->second
;
283 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
286 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
287 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
288 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
289 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
290 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
294 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
295 //Probably not on a branch. Select root node.
296 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
300 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
304 CString newRefName
= GetFullRefName(refName
);
305 if(!newRefName
.IsEmpty())
306 refName
= newRefName
;
307 //else refName is not a valid ref. Try to select as good as possible.
309 if(wcsnicmp(refName
,L
"refs/",5)!=0)
310 return false; // Not a ref name
312 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
313 if(treeLeafHead
.m_hTree
!= NULL
)
315 //Not a leaf. Select tree node and return
316 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
320 if(treeLeafHead
.m_pParent
==NULL
)
321 return false; //Weird... should not occur.
323 //This is the current head.
324 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
326 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
328 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
329 if(pCurrShadowTree
== &treeLeafHead
)
331 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
332 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
339 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
343 if(wcsnicmp(refName
,L
"refs/",5)==0)
344 refName
=refName
.Mid(5);
345 pTreePos
=&m_TreeRoot
;
347 if(refName
.IsEmpty())
348 return *pTreePos
;//Found leaf
350 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
353 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
354 ASSERT(!bCreateIfNotExist
);
358 if(!refName
.IsEmpty())
360 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
361 //Leafs are for the list control.
362 if(pNextTree
->m_hTree
==NULL
)
364 //New tree. Create node in control.
365 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
366 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
370 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
374 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
376 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
379 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
382 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
384 m_ListRefLeafs
.DeleteAllItems();
386 m_currSortDesc
= false;
387 SetSortArrow(&m_ListRefLeafs
,-1,false);
389 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
395 FillListCtrlForShadowTree(pTree
,L
"",true);
398 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
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
);
415 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
417 m_pListCtrlRoot
= pTree
;
418 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
420 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
425 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
427 ASSERT(!leafs
.empty());
432 UINT mbIcon
=MB_ICONQUESTION
;
433 csMessage
= L
"Are you sure you want to delete ";
435 bool bIsRemoteBranch
= false;
436 bool bIsBranch
= false;
437 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
438 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
442 if(leafs
.size() == 1)
444 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
445 csTitle
.Format(L
"Confirm deletion of %sbranch %s",
446 bIsRemoteBranch
? L
"remote ": L
"",
451 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
452 csMessage
+= L
"branch:\r\n\r\n<b>";
453 csMessage
+= branchToDelete
;
454 csMessage
+= L
"</b>";
456 //Check if branch is fully merged in HEAD
457 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
458 CGitHash commonAncestor
;
459 CString commonAncestorstr
;
461 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
462 g_Git
.Run(cmd
,&commonAncestorstr
,CP_UTF8
);
464 commonAncestor
=commonAncestorstr
;
466 if(commonAncestor
!= branchHash
)
468 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
469 mbIcon
= MB_ICONWARNING
;
473 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
474 mbIcon
= MB_ICONWARNING
;
479 csTitle
.Format(L
"Confirm deletion of %d %sbranches",
481 bIsRemoteBranch
? L
"remote ": L
"");
483 CString csMoreMsgText
;
484 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
485 csMessage
+= csMoreMsgText
;
487 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
488 csMessage
+= L
"branches";
490 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
491 mbIcon
= MB_ICONWARNING
;
495 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
496 mbIcon
= MB_ICONWARNING
;
501 else if(leafs
[0]->IsFrom(L
"refs/tags"))
503 if(leafs
.size() == 1)
505 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
506 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
507 csMessage
+= "the tag:\r\n\r\n<b>";
508 csMessage
+= tagToDelete
;
513 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
514 csTitle
.Format(L
"Confirm deletion of %d tags", leafs
.size());
515 CString csMoreMsgText
;
516 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
517 csMessage
+= csMoreMsgText
;
518 csMessage
+= L
"tags";
522 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
526 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
528 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
529 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
534 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
536 bool bIsRemoteBranch
= false;
537 bool bIsBranch
= false;
538 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
539 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
543 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
547 int slash
= branchToDelete
.Find(L
'/');
550 CString remoteName
= branchToDelete
.Left(slash
);
551 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
553 if(CAppUtils::IsSSHPutty())
555 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
558 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
561 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
563 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
566 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
567 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
571 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
573 CString tagToDelete
= completeRefName
.Mid(10);
575 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
577 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
580 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
581 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
588 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
590 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
593 return pLeaf
->GetRefName();
597 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
599 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
600 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
603 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
605 CPoint clientPoint
=point
;
606 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
608 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
610 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
612 VectorPShadowTree tree
;
613 ShowContextMenu(point
,hTreeItem
,tree
);
617 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
619 std::vector
<CShadowTree
*> selectedLeafs
;
620 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
621 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
624 selectedLeafs
.push_back(
625 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
626 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
629 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
632 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
635 popupMenu
.CreatePopupMenu();
637 bool bAddSeparator
= false;
640 if(selectedLeafs
.size()==1)
642 bAddSeparator
= true;
644 bool bShowReflogOption
= false;
645 bool bShowFetchOption
= false;
646 bool bShowSwitchOption
= false;
647 bool bShowRenameOption
= false;
648 bool bShowCreateBranchOption
= false;
650 CString fetchFromCmd
;
652 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
654 bShowReflogOption
= true;
655 bShowSwitchOption
= true;
656 bShowRenameOption
= true;
658 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
660 bShowReflogOption
= true;
661 bShowFetchOption
= true;
662 bShowCreateBranchOption
= true;
664 int dummy
= 0;//Needed for tokenize
665 remoteName
= selectedLeafs
[0]->GetRefName();
666 remoteName
= remoteName
.Mid(13);
667 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
668 fetchFromCmd
.Format(L
"Fetch from \"%s\"", remoteName
);
670 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
674 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, L
"Show Log", IDI_LOG
);
675 if(bShowReflogOption
) popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, L
"Show Reflog", IDI_LOG
);
677 popupMenu
.AppendMenu(MF_SEPARATOR
);
678 bAddSeparator
= false;
682 bAddSeparator
= true;
683 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
687 popupMenu
.AppendMenu(MF_SEPARATOR
);
689 popupMenu
.AppendMenuIcon(eCmd_Switch
, L
"Switch to this Ref", IDI_SWITCH
);
690 bAddSeparator
= false;
691 popupMenu
.AppendMenu(MF_SEPARATOR
);
693 if(bShowCreateBranchOption
)
695 bAddSeparator
= true;
696 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
699 if(bShowRenameOption
)
701 bAddSeparator
= true;
702 popupMenu
.AppendMenuIcon(eCmd_Rename
, L
"Rename", IDI_RENAME
);
705 else if(selectedLeafs
.size() == 2)
707 bAddSeparator
= true;
708 popupMenu
.AppendMenuIcon(eCmd_Diff
, L
"Compare these Refs", IDI_DIFF
);
711 if(!selectedLeafs
.empty())
713 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
716 popupMenu
.AppendMenu(MF_SEPARATOR
);
717 CString menuItemName
;
718 if(selectedLeafs
.size() == 1)
719 menuItemName
= L
"Delete remote branch";
721 menuItemName
.Format(L
"Delete %d remote branches", selectedLeafs
.size());
723 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
724 bAddSeparator
= true;
726 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
729 popupMenu
.AppendMenu(MF_SEPARATOR
);
730 CString menuItemName
;
731 if(selectedLeafs
.size() == 1)
732 menuItemName
= L
"Delete branch";
734 menuItemName
.Format(L
"Delete %d branches", selectedLeafs
.size());
736 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
737 bAddSeparator
= true;
739 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
742 popupMenu
.AppendMenu(MF_SEPARATOR
);
743 CString menuItemName
;
744 if(selectedLeafs
.size() == 1)
745 menuItemName
= L
"Delete tag";
747 menuItemName
.Format(L
"Delete %d tags", selectedLeafs
.size());
749 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
750 bAddSeparator
= true;
755 if(hTreePos
!=NULL
&& selectedLeafs
.empty())
757 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
758 if(pTree
->IsFrom(L
"refs/remotes"))
761 popupMenu
.AppendMenu(MF_SEPARATOR
);
762 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, L
"Manage Remotes", IDI_SETTINGS
);
763 bAddSeparator
= true;
764 if(selectedLeafs
.empty())
766 int dummy
= 0;//Needed for tokenize
767 remoteName
= pTree
->GetRefName();
768 remoteName
= remoteName
.Mid(13);
769 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
770 if(!remoteName
.IsEmpty())
772 CString fetchFromCmd
;
773 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
774 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
778 if(pTree
->IsFrom(L
"refs/heads"))
781 popupMenu
.AppendMenu(MF_SEPARATOR
);
782 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create branch", IDI_COPY
);
784 if(pTree
->IsFrom(L
"refs/tags"))
787 popupMenu
.AppendMenu(MF_SEPARATOR
);
788 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, L
"Create tag", IDI_TAG
);
793 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
799 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
803 case eCmd_DeleteBranch
:
804 case eCmd_DeleteRemoteBranch
:
806 if(ConfirmDeleteRef(selectedLeafs
))
807 DoDeleteRefs(selectedLeafs
, true);
813 if(ConfirmDeleteRef(selectedLeafs
))
814 DoDeleteRefs(selectedLeafs
, true);
818 case eCmd_ShowReflog
:
820 CRefLogDlg
refLogDlg(this);
821 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
827 CAppUtils::Fetch(remoteName
);
833 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
838 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
840 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
845 CAddRemoteDlg(this).DoModal();
849 case eCmd_ManageRemotes
:
851 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(g_Git
.m_CurrentDir
),this).DoModal();
852 // CSettingGitRemote W_Remotes(m_cmdPath);
853 // W_Remotes.DoModal();
857 case eCmd_CreateBranch
:
859 CString
*commitHash
= NULL
;
860 if (selectedLeafs
.size() == 1)
861 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
862 CAppUtils::CreateBranchTag(false, commitHash
);
868 CAppUtils::CreateBranchTag(true);
877 selectedLeafs
[0]->m_csRefHash
,
878 selectedLeafs
[1]->m_csRefHash
);
885 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
887 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
888 if(!(*i
)->IsFrom(from
))
893 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
895 if (pMsg
->message
== WM_KEYDOWN
)
897 switch (pMsg
->wParam
)
901 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
903 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
905 PostMessage(WM_COMMAND, IDOK);
913 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
915 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
917 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
931 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
934 class CRefLeafListCompareFunc
937 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
939 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
941 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
944 int Compare(LPARAM lParam1
, LPARAM lParam2
)
947 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
948 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
951 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
953 int result
=CompareNoDesc(pLeft
,pRight
);
959 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
963 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
964 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
965 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
966 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
979 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
981 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
984 if(m_currSortCol
== pNMLV
->iSubItem
)
985 m_currSortDesc
= !m_currSortDesc
;
988 m_currSortCol
= pNMLV
->iSubItem
;
989 m_currSortDesc
= false;
992 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
993 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
995 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
998 void CBrowseRefsDlg::OnDestroy()
1000 m_pickedRef
= GetSelectedRef(true, false);
1002 CResizableStandAloneDialog::OnDestroy();
1005 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1007 UNREFERENCED_PARAMETER(pNMHDR
);
1013 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
1015 CBrowseRefsDlg
dlg(CString(),NULL
);
1017 if(initialRef
.IsEmpty())
1018 initialRef
= L
"HEAD";
1019 dlg
.m_initialRef
= initialRef
;
1020 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1022 if(dlg
.DoModal() != IDOK
)
1025 return dlg
.m_pickedRef
;
1028 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1031 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1032 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1033 if(resultRef
.IsEmpty())
1035 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1036 resultRef
= resultRef
.Mid(5);
1037 // if(wcsncmp(resultRef,L"heads/",6)==0)
1038 // resultRef = resultRef.Mid(6);
1040 //Find closest match of choice in combobox
1042 int matchLength
= 0;
1043 CString comboRefName
;
1044 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1046 pComboBox
->GetLBText(i
, comboRefName
);
1047 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1048 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1049 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1051 matchLength
= comboRefName
.GetLength();
1056 pComboBox
->SetCurSel(ixFound
);
1058 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)
1063 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1065 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1068 if(pDispInfo
->item
.pszText
== NULL
)
1069 return; //User canceled changing
1071 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1073 if(!pTree
->IsFrom(L
"refs/heads"))
1075 CMessageBox::Show(m_hWnd
, L
"At the moment, you can only rename branches.", L
"Cannot Rename This Ref",MB_OK
|MB_ICONERROR
);
1079 CString origName
= pTree
->GetRefName().Mid(11);
1082 if(m_pListCtrlRoot
!= NULL
)
1083 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1084 newName
+= pDispInfo
->item
.pszText
;
1086 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1088 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
);
1092 CString newNameTrunced
= newName
.Mid(11);
1095 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &result
, CP_UTF8
) != 0)
1098 errorMsg
.Format(L
"Could not rename branch %s. Message from git:\r\n\r\n%s",origName
,result
);
1099 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error Renaming Branch",MB_OK
|MB_ICONERROR
);
1102 //Do as if it failed to rename. Let Refresh() do the job.
1107 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1109 // AfxMessageBox(W_csPopup);
1113 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1115 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1118 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1120 if(!pTree
->IsFrom(L
"refs/heads"))
1122 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.