1 // BrowseRefsDlg.cpp : implementation file
5 #include "TortoiseProc.h"
6 #include "BrowseRefsDlg.h"
8 #include "AddRemoteDlg.h"
10 #include "Settings\SettingGitRemote.h"
11 #include "SinglePropSheetDlg.h"
12 #include "MessageBox.h"
13 #include "RefLogDlg.h"
15 #include "FileDiffDlg.h"
17 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
22 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
23 HDITEM HeaderItem
= {0};
24 HeaderItem
.mask
= HDI_FORMAT
;
25 for (int i
=0; i
<pHeader
->GetItemCount(); ++i
)
27 pHeader
->GetItem(i
, &HeaderItem
);
28 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
29 pHeader
->SetItem(i
, &HeaderItem
);
33 pHeader
->GetItem(nColumn
, &HeaderItem
);
34 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
35 pHeader
->SetItem(nColumn
, &HeaderItem
);
39 // CBrowseRefsDlg dialog
41 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
43 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=NULL*/)
44 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
47 m_currSortDesc(false),
48 m_initialRef(L
"HEAD"),
49 m_pickRef_Kind(gPickRef_All
),
55 CBrowseRefsDlg::~CBrowseRefsDlg()
59 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
61 CDialog::DoDataExchange(pDX
);
62 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
63 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
67 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
68 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
69 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
71 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
73 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
74 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
75 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
79 // CBrowseRefsDlg message handlers
81 void CBrowseRefsDlg::OnBnClickedOk()
86 BOOL
CBrowseRefsDlg::OnInitDialog()
88 CResizableStandAloneDialog::OnInitDialog();
89 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
91 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
92 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
93 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
95 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
96 m_ListRefLeafs
.InsertColumn(eCol_Name
, L
"Name",0,150);
97 m_ListRefLeafs
.InsertColumn(eCol_Date
, L
"Date Last Commit",0,100);
98 m_ListRefLeafs
.InsertColumn(eCol_Msg
, L
"Last Commit",0,300);
99 m_ListRefLeafs
.InsertColumn(eCol_Hash
, L
"Hash",0,80);
101 AddAnchor(IDOK
,BOTTOM_RIGHT
);
102 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
104 Refresh(m_initialRef
);
106 EnableSaveRestore(L
"BrowseRefs");
109 m_ListRefLeafs
.SetFocus();
113 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
115 int posSlash
=nameLeft
.Find('/');
120 nameLeft
.Empty();//Nothing left
124 nameSub
=nameLeft
.Left(posSlash
);
125 nameLeft
=nameLeft
.Mid(posSlash
+1);
127 if(nameSub
.IsEmpty())
130 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
133 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
134 nextNode
.m_csRefName
=nameSub
;
135 nextNode
.m_pParent
=this;
139 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
143 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
145 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
147 //Match of leaf name. Try match on total name.
148 CString totalRefName
= GetRefName();
149 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
150 return this; //Also match. Found.
155 //Not a leaf. Search all nodes.
156 for(TShadowTreeMap::iterator itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
158 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
160 return pSubtree
; //Found
163 return NULL
;//Not found
167 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
169 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
171 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
172 //List ctrl selection?
173 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
176 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
177 m_ListRefLeafs
.GetNextSelectedItem(pos
));
178 return pTree
->GetRefName();
182 //Tree ctrl selection?
183 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
186 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
187 return pTree
->GetRefName();
190 return CString();//None
193 void CBrowseRefsDlg::Refresh(CString selectRef
)
196 // g_Git.GetMapHashToFriendName(m_RefMap);
198 if(!selectRef
.IsEmpty())
200 if(selectRef
== "HEAD")
202 selectRef
= g_Git
.GetSymbolicRef(selectRef
, false);
207 selectRef
= GetSelectedRef(false, true);
210 m_RefTreeCtrl
.DeleteAllItems();
211 m_ListRefLeafs
.DeleteAllItems();
212 m_TreeRoot
.m_ShadowTree
.clear();
213 m_TreeRoot
.m_csRefName
="refs";
214 // m_TreeRoot.m_csShowName="Refs";
215 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
216 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
219 g_Git
.Run(L
"git for-each-ref --format="
222 L
"%(authordate:relative)%04"
225 L
"%(authordate:iso8601)%03",
231 MAP_STRING_STRING refMap
;
233 //First sort on ref name
234 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
236 singleRef
.TrimLeft(L
"\r\n");
238 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
239 if(refName
.IsEmpty())
241 CString refRest
=singleRef
.Mid(valuePos
);
244 //Use ref based on m_pickRef_Kind
245 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
247 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
249 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
252 refMap
[refName
] = refRest
; //Use
258 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
260 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
261 CString values
=iterRefMap
->second
;
262 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
265 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
266 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
267 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
268 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
269 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
273 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
274 //Probably not on a branch. Select root node.
275 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
279 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
283 CString newRefName
= GetFullRefName(refName
);
284 if(!newRefName
.IsEmpty())
285 refName
= newRefName
;
286 //else refName is not a valid ref. Try to select as good as possible.
288 if(wcsnicmp(refName
,L
"refs/",5)!=0)
289 return false; // Not a ref name
291 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
292 if(treeLeafHead
.m_hTree
!= NULL
)
294 //Not a leaf. Select tree node and return
295 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
299 if(treeLeafHead
.m_pParent
==NULL
)
300 return false; //Weird... should not occur.
302 //This is the current head.
303 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
305 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
307 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
308 if(pCurrShadowTree
== &treeLeafHead
)
310 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
311 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
318 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
322 if(wcsnicmp(refName
,L
"refs/",5)==0)
323 refName
=refName
.Mid(5);
324 pTreePos
=&m_TreeRoot
;
326 if(refName
.IsEmpty())
327 return *pTreePos
;//Found leaf
329 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
332 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
333 ASSERT(!bCreateIfNotExist
);
337 if(!refName
.IsEmpty())
339 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
340 //Leafs are for the list control.
341 if(pNextTree
->m_hTree
==NULL
)
343 //New tree. Create node in control.
344 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
345 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
349 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
353 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
355 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
358 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
361 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
363 m_ListRefLeafs
.DeleteAllItems();
365 m_currSortDesc
= false;
366 SetSortArrow(&m_ListRefLeafs
,-1,false);
368 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
374 FillListCtrlForShadowTree(pTree
,L
"",true);
377 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
381 int indexItem
=m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(),L
"");
383 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
384 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, refNamePrefix
+pTree
->m_csRefName
);
385 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
386 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
387 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
394 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
396 m_pListCtrlRoot
= pTree
;
397 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
399 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
404 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
406 ASSERT(!leafs
.empty());
411 UINT mbIcon
=MB_ICONQUESTION
;
412 csMessage
= L
"Are you sure you want to delete ";
414 bool bIsRemoteBranch
= false;
415 bool bIsBranch
= false;
416 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
417 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
421 if(leafs
.size() == 1)
423 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
424 csTitle
.Format(L
"Confirm deletion of %sbranch %s",
425 bIsRemoteBranch
? L
"remote ": L
"",
430 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
431 csMessage
+= L
"branch:\r\n\r\n<b>";
432 csMessage
+= branchToDelete
;
433 csMessage
+= L
"</b>";
435 //Check if branch is fully merged in HEAD
436 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
437 CGitHash commonAncestor
;
438 CString commonAncestorstr
;
440 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
441 g_Git
.Run(cmd
,&commonAncestorstr
,CP_UTF8
);
443 commonAncestor
=commonAncestorstr
;
445 if(commonAncestor
!= branchHash
)
447 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
448 mbIcon
= MB_ICONWARNING
;
452 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
453 mbIcon
= MB_ICONWARNING
;
458 csTitle
.Format(L
"Confirm deletion of %d %sbranches",
460 bIsRemoteBranch
? L
"remote ": L
"");
462 CString csMoreMsgText
;
463 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
464 csMessage
+= csMoreMsgText
;
466 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
467 csMessage
+= L
"branches";
469 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
470 mbIcon
= MB_ICONWARNING
;
474 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
475 mbIcon
= MB_ICONWARNING
;
480 else if(leafs
[0]->IsFrom(L
"refs/tags"))
482 if(leafs
.size() == 1)
484 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
485 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
486 csMessage
+= "the tag:\r\n\r\n<b>";
487 csMessage
+= tagToDelete
;
492 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
493 csTitle
.Format(L
"Confirm deletion of %d tags", leafs
.size());
494 CString csMoreMsgText
;
495 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
496 csMessage
+= csMoreMsgText
;
497 csMessage
+= L
"tags";
501 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
505 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
507 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
508 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
513 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
515 bool bIsRemoteBranch
= false;
516 bool bIsBranch
= false;
517 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
518 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
522 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
526 int slash
= branchToDelete
.Find(L
'/');
529 CString remoteName
= branchToDelete
.Left(slash
);
530 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
532 if(CAppUtils::IsSSHPutty())
534 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
537 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
540 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
542 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
545 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
546 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
550 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
552 CString tagToDelete
= completeRefName
.Mid(10);
554 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
556 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
559 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
560 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
567 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
569 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
572 return pLeaf
->GetRefName();
576 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
578 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
579 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
582 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
584 CPoint clientPoint
=point
;
585 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
587 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
589 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
591 VectorPShadowTree tree
;
592 ShowContextMenu(point
,hTreeItem
,tree
);
596 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
598 std::vector
<CShadowTree
*> selectedLeafs
;
599 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
600 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
603 selectedLeafs
.push_back(
604 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
605 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
608 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
611 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
614 popupMenu
.CreatePopupMenu();
616 bool bAddSeparator
= false;
619 if(selectedLeafs
.size()==1)
621 bAddSeparator
= true;
623 bool bShowReflogOption
= false;
624 bool bShowFetchOption
= false;
625 bool bShowSwitchOption
= false;
626 bool bShowRenameOption
= false;
628 CString fetchFromCmd
;
630 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
632 bShowReflogOption
= true;
633 bShowSwitchOption
= true;
634 bShowRenameOption
= true;
636 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
638 bShowReflogOption
= true;
639 bShowFetchOption
= true;
641 int dummy
= 0;//Needed for tokenize
642 remoteName
= selectedLeafs
[0]->GetRefName();
643 remoteName
= remoteName
.Mid(13);
644 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
645 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
647 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
651 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, L
"Show Log", IDI_LOG
);
652 if(bShowReflogOption
) popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, L
"Show Reflog", IDI_LOG
);
653 popupMenu
.AppendMenu(MF_SEPARATOR
);
654 bAddSeparator
= false;
657 bAddSeparator
= true;
658 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
660 if(bShowSwitchOption
)
662 bAddSeparator
= true;
663 popupMenu
.AppendMenuIcon(eCmd_Switch
, L
"Switch to this Ref", IDI_SWITCH
);
667 bAddSeparator
= false;
668 popupMenu
.AppendMenu(MF_SEPARATOR
);
670 if(bShowRenameOption
)
672 bAddSeparator
= true;
673 popupMenu
.AppendMenuIcon(eCmd_Rename
, L
"Rename", IDI_RENAME
);
676 else if(selectedLeafs
.size() == 2)
678 bAddSeparator
= true;
679 popupMenu
.AppendMenuIcon(eCmd_Diff
, L
"Compare These Refs", IDI_DIFF
);
682 if(!selectedLeafs
.empty())
684 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
687 popupMenu
.AppendMenu(MF_SEPARATOR
);
688 CString menuItemName
;
689 if(selectedLeafs
.size() == 1)
690 menuItemName
= L
"Delete Remote Branch";
692 menuItemName
.Format(L
"Delete %d Remote Branches", selectedLeafs
.size());
694 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
695 bAddSeparator
= true;
697 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
700 popupMenu
.AppendMenu(MF_SEPARATOR
);
701 CString menuItemName
;
702 if(selectedLeafs
.size() == 1)
703 menuItemName
= L
"Delete Branch";
705 menuItemName
.Format(L
"Delete %d Branches", selectedLeafs
.size());
707 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
708 bAddSeparator
= true;
710 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
713 popupMenu
.AppendMenu(MF_SEPARATOR
);
714 CString menuItemName
;
715 if(selectedLeafs
.size() == 1)
716 menuItemName
= L
"Delete Tag";
718 menuItemName
.Format(L
"Delete %d Tags", selectedLeafs
.size());
720 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
721 bAddSeparator
= true;
728 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
729 if(pTree
->IsFrom(L
"refs/remotes"))
732 popupMenu
.AppendMenu(MF_SEPARATOR
);
733 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, L
"Manage Remotes", IDI_SETTINGS
);
734 bAddSeparator
= true;
735 if(selectedLeafs
.empty())
737 int dummy
= 0;//Needed for tokenize
738 remoteName
= pTree
->GetRefName();
739 remoteName
= remoteName
.Mid(13);
740 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
741 if(!remoteName
.IsEmpty())
743 CString fetchFromCmd
;
744 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
745 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
749 if(pTree
->IsFrom(L
"refs/heads"))
752 popupMenu
.AppendMenu(MF_SEPARATOR
);
753 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create Branch", IDI_COPY
);
755 if(pTree
->IsFrom(L
"refs/tags"))
758 popupMenu
.AppendMenu(MF_SEPARATOR
);
759 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, L
"Create Tag", IDI_TAG
);
764 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
770 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
774 case eCmd_DeleteBranch
:
775 case eCmd_DeleteRemoteBranch
:
777 if(ConfirmDeleteRef(selectedLeafs
))
778 DoDeleteRefs(selectedLeafs
, true);
784 if(ConfirmDeleteRef(selectedLeafs
))
785 DoDeleteRefs(selectedLeafs
, true);
789 case eCmd_ShowReflog
:
791 CRefLogDlg
refLogDlg(this);
792 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
798 CAppUtils::Fetch(remoteName
);
804 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
809 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
811 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
816 CAddRemoteDlg(this).DoModal();
820 case eCmd_ManageRemotes
:
822 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(g_Git
.m_CurrentDir
),this).DoModal();
823 // CSettingGitRemote W_Remotes(m_cmdPath);
824 // W_Remotes.DoModal();
828 case eCmd_CreateBranch
:
830 CAppUtils::CreateBranchTag(false);
836 CAppUtils::CreateBranchTag(true);
845 selectedLeafs
[0]->m_csRefHash
,
846 selectedLeafs
[1]->m_csRefHash
);
853 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
855 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
856 if(!(*i
)->IsFrom(from
))
861 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
863 if (pMsg
->message
== WM_KEYDOWN
)
865 switch (pMsg
->wParam
)
869 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
871 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
873 PostMessage(WM_COMMAND, IDOK);
881 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
883 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
885 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
899 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
902 class CRefLeafListCompareFunc
905 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
907 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
909 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
912 int Compare(LPARAM lParam1
, LPARAM lParam2
)
915 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
916 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
919 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
921 int result
=CompareNoDesc(pLeft
,pRight
);
927 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
931 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
932 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
933 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
934 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
947 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
949 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
952 if(m_currSortCol
== pNMLV
->iSubItem
)
953 m_currSortDesc
= !m_currSortDesc
;
956 m_currSortCol
= pNMLV
->iSubItem
;
957 m_currSortDesc
= false;
960 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
961 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
963 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
966 void CBrowseRefsDlg::OnDestroy()
968 m_pickedRef
= GetSelectedRef(true, false);
970 CResizableStandAloneDialog::OnDestroy();
973 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
975 UNREFERENCED_PARAMETER(pNMHDR
);
981 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
983 CBrowseRefsDlg
dlg(CString(),NULL
);
985 if(initialRef
.IsEmpty())
986 initialRef
= L
"HEAD";
987 dlg
.m_initialRef
= initialRef
;
988 dlg
.m_pickRef_Kind
= pickRef_Kind
;
990 if(dlg
.DoModal() != IDOK
)
993 return dlg
.m_pickedRef
;
996 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
999 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1000 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1001 if(resultRef
.IsEmpty())
1003 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1004 resultRef
= resultRef
.Mid(5);
1005 // if(wcsncmp(resultRef,L"heads/",6)==0)
1006 // resultRef = resultRef.Mid(6);
1008 //Find closest match of choice in combobox
1010 int matchLength
= 0;
1011 CString comboRefName
;
1012 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1014 pComboBox
->GetLBText(i
, comboRefName
);
1015 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1016 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1017 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1019 matchLength
= comboRefName
.GetLength();
1024 pComboBox
->SetCurSel(ixFound
);
1026 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)
1031 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1033 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1036 if(pDispInfo
->item
.pszText
== NULL
)
1037 return; //User canceled changing
1039 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1041 if(!pTree
->IsFrom(L
"refs/heads"))
1043 CMessageBox::Show(m_hWnd
, L
"At the moment, you can only rename branches.", L
"Cannot Rename This Ref",MB_OK
|MB_ICONERROR
);
1047 CString origName
= pTree
->GetRefName().Mid(11);
1050 if(m_pListCtrlRoot
!= NULL
)
1051 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1052 newName
+= pDispInfo
->item
.pszText
;
1054 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1056 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
);
1060 CString newNameTrunced
= newName
.Mid(11);
1063 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &result
, CP_UTF8
) != 0)
1066 errorMsg
.Format(L
"Could not rename branch %s. Message from git:\r\n\r\n%s",origName
,result
);
1067 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error Renaming Branch",MB_OK
|MB_ICONERROR
);
1070 //Do as if it failed to rename. Let Refresh() do the job.
1075 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1077 // AfxMessageBox(W_csPopup);
1081 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1083 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1086 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1088 if(!pTree
->IsFrom(L
"refs/heads"))
1090 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.