added new log filter (for subject)
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob3af80366d89b501be973299c73553759e9bc4496
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_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
236 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
238 CString allRefs;
239 g_Git.Run(L"git for-each-ref --format="
240 L"%(refname)%04"
241 L"%(objectname)%04"
242 L"%(authordate:relative)%04"
243 L"%(subject)%04"
244 L"%(authorname)%04"
245 L"%(authordate:iso8601)%03",
246 &allRefs, NULL, CP_UTF8);
248 int linePos=0;
249 CString singleRef;
251 MAP_STRING_STRING refMap;
253 //First sort on ref name
254 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
256 singleRef.TrimLeft(L"\r\n");
257 int valuePos=0;
258 CString refName=singleRef.Tokenize(L"\04",valuePos);
259 if(refName.IsEmpty())
260 continue;
261 CString refRest=singleRef.Mid(valuePos);
264 //Use ref based on m_pickRef_Kind
265 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
266 continue; //Skip
267 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
268 continue; //Skip
269 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
270 continue; //Skip
272 refMap[refName] = refRest; //Use
277 //Populate ref tree
278 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
280 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
281 CString values=iterRefMap->second;
282 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
284 int valuePos=0;
285 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
286 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
287 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
288 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
289 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
293 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
294 //Probably not on a branch. Select root node.
295 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
299 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
301 if(!bExactMatch)
303 CString newRefName = GetFullRefName(refName);
304 if(!newRefName.IsEmpty())
305 refName = newRefName;
306 //else refName is not a valid ref. Try to select as good as possible.
308 if(wcsnicmp(refName,L"refs/",5)!=0)
309 return false; // Not a ref name
311 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
312 if(treeLeafHead.m_hTree != NULL)
314 //Not a leaf. Select tree node and return
315 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
316 return true;
319 if(treeLeafHead.m_pParent==NULL)
320 return false; //Weird... should not occur.
322 //This is the current head.
323 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
325 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
327 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
328 if(pCurrShadowTree == &treeLeafHead)
330 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
331 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
335 return true;
338 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
340 if(pTreePos==NULL)
342 if(wcsnicmp(refName,L"refs/",5)==0)
343 refName=refName.Mid(5);
344 pTreePos=&m_TreeRoot;
346 if(refName.IsEmpty())
347 return *pTreePos;//Found leaf
349 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
350 if(pNextTree==NULL)
352 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
353 ASSERT(!bCreateIfNotExist);
354 return *pTreePos;
357 if(!refName.IsEmpty())
359 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
360 //Leafs are for the list control.
361 if(pNextTree->m_hTree==NULL)
363 //New tree. Create node in control.
364 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
365 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
369 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
373 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
375 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
376 *pResult = 0;
378 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
381 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
383 m_ListRefLeafs.DeleteAllItems();
384 m_currSortCol = -1;
385 m_currSortDesc = false;
386 SetSortArrow(&m_ListRefLeafs,-1,false);
388 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
389 if(pTree==NULL)
391 ASSERT(FALSE);
392 return;
394 FillListCtrlForShadowTree(pTree,L"",true);
397 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
399 if(pTree->IsLeaf())
401 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL))
403 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
405 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
406 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, refNamePrefix+pTree->m_csRefName);
407 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
408 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
409 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
412 else
415 CString csThisName;
416 if(!isFirstLevel)
417 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
418 else
419 m_pListCtrlRoot = pTree;
420 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
422 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
427 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
429 ASSERT(!leafs.empty());
431 CString csMessage;
432 CString csTitle;
434 UINT mbIcon=MB_ICONQUESTION;
435 csMessage = L"Are you sure you want to delete ";
437 bool bIsRemoteBranch = false;
438 bool bIsBranch = false;
439 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
440 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
442 if(bIsBranch)
444 if(leafs.size() == 1)
446 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
447 csTitle.Format(L"Confirm deletion of %sbranch %s",
448 bIsRemoteBranch? L"remote ": L"",
449 branchToDelete);
451 csMessage += "the ";
452 if(bIsRemoteBranch)
453 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
454 csMessage += L"branch:\r\n\r\n<b>";
455 csMessage += branchToDelete;
456 csMessage += L"</b>";
458 //Check if branch is fully merged in HEAD
459 CGitHash branchHash = g_Git.GetHash(leafs[0]->GetRefName());
460 CGitHash commonAncestor;
461 CString commonAncestorstr;
462 CString cmd;
463 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
464 g_Git.Run(cmd, &commonAncestorstr, NULL, CP_UTF8);
466 commonAncestor=commonAncestorstr;
468 if(commonAncestor != branchHash)
470 csMessage += L"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
471 mbIcon = MB_ICONWARNING;
473 if(bIsRemoteBranch)
475 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
476 mbIcon = MB_ICONWARNING;
479 else
481 csTitle.Format(L"Confirm deletion of %d %sbranches",
482 leafs.size(),
483 bIsRemoteBranch? L"remote ": L"");
485 CString csMoreMsgText;
486 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
487 csMessage += csMoreMsgText;
488 if(bIsRemoteBranch)
489 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
490 csMessage += L"branches";
492 csMessage += L"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
493 mbIcon = MB_ICONWARNING;
495 if(bIsRemoteBranch)
497 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
498 mbIcon = MB_ICONWARNING;
503 else if(leafs[0]->IsFrom(L"refs/tags"))
505 if(leafs.size() == 1)
507 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
508 csTitle.Format(L"Confirm deletion of tag %s", tagToDelete);
509 csMessage += "the tag:\r\n\r\n<b>";
510 csMessage += tagToDelete;
511 csMessage += "</b>";
513 else
515 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
516 csTitle.Format(L"Confirm deletion of %d tags", leafs.size());
517 CString csMoreMsgText;
518 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
519 csMessage += csMoreMsgText;
520 csMessage += L"tags";
524 return CMessageBox::Show(m_hWnd,csMessage,csTitle,MB_YESNO|mbIcon)==IDYES;
528 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
530 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
531 if(!DoDeleteRef((*i)->GetRefName(), bForce))
532 return false;
533 return true;
536 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
538 bool bIsRemoteBranch = false;
539 bool bIsBranch = false;
540 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
541 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
543 if(bIsBranch)
545 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
546 CString cmd;
547 if(bIsRemoteBranch)
549 int slash = branchToDelete.Find(L'/');
550 if(slash < 0)
551 return false;
552 CString remoteName = branchToDelete.Left(slash);
553 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
555 if(CAppUtils::IsSSHPutty())
557 CAppUtils::LaunchPAgent(NULL, &remoteName);
560 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
562 else
563 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
564 CString resultDummy;
565 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
567 CString errorMsg;
568 errorMsg.Format(L"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete,resultDummy);
569 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting branch",MB_OK|MB_ICONERROR);
570 return false;
573 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
575 CString tagToDelete = completeRefName.Mid(10);
576 CString cmd;
577 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
578 CString resultDummy;
579 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
581 CString errorMsg;
582 errorMsg.Format(L"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete,resultDummy);
583 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting tag",MB_OK|MB_ICONERROR);
584 return false;
587 return true;
590 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
592 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
593 if(pLeaf == NULL)
594 return CString();
595 return pLeaf->GetRefName();
599 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
601 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
602 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
605 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
607 CPoint clientPoint=point;
608 m_RefTreeCtrl.ScreenToClient(&clientPoint);
610 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
611 if(hTreeItem!=NULL)
612 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
614 VectorPShadowTree tree;
615 ShowContextMenu(point,hTreeItem,tree);
619 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
621 std::vector<CShadowTree*> selectedLeafs;
622 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
623 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
624 while(pos)
626 selectedLeafs.push_back(
627 (CShadowTree*)m_ListRefLeafs.GetItemData(
628 m_ListRefLeafs.GetNextSelectedItem(pos)));
631 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
634 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
636 CIconMenu popupMenu;
637 popupMenu.CreatePopupMenu();
639 bool bAddSeparator = false;
640 CString remoteName;
642 if(selectedLeafs.size()==1)
644 bAddSeparator = true;
646 bool bShowReflogOption = false;
647 bool bShowFetchOption = false;
648 bool bShowSwitchOption = false;
649 bool bShowRenameOption = false;
650 bool bShowCreateBranchOption = false;
652 CString fetchFromCmd;
654 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
656 bShowReflogOption = true;
657 bShowSwitchOption = true;
658 bShowRenameOption = true;
660 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
662 bShowReflogOption = true;
663 bShowFetchOption = true;
664 bShowCreateBranchOption = true;
666 int dummy = 0;//Needed for tokenize
667 remoteName = selectedLeafs[0]->GetRefName();
668 remoteName = remoteName.Mid(13);
669 remoteName = remoteName.Tokenize(L"/", dummy);
670 fetchFromCmd.Format(L"Fetch from \"%s\"", remoteName);
672 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
676 popupMenu.AppendMenuIcon(eCmd_ViewLog, L"Show Log", IDI_LOG);
677 if(bShowReflogOption) popupMenu.AppendMenuIcon(eCmd_ShowReflog, L"Show Reflog", IDI_LOG);
679 popupMenu.AppendMenu(MF_SEPARATOR);
680 bAddSeparator = false;
682 if(bShowFetchOption)
684 bAddSeparator = true;
685 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
688 if(bAddSeparator)
689 popupMenu.AppendMenu(MF_SEPARATOR);
691 popupMenu.AppendMenuIcon(eCmd_Switch, L"Switch to this Ref", IDI_SWITCH);
692 bAddSeparator = false;
693 popupMenu.AppendMenu(MF_SEPARATOR);
695 if(bShowCreateBranchOption)
697 bAddSeparator = true;
698 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
701 if(bShowRenameOption)
703 bAddSeparator = true;
704 popupMenu.AppendMenuIcon(eCmd_Rename, L"Rename", IDI_RENAME);
707 else if(selectedLeafs.size() == 2)
709 bAddSeparator = true;
710 popupMenu.AppendMenuIcon(eCmd_Diff, L"Compare these Refs", IDI_DIFF);
713 if(!selectedLeafs.empty())
715 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
717 if(bAddSeparator)
718 popupMenu.AppendMenu(MF_SEPARATOR);
719 CString menuItemName;
720 if(selectedLeafs.size() == 1)
721 menuItemName = L"Delete remote branch";
722 else
723 menuItemName.Format(L"Delete %d remote branches", selectedLeafs.size());
725 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
726 bAddSeparator = true;
728 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
730 if(bAddSeparator)
731 popupMenu.AppendMenu(MF_SEPARATOR);
732 CString menuItemName;
733 if(selectedLeafs.size() == 1)
734 menuItemName = L"Delete branch";
735 else
736 menuItemName.Format(L"Delete %d branches", selectedLeafs.size());
738 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
739 bAddSeparator = true;
741 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
743 if(bAddSeparator)
744 popupMenu.AppendMenu(MF_SEPARATOR);
745 CString menuItemName;
746 if(selectedLeafs.size() == 1)
747 menuItemName = L"Delete tag";
748 else
749 menuItemName.Format(L"Delete %d tags", selectedLeafs.size());
751 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
752 bAddSeparator = true;
757 if(hTreePos!=NULL && selectedLeafs.empty())
759 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
760 if(pTree->IsFrom(L"refs/remotes"))
762 if(bAddSeparator)
763 popupMenu.AppendMenu(MF_SEPARATOR);
764 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, L"Manage Remotes", IDI_SETTINGS);
765 bAddSeparator = true;
766 if(selectedLeafs.empty())
768 int dummy = 0;//Needed for tokenize
769 remoteName = pTree->GetRefName();
770 remoteName = remoteName.Mid(13);
771 remoteName = remoteName.Tokenize(L"/", dummy);
772 if(!remoteName.IsEmpty())
774 CString fetchFromCmd;
775 fetchFromCmd.Format(L"Fetch from %s", remoteName);
776 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
780 if(pTree->IsFrom(L"refs/heads"))
782 if(bAddSeparator)
783 popupMenu.AppendMenu(MF_SEPARATOR);
784 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
786 if(pTree->IsFrom(L"refs/tags"))
788 if(bAddSeparator)
789 popupMenu.AppendMenu(MF_SEPARATOR);
790 popupMenu.AppendMenuIcon(eCmd_CreateTag, L"Create tag", IDI_TAG);
795 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
796 switch(cmd)
798 case eCmd_ViewLog:
800 CLogDlg dlg;
801 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
802 dlg.DoModal();
804 break;
805 case eCmd_DeleteBranch:
806 case eCmd_DeleteRemoteBranch:
808 if(ConfirmDeleteRef(selectedLeafs))
809 DoDeleteRefs(selectedLeafs, true);
810 Refresh();
812 break;
813 case eCmd_DeleteTag:
815 if(ConfirmDeleteRef(selectedLeafs))
816 DoDeleteRefs(selectedLeafs, true);
817 Refresh();
819 break;
820 case eCmd_ShowReflog:
822 CRefLogDlg refLogDlg(this);
823 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
824 refLogDlg.DoModal();
826 break;
827 case eCmd_Fetch:
829 CAppUtils::Fetch(remoteName);
830 Refresh();
832 break;
833 case eCmd_Switch:
835 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
837 break;
838 case eCmd_Rename:
840 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
841 if(pos != NULL)
842 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
844 break;
845 case eCmd_AddRemote:
847 CAddRemoteDlg(this).DoModal();
848 Refresh();
850 break;
851 case eCmd_ManageRemotes:
853 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(g_Git.m_CurrentDir),this).DoModal();
854 // CSettingGitRemote W_Remotes(m_cmdPath);
855 // W_Remotes.DoModal();
856 Refresh();
858 break;
859 case eCmd_CreateBranch:
861 CString *commitHash = NULL;
862 if (selectedLeafs.size() == 1)
863 commitHash = &(selectedLeafs[0]->m_csRefHash);
864 CAppUtils::CreateBranchTag(false, commitHash);
865 Refresh();
867 break;
868 case eCmd_CreateTag:
870 CAppUtils::CreateBranchTag(true);
871 Refresh();
873 break;
874 case eCmd_Diff:
876 CFileDiffDlg dlg;
877 dlg.SetDiff(
878 NULL,
879 selectedLeafs[0]->m_csRefHash,
880 selectedLeafs[1]->m_csRefHash);
881 dlg.DoModal();
883 break;
887 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
889 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
890 if(!(*i)->IsFrom(from))
891 return false;
892 return true;
895 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
897 if (pMsg->message == WM_KEYDOWN)
899 switch (pMsg->wParam)
901 /* case VK_RETURN:
903 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
905 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
907 PostMessage(WM_COMMAND, IDOK);
909 return TRUE;
912 break;
913 */ case VK_F2:
915 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
917 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
918 if(pos != NULL)
919 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
922 break;
924 case VK_F5:
926 Refresh();
928 break;
933 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
936 class CRefLeafListCompareFunc
938 public:
939 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
941 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
943 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
946 int Compare(LPARAM lParam1, LPARAM lParam2)
948 return Compare(
949 (CShadowTree*)m_pList->GetItemData(lParam1),
950 (CShadowTree*)m_pList->GetItemData(lParam2));
953 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
955 int result=CompareNoDesc(pLeft,pRight);
956 if(m_desc)
957 return -result;
958 return result;
961 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
963 switch(m_col)
965 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
966 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
967 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
968 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
970 return 0;
973 int m_col;
974 bool m_desc;
975 CListCtrl* m_pList;
981 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
983 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
984 *pResult = 0;
986 if(m_currSortCol == pNMLV->iSubItem)
987 m_currSortDesc = !m_currSortDesc;
988 else
990 m_currSortCol = pNMLV->iSubItem;
991 m_currSortDesc = false;
994 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
995 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
997 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1000 void CBrowseRefsDlg::OnDestroy()
1002 m_pickedRef = GetSelectedRef(true, false);
1004 CResizableStandAloneDialog::OnDestroy();
1007 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1009 UNREFERENCED_PARAMETER(pNMHDR);
1010 *pResult = 0;
1012 EndDialog(IDOK);
1015 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1017 CBrowseRefsDlg dlg(CString(),NULL);
1019 if(initialRef.IsEmpty())
1020 initialRef = L"HEAD";
1021 dlg.m_initialRef = initialRef;
1022 dlg.m_pickRef_Kind = pickRef_Kind;
1024 if(dlg.DoModal() != IDOK)
1025 return CString();
1027 return dlg.m_pickedRef;
1030 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1032 CString origRef;
1033 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1034 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1035 if(resultRef.IsEmpty())
1036 return false;
1037 if(wcsncmp(resultRef,L"refs/",5)==0)
1038 resultRef = resultRef.Mid(5);
1039 // if(wcsncmp(resultRef,L"heads/",6)==0)
1040 // resultRef = resultRef.Mid(6);
1042 //Find closest match of choice in combobox
1043 int ixFound = -1;
1044 int matchLength = 0;
1045 CString comboRefName;
1046 for(int i = 0; i < pComboBox->GetCount(); ++i)
1048 pComboBox->GetLBText(i, comboRefName);
1049 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1050 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1051 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1053 matchLength = comboRefName.GetLength();
1054 ixFound = i;
1057 if(ixFound >= 0)
1058 pComboBox->SetCurSel(ixFound);
1059 else
1060 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)
1062 return true;
1065 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1067 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1068 *pResult = FALSE;
1070 if(pDispInfo->item.pszText == NULL)
1071 return; //User canceled changing
1073 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1075 if(!pTree->IsFrom(L"refs/heads"))
1077 CMessageBox::Show(m_hWnd, L"At the moment, you can only rename branches.", L"Cannot Rename This Ref",MB_OK|MB_ICONERROR);
1078 return;
1081 CString origName = pTree->GetRefName().Mid(11);
1083 CString newName;
1084 if(m_pListCtrlRoot != NULL)
1085 newName = m_pListCtrlRoot->GetRefName() + L'/';
1086 newName += pDispInfo->item.pszText;
1088 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1090 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);
1091 return;
1094 CString newNameTrunced = newName.Mid(11);
1096 CString result;
1097 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &result, CP_UTF8) != 0)
1099 CString errorMsg;
1100 errorMsg.Format(L"Could not rename branch %s. Message from git:\r\n\r\n%s",origName,result);
1101 CMessageBox::Show(m_hWnd,errorMsg,L"Error Renaming Branch",MB_OK|MB_ICONERROR);
1102 return;
1104 //Do as if it failed to rename. Let Refresh() do the job.
1105 //*pResult = TRUE;
1107 Refresh(newName);
1109 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1111 // AfxMessageBox(W_csPopup);
1115 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1117 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1118 *pResult = FALSE;
1120 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1122 if(!pTree->IsFrom(L"refs/heads"))
1124 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1125 return;