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"
14 // CBrowseRefsDlg dialog
16 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
18 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=NULL*/)
19 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
27 CBrowseRefsDlg::~CBrowseRefsDlg()
31 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
33 CDialog::DoDataExchange(pDX
);
34 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
35 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
39 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
40 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
41 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
43 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
47 // CBrowseRefsDlg message handlers
49 void CBrowseRefsDlg::OnBnClickedOk()
54 BOOL
CBrowseRefsDlg::OnInitDialog()
56 CResizableStandAloneDialog::OnInitDialog();
58 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
59 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
61 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
62 m_ListRefLeafs
.InsertColumn(eCol_Name
, L
"Name",0,150);
63 m_ListRefLeafs
.InsertColumn(eCol_Date
, L
"Date Last Commit",0,100);
64 m_ListRefLeafs
.InsertColumn(eCol_Msg
, L
"Last Commit",0,300);
65 m_ListRefLeafs
.InsertColumn(eCol_Hash
, L
"Hash",0,80);
67 AddAnchor(IDOK
,BOTTOM_RIGHT
);
68 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
76 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
78 int posSlash
=nameLeft
.Find('/');
83 nameLeft
.Empty();//Nothing left
87 nameSub
=nameLeft
.Left(posSlash
);
88 nameLeft
=nameLeft
.Mid(posSlash
+1);
93 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
96 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
97 nextNode
.m_csRefName
=nameSub
;
98 nextNode
.m_pParent
=this;
102 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
104 void CBrowseRefsDlg::Refresh(bool bSelectCurHead
)
107 // g_Git.GetMapHashToFriendName(m_RefMap);
112 g_Git
.Run(L
"git symbolic-ref HEAD",&selectRef
,CP_UTF8
);
113 selectRef
.Trim(L
"\r\n\t ");
117 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
118 //List ctrl selection?
121 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
122 m_ListRefLeafs
.GetNextSelectedItem(pos
));
123 selectRef
=pTree
->GetRefName();
127 //Tree ctrl selection?
128 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
131 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
132 selectRef
=pTree
->GetRefName();
137 m_RefTreeCtrl
.DeleteAllItems();
138 m_ListRefLeafs
.DeleteAllItems();
139 m_TreeRoot
.m_ShadowTree
.clear();
140 m_TreeRoot
.m_csRefName
="refs";
141 // m_TreeRoot.m_csShowName="Refs";
142 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
143 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
146 g_Git
.Run(L
"git for-each-ref --format="
149 L
"%(authordate:relative)%04"
152 L
"%(authordate:iso8601)",
158 MAP_STRING_STRING refMap
;
160 //First sort on ref name
161 while(!(singleRef
=allRefs
.Tokenize(L
"\r\n",linePos
)).IsEmpty())
164 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
165 CString refRest
=singleRef
.Mid(valuePos
);
166 refMap
[refName
]=refRest
;
171 // for(MAP_HASH_NAME::iterator iterRef=m_RefMap.begin();iterRef!=m_RefMap.end();++iterRef)
172 // for(STRING_VECTOR::iterator iterRefName=iterRef->second.begin();iterRefName!=iterRef->second.end();++iterRefName)
173 // refName[*iterRefName]=iterRef->first;
176 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
178 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
179 CString values
=iterRefMap
->second
;
182 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
);
183 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
);
184 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
);
185 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
);
186 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
190 if(selectRef
.IsEmpty() || !SelectRef(selectRef
))
191 //Probably not on a branch. Select root node.
192 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
196 bool CBrowseRefsDlg::SelectRef(CString refName
)
198 if(wcsnicmp(refName
,L
"refs/",5)!=0)
199 return false; // Not a ref name
201 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
202 if(treeLeafHead
.m_hTree
!= NULL
)
204 //Not a leaf. Select tree node and return
205 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
209 if(treeLeafHead
.m_pParent
==NULL
)
210 return false; //Weird... should not occur.
212 //This is the current head.
213 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
215 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
217 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
218 if(pCurrShadowTree
== &treeLeafHead
)
220 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
221 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
228 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
232 if(wcsnicmp(refName
,L
"refs/",5)==0)
233 refName
=refName
.Mid(5);
234 pTreePos
=&m_TreeRoot
;
236 if(refName
.IsEmpty())
237 return *pTreePos
;//Found leaf
239 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
242 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
243 ASSERT(!bCreateIfNotExist
);
247 if(!refName
.IsEmpty())
249 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
250 //Leafs are for the list control.
251 if(pNextTree
->m_hTree
==NULL
)
253 //New tree. Create node in control.
254 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
255 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
259 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
263 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
265 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
268 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
271 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
273 m_ListRefLeafs
.DeleteAllItems();
275 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
281 FillListCtrlForShadowTree(pTree
,L
"",true);
284 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
288 int indexItem
=m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(),L
"");
290 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
291 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, refNamePrefix
+pTree
->m_csRefName
);
292 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
293 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
294 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
301 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
302 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
304 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
309 bool CBrowseRefsDlg::ConfirmDeleteRef(CString completeRefName
)
314 UINT mbIcon
=MB_ICONQUESTION
;
315 csMessage
=L
"Are you sure you want to delete the ";
316 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
318 CString branchToDelete
= completeRefName
.Mid(11);
319 csTitle
.Format(L
"Confirm deletion of branch %s", branchToDelete
);
320 csMessage
+= "branch:\r\n\r\n<b>";
321 csMessage
+= branchToDelete
;
324 //Check if branch is fully merged in HEAD
325 CString branchHash
= g_Git
.GetHash(completeRefName
);
326 CString commonAncestor
;
328 cmd
.Format(L
"git.exe merge-base HEAD %s",completeRefName
);
329 g_Git
.Run(cmd
,&commonAncestor
,CP_UTF8
);
331 branchHash
=branchHash
.Left(40);
332 commonAncestor
=commonAncestor
.Left(40);
334 if(commonAncestor
!= branchHash
)
336 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
337 mbIcon
=MB_ICONWARNING
;
340 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
342 CString tagToDelete
= completeRefName
.Mid(10);
343 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
344 csMessage
+= "tag:\r\n\r\n<b>";
345 csMessage
+= tagToDelete
;
349 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
354 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
356 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
358 CString branchToDelete
= completeRefName
.Mid(11);
360 cmd
.Format(L
"git.exe branch -%c %s",bForce
?L
'D':L
'd',branchToDelete
);
362 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
365 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
366 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
370 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
372 CString tagToDelete
= completeRefName
.Mid(10);
374 cmd
.Format(L
"git.exe tag -d %s",tagToDelete
);
376 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
379 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
380 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
387 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
389 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
390 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
393 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
395 CPoint clientPoint
=point
;
396 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
398 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
400 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
402 ShowContextMenu(point
,hTreeItem
,VectorPShadowTree());
406 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
408 std::vector
<CShadowTree
*> selectedLeafs
;
409 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
410 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
413 selectedLeafs
.push_back(
414 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
415 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
418 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
421 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
424 popupMenu
.CreatePopupMenu();
426 if(selectedLeafs
.size()==1)
428 popupMenu
.AppendMenu(MF_STRING
,eCmd_ViewLog
,L
"View log");
429 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
430 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteBranch
,L
"Delete Branch");
431 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
432 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteTag
,L
"Delete Tag");
434 // CShadowTree* pTree = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMHDR->idFrom);
441 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
442 if(pTree
->IsFrom(L
"refs/remotes"))
444 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
445 popupMenu
.AppendMenu(MF_STRING
,eCmd_ManageRemotes
,L
"Manage Remotes");
447 else if(pTree
->IsFrom(L
"refs/heads"))
448 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateBranch
,L
"Create Branch");
449 else if(pTree
->IsFrom(L
"refs/tags"))
450 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateTag
,L
"Create Tag");
454 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
460 dlg
.SetStartRef(selectedLeafs
[0]->m_csRefHash
);
464 case eCmd_DeleteBranch
:
466 if(ConfirmDeleteRef(selectedLeafs
[0]->GetRefName()))
467 DoDeleteRef(selectedLeafs
[0]->GetRefName(), true);
473 if(ConfirmDeleteRef(selectedLeafs
[0]->GetRefName()))
474 DoDeleteRef(selectedLeafs
[0]->GetRefName(), true);
480 CAddRemoteDlg(this).DoModal();
484 case eCmd_ManageRemotes
:
486 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(m_cmdPath
),this).DoModal();
487 // CSettingGitRemote W_Remotes(m_cmdPath);
488 // W_Remotes.DoModal();
492 case eCmd_CreateBranch
:
494 CAppUtils::CreateBranchTag(false);
500 CAppUtils::CreateBranchTag(true);
507 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
509 if (pMsg
->message
== WM_KEYDOWN
)
511 switch (pMsg
->wParam
)
515 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
517 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
519 PostMessage(WM_COMMAND, IDOK);
534 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
537 class CRefLeafListCompareFunc
540 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
542 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
544 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
547 int Compare(LPARAM lParam1
, LPARAM lParam2
)
550 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
551 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
554 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
556 int result
=CompareNoDesc(pLeft
,pRight
);
562 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
566 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
567 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
568 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
569 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
581 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
583 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
586 if(m_currSortCol
== pNMLV
->iSubItem
)
587 m_currSortDesc
= !m_currSortDesc
;
590 m_currSortCol
= pNMLV
->iSubItem
;
591 m_currSortDesc
= false;
594 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
595 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);