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
)
54 CBrowseRefsDlg::~CBrowseRefsDlg()
58 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
60 CDialog::DoDataExchange(pDX
);
61 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
62 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
66 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
67 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
68 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
70 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
72 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
76 // CBrowseRefsDlg message handlers
78 void CBrowseRefsDlg::OnBnClickedOk()
83 BOOL
CBrowseRefsDlg::OnInitDialog()
85 CResizableStandAloneDialog::OnInitDialog();
87 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
88 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
89 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
91 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
92 m_ListRefLeafs
.InsertColumn(eCol_Name
, L
"Name",0,150);
93 m_ListRefLeafs
.InsertColumn(eCol_Date
, L
"Date Last Commit",0,100);
94 m_ListRefLeafs
.InsertColumn(eCol_Msg
, L
"Last Commit",0,300);
95 m_ListRefLeafs
.InsertColumn(eCol_Hash
, L
"Hash",0,80);
97 AddAnchor(IDOK
,BOTTOM_RIGHT
);
98 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
100 Refresh(m_initialRef
);
102 EnableSaveRestore(L
"BrowseRefs");
105 m_ListRefLeafs
.SetFocus();
109 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
111 int posSlash
=nameLeft
.Find('/');
116 nameLeft
.Empty();//Nothing left
120 nameSub
=nameLeft
.Left(posSlash
);
121 nameLeft
=nameLeft
.Mid(posSlash
+1);
123 if(nameSub
.IsEmpty())
126 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
129 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
130 nextNode
.m_csRefName
=nameSub
;
131 nextNode
.m_pParent
=this;
135 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
139 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
141 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
143 //Match of leaf name. Try match on total name.
144 CString totalRefName
= GetRefName();
145 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
146 return this; //Also match. Found.
151 //Not a leaf. Search all nodes.
152 for(TShadowTreeMap::iterator itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
154 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
156 return pSubtree
; //Found
159 return NULL
;//Not found
163 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
165 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
167 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
168 //List ctrl selection?
169 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
172 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
173 m_ListRefLeafs
.GetNextSelectedItem(pos
));
174 return pTree
->GetRefName();
178 //Tree ctrl selection?
179 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
182 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
183 return pTree
->GetRefName();
186 return CString();//None
189 void CBrowseRefsDlg::Refresh(CString selectRef
)
192 // g_Git.GetMapHashToFriendName(m_RefMap);
194 if(!selectRef
.IsEmpty())
196 if(selectRef
== "HEAD")
198 selectRef
= g_Git
.GetSymbolicRef(selectRef
, false);
203 selectRef
= GetSelectedRef(false, true);
206 m_RefTreeCtrl
.DeleteAllItems();
207 m_ListRefLeafs
.DeleteAllItems();
208 m_TreeRoot
.m_ShadowTree
.clear();
209 m_TreeRoot
.m_csRefName
="refs";
210 // m_TreeRoot.m_csShowName="Refs";
211 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
212 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
215 g_Git
.Run(L
"git for-each-ref --format="
218 L
"%(authordate:relative)%04"
221 L
"%(authordate:iso8601)%03",
227 MAP_STRING_STRING refMap
;
229 //First sort on ref name
230 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
232 singleRef
.TrimLeft(L
"\r\n");
234 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
235 if(refName
.IsEmpty())
237 CString refRest
=singleRef
.Mid(valuePos
);
240 //Use ref based on m_pickRef_Kind
241 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
243 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
245 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
248 refMap
[refName
] = refRest
; //Use
254 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
256 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
257 CString values
=iterRefMap
->second
;
258 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
261 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
262 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
263 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
264 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
265 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
269 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
270 //Probably not on a branch. Select root node.
271 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
275 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
279 CString newRefName
= GetFullRefName(refName
);
280 if(!newRefName
.IsEmpty())
281 refName
= newRefName
;
282 //else refName is not a valid ref. Try to select as good as possible.
284 if(wcsnicmp(refName
,L
"refs/",5)!=0)
285 return false; // Not a ref name
287 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
288 if(treeLeafHead
.m_hTree
!= NULL
)
290 //Not a leaf. Select tree node and return
291 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
295 if(treeLeafHead
.m_pParent
==NULL
)
296 return false; //Weird... should not occur.
298 //This is the current head.
299 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
301 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
303 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
304 if(pCurrShadowTree
== &treeLeafHead
)
306 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
307 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
314 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
318 if(wcsnicmp(refName
,L
"refs/",5)==0)
319 refName
=refName
.Mid(5);
320 pTreePos
=&m_TreeRoot
;
322 if(refName
.IsEmpty())
323 return *pTreePos
;//Found leaf
325 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
328 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
329 ASSERT(!bCreateIfNotExist
);
333 if(!refName
.IsEmpty())
335 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
336 //Leafs are for the list control.
337 if(pNextTree
->m_hTree
==NULL
)
339 //New tree. Create node in control.
340 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
341 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
345 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
349 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
351 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
354 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
357 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
359 m_ListRefLeafs
.DeleteAllItems();
361 m_currSortDesc
= false;
362 SetSortArrow(&m_ListRefLeafs
,-1,false);
364 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
370 FillListCtrlForShadowTree(pTree
,L
"",true);
373 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
377 int indexItem
=m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(),L
"");
379 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
380 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, refNamePrefix
+pTree
->m_csRefName
);
381 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
382 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
383 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
390 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
391 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
393 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
398 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
400 ASSERT(!leafs
.empty());
405 UINT mbIcon
=MB_ICONQUESTION
;
406 csMessage
= L
"Are you sure you want to delete ";
408 bool bIsRemoteBranch
= false;
409 bool bIsBranch
= false;
410 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
411 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
415 if(leafs
.size() == 1)
417 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
418 csTitle
.Format(L
"Confirm deletion of %sbranch %s",
419 bIsRemoteBranch
? L
"remote ": L
"",
424 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
425 csMessage
+= L
"branch:\r\n\r\n<b>";
426 csMessage
+= branchToDelete
;
427 csMessage
+= L
"</b>";
429 //Check if branch is fully merged in HEAD
430 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
431 CGitHash commonAncestor
;
432 CString commonAncestorstr
;
434 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
435 g_Git
.Run(cmd
,&commonAncestorstr
,CP_UTF8
);
437 commonAncestor
=commonAncestorstr
;
439 if(commonAncestor
!= branchHash
)
441 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
442 mbIcon
= MB_ICONWARNING
;
446 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
447 mbIcon
= MB_ICONWARNING
;
452 csTitle
.Format(L
"Confirm deletion of %d %sbranches",
454 bIsRemoteBranch
? L
"remote ": L
"");
456 CString csMoreMsgText
;
457 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
458 csMessage
+= csMoreMsgText
;
460 csMessage
+= L
"<ct=0x0000FF><i>remote</i></ct> ";
461 csMessage
+= L
"branches";
463 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
464 mbIcon
= MB_ICONWARNING
;
468 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
469 mbIcon
= MB_ICONWARNING
;
474 else if(leafs
[0]->IsFrom(L
"refs/tags"))
476 if(leafs
.size() == 1)
478 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
479 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
480 csMessage
+= "the tag:\r\n\r\n<b>";
481 csMessage
+= tagToDelete
;
486 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
487 csTitle
.Format(L
"Confirm deletion of %d tags", leafs
.size());
488 CString csMoreMsgText
;
489 csMoreMsgText
.Format(L
"<b>%d</b> ", leafs
.size());
490 csMessage
+= csMoreMsgText
;
491 csMessage
+= L
"tags";
495 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
499 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
501 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
502 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
507 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
509 bool bIsRemoteBranch
= false;
510 bool bIsBranch
= false;
511 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
512 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
516 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
520 int slash
= branchToDelete
.Find(L
'/');
523 CString remoteName
= branchToDelete
.Left(slash
);
524 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
525 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
528 cmd
.Format(L
"git.exe branch -%c %s",bForce
?L
'D':L
'd',branchToDelete
);
530 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
533 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
534 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
538 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
540 CString tagToDelete
= completeRefName
.Mid(10);
542 cmd
.Format(L
"git.exe tag -d %s",tagToDelete
);
544 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
547 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
548 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
555 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
557 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
560 return pLeaf
->GetRefName();
564 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
566 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
567 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
570 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
572 CPoint clientPoint
=point
;
573 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
575 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
577 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
579 VectorPShadowTree tree
;
580 ShowContextMenu(point
,hTreeItem
,tree
);
584 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
586 std::vector
<CShadowTree
*> selectedLeafs
;
587 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
588 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
591 selectedLeafs
.push_back(
592 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
593 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
596 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
599 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
602 popupMenu
.CreatePopupMenu();
604 bool bAddSeparator
= false;
607 if(selectedLeafs
.size()==1)
609 bAddSeparator
= true;
611 bool bShowReflogOption
= false;
612 bool bShowFetchOption
= false;
613 bool bShowSwitchOption
= false;
615 CString fetchFromCmd
;
617 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
619 bShowReflogOption
= true;
620 bShowSwitchOption
= true;
622 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
624 bShowReflogOption
= true;
625 bShowFetchOption
= true;
627 int dummy
= 0;//Needed for tokenize
628 remoteName
= selectedLeafs
[0]->GetRefName();
629 remoteName
= remoteName
.Mid(13);
630 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
631 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
633 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
637 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, L
"Show Log", IDI_LOG
);
638 if(bShowReflogOption
) popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, L
"Show Reflog", IDI_LOG
);
639 if(bShowFetchOption
) popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
640 if(bShowSwitchOption
) popupMenu
.AppendMenuIcon(eCmd_Switch
, L
"Switch to this Ref", IDI_SWITCH
);
643 else if(selectedLeafs
.size() == 2)
645 bAddSeparator
= true;
647 popupMenu
.AppendMenuIcon(eCmd_Diff
, L
"Compare These Refs", IDI_DIFF
);
650 if(!selectedLeafs
.empty())
652 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
654 CString menuItemName
;
655 if(selectedLeafs
.size() == 1)
656 menuItemName
= L
"Delete Remote Branch";
658 menuItemName
.Format(L
"Delete %d Remote Branches", selectedLeafs
.size());
660 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
663 if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
665 CString menuItemName
;
666 if(selectedLeafs
.size() == 1)
667 menuItemName
= L
"Delete Branch";
669 menuItemName
.Format(L
"Delete %d Branches", selectedLeafs
.size());
671 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
674 if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
676 CString menuItemName
;
677 if(selectedLeafs
.size() == 1)
678 menuItemName
= L
"Delete Tag";
680 menuItemName
.Format(L
"Delete %d Tags", selectedLeafs
.size());
682 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
686 if(bAddSeparator
) popupMenu
.AppendMenu(MF_SEPARATOR
);
690 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
691 if(pTree
->IsFrom(L
"refs/remotes"))
693 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
694 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, L
"Manage Remotes", IDI_SETTINGS
);
695 if(selectedLeafs
.empty())
697 int dummy
= 0;//Needed for tokenize
698 remoteName
= pTree
->GetRefName();
699 remoteName
= remoteName
.Mid(13);
700 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
701 if(!remoteName
.IsEmpty())
703 CString fetchFromCmd
;
704 fetchFromCmd
.Format(L
"Fetch from %s", remoteName
);
705 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
709 else if(pTree
->IsFrom(L
"refs/heads"))
710 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, L
"Create Branch", IDI_COPY
);
711 else if(pTree
->IsFrom(L
"refs/tags"))
712 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, L
"Create Tag", IDI_TAG
);
716 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
722 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
726 case eCmd_DeleteBranch
:
727 case eCmd_DeleteRemoteBranch
:
729 if(ConfirmDeleteRef(selectedLeafs
))
730 DoDeleteRefs(selectedLeafs
, true);
736 if(ConfirmDeleteRef(selectedLeafs
))
737 DoDeleteRefs(selectedLeafs
, true);
741 case eCmd_ShowReflog
:
743 CRefLogDlg
refLogDlg(this);
744 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
751 cmd
.Format(_T("git.exe fetch %s"), remoteName
);
752 CProgressDlg progress
;
753 progress
.m_GitCmd
=cmd
;
760 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
765 CAddRemoteDlg(this).DoModal();
769 case eCmd_ManageRemotes
:
771 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(g_Git
.m_CurrentDir
),this).DoModal();
772 // CSettingGitRemote W_Remotes(m_cmdPath);
773 // W_Remotes.DoModal();
777 case eCmd_CreateBranch
:
779 CAppUtils::CreateBranchTag(false);
785 CAppUtils::CreateBranchTag(true);
794 selectedLeafs
[0]->m_csRefHash
,
795 selectedLeafs
[1]->m_csRefHash
);
802 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
804 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
805 if(!(*i
)->IsFrom(from
))
810 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
812 if (pMsg
->message
== WM_KEYDOWN
)
814 switch (pMsg
->wParam
)
818 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
820 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
822 PostMessage(WM_COMMAND, IDOK);
837 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
840 class CRefLeafListCompareFunc
843 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
845 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
847 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
850 int Compare(LPARAM lParam1
, LPARAM lParam2
)
853 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
854 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
857 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
859 int result
=CompareNoDesc(pLeft
,pRight
);
865 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
869 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
870 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
871 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
872 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
885 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
887 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
890 if(m_currSortCol
== pNMLV
->iSubItem
)
891 m_currSortDesc
= !m_currSortDesc
;
894 m_currSortCol
= pNMLV
->iSubItem
;
895 m_currSortDesc
= false;
898 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
899 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
901 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
904 void CBrowseRefsDlg::OnDestroy()
906 m_pickedRef
= GetSelectedRef(true, false);
908 CResizableStandAloneDialog::OnDestroy();
911 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
913 UNREFERENCED_PARAMETER(pNMHDR
);
919 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
921 CBrowseRefsDlg
dlg(CString(),NULL
);
923 if(initialRef
.IsEmpty())
924 initialRef
= L
"HEAD";
925 dlg
.m_initialRef
= initialRef
;
926 dlg
.m_pickRef_Kind
= pickRef_Kind
;
928 if(dlg
.DoModal() != IDOK
)
931 return dlg
.m_pickedRef
;
934 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
937 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
938 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
939 if(resultRef
.IsEmpty())
941 if(wcsncmp(resultRef
,L
"refs/",5)==0)
942 resultRef
= resultRef
.Mid(5);
943 // if(wcsncmp(resultRef,L"heads/",6)==0)
944 // resultRef = resultRef.Mid(6);
946 //Find closest match of choice in combobox
949 CString comboRefName
;
950 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
952 pComboBox
->GetLBText(i
, comboRefName
);
953 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
954 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
955 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
957 matchLength
= comboRefName
.GetLength();
962 pComboBox
->SetCurSel(ixFound
);
964 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)