added menu separators
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob74b3692d788b96d99f7255c2496f67791e6bb4ea
1 // BrowseRefsDlg.cpp : implementation file
2 //
4 #include "stdafx.h"
5 #include "TortoiseProc.h"
6 #include "BrowseRefsDlg.h"
7 #include "LogDlg.h"
8 #include "AddRemoteDlg.h"
9 #include "AppUtils.h"
10 #include "Settings\SettingGitRemote.h"
11 #include "SinglePropSheetDlg.h"
12 #include "MessageBox.h"
13 #include "RefLogDlg.h"
14 #include "IconMenu.h"
15 #include "FileDiffDlg.h"
17 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
19 if (control == NULL)
20 return;
21 // set the sort arrow
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);
31 if (nColumn >= 0)
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),
45 m_cmdPath(cmdPath),
46 m_currSortCol(-1),
47 m_currSortDesc(false),
48 m_initialRef(L"HEAD"),
49 m_pickRef_Kind(gPickRef_All),
50 m_pListCtrlRoot(NULL)
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)
70 ON_WM_CONTEXTMENU()
71 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
72 ON_WM_DESTROY()
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)
76 END_MESSAGE_MAP()
79 // CBrowseRefsDlg message handlers
81 void CBrowseRefsDlg::OnBnClickedOk()
83 OnOK();
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();
110 return FALSE;
113 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
115 int posSlash=nameLeft.Find('/');
116 CString nameSub;
117 if(posSlash<0)
119 nameSub=nameLeft;
120 nameLeft.Empty();//Nothing left
122 else
124 nameSub=nameLeft.Left(posSlash);
125 nameLeft=nameLeft.Mid(posSlash+1);
127 if(nameSub.IsEmpty())
128 return NULL;
130 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
131 return NULL;
133 CShadowTree& nextNode=m_ShadowTree[nameSub];
134 nextNode.m_csRefName=nameSub;
135 nextNode.m_pParent=this;
136 return &nextNode;
139 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
141 if(IsLeaf())
143 if(m_csRefName.GetLength() > partialRefName.GetLength())
144 return NULL;
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.
153 else
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);
159 if(pSubtree != NULL)
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))
175 //A leaf is selected
176 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
177 m_ListRefLeafs.GetNextSelectedItem(pos));
178 return pTree->GetRefName();
180 else if(!onlyIfLeaf)
182 //Tree ctrl selection?
183 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
184 if(hTree!=NULL)
186 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
187 return pTree->GetRefName();
190 return CString();//None
193 void CBrowseRefsDlg::Refresh(CString selectRef)
195 // m_RefMap.clear();
196 // g_Git.GetMapHashToFriendName(m_RefMap);
198 if(!selectRef.IsEmpty())
200 if(selectRef == "HEAD")
202 selectRef = g_Git.GetSymbolicRef(selectRef, false);
205 else
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);
218 CString allRefs;
219 g_Git.Run(L"git for-each-ref --format="
220 L"%(refname)%04"
221 L"%(objectname)%04"
222 L"%(authordate:relative)%04"
223 L"%(subject)%04"
224 L"%(authorname)%04"
225 L"%(authordate:iso8601)%03",
226 &allRefs,CP_UTF8);
228 int linePos=0;
229 CString singleRef;
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");
237 int valuePos=0;
238 CString refName=singleRef.Tokenize(L"\04",valuePos);
239 if(refName.IsEmpty())
240 continue;
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) )
246 continue; //Skip
247 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
248 continue; //Skip
249 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
250 continue; //Skip
252 refMap[refName] = refRest; //Use
257 //Populate ref tree
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)
264 int valuePos=0;
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)
281 if(!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);
296 return true;
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);
315 return true;
318 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
320 if(pTreePos==NULL)
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);
330 if(pNextTree==NULL)
332 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
333 ASSERT(!bCreateIfNotExist);
334 return *pTreePos;
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);
356 *pResult = 0;
358 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
361 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
363 m_ListRefLeafs.DeleteAllItems();
364 m_currSortCol = -1;
365 m_currSortDesc = false;
366 SetSortArrow(&m_ListRefLeafs,-1,false);
368 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
369 if(pTree==NULL)
371 ASSERT(FALSE);
372 return;
374 FillListCtrlForShadowTree(pTree,L"",true);
377 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
379 if(pTree->IsLeaf())
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);
389 else
392 CString csThisName;
393 if(!isFirstLevel)
394 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
395 else
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());
408 CString csMessage;
409 CString csTitle;
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;}
419 if(bIsBranch)
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"",
426 branchToDelete);
428 csMessage += "the ";
429 if(bIsRemoteBranch)
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;
439 CString cmd;
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;
450 if(bIsRemoteBranch)
452 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
453 mbIcon = MB_ICONWARNING;
456 else
458 csTitle.Format(L"Confirm deletion of %d %sbranches",
459 leafs.size(),
460 bIsRemoteBranch? L"remote ": L"");
462 CString csMoreMsgText;
463 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
464 csMessage += csMoreMsgText;
465 if(bIsRemoteBranch)
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;
472 if(bIsRemoteBranch)
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;
488 csMessage += "</b>";
490 else
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))
509 return false;
510 return true;
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;}
520 if(bIsBranch)
522 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
523 CString cmd;
524 if(bIsRemoteBranch)
526 int slash = branchToDelete.Find(L'/');
527 if(slash < 0)
528 return false;
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);
539 else
540 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
541 CString resultDummy;
542 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
544 CString errorMsg;
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);
547 return false;
550 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
552 CString tagToDelete = completeRefName.Mid(10);
553 CString cmd;
554 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
555 CString resultDummy;
556 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
558 CString errorMsg;
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);
561 return false;
564 return true;
567 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
569 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
570 if(pLeaf == NULL)
571 return CString();
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);
588 if(hTreeItem!=NULL)
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();
601 while(pos)
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)
613 CIconMenu popupMenu;
614 popupMenu.CreatePopupMenu();
616 bool bAddSeparator = false;
617 CString remoteName;
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;
655 if(bShowFetchOption)
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);
665 if(bAddSeparator)
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/"))
686 if(bAddSeparator)
687 popupMenu.AppendMenu(MF_SEPARATOR);
688 CString menuItemName;
689 if(selectedLeafs.size() == 1)
690 menuItemName = L"Delete Remote Branch";
691 else
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/"))
699 if(bAddSeparator)
700 popupMenu.AppendMenu(MF_SEPARATOR);
701 CString menuItemName;
702 if(selectedLeafs.size() == 1)
703 menuItemName = L"Delete Branch";
704 else
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/"))
712 if(bAddSeparator)
713 popupMenu.AppendMenu(MF_SEPARATOR);
714 CString menuItemName;
715 if(selectedLeafs.size() == 1)
716 menuItemName = L"Delete Tag";
717 else
718 menuItemName.Format(L"Delete %d Tags", selectedLeafs.size());
720 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
721 bAddSeparator = true;
726 if(hTreePos!=NULL)
728 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
729 if(pTree->IsFrom(L"refs/remotes"))
731 if(bAddSeparator)
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"))
751 if(bAddSeparator)
752 popupMenu.AppendMenu(MF_SEPARATOR);
753 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create Branch", IDI_COPY);
755 if(pTree->IsFrom(L"refs/tags"))
757 if(bAddSeparator)
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);
765 switch(cmd)
767 case eCmd_ViewLog:
769 CLogDlg dlg;
770 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
771 dlg.DoModal();
773 break;
774 case eCmd_DeleteBranch:
775 case eCmd_DeleteRemoteBranch:
777 if(ConfirmDeleteRef(selectedLeafs))
778 DoDeleteRefs(selectedLeafs, true);
779 Refresh();
781 break;
782 case eCmd_DeleteTag:
784 if(ConfirmDeleteRef(selectedLeafs))
785 DoDeleteRefs(selectedLeafs, true);
786 Refresh();
788 break;
789 case eCmd_ShowReflog:
791 CRefLogDlg refLogDlg(this);
792 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
793 refLogDlg.DoModal();
795 break;
796 case eCmd_Fetch:
798 CAppUtils::Fetch(remoteName);
799 Refresh();
801 break;
802 case eCmd_Switch:
804 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
806 break;
807 case eCmd_Rename:
809 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
810 if(pos != NULL)
811 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
813 break;
814 case eCmd_AddRemote:
816 CAddRemoteDlg(this).DoModal();
817 Refresh();
819 break;
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();
825 Refresh();
827 break;
828 case eCmd_CreateBranch:
830 CAppUtils::CreateBranchTag(false);
831 Refresh();
833 break;
834 case eCmd_CreateTag:
836 CAppUtils::CreateBranchTag(true);
837 Refresh();
839 break;
840 case eCmd_Diff:
842 CFileDiffDlg dlg;
843 dlg.SetDiff(
844 NULL,
845 selectedLeafs[0]->m_csRefHash,
846 selectedLeafs[1]->m_csRefHash);
847 dlg.DoModal();
849 break;
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))
857 return false;
858 return true;
861 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
863 if (pMsg->message == WM_KEYDOWN)
865 switch (pMsg->wParam)
867 /* case VK_RETURN:
869 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
871 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
873 PostMessage(WM_COMMAND, IDOK);
875 return TRUE;
878 break;
879 */ case VK_F2:
881 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
883 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
884 if(pos != NULL)
885 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
888 break;
890 case VK_F5:
892 Refresh();
894 break;
899 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
902 class CRefLeafListCompareFunc
904 public:
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)
914 return Compare(
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);
922 if(m_desc)
923 return -result;
924 return result;
927 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
929 switch(m_col)
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);
936 return 0;
939 int m_col;
940 bool m_desc;
941 CListCtrl* m_pList;
947 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
949 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
950 *pResult = 0;
952 if(m_currSortCol == pNMLV->iSubItem)
953 m_currSortDesc = !m_currSortDesc;
954 else
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);
976 *pResult = 0;
978 EndDialog(IDOK);
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)
991 return CString();
993 return dlg.m_pickedRef;
996 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
998 CString origRef;
999 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1000 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1001 if(resultRef.IsEmpty())
1002 return false;
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
1009 int ixFound = -1;
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();
1020 ixFound = i;
1023 if(ixFound >= 0)
1024 pComboBox->SetCurSel(ixFound);
1025 else
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)
1028 return true;
1031 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1033 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1034 *pResult = FALSE;
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);
1044 return;
1047 CString origName = pTree->GetRefName().Mid(11);
1049 CString newName;
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);
1057 return;
1060 CString newNameTrunced = newName.Mid(11);
1062 CString result;
1063 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &result, CP_UTF8) != 0)
1065 CString errorMsg;
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);
1068 return;
1070 //Do as if it failed to rename. Let Refresh() do the job.
1071 //*pResult = TRUE;
1073 Refresh(newName);
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);
1084 *pResult = FALSE;
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.
1091 return;