Fixed issue #933: implement git stash --include-untracked
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob5cf4e706ea6b379fba7d98c08b230606fbef8334
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_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
235 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
237 CString allRefs;
238 g_Git.Run(L"git for-each-ref --format="
239 L"%(refname)%04"
240 L"%(objectname)%04"
241 L"%(authordate:relative)%04"
242 L"%(subject)%04"
243 L"%(authorname)%04"
244 L"%(authordate:iso8601)%03",
245 &allRefs, NULL, CP_UTF8);
247 int linePos=0;
248 CString singleRef;
250 MAP_STRING_STRING refMap;
252 //First sort on ref name
253 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
255 singleRef.TrimLeft(L"\r\n");
256 int valuePos=0;
257 CString refName=singleRef.Tokenize(L"\04",valuePos);
258 if(refName.IsEmpty())
259 continue;
260 CString refRest=singleRef.Mid(valuePos);
263 //Use ref based on m_pickRef_Kind
264 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
265 continue; //Skip
266 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
267 continue; //Skip
268 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
269 continue; //Skip
271 refMap[refName] = refRest; //Use
276 //Populate ref tree
277 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
279 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
280 CString values=iterRefMap->second;
281 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
283 int valuePos=0;
284 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
285 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
286 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
287 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
288 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
292 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
293 //Probably not on a branch. Select root node.
294 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
298 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
300 if(!bExactMatch)
302 CString newRefName = GetFullRefName(refName);
303 if(!newRefName.IsEmpty())
304 refName = newRefName;
305 //else refName is not a valid ref. Try to select as good as possible.
307 if(wcsnicmp(refName,L"refs/",5)!=0)
308 return false; // Not a ref name
310 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
311 if(treeLeafHead.m_hTree != NULL)
313 //Not a leaf. Select tree node and return
314 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
315 return true;
318 if(treeLeafHead.m_pParent==NULL)
319 return false; //Weird... should not occur.
321 //This is the current head.
322 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
324 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
326 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
327 if(pCurrShadowTree == &treeLeafHead)
329 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
330 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
334 return true;
337 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
339 if(pTreePos==NULL)
341 if(wcsnicmp(refName,L"refs/",5)==0)
342 refName=refName.Mid(5);
343 pTreePos=&m_TreeRoot;
345 if(refName.IsEmpty())
346 return *pTreePos;//Found leaf
348 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
349 if(pNextTree==NULL)
351 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
352 ASSERT(!bCreateIfNotExist);
353 return *pTreePos;
356 if(!refName.IsEmpty())
358 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
359 //Leafs are for the list control.
360 if(pNextTree->m_hTree==NULL)
362 //New tree. Create node in control.
363 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
364 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
368 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
372 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
374 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
375 *pResult = 0;
377 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
380 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
382 m_ListRefLeafs.DeleteAllItems();
383 m_currSortCol = -1;
384 m_currSortDesc = false;
385 SetSortArrow(&m_ListRefLeafs,-1,false);
387 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
388 if(pTree==NULL)
390 ASSERT(FALSE);
391 return;
393 FillListCtrlForShadowTree(pTree,L"",true);
396 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
398 if(pTree->IsLeaf())
400 if (!pTree->m_csRefName.IsEmpty())
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);
411 else
414 CString csThisName;
415 if(!isFirstLevel)
416 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
417 else
418 m_pListCtrlRoot = pTree;
419 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
421 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
426 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
428 ASSERT(!leafs.empty());
430 CString csMessage;
431 CString csTitle;
433 UINT mbIcon=MB_ICONQUESTION;
434 csMessage = L"Are you sure you want to delete ";
436 bool bIsRemoteBranch = false;
437 bool bIsBranch = false;
438 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
439 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
441 if(bIsBranch)
443 if(leafs.size() == 1)
445 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
446 csTitle.Format(L"Confirm deletion of %sbranch %s",
447 bIsRemoteBranch? L"remote ": L"",
448 branchToDelete);
450 csMessage += "the ";
451 if(bIsRemoteBranch)
452 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
453 csMessage += L"branch:\r\n\r\n<b>";
454 csMessage += branchToDelete;
455 csMessage += L"</b>";
457 //Check if branch is fully merged in HEAD
458 CGitHash branchHash = g_Git.GetHash(leafs[0]->GetRefName());
459 CGitHash commonAncestor;
460 CString commonAncestorstr;
461 CString cmd;
462 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
463 g_Git.Run(cmd, &commonAncestorstr, NULL, CP_UTF8);
465 commonAncestor=commonAncestorstr;
467 if(commonAncestor != branchHash)
469 csMessage += L"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
470 mbIcon = MB_ICONWARNING;
472 if(bIsRemoteBranch)
474 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
475 mbIcon = MB_ICONWARNING;
478 else
480 csTitle.Format(L"Confirm deletion of %d %sbranches",
481 leafs.size(),
482 bIsRemoteBranch? L"remote ": L"");
484 CString csMoreMsgText;
485 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
486 csMessage += csMoreMsgText;
487 if(bIsRemoteBranch)
488 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
489 csMessage += L"branches";
491 csMessage += L"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
492 mbIcon = MB_ICONWARNING;
494 if(bIsRemoteBranch)
496 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
497 mbIcon = MB_ICONWARNING;
502 else if(leafs[0]->IsFrom(L"refs/tags"))
504 if(leafs.size() == 1)
506 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
507 csTitle.Format(L"Confirm deletion of tag %s", tagToDelete);
508 csMessage += "the tag:\r\n\r\n<b>";
509 csMessage += tagToDelete;
510 csMessage += "</b>";
512 else
514 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
515 csTitle.Format(L"Confirm deletion of %d tags", leafs.size());
516 CString csMoreMsgText;
517 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
518 csMessage += csMoreMsgText;
519 csMessage += L"tags";
523 return CMessageBox::Show(m_hWnd,csMessage,csTitle,MB_YESNO|mbIcon)==IDYES;
527 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
529 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
530 if(!DoDeleteRef((*i)->GetRefName(), bForce))
531 return false;
532 return true;
535 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
537 bool bIsRemoteBranch = false;
538 bool bIsBranch = false;
539 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
540 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
542 if(bIsBranch)
544 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
545 CString cmd;
546 if(bIsRemoteBranch)
548 int slash = branchToDelete.Find(L'/');
549 if(slash < 0)
550 return false;
551 CString remoteName = branchToDelete.Left(slash);
552 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
554 if(CAppUtils::IsSSHPutty())
556 CAppUtils::LaunchPAgent(NULL, &remoteName);
559 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
561 else
562 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
563 CString resultDummy;
564 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
566 CString errorMsg;
567 errorMsg.Format(L"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete,resultDummy);
568 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting branch",MB_OK|MB_ICONERROR);
569 return false;
572 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
574 CString tagToDelete = completeRefName.Mid(10);
575 CString cmd;
576 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
577 CString resultDummy;
578 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
580 CString errorMsg;
581 errorMsg.Format(L"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete,resultDummy);
582 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting tag",MB_OK|MB_ICONERROR);
583 return false;
586 return true;
589 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
591 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
592 if(pLeaf == NULL)
593 return CString();
594 return pLeaf->GetRefName();
598 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
600 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
601 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
604 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
606 CPoint clientPoint=point;
607 m_RefTreeCtrl.ScreenToClient(&clientPoint);
609 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
610 if(hTreeItem!=NULL)
611 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
613 VectorPShadowTree tree;
614 ShowContextMenu(point,hTreeItem,tree);
618 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
620 std::vector<CShadowTree*> selectedLeafs;
621 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
622 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
623 while(pos)
625 selectedLeafs.push_back(
626 (CShadowTree*)m_ListRefLeafs.GetItemData(
627 m_ListRefLeafs.GetNextSelectedItem(pos)));
630 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
633 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
635 CIconMenu popupMenu;
636 popupMenu.CreatePopupMenu();
638 bool bAddSeparator = false;
639 CString remoteName;
641 if(selectedLeafs.size()==1)
643 bAddSeparator = true;
645 bool bShowReflogOption = false;
646 bool bShowFetchOption = false;
647 bool bShowSwitchOption = false;
648 bool bShowRenameOption = false;
649 bool bShowCreateBranchOption = false;
651 CString fetchFromCmd;
653 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
655 bShowReflogOption = true;
656 bShowSwitchOption = true;
657 bShowRenameOption = true;
659 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
661 bShowReflogOption = true;
662 bShowFetchOption = true;
663 bShowCreateBranchOption = true;
665 int dummy = 0;//Needed for tokenize
666 remoteName = selectedLeafs[0]->GetRefName();
667 remoteName = remoteName.Mid(13);
668 remoteName = remoteName.Tokenize(L"/", dummy);
669 fetchFromCmd.Format(L"Fetch from \"%s\"", remoteName);
671 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
675 popupMenu.AppendMenuIcon(eCmd_ViewLog, L"Show Log", IDI_LOG);
676 if(bShowReflogOption) popupMenu.AppendMenuIcon(eCmd_ShowReflog, L"Show Reflog", IDI_LOG);
678 popupMenu.AppendMenu(MF_SEPARATOR);
679 bAddSeparator = false;
681 if(bShowFetchOption)
683 bAddSeparator = true;
684 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
687 if(bAddSeparator)
688 popupMenu.AppendMenu(MF_SEPARATOR);
690 popupMenu.AppendMenuIcon(eCmd_Switch, L"Switch to this Ref", IDI_SWITCH);
691 bAddSeparator = false;
692 popupMenu.AppendMenu(MF_SEPARATOR);
694 if(bShowCreateBranchOption)
696 bAddSeparator = true;
697 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
700 if(bShowRenameOption)
702 bAddSeparator = true;
703 popupMenu.AppendMenuIcon(eCmd_Rename, L"Rename", IDI_RENAME);
706 else if(selectedLeafs.size() == 2)
708 bAddSeparator = true;
709 popupMenu.AppendMenuIcon(eCmd_Diff, L"Compare these Refs", IDI_DIFF);
712 if(!selectedLeafs.empty())
714 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
716 if(bAddSeparator)
717 popupMenu.AppendMenu(MF_SEPARATOR);
718 CString menuItemName;
719 if(selectedLeafs.size() == 1)
720 menuItemName = L"Delete remote branch";
721 else
722 menuItemName.Format(L"Delete %d remote branches", selectedLeafs.size());
724 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
725 bAddSeparator = true;
727 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
729 if(bAddSeparator)
730 popupMenu.AppendMenu(MF_SEPARATOR);
731 CString menuItemName;
732 if(selectedLeafs.size() == 1)
733 menuItemName = L"Delete branch";
734 else
735 menuItemName.Format(L"Delete %d branches", selectedLeafs.size());
737 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
738 bAddSeparator = true;
740 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
742 if(bAddSeparator)
743 popupMenu.AppendMenu(MF_SEPARATOR);
744 CString menuItemName;
745 if(selectedLeafs.size() == 1)
746 menuItemName = L"Delete tag";
747 else
748 menuItemName.Format(L"Delete %d tags", selectedLeafs.size());
750 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
751 bAddSeparator = true;
756 if(hTreePos!=NULL && selectedLeafs.empty())
758 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
759 if(pTree->IsFrom(L"refs/remotes"))
761 if(bAddSeparator)
762 popupMenu.AppendMenu(MF_SEPARATOR);
763 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, L"Manage Remotes", IDI_SETTINGS);
764 bAddSeparator = true;
765 if(selectedLeafs.empty())
767 int dummy = 0;//Needed for tokenize
768 remoteName = pTree->GetRefName();
769 remoteName = remoteName.Mid(13);
770 remoteName = remoteName.Tokenize(L"/", dummy);
771 if(!remoteName.IsEmpty())
773 CString fetchFromCmd;
774 fetchFromCmd.Format(L"Fetch from %s", remoteName);
775 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
779 if(pTree->IsFrom(L"refs/heads"))
781 if(bAddSeparator)
782 popupMenu.AppendMenu(MF_SEPARATOR);
783 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create branch", IDI_COPY);
785 if(pTree->IsFrom(L"refs/tags"))
787 if(bAddSeparator)
788 popupMenu.AppendMenu(MF_SEPARATOR);
789 popupMenu.AppendMenuIcon(eCmd_CreateTag, L"Create tag", IDI_TAG);
794 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
795 switch(cmd)
797 case eCmd_ViewLog:
799 CLogDlg dlg;
800 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
801 dlg.DoModal();
803 break;
804 case eCmd_DeleteBranch:
805 case eCmd_DeleteRemoteBranch:
807 if(ConfirmDeleteRef(selectedLeafs))
808 DoDeleteRefs(selectedLeafs, true);
809 Refresh();
811 break;
812 case eCmd_DeleteTag:
814 if(ConfirmDeleteRef(selectedLeafs))
815 DoDeleteRefs(selectedLeafs, true);
816 Refresh();
818 break;
819 case eCmd_ShowReflog:
821 CRefLogDlg refLogDlg(this);
822 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
823 refLogDlg.DoModal();
825 break;
826 case eCmd_Fetch:
828 CAppUtils::Fetch(remoteName);
829 Refresh();
831 break;
832 case eCmd_Switch:
834 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
836 break;
837 case eCmd_Rename:
839 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
840 if(pos != NULL)
841 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
843 break;
844 case eCmd_AddRemote:
846 CAddRemoteDlg(this).DoModal();
847 Refresh();
849 break;
850 case eCmd_ManageRemotes:
852 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(g_Git.m_CurrentDir),this).DoModal();
853 // CSettingGitRemote W_Remotes(m_cmdPath);
854 // W_Remotes.DoModal();
855 Refresh();
857 break;
858 case eCmd_CreateBranch:
860 CString *commitHash = NULL;
861 if (selectedLeafs.size() == 1)
862 commitHash = &(selectedLeafs[0]->m_csRefHash);
863 CAppUtils::CreateBranchTag(false, commitHash);
864 Refresh();
866 break;
867 case eCmd_CreateTag:
869 CAppUtils::CreateBranchTag(true);
870 Refresh();
872 break;
873 case eCmd_Diff:
875 CFileDiffDlg dlg;
876 dlg.SetDiff(
877 NULL,
878 selectedLeafs[0]->m_csRefHash,
879 selectedLeafs[1]->m_csRefHash);
880 dlg.DoModal();
882 break;
886 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
888 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
889 if(!(*i)->IsFrom(from))
890 return false;
891 return true;
894 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
896 if (pMsg->message == WM_KEYDOWN)
898 switch (pMsg->wParam)
900 /* case VK_RETURN:
902 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
904 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
906 PostMessage(WM_COMMAND, IDOK);
908 return TRUE;
911 break;
912 */ case VK_F2:
914 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
916 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
917 if(pos != NULL)
918 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
921 break;
923 case VK_F5:
925 Refresh();
927 break;
932 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
935 class CRefLeafListCompareFunc
937 public:
938 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
940 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
942 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
945 int Compare(LPARAM lParam1, LPARAM lParam2)
947 return Compare(
948 (CShadowTree*)m_pList->GetItemData(lParam1),
949 (CShadowTree*)m_pList->GetItemData(lParam2));
952 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
954 int result=CompareNoDesc(pLeft,pRight);
955 if(m_desc)
956 return -result;
957 return result;
960 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
962 switch(m_col)
964 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
965 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
966 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
967 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
969 return 0;
972 int m_col;
973 bool m_desc;
974 CListCtrl* m_pList;
980 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
982 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
983 *pResult = 0;
985 if(m_currSortCol == pNMLV->iSubItem)
986 m_currSortDesc = !m_currSortDesc;
987 else
989 m_currSortCol = pNMLV->iSubItem;
990 m_currSortDesc = false;
993 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
994 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
996 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
999 void CBrowseRefsDlg::OnDestroy()
1001 m_pickedRef = GetSelectedRef(true, false);
1003 CResizableStandAloneDialog::OnDestroy();
1006 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1008 UNREFERENCED_PARAMETER(pNMHDR);
1009 *pResult = 0;
1011 EndDialog(IDOK);
1014 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1016 CBrowseRefsDlg dlg(CString(),NULL);
1018 if(initialRef.IsEmpty())
1019 initialRef = L"HEAD";
1020 dlg.m_initialRef = initialRef;
1021 dlg.m_pickRef_Kind = pickRef_Kind;
1023 if(dlg.DoModal() != IDOK)
1024 return CString();
1026 return dlg.m_pickedRef;
1029 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1031 CString origRef;
1032 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1033 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1034 if(resultRef.IsEmpty())
1035 return false;
1036 if(wcsncmp(resultRef,L"refs/",5)==0)
1037 resultRef = resultRef.Mid(5);
1038 // if(wcsncmp(resultRef,L"heads/",6)==0)
1039 // resultRef = resultRef.Mid(6);
1041 //Find closest match of choice in combobox
1042 int ixFound = -1;
1043 int matchLength = 0;
1044 CString comboRefName;
1045 for(int i = 0; i < pComboBox->GetCount(); ++i)
1047 pComboBox->GetLBText(i, comboRefName);
1048 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1049 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1050 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1052 matchLength = comboRefName.GetLength();
1053 ixFound = i;
1056 if(ixFound >= 0)
1057 pComboBox->SetCurSel(ixFound);
1058 else
1059 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)
1061 return true;
1064 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1066 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1067 *pResult = FALSE;
1069 if(pDispInfo->item.pszText == NULL)
1070 return; //User canceled changing
1072 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1074 if(!pTree->IsFrom(L"refs/heads"))
1076 CMessageBox::Show(m_hWnd, L"At the moment, you can only rename branches.", L"Cannot Rename This Ref",MB_OK|MB_ICONERROR);
1077 return;
1080 CString origName = pTree->GetRefName().Mid(11);
1082 CString newName;
1083 if(m_pListCtrlRoot != NULL)
1084 newName = m_pListCtrlRoot->GetRefName() + L'/';
1085 newName += pDispInfo->item.pszText;
1087 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1089 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);
1090 return;
1093 CString newNameTrunced = newName.Mid(11);
1095 CString result;
1096 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &result, CP_UTF8) != 0)
1098 CString errorMsg;
1099 errorMsg.Format(L"Could not rename branch %s. Message from git:\r\n\r\n%s",origName,result);
1100 CMessageBox::Show(m_hWnd,errorMsg,L"Error Renaming Branch",MB_OK|MB_ICONERROR);
1101 return;
1103 //Do as if it failed to rename. Let Refresh() do the job.
1104 //*pResult = TRUE;
1106 Refresh(newName);
1108 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1110 // AfxMessageBox(W_csPopup);
1114 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1116 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1117 *pResult = FALSE;
1119 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1121 if(!pTree->IsFrom(L"refs/heads"))
1123 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1124 return;