TortoiseSVN -> TortoiseGit
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob0336519cfdee53208d4d5bfe2841f26e5b55cbf9
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;
649 CString fetchFromCmd;
651 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
653 bShowReflogOption = true;
654 bShowSwitchOption = true;
655 bShowRenameOption = true;
657 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
659 bShowReflogOption = true;
660 bShowFetchOption = true;
662 int dummy = 0;//Needed for tokenize
663 remoteName = selectedLeafs[0]->GetRefName();
664 remoteName = remoteName.Mid(13);
665 remoteName = remoteName.Tokenize(L"/", dummy);
666 fetchFromCmd.Format(L"Fetch from \"%s\"", remoteName);
668 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
672 popupMenu.AppendMenuIcon(eCmd_ViewLog, L"Show Log", IDI_LOG);
673 if(bShowReflogOption) popupMenu.AppendMenuIcon(eCmd_ShowReflog, L"Show Reflog", IDI_LOG);
675 popupMenu.AppendMenu(MF_SEPARATOR);
676 bAddSeparator = false;
678 if(bShowFetchOption)
680 bAddSeparator = true;
681 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
684 if(bAddSeparator)
685 popupMenu.AppendMenu(MF_SEPARATOR);
687 popupMenu.AppendMenuIcon(eCmd_Switch, L"Switch to this Ref", IDI_SWITCH);
688 bAddSeparator = false;
689 popupMenu.AppendMenu(MF_SEPARATOR);
691 if(bShowRenameOption)
693 bAddSeparator = true;
694 popupMenu.AppendMenuIcon(eCmd_Rename, L"Rename", IDI_RENAME);
697 else if(selectedLeafs.size() == 2)
699 bAddSeparator = true;
700 popupMenu.AppendMenuIcon(eCmd_Diff, L"Compare these Refs", IDI_DIFF);
703 if(!selectedLeafs.empty())
705 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
707 if(bAddSeparator)
708 popupMenu.AppendMenu(MF_SEPARATOR);
709 CString menuItemName;
710 if(selectedLeafs.size() == 1)
711 menuItemName = L"Delete remote branch";
712 else
713 menuItemName.Format(L"Delete %d remote branches", selectedLeafs.size());
715 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
716 bAddSeparator = true;
718 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
720 if(bAddSeparator)
721 popupMenu.AppendMenu(MF_SEPARATOR);
722 CString menuItemName;
723 if(selectedLeafs.size() == 1)
724 menuItemName = L"Delete branch";
725 else
726 menuItemName.Format(L"Delete %d branches", selectedLeafs.size());
728 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
729 bAddSeparator = true;
731 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
733 if(bAddSeparator)
734 popupMenu.AppendMenu(MF_SEPARATOR);
735 CString menuItemName;
736 if(selectedLeafs.size() == 1)
737 menuItemName = L"Delete tag";
738 else
739 menuItemName.Format(L"Delete %d tags", selectedLeafs.size());
741 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
742 bAddSeparator = true;
747 if(hTreePos!=NULL)
749 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
750 if(pTree->IsFrom(L"refs/remotes"))
752 if(bAddSeparator)
753 popupMenu.AppendMenu(MF_SEPARATOR);
754 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, L"Manage Remotes", IDI_SETTINGS);
755 bAddSeparator = true;
756 if(selectedLeafs.empty())
758 int dummy = 0;//Needed for tokenize
759 remoteName = pTree->GetRefName();
760 remoteName = remoteName.Mid(13);
761 remoteName = remoteName.Tokenize(L"/", dummy);
762 if(!remoteName.IsEmpty())
764 CString fetchFromCmd;
765 fetchFromCmd.Format(L"Fetch from %s", remoteName);
766 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
770 if(pTree->IsFrom(L"refs/heads"))
772 if(bAddSeparator)
773 popupMenu.AppendMenu(MF_SEPARATOR);
774 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
776 if(pTree->IsFrom(L"refs/tags"))
778 if(bAddSeparator)
779 popupMenu.AppendMenu(MF_SEPARATOR);
780 popupMenu.AppendMenuIcon(eCmd_CreateTag, L"Create tag", IDI_TAG);
785 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
786 switch(cmd)
788 case eCmd_ViewLog:
790 CLogDlg dlg;
791 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
792 dlg.DoModal();
794 break;
795 case eCmd_DeleteBranch:
796 case eCmd_DeleteRemoteBranch:
798 if(ConfirmDeleteRef(selectedLeafs))
799 DoDeleteRefs(selectedLeafs, true);
800 Refresh();
802 break;
803 case eCmd_DeleteTag:
805 if(ConfirmDeleteRef(selectedLeafs))
806 DoDeleteRefs(selectedLeafs, true);
807 Refresh();
809 break;
810 case eCmd_ShowReflog:
812 CRefLogDlg refLogDlg(this);
813 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
814 refLogDlg.DoModal();
816 break;
817 case eCmd_Fetch:
819 CAppUtils::Fetch(remoteName);
820 Refresh();
822 break;
823 case eCmd_Switch:
825 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
827 break;
828 case eCmd_Rename:
830 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
831 if(pos != NULL)
832 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
834 break;
835 case eCmd_AddRemote:
837 CAddRemoteDlg(this).DoModal();
838 Refresh();
840 break;
841 case eCmd_ManageRemotes:
843 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(g_Git.m_CurrentDir),this).DoModal();
844 // CSettingGitRemote W_Remotes(m_cmdPath);
845 // W_Remotes.DoModal();
846 Refresh();
848 break;
849 case eCmd_CreateBranch:
851 CAppUtils::CreateBranchTag(false);
852 Refresh();
854 break;
855 case eCmd_CreateTag:
857 CAppUtils::CreateBranchTag(true);
858 Refresh();
860 break;
861 case eCmd_Diff:
863 CFileDiffDlg dlg;
864 dlg.SetDiff(
865 NULL,
866 selectedLeafs[0]->m_csRefHash,
867 selectedLeafs[1]->m_csRefHash);
868 dlg.DoModal();
870 break;
874 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
876 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
877 if(!(*i)->IsFrom(from))
878 return false;
879 return true;
882 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
884 if (pMsg->message == WM_KEYDOWN)
886 switch (pMsg->wParam)
888 /* case VK_RETURN:
890 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
892 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
894 PostMessage(WM_COMMAND, IDOK);
896 return TRUE;
899 break;
900 */ case VK_F2:
902 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
904 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
905 if(pos != NULL)
906 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
909 break;
911 case VK_F5:
913 Refresh();
915 break;
920 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
923 class CRefLeafListCompareFunc
925 public:
926 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
928 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
930 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
933 int Compare(LPARAM lParam1, LPARAM lParam2)
935 return Compare(
936 (CShadowTree*)m_pList->GetItemData(lParam1),
937 (CShadowTree*)m_pList->GetItemData(lParam2));
940 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
942 int result=CompareNoDesc(pLeft,pRight);
943 if(m_desc)
944 return -result;
945 return result;
948 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
950 switch(m_col)
952 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
953 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
954 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
955 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
957 return 0;
960 int m_col;
961 bool m_desc;
962 CListCtrl* m_pList;
968 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
970 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
971 *pResult = 0;
973 if(m_currSortCol == pNMLV->iSubItem)
974 m_currSortDesc = !m_currSortDesc;
975 else
977 m_currSortCol = pNMLV->iSubItem;
978 m_currSortDesc = false;
981 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
982 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
984 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
987 void CBrowseRefsDlg::OnDestroy()
989 m_pickedRef = GetSelectedRef(true, false);
991 CResizableStandAloneDialog::OnDestroy();
994 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
996 UNREFERENCED_PARAMETER(pNMHDR);
997 *pResult = 0;
999 EndDialog(IDOK);
1002 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1004 CBrowseRefsDlg dlg(CString(),NULL);
1006 if(initialRef.IsEmpty())
1007 initialRef = L"HEAD";
1008 dlg.m_initialRef = initialRef;
1009 dlg.m_pickRef_Kind = pickRef_Kind;
1011 if(dlg.DoModal() != IDOK)
1012 return CString();
1014 return dlg.m_pickedRef;
1017 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1019 CString origRef;
1020 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1021 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1022 if(resultRef.IsEmpty())
1023 return false;
1024 if(wcsncmp(resultRef,L"refs/",5)==0)
1025 resultRef = resultRef.Mid(5);
1026 // if(wcsncmp(resultRef,L"heads/",6)==0)
1027 // resultRef = resultRef.Mid(6);
1029 //Find closest match of choice in combobox
1030 int ixFound = -1;
1031 int matchLength = 0;
1032 CString comboRefName;
1033 for(int i = 0; i < pComboBox->GetCount(); ++i)
1035 pComboBox->GetLBText(i, comboRefName);
1036 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1037 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1038 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1040 matchLength = comboRefName.GetLength();
1041 ixFound = i;
1044 if(ixFound >= 0)
1045 pComboBox->SetCurSel(ixFound);
1046 else
1047 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)
1049 return true;
1052 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1054 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1055 *pResult = FALSE;
1057 if(pDispInfo->item.pszText == NULL)
1058 return; //User canceled changing
1060 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1062 if(!pTree->IsFrom(L"refs/heads"))
1064 CMessageBox::Show(m_hWnd, L"At the moment, you can only rename branches.", L"Cannot Rename This Ref",MB_OK|MB_ICONERROR);
1065 return;
1068 CString origName = pTree->GetRefName().Mid(11);
1070 CString newName;
1071 if(m_pListCtrlRoot != NULL)
1072 newName = m_pListCtrlRoot->GetRefName() + L'/';
1073 newName += pDispInfo->item.pszText;
1075 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1077 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);
1078 return;
1081 CString newNameTrunced = newName.Mid(11);
1083 CString result;
1084 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &result, CP_UTF8) != 0)
1086 CString errorMsg;
1087 errorMsg.Format(L"Could not rename branch %s. Message from git:\r\n\r\n%s",origName,result);
1088 CMessageBox::Show(m_hWnd,errorMsg,L"Error Renaming Branch",MB_OK|MB_ICONERROR);
1089 return;
1091 //Do as if it failed to rename. Let Refresh() do the job.
1092 //*pResult = TRUE;
1094 Refresh(newName);
1096 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1098 // AfxMessageBox(W_csPopup);
1102 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1104 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1105 *pResult = FALSE;
1107 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1109 if(!pTree->IsFrom(L"refs/heads"))
1111 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1112 return;