1 // BrowseRefsDlg.cpp : implementation file
5 #include "TortoiseProc.h"
6 #include "BrowseRefsDlg.h"
8 #include "AddRemoteDlg.h"
9 #include "CreateBranchTagDlg.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 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
307 CPoint clientPoint
=point
;
308 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
311 std::vector
<CShadowTree
*> selectedTrees
;
312 selectedTrees
.reserve(m_ListRefLeafs
.GetSelectedCount());
313 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
316 selectedTrees
.push_back(
317 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
318 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
322 popupMenu
.CreatePopupMenu();
324 if(selectedTrees
.size()==1)
326 popupMenu
.AppendMenu(MF_STRING
,eCmd_ViewLog
,L
"View log");
327 if(selectedTrees
[0]->IsFrom(L
"refs/heads"))
328 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteBranch
,L
"Delete Branch");
329 else if(selectedTrees
[0]->IsFrom(L
"refs/tags"))
330 popupMenu
.AppendMenu(MF_STRING
,eCmd_DeleteTag
,L
"Delete Tag");
332 // CShadowTree* pTree = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMHDR->idFrom);
338 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
344 dlg
.SetStartRef(selectedTrees
[0]->m_csRefHash
);
348 case eCmd_DeleteBranch
:
350 if(ConfirmDeleteRef(selectedTrees
[0]->GetRefName()))
351 DoDeleteRef(selectedTrees
[0]->GetRefName(), true);
357 if(ConfirmDeleteRef(selectedTrees
[0]->GetRefName()))
358 DoDeleteRef(selectedTrees
[0]->GetRefName(), true);
365 bool CBrowseRefsDlg::ConfirmDeleteRef(CString completeRefName
)
370 UINT mbIcon
=MB_ICONQUESTION
;
371 csMessage
=L
"Are you sure you want to delete the ";
372 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
374 CString branchToDelete
= completeRefName
.Mid(11);
375 csTitle
.Format(L
"Confirm deletion of branch %s", branchToDelete
);
376 csMessage
+= "branch:\r\n\r\n<b>";
377 csMessage
+= branchToDelete
;
380 //Check if branch is fully merged in HEAD
381 CString branchHash
= g_Git
.GetHash(completeRefName
);
382 CString commonAncestor
;
384 cmd
.Format(L
"git.exe merge-base HEAD %s",completeRefName
);
385 g_Git
.Run(cmd
,&commonAncestor
,CP_UTF8
);
387 branchHash
=branchHash
.Left(40);
388 commonAncestor
=commonAncestor
.Left(40);
390 if(commonAncestor
!= branchHash
)
392 csMessage
+= L
"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
393 mbIcon
=MB_ICONWARNING
;
396 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
398 CString tagToDelete
= completeRefName
.Mid(10);
399 csTitle
.Format(L
"Confirm deletion of tag %s", tagToDelete
);
400 csMessage
+= "tag:\r\n\r\n<b>";
401 csMessage
+= tagToDelete
;
405 return CMessageBox::Show(m_hWnd
,csMessage
,csTitle
,MB_YESNO
|mbIcon
)==IDYES
;
410 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
412 if(wcsncmp(completeRefName
,L
"refs/heads",10)==0)
414 CString branchToDelete
= completeRefName
.Mid(11);
416 cmd
.Format(L
"git.exe branch -%c %s",bForce
?L
'D':L
'd',branchToDelete
);
418 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
421 errorMsg
.Format(L
"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete
,resultDummy
);
422 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting branch",MB_OK
|MB_ICONERROR
);
426 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
428 CString tagToDelete
= completeRefName
.Mid(10);
430 cmd
.Format(L
"git.exe tag -d %s",tagToDelete
);
432 if(g_Git
.Run(cmd
,&resultDummy
,CP_UTF8
)!=0)
435 errorMsg
.Format(L
"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete
,resultDummy
);
436 CMessageBox::Show(m_hWnd
,errorMsg
,L
"Error deleting tag",MB_OK
|MB_ICONERROR
);
443 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
445 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
446 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
449 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
452 popupMenu
.CreatePopupMenu();
454 CPoint clientPoint
=point
;
455 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
457 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
460 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
461 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreeItem
);
462 if(pTree
->IsFrom(L
"refs/remotes"))
464 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
465 popupMenu
.AppendMenu(MF_STRING
,eCmd_ManageRemotes
,L
"Manage Remotes");
467 else if(pTree
->IsFrom(L
"refs/heads"))
468 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateBranch
,L
"Create Branch");
469 else if(pTree
->IsFrom(L
"refs/tags"))
470 popupMenu
.AppendMenu(MF_STRING
,eCmd_CreateTag
,L
"Create Tag");
473 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
478 CAddRemoteDlg(this).DoModal();
482 case eCmd_ManageRemotes
:
484 CSinglePropSheetDlg(L
"Git Remote Settings",new CSettingGitRemote(m_cmdPath
),this).DoModal();
485 // CSettingGitRemote W_Remotes(m_cmdPath);
486 // W_Remotes.DoModal();
490 case eCmd_CreateBranch
:
492 CCreateBranchTagDlg
dlg(this);
500 CCreateBranchTagDlg
dlg(this);
509 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
511 if (pMsg
->message
== WM_KEYDOWN
)
513 switch (pMsg
->wParam
)
517 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
519 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
521 PostMessage(WM_COMMAND, IDOK);
536 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);