show "create branch" for remote branches
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob1660c5c98f09139a3b741e0a4be4040c57ea82b2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2011 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // BrowseRefsDlg.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
25 #include "LogDlg.h"
26 #include "AddRemoteDlg.h"
27 #include "AppUtils.h"
28 #include "Settings\SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
32 #include "IconMenu.h"
33 #include "FileDiffDlg.h"
35 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
37 if (control == NULL)
38 return;
39 // set the sort arrow
40 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
41 HDITEM HeaderItem = {0};
42 HeaderItem.mask = HDI_FORMAT;
43 for (int i=0; i<pHeader->GetItemCount(); ++i)
45 pHeader->GetItem(i, &HeaderItem);
46 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
47 pHeader->SetItem(i, &HeaderItem);
49 if (nColumn >= 0)
51 pHeader->GetItem(nColumn, &HeaderItem);
52 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
53 pHeader->SetItem(nColumn, &HeaderItem);
57 // CBrowseRefsDlg dialog
59 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
61 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
62 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
63 m_cmdPath(cmdPath),
64 m_currSortCol(-1),
65 m_currSortDesc(false),
66 m_initialRef(L"HEAD"),
67 m_pickRef_Kind(gPickRef_All),
68 m_pListCtrlRoot(NULL)
73 CBrowseRefsDlg::~CBrowseRefsDlg()
77 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
79 CDialog::DoDataExchange(pDX);
80 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
81 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
85 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
86 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
87 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
88 ON_WM_CONTEXTMENU()
89 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
90 ON_WM_DESTROY()
91 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
92 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
93 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
94 END_MESSAGE_MAP()
97 // CBrowseRefsDlg message handlers
99 void CBrowseRefsDlg::OnBnClickedOk()
101 OnOK();
104 BOOL CBrowseRefsDlg::OnInitDialog()
106 CResizableStandAloneDialog::OnInitDialog();
107 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
109 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
110 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
111 AddAnchor(IDHELP, BOTTOM_RIGHT);
113 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
114 m_ListRefLeafs.InsertColumn(eCol_Name, L"Name",0,150);
115 m_ListRefLeafs.InsertColumn(eCol_Date, L"Date Last Commit",0,100);
116 m_ListRefLeafs.InsertColumn(eCol_Msg, L"Last Commit",0,300);
117 m_ListRefLeafs.InsertColumn(eCol_Hash, L"Hash",0,80);
119 AddAnchor(IDOK,BOTTOM_RIGHT);
120 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
122 Refresh(m_initialRef);
124 EnableSaveRestore(L"BrowseRefs");
126 CString sWindowTitle;
127 GetWindowText(sWindowTitle);
128 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
130 m_ListRefLeafs.SetFocus();
131 return FALSE;
134 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
136 int posSlash=nameLeft.Find('/');
137 CString nameSub;
138 if(posSlash<0)
140 nameSub=nameLeft;
141 nameLeft.Empty();//Nothing left
143 else
145 nameSub=nameLeft.Left(posSlash);
146 nameLeft=nameLeft.Mid(posSlash+1);
148 if(nameSub.IsEmpty())
149 return NULL;
151 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
152 return NULL;
154 CShadowTree& nextNode=m_ShadowTree[nameSub];
155 nextNode.m_csRefName=nameSub;
156 nextNode.m_pParent=this;
157 return &nextNode;
160 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
162 if(IsLeaf())
164 if(m_csRefName.GetLength() > partialRefName.GetLength())
165 return NULL;
166 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
168 //Match of leaf name. Try match on total name.
169 CString totalRefName = GetRefName();
170 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
171 return this; //Also match. Found.
174 else
176 //Not a leaf. Search all nodes.
177 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
179 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
180 if(pSubtree != NULL)
181 return pSubtree; //Found
184 return NULL;//Not found
188 typedef std::map<CString,CString> MAP_STRING_STRING;
190 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
192 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
193 //List ctrl selection?
194 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
196 //A leaf is selected
197 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
198 m_ListRefLeafs.GetNextSelectedItem(pos));
199 return pTree->GetRefName();
201 else if(!onlyIfLeaf)
203 //Tree ctrl selection?
204 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
205 if(hTree!=NULL)
207 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
208 return pTree->GetRefName();
211 return CString();//None
214 void CBrowseRefsDlg::Refresh(CString selectRef)
216 // m_RefMap.clear();
217 // g_Git.GetMapHashToFriendName(m_RefMap);
219 if(!selectRef.IsEmpty())
221 if(selectRef == "HEAD")
223 selectRef = g_Git.GetSymbolicRef(selectRef, false);
226 else
228 selectRef = GetSelectedRef(false, true);
231 m_RefTreeCtrl.DeleteAllItems();
232 m_ListRefLeafs.DeleteAllItems();
233 m_TreeRoot.m_ShadowTree.clear();
234 m_TreeRoot.m_csRefName="refs";
235 // m_TreeRoot.m_csShowName="Refs";
236 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
237 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
239 CString allRefs;
240 g_Git.Run(L"git for-each-ref --format="
241 L"%(refname)%04"
242 L"%(objectname)%04"
243 L"%(authordate:relative)%04"
244 L"%(subject)%04"
245 L"%(authorname)%04"
246 L"%(authordate:iso8601)%03",
247 &allRefs,CP_UTF8);
249 int linePos=0;
250 CString singleRef;
252 MAP_STRING_STRING refMap;
254 //First sort on ref name
255 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
257 singleRef.TrimLeft(L"\r\n");
258 int valuePos=0;
259 CString refName=singleRef.Tokenize(L"\04",valuePos);
260 if(refName.IsEmpty())
261 continue;
262 CString refRest=singleRef.Mid(valuePos);
265 //Use ref based on m_pickRef_Kind
266 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
267 continue; //Skip
268 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
269 continue; //Skip
270 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
271 continue; //Skip
273 refMap[refName] = refRest; //Use
278 //Populate ref tree
279 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
281 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
282 CString values=iterRefMap->second;
283 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
285 int valuePos=0;
286 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
287 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
288 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
289 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
290 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
294 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
295 //Probably not on a branch. Select root node.
296 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
300 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
302 if(!bExactMatch)
304 CString newRefName = GetFullRefName(refName);
305 if(!newRefName.IsEmpty())
306 refName = newRefName;
307 //else refName is not a valid ref. Try to select as good as possible.
309 if(wcsnicmp(refName,L"refs/",5)!=0)
310 return false; // Not a ref name
312 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
313 if(treeLeafHead.m_hTree != NULL)
315 //Not a leaf. Select tree node and return
316 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
317 return true;
320 if(treeLeafHead.m_pParent==NULL)
321 return false; //Weird... should not occur.
323 //This is the current head.
324 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
326 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
328 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
329 if(pCurrShadowTree == &treeLeafHead)
331 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
332 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
336 return true;
339 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
341 if(pTreePos==NULL)
343 if(wcsnicmp(refName,L"refs/",5)==0)
344 refName=refName.Mid(5);
345 pTreePos=&m_TreeRoot;
347 if(refName.IsEmpty())
348 return *pTreePos;//Found leaf
350 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
351 if(pNextTree==NULL)
353 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
354 ASSERT(!bCreateIfNotExist);
355 return *pTreePos;
358 if(!refName.IsEmpty())
360 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
361 //Leafs are for the list control.
362 if(pNextTree->m_hTree==NULL)
364 //New tree. Create node in control.
365 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
366 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
370 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
374 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
376 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
377 *pResult = 0;
379 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
382 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
384 m_ListRefLeafs.DeleteAllItems();
385 m_currSortCol = -1;
386 m_currSortDesc = false;
387 SetSortArrow(&m_ListRefLeafs,-1,false);
389 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
390 if(pTree==NULL)
392 ASSERT(FALSE);
393 return;
395 FillListCtrlForShadowTree(pTree,L"",true);
398 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
400 if(pTree->IsLeaf())
402 int indexItem=m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(),L"");
404 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
405 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, refNamePrefix+pTree->m_csRefName);
406 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
407 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
408 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
410 else
413 CString csThisName;
414 if(!isFirstLevel)
415 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
416 else
417 m_pListCtrlRoot = pTree;
418 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
420 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
425 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
427 ASSERT(!leafs.empty());
429 CString csMessage;
430 CString csTitle;
432 UINT mbIcon=MB_ICONQUESTION;
433 csMessage = L"Are you sure you want to delete ";
435 bool bIsRemoteBranch = false;
436 bool bIsBranch = false;
437 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
438 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
440 if(bIsBranch)
442 if(leafs.size() == 1)
444 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
445 csTitle.Format(L"Confirm deletion of %sbranch %s",
446 bIsRemoteBranch? L"remote ": L"",
447 branchToDelete);
449 csMessage += "the ";
450 if(bIsRemoteBranch)
451 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
452 csMessage += L"branch:\r\n\r\n<b>";
453 csMessage += branchToDelete;
454 csMessage += L"</b>";
456 //Check if branch is fully merged in HEAD
457 CGitHash branchHash = g_Git.GetHash(leafs[0]->GetRefName());
458 CGitHash commonAncestor;
459 CString commonAncestorstr;
460 CString cmd;
461 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
462 g_Git.Run(cmd,&commonAncestorstr,CP_UTF8);
464 commonAncestor=commonAncestorstr;
466 if(commonAncestor != branchHash)
468 csMessage += L"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
469 mbIcon = MB_ICONWARNING;
471 if(bIsRemoteBranch)
473 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
474 mbIcon = MB_ICONWARNING;
477 else
479 csTitle.Format(L"Confirm deletion of %d %sbranches",
480 leafs.size(),
481 bIsRemoteBranch? L"remote ": L"");
483 CString csMoreMsgText;
484 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
485 csMessage += csMoreMsgText;
486 if(bIsRemoteBranch)
487 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
488 csMessage += L"branches";
490 csMessage += L"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
491 mbIcon = MB_ICONWARNING;
493 if(bIsRemoteBranch)
495 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
496 mbIcon = MB_ICONWARNING;
501 else if(leafs[0]->IsFrom(L"refs/tags"))
503 if(leafs.size() == 1)
505 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
506 csTitle.Format(L"Confirm deletion of tag %s", tagToDelete);
507 csMessage += "the tag:\r\n\r\n<b>";
508 csMessage += tagToDelete;
509 csMessage += "</b>";
511 else
513 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
514 csTitle.Format(L"Confirm deletion of %d tags", leafs.size());
515 CString csMoreMsgText;
516 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
517 csMessage += csMoreMsgText;
518 csMessage += L"tags";
522 return CMessageBox::Show(m_hWnd,csMessage,csTitle,MB_YESNO|mbIcon)==IDYES;
526 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
528 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
529 if(!DoDeleteRef((*i)->GetRefName(), bForce))
530 return false;
531 return true;
534 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
536 bool bIsRemoteBranch = false;
537 bool bIsBranch = false;
538 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
539 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
541 if(bIsBranch)
543 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
544 CString cmd;
545 if(bIsRemoteBranch)
547 int slash = branchToDelete.Find(L'/');
548 if(slash < 0)
549 return false;
550 CString remoteName = branchToDelete.Left(slash);
551 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
553 if(CAppUtils::IsSSHPutty())
555 CAppUtils::LaunchPAgent(NULL, &remoteName);
558 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
560 else
561 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
562 CString resultDummy;
563 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
565 CString errorMsg;
566 errorMsg.Format(L"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete,resultDummy);
567 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting branch",MB_OK|MB_ICONERROR);
568 return false;
571 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
573 CString tagToDelete = completeRefName.Mid(10);
574 CString cmd;
575 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
576 CString resultDummy;
577 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
579 CString errorMsg;
580 errorMsg.Format(L"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete,resultDummy);
581 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting tag",MB_OK|MB_ICONERROR);
582 return false;
585 return true;
588 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
590 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
591 if(pLeaf == NULL)
592 return CString();
593 return pLeaf->GetRefName();
597 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
599 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
600 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
603 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
605 CPoint clientPoint=point;
606 m_RefTreeCtrl.ScreenToClient(&clientPoint);
608 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
609 if(hTreeItem!=NULL)
610 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
612 VectorPShadowTree tree;
613 ShowContextMenu(point,hTreeItem,tree);
617 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
619 std::vector<CShadowTree*> selectedLeafs;
620 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
621 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
622 while(pos)
624 selectedLeafs.push_back(
625 (CShadowTree*)m_ListRefLeafs.GetItemData(
626 m_ListRefLeafs.GetNextSelectedItem(pos)));
629 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
632 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
634 CIconMenu popupMenu;
635 popupMenu.CreatePopupMenu();
637 bool bAddSeparator = false;
638 CString remoteName;
640 if(selectedLeafs.size()==1)
642 bAddSeparator = true;
644 bool bShowReflogOption = false;
645 bool bShowFetchOption = false;
646 bool bShowSwitchOption = false;
647 bool bShowRenameOption = false;
648 bool bShowCreateBranchOption = false;
650 CString fetchFromCmd;
652 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
654 bShowReflogOption = true;
655 bShowSwitchOption = true;
656 bShowRenameOption = true;
658 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
660 bShowReflogOption = true;
661 bShowFetchOption = true;
662 bShowCreateBranchOption = true;
664 int dummy = 0;//Needed for tokenize
665 remoteName = selectedLeafs[0]->GetRefName();
666 remoteName = remoteName.Mid(13);
667 remoteName = remoteName.Tokenize(L"/", dummy);
668 fetchFromCmd.Format(L"Fetch from \"%s\"", remoteName);
670 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
674 popupMenu.AppendMenuIcon(eCmd_ViewLog, L"Show Log", IDI_LOG);
675 if(bShowReflogOption) popupMenu.AppendMenuIcon(eCmd_ShowReflog, L"Show Reflog", IDI_LOG);
677 popupMenu.AppendMenu(MF_SEPARATOR);
678 bAddSeparator = false;
680 if(bShowFetchOption)
682 bAddSeparator = true;
683 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
686 if(bAddSeparator)
687 popupMenu.AppendMenu(MF_SEPARATOR);
689 popupMenu.AppendMenuIcon(eCmd_Switch, L"Switch to this Ref", IDI_SWITCH);
690 bAddSeparator = false;
691 popupMenu.AppendMenu(MF_SEPARATOR);
693 if(bShowCreateBranchOption)
695 bAddSeparator = true;
696 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
699 if(bShowRenameOption)
701 bAddSeparator = true;
702 popupMenu.AppendMenuIcon(eCmd_Rename, L"Rename", IDI_RENAME);
705 else if(selectedLeafs.size() == 2)
707 bAddSeparator = true;
708 popupMenu.AppendMenuIcon(eCmd_Diff, L"Compare these Refs", IDI_DIFF);
711 if(!selectedLeafs.empty())
713 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
715 if(bAddSeparator)
716 popupMenu.AppendMenu(MF_SEPARATOR);
717 CString menuItemName;
718 if(selectedLeafs.size() == 1)
719 menuItemName = L"Delete remote branch";
720 else
721 menuItemName.Format(L"Delete %d remote branches", selectedLeafs.size());
723 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
724 bAddSeparator = true;
726 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
728 if(bAddSeparator)
729 popupMenu.AppendMenu(MF_SEPARATOR);
730 CString menuItemName;
731 if(selectedLeafs.size() == 1)
732 menuItemName = L"Delete branch";
733 else
734 menuItemName.Format(L"Delete %d branches", selectedLeafs.size());
736 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
737 bAddSeparator = true;
739 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
741 if(bAddSeparator)
742 popupMenu.AppendMenu(MF_SEPARATOR);
743 CString menuItemName;
744 if(selectedLeafs.size() == 1)
745 menuItemName = L"Delete tag";
746 else
747 menuItemName.Format(L"Delete %d tags", selectedLeafs.size());
749 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
750 bAddSeparator = true;
755 if(hTreePos!=NULL && selectedLeafs.empty())
757 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
758 if(pTree->IsFrom(L"refs/remotes"))
760 if(bAddSeparator)
761 popupMenu.AppendMenu(MF_SEPARATOR);
762 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, L"Manage Remotes", IDI_SETTINGS);
763 bAddSeparator = true;
764 if(selectedLeafs.empty())
766 int dummy = 0;//Needed for tokenize
767 remoteName = pTree->GetRefName();
768 remoteName = remoteName.Mid(13);
769 remoteName = remoteName.Tokenize(L"/", dummy);
770 if(!remoteName.IsEmpty())
772 CString fetchFromCmd;
773 fetchFromCmd.Format(L"Fetch from %s", remoteName);
774 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
778 if(pTree->IsFrom(L"refs/heads"))
780 if(bAddSeparator)
781 popupMenu.AppendMenu(MF_SEPARATOR);
782 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
784 if(pTree->IsFrom(L"refs/tags"))
786 if(bAddSeparator)
787 popupMenu.AppendMenu(MF_SEPARATOR);
788 popupMenu.AppendMenuIcon(eCmd_CreateTag, L"Create tag", IDI_TAG);
793 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
794 switch(cmd)
796 case eCmd_ViewLog:
798 CLogDlg dlg;
799 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
800 dlg.DoModal();
802 break;
803 case eCmd_DeleteBranch:
804 case eCmd_DeleteRemoteBranch:
806 if(ConfirmDeleteRef(selectedLeafs))
807 DoDeleteRefs(selectedLeafs, true);
808 Refresh();
810 break;
811 case eCmd_DeleteTag:
813 if(ConfirmDeleteRef(selectedLeafs))
814 DoDeleteRefs(selectedLeafs, true);
815 Refresh();
817 break;
818 case eCmd_ShowReflog:
820 CRefLogDlg refLogDlg(this);
821 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
822 refLogDlg.DoModal();
824 break;
825 case eCmd_Fetch:
827 CAppUtils::Fetch(remoteName);
828 Refresh();
830 break;
831 case eCmd_Switch:
833 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
835 break;
836 case eCmd_Rename:
838 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
839 if(pos != NULL)
840 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
842 break;
843 case eCmd_AddRemote:
845 CAddRemoteDlg(this).DoModal();
846 Refresh();
848 break;
849 case eCmd_ManageRemotes:
851 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(g_Git.m_CurrentDir),this).DoModal();
852 // CSettingGitRemote W_Remotes(m_cmdPath);
853 // W_Remotes.DoModal();
854 Refresh();
856 break;
857 case eCmd_CreateBranch:
859 CString *commitHash = NULL;
860 if (selectedLeafs.size() == 1)
861 commitHash = &(selectedLeafs[0]->m_csRefHash);
862 CAppUtils::CreateBranchTag(false, commitHash);
863 Refresh();
865 break;
866 case eCmd_CreateTag:
868 CAppUtils::CreateBranchTag(true);
869 Refresh();
871 break;
872 case eCmd_Diff:
874 CFileDiffDlg dlg;
875 dlg.SetDiff(
876 NULL,
877 selectedLeafs[0]->m_csRefHash,
878 selectedLeafs[1]->m_csRefHash);
879 dlg.DoModal();
881 break;
885 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
887 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
888 if(!(*i)->IsFrom(from))
889 return false;
890 return true;
893 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
895 if (pMsg->message == WM_KEYDOWN)
897 switch (pMsg->wParam)
899 /* case VK_RETURN:
901 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
903 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
905 PostMessage(WM_COMMAND, IDOK);
907 return TRUE;
910 break;
911 */ case VK_F2:
913 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
915 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
916 if(pos != NULL)
917 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
920 break;
922 case VK_F5:
924 Refresh();
926 break;
931 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
934 class CRefLeafListCompareFunc
936 public:
937 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
939 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
941 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
944 int Compare(LPARAM lParam1, LPARAM lParam2)
946 return Compare(
947 (CShadowTree*)m_pList->GetItemData(lParam1),
948 (CShadowTree*)m_pList->GetItemData(lParam2));
951 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
953 int result=CompareNoDesc(pLeft,pRight);
954 if(m_desc)
955 return -result;
956 return result;
959 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
961 switch(m_col)
963 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
964 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
965 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
966 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
968 return 0;
971 int m_col;
972 bool m_desc;
973 CListCtrl* m_pList;
979 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
981 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
982 *pResult = 0;
984 if(m_currSortCol == pNMLV->iSubItem)
985 m_currSortDesc = !m_currSortDesc;
986 else
988 m_currSortCol = pNMLV->iSubItem;
989 m_currSortDesc = false;
992 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
993 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
995 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
998 void CBrowseRefsDlg::OnDestroy()
1000 m_pickedRef = GetSelectedRef(true, false);
1002 CResizableStandAloneDialog::OnDestroy();
1005 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1007 UNREFERENCED_PARAMETER(pNMHDR);
1008 *pResult = 0;
1010 EndDialog(IDOK);
1013 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1015 CBrowseRefsDlg dlg(CString(),NULL);
1017 if(initialRef.IsEmpty())
1018 initialRef = L"HEAD";
1019 dlg.m_initialRef = initialRef;
1020 dlg.m_pickRef_Kind = pickRef_Kind;
1022 if(dlg.DoModal() != IDOK)
1023 return CString();
1025 return dlg.m_pickedRef;
1028 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1030 CString origRef;
1031 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1032 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1033 if(resultRef.IsEmpty())
1034 return false;
1035 if(wcsncmp(resultRef,L"refs/",5)==0)
1036 resultRef = resultRef.Mid(5);
1037 // if(wcsncmp(resultRef,L"heads/",6)==0)
1038 // resultRef = resultRef.Mid(6);
1040 //Find closest match of choice in combobox
1041 int ixFound = -1;
1042 int matchLength = 0;
1043 CString comboRefName;
1044 for(int i = 0; i < pComboBox->GetCount(); ++i)
1046 pComboBox->GetLBText(i, comboRefName);
1047 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1048 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1049 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1051 matchLength = comboRefName.GetLength();
1052 ixFound = i;
1055 if(ixFound >= 0)
1056 pComboBox->SetCurSel(ixFound);
1057 else
1058 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)
1060 return true;
1063 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1065 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1066 *pResult = FALSE;
1068 if(pDispInfo->item.pszText == NULL)
1069 return; //User canceled changing
1071 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1073 if(!pTree->IsFrom(L"refs/heads"))
1075 CMessageBox::Show(m_hWnd, L"At the moment, you can only rename branches.", L"Cannot Rename This Ref",MB_OK|MB_ICONERROR);
1076 return;
1079 CString origName = pTree->GetRefName().Mid(11);
1081 CString newName;
1082 if(m_pListCtrlRoot != NULL)
1083 newName = m_pListCtrlRoot->GetRefName() + L'/';
1084 newName += pDispInfo->item.pszText;
1086 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1088 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);
1089 return;
1092 CString newNameTrunced = newName.Mid(11);
1094 CString result;
1095 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &result, CP_UTF8) != 0)
1097 CString errorMsg;
1098 errorMsg.Format(L"Could not rename branch %s. Message from git:\r\n\r\n%s",origName,result);
1099 CMessageBox::Show(m_hWnd,errorMsg,L"Error Renaming Branch",MB_OK|MB_ICONERROR);
1100 return;
1102 //Do as if it failed to rename. Let Refresh() do the job.
1103 //*pResult = TRUE;
1105 Refresh(newName);
1107 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1109 // AfxMessageBox(W_csPopup);
1113 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1115 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1116 *pResult = FALSE;
1118 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1120 if(!pTree->IsFrom(L"refs/heads"))
1122 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1123 return;