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
),
25 CBrowseRefsDlg::~CBrowseRefsDlg()
29 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
31 CDialog::DoDataExchange(pDX
);
32 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
33 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
37 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
38 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
39 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
44 // CBrowseRefsDlg message handlers
46 void CBrowseRefsDlg::OnBnClickedOk()
51 BOOL
CBrowseRefsDlg::OnInitDialog()
53 CResizableStandAloneDialog::OnInitDialog();
55 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
56 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
58 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
59 m_ListRefLeafs
.InsertColumn(0,L
"Name",0,150);
60 m_ListRefLeafs
.InsertColumn(1,L
"Date Last Commit",0,100);
61 m_ListRefLeafs
.InsertColumn(2,L
"Last Commit",0,300);
62 m_ListRefLeafs
.InsertColumn(3,L
"Hash",0,80);
64 AddAnchor(IDOK
,BOTTOM_RIGHT
);
65 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
73 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
75 int posSlash
=nameLeft
.Find('/');
80 nameLeft
.Empty();//Nothing left
84 nameSub
=nameLeft
.Left(posSlash
);
85 nameLeft
=nameLeft
.Mid(posSlash
+1);
90 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
93 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
94 nextNode
.m_csRefName
=nameSub
;
95 nextNode
.m_pParent
=this;
99 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
101 void CBrowseRefsDlg::Refresh(bool bSelectCurHead
)
104 // g_Git.GetMapHashToFriendName(m_RefMap);
109 g_Git
.Run(L
"git symbolic-ref HEAD",&selectRef
,CP_UTF8
);
110 selectRef
.Trim(L
"\r\n\t ");
114 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
115 //List ctrl selection?
118 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
119 m_ListRefLeafs
.GetNextSelectedItem(pos
));
120 selectRef
=pTree
->GetRefName();
124 //Tree ctrl selection?
125 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
128 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
129 selectRef
=pTree
->GetRefName();
134 m_RefTreeCtrl
.DeleteAllItems();
135 m_ListRefLeafs
.DeleteAllItems();
136 m_TreeRoot
.m_ShadowTree
.clear();
137 m_TreeRoot
.m_csRefName
="refs";
138 // m_TreeRoot.m_csShowName="Refs";
139 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
140 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
143 g_Git
.Run(L
"git for-each-ref --format="
146 L
"%(authordate:relative)%04"
154 MAP_STRING_STRING refMap
;
156 //First sort on ref name
157 while(!(singleRef
=allRefs
.Tokenize(L
"\r\n",linePos
)).IsEmpty())
160 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
161 CString refRest
=singleRef
.Mid(valuePos
);
162 refMap
[refName
]=refRest
;
167 // for(MAP_HASH_NAME::iterator iterRef=m_RefMap.begin();iterRef!=m_RefMap.end();++iterRef)
168 // for(STRING_VECTOR::iterator iterRefName=iterRef->second.begin();iterRefName!=iterRef->second.end();++iterRefName)
169 // refName[*iterRefName]=iterRef->first;
172 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
174 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
175 CString values
=iterRefMap
->second
;
178 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
);
179 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
);
180 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
);
181 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
);
185 if(selectRef
.IsEmpty() || !SelectRef(selectRef
))
186 //Probably not on a branch. Select root node.
187 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
191 bool CBrowseRefsDlg::SelectRef(CString refName
)
193 if(wcsnicmp(refName
,L
"refs/",5)!=0)
194 return false; // Not a ref name
196 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
197 if(treeLeafHead
.m_hTree
!= NULL
)
199 //Not a leaf. Select tree node and return
200 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
204 if(treeLeafHead
.m_pParent
==NULL
)
205 return false; //Weird... should not occur.
207 //This is the current head.
208 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
210 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
212 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
213 if(pCurrShadowTree
== &treeLeafHead
)
215 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
216 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
223 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
227 if(wcsnicmp(refName
,L
"refs/",5)==0)
228 refName
=refName
.Mid(5);
229 pTreePos
=&m_TreeRoot
;
231 if(refName
.IsEmpty())
232 return *pTreePos
;//Found leaf
234 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
237 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
238 ASSERT(!bCreateIfNotExist
);
242 if(!refName
.IsEmpty())
244 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
245 //Leafs are for the list control.
246 if(pNextTree
->m_hTree
==NULL
)
248 //New tree. Create node in control.
249 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
250 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
254 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
258 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
260 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
263 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
266 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
268 m_ListRefLeafs
.DeleteAllItems();
270 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
276 FillListCtrlForShadowTree(pTree
,L
"",true);
279 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
283 int indexItem
=m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(),L
"");
285 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
286 m_ListRefLeafs
.SetItemText(indexItem
,0,refNamePrefix
+pTree
->m_csRefName
);
287 m_ListRefLeafs
.SetItemText(indexItem
,1,pTree
->m_csDate
);
288 m_ListRefLeafs
.SetItemText(indexItem
,2,pTree
->m_csSubject
);
289 m_ListRefLeafs
.SetItemText(indexItem
,3,pTree
->m_csRefHash
);
296 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
297 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
299 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
304 bool CBrowseRefsDlg::ConfirmDeleteRef(CString completeRefName
)
309 UINT mbIcon
=MB_ICONQUESTION
;
310 csMessage
=L
"Are you sure you want to delete the ";
311 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
313 CString branchToDelete
= completeRefName
.Mid(11);
314 csTitle
.Format(L
"Confirm deletion of branch %s", branchToDelete
);
315 csMessage
+= "branch:\r\n\r\n<b>";
316 csMessage
+= branchToDelete
;
319 //Check if branch is fully merged in HEAD
320 CString branchHash
= g_Git
.GetHash(completeRefName
);
321 CString commonAncestor
;
323 cmd
.Format(L
"git.exe merge-base HEAD %s",completeRefName
);
324 g_Git
.Run(cmd
,&commonAncestor
,CP_UTF8
);
326 branchHash
=branchHash
.Left(40);
327 commonAncestor
=commonAncestor
.Left(40);
329 if(commonAncestor
!= branchHash
)
331 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
332 mbIcon
=MB_ICONWARNING
;
335 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
337 CString tagToDelete
= completeRefName
.Mid(10);
338 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
339 csMessage
+= "tag:\r\n\r\n<b>";
340 csMessage
+= tagToDelete
;
344 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
349 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
351 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
353 CString branchToDelete
= completeRefName
.Mid(11);
355 cmd
.Format(L
"git.exe branch -%c %s",bForce
?L
'D':L
'd',branchToDelete
);
357 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
360 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
361 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
365 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
367 CString tagToDelete
= completeRefName
.Mid(10);
369 cmd
.Format(L
"git.exe tag -d %s",tagToDelete
);
371 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
374 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
375 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
382 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
384 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
385 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
388 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
390 CPoint clientPoint
=point
;
391 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
393 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
395 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
397 ShowContextMenu(point
,hTreeItem
,VectorPShadowTree());
401 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
403 std::vector
<CShadowTree
*> selectedLeafs
;
404 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
405 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
408 selectedLeafs
.push_back(
409 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
410 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
413 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
416 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
419 popupMenu
.CreatePopupMenu();
421 if(selectedLeafs
.size()==1)
423 popupMenu
.AppendMenu(MF_STRING
,eCmd_ViewLog
,L
"View log");
424 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
425 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteBranch
,L
"Delete Branch");
426 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
427 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteTag
,L
"Delete Tag");
429 // CShadowTree* pTree = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMHDR->idFrom);
436 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
437 if(pTree
->IsFrom(L
"refs/remotes"))
439 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
440 popupMenu
.AppendMenu(MF_STRING
,eCmd_ManageRemotes
,L
"Manage Remotes");
442 else if(pTree
->IsFrom(L
"refs/heads"))
443 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateBranch
,L
"Create Branch");
444 else if(pTree
->IsFrom(L
"refs/tags"))
445 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateTag
,L
"Create Tag");
449 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
455 dlg
.SetStartRef(selectedLeafs
[0]->m_csRefHash
);
459 case eCmd_DeleteBranch
:
461 if(ConfirmDeleteRef(selectedLeafs
[0]->GetRefName()))
462 DoDeleteRef(selectedLeafs
[0]->GetRefName(), true);
468 if(ConfirmDeleteRef(selectedLeafs
[0]->GetRefName()))
469 DoDeleteRef(selectedLeafs
[0]->GetRefName(), true);
475 CAddRemoteDlg(this).DoModal();
479 case eCmd_ManageRemotes
:
481 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(m_cmdPath
),this).DoModal();
482 // CSettingGitRemote W_Remotes(m_cmdPath);
483 // W_Remotes.DoModal();
487 case eCmd_CreateBranch
:
489 CAppUtils::CreateBranchTag(false);
495 CAppUtils::CreateBranchTag(true);
502 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
504 if (pMsg
->message
== WM_KEYDOWN
)
506 switch (pMsg
->wParam
)
510 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
512 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
514 PostMessage(WM_COMMAND, IDOK);
529 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);