Moved last remaining strings to resources
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobcff9f4fc84959ea6d8e1f8d3e89aa5d82cb539f2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2012 - 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),
69 m_bHasWC(true)
74 CBrowseRefsDlg::~CBrowseRefsDlg()
78 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
80 CDialog::DoDataExchange(pDX);
81 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
82 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
86 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
87 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
88 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
89 ON_WM_CONTEXTMENU()
90 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
91 ON_WM_DESTROY()
92 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
93 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
94 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
95 END_MESSAGE_MAP()
98 // CBrowseRefsDlg message handlers
100 void CBrowseRefsDlg::OnBnClickedOk()
102 OnOK();
105 BOOL CBrowseRefsDlg::OnInitDialog()
107 CResizableStandAloneDialog::OnInitDialog();
108 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
110 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
111 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
112 AddAnchor(IDHELP, BOTTOM_RIGHT);
114 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
115 CString temp;
116 temp.LoadString(IDS_BRANCHNAME);
117 m_ListRefLeafs.InsertColumn(eCol_Name, temp, 0, 150);
118 temp.LoadString(IDS_DATELASTCOMMIT);
119 m_ListRefLeafs.InsertColumn(eCol_Date, temp, 0, 100);
120 temp.LoadString(IDS_LASTCOMMIT);
121 m_ListRefLeafs.InsertColumn(eCol_Msg, temp, 0, 300);
122 temp.LoadString(IDS_HASH);
123 m_ListRefLeafs.InsertColumn(eCol_Hash, temp, 0, 80);
125 AddAnchor(IDOK,BOTTOM_RIGHT);
126 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
128 Refresh(m_initialRef);
130 EnableSaveRestore(L"BrowseRefs");
132 CString sWindowTitle;
133 GetWindowText(sWindowTitle);
134 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
136 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
138 m_ListRefLeafs.SetFocus();
139 return FALSE;
142 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
144 int posSlash=nameLeft.Find('/');
145 CString nameSub;
146 if(posSlash<0)
148 nameSub=nameLeft;
149 nameLeft.Empty();//Nothing left
151 else
153 nameSub=nameLeft.Left(posSlash);
154 nameLeft=nameLeft.Mid(posSlash+1);
156 if(nameSub.IsEmpty())
157 return NULL;
159 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
160 return NULL;
162 CShadowTree& nextNode=m_ShadowTree[nameSub];
163 nextNode.m_csRefName=nameSub;
164 nextNode.m_pParent=this;
165 return &nextNode;
168 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
170 if(IsLeaf())
172 if(m_csRefName.GetLength() > partialRefName.GetLength())
173 return NULL;
174 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
176 //Match of leaf name. Try match on total name.
177 CString totalRefName = GetRefName();
178 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
179 return this; //Also match. Found.
182 else
184 //Not a leaf. Search all nodes.
185 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
187 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
188 if(pSubtree != NULL)
189 return pSubtree; //Found
192 return NULL;//Not found
196 typedef std::map<CString,CString> MAP_STRING_STRING;
198 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
200 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
201 //List ctrl selection?
202 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
204 //A leaf is selected
205 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
206 m_ListRefLeafs.GetNextSelectedItem(pos));
207 return pTree->GetRefName();
209 else if(!onlyIfLeaf)
211 //Tree ctrl selection?
212 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
213 if(hTree!=NULL)
215 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
216 return pTree->GetRefName();
219 return CString();//None
222 void CBrowseRefsDlg::Refresh(CString selectRef)
224 // m_RefMap.clear();
225 // g_Git.GetMapHashToFriendName(m_RefMap);
227 if(!selectRef.IsEmpty())
229 if(selectRef == "HEAD")
231 selectRef = g_Git.GetSymbolicRef(selectRef, false);
234 else
236 selectRef = GetSelectedRef(false, true);
239 m_RefTreeCtrl.DeleteAllItems();
240 m_ListRefLeafs.DeleteAllItems();
241 m_TreeRoot.m_ShadowTree.clear();
242 m_TreeRoot.m_csRefName = "refs";
243 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
244 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
246 CString allRefs;
247 g_Git.Run(L"git for-each-ref --format="
248 L"%(refname)%04"
249 L"%(objectname)%04"
250 L"%(authordate:relative)%04"
251 L"%(subject)%04"
252 L"%(authorname)%04"
253 L"%(authordate:iso8601)%03",
254 &allRefs, NULL, CP_UTF8);
256 int linePos=0;
257 CString singleRef;
259 MAP_STRING_STRING refMap;
261 //First sort on ref name
262 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
264 singleRef.TrimLeft(L"\r\n");
265 int valuePos=0;
266 CString refName=singleRef.Tokenize(L"\04",valuePos);
267 if(refName.IsEmpty())
268 continue;
269 CString refRest=singleRef.Mid(valuePos);
272 //Use ref based on m_pickRef_Kind
273 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
274 continue; //Skip
275 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
276 continue; //Skip
277 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
278 continue; //Skip
280 refMap[refName] = refRest; //Use
285 //Populate ref tree
286 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
288 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
289 CString values=iterRefMap->second;
290 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
292 int valuePos=0;
293 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
294 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
295 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
296 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
297 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
301 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
302 //Probably not on a branch. Select root node.
303 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
307 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
309 if(!bExactMatch)
311 CString newRefName = GetFullRefName(refName);
312 if(!newRefName.IsEmpty())
313 refName = newRefName;
314 //else refName is not a valid ref. Try to select as good as possible.
316 if(_wcsnicmp(refName, L"refs/", 5) != 0)
317 return false; // Not a ref name
319 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
320 if(treeLeafHead.m_hTree != NULL)
322 //Not a leaf. Select tree node and return
323 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
324 return true;
327 if(treeLeafHead.m_pParent==NULL)
328 return false; //Weird... should not occur.
330 //This is the current head.
331 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
333 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
335 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
336 if(pCurrShadowTree == &treeLeafHead)
338 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
339 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
343 return true;
346 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
348 if(pTreePos==NULL)
350 if(_wcsnicmp(refName, L"refs/", 5) == 0)
351 refName=refName.Mid(5);
352 pTreePos=&m_TreeRoot;
354 if(refName.IsEmpty())
355 return *pTreePos;//Found leaf
357 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
358 if(pNextTree==NULL)
360 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
361 ASSERT(!bCreateIfNotExist);
362 return *pTreePos;
365 if(!refName.IsEmpty())
367 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
368 //Leafs are for the list control.
369 if(pNextTree->m_hTree==NULL)
371 //New tree. Create node in control.
372 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
373 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
377 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
381 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
383 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
384 *pResult = 0;
386 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
389 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
391 m_ListRefLeafs.DeleteAllItems();
392 m_currSortCol = -1;
393 m_currSortDesc = false;
394 SetSortArrow(&m_ListRefLeafs,-1,false);
396 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
397 if(pTree==NULL)
399 ASSERT(FALSE);
400 return;
402 FillListCtrlForShadowTree(pTree,L"",true);
405 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
407 if(pTree->IsLeaf())
409 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL))
411 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
413 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
414 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, refNamePrefix+pTree->m_csRefName);
415 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
416 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
417 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
420 else
423 CString csThisName;
424 if(!isFirstLevel)
425 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
426 else
427 m_pListCtrlRoot = pTree;
428 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
430 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
435 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
437 ASSERT(!leafs.empty());
439 CString csMessage;
440 UINT mbIcon=MB_ICONQUESTION;
442 bool bIsRemoteBranch = false;
443 bool bIsBranch = false;
444 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
445 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
447 if(bIsBranch)
449 if(leafs.size() == 1)
451 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
452 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
454 //Check if branch is fully merged in HEAD
455 CGitHash branchHash = g_Git.GetHash(leafs[0]->GetRefName());
456 CGitHash commonAncestor;
457 CString commonAncestorstr;
458 CString cmd;
459 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
460 g_Git.Run(cmd, &commonAncestorstr, NULL, CP_UTF8);
462 commonAncestor=commonAncestorstr;
464 if(commonAncestor != branchHash)
466 csMessage += L"\r\n\r\n";
467 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
468 mbIcon = MB_ICONWARNING;
471 if(bIsRemoteBranch)
473 csMessage += L"\r\n\r\n";
474 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
475 mbIcon = MB_ICONWARNING;
478 else
480 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
482 csMessage += L"\r\n\r\n";
483 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
484 mbIcon = MB_ICONWARNING;
486 if(bIsRemoteBranch)
488 csMessage += L"\r\n\r\n";
489 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
490 mbIcon = MB_ICONWARNING;
495 else if(leafs[0]->IsFrom(L"refs/tags"))
497 if(leafs.size() == 1)
499 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
500 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
502 else
504 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
508 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
512 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
514 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
515 if(!DoDeleteRef((*i)->GetRefName(), bForce))
516 return false;
517 return true;
520 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
522 bool bIsRemoteBranch = false;
523 bool bIsBranch = false;
524 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
525 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
527 if(bIsBranch)
529 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
530 CString cmd;
531 if(bIsRemoteBranch)
533 int slash = branchToDelete.Find(L'/');
534 if(slash < 0)
535 return false;
536 CString remoteName = branchToDelete.Left(slash);
537 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
539 if(CAppUtils::IsSSHPutty())
541 CAppUtils::LaunchPAgent(NULL, &remoteName);
544 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
546 else
547 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
548 CString errorMsg;
549 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
551 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
552 return false;
555 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
557 CString tagToDelete = completeRefName.Mid(10);
558 CString cmd;
559 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
560 CString errorMsg;
561 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
563 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
564 return false;
567 return true;
570 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
572 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
573 if(pLeaf == NULL)
574 return CString();
575 return pLeaf->GetRefName();
579 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
581 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
582 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
585 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
587 CPoint clientPoint=point;
588 m_RefTreeCtrl.ScreenToClient(&clientPoint);
590 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
591 if(hTreeItem!=NULL)
592 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
594 VectorPShadowTree tree;
595 ShowContextMenu(point,hTreeItem,tree);
599 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
601 std::vector<CShadowTree*> selectedLeafs;
602 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
603 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
604 while(pos)
606 selectedLeafs.push_back(
607 (CShadowTree*)m_ListRefLeafs.GetItemData(
608 m_ListRefLeafs.GetNextSelectedItem(pos)));
611 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
614 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
616 CIconMenu popupMenu;
617 popupMenu.CreatePopupMenu();
619 bool bAddSeparator = false;
620 CString remoteName;
622 if(selectedLeafs.size()==1)
624 bAddSeparator = true;
626 bool bShowReflogOption = false;
627 bool bShowFetchOption = false;
628 bool bShowSwitchOption = false;
629 bool bShowRenameOption = false;
630 bool bShowCreateBranchOption = false;
632 CString fetchFromCmd;
634 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
636 bShowReflogOption = true;
637 bShowSwitchOption = true;
638 bShowRenameOption = true;
640 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
642 bShowReflogOption = true;
643 bShowFetchOption = true;
644 bShowCreateBranchOption = true;
646 int dummy = 0;//Needed for tokenize
647 remoteName = selectedLeafs[0]->GetRefName();
648 remoteName = remoteName.Mid(13);
649 remoteName = remoteName.Tokenize(L"/", dummy);
650 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
652 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
656 CString temp;
657 temp.LoadString(IDS_MENULOG);
658 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
659 if(bShowReflogOption)
661 temp.LoadString(IDS_MENUREFLOG);
662 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
665 popupMenu.AppendMenu(MF_SEPARATOR);
666 bAddSeparator = false;
668 if(bShowFetchOption)
670 bAddSeparator = true;
671 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
674 if(bAddSeparator)
675 popupMenu.AppendMenu(MF_SEPARATOR);
677 bAddSeparator = false;
678 if (m_bHasWC)
680 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
681 popupMenu.AppendMenu(MF_SEPARATOR);
684 if(bShowCreateBranchOption)
686 bAddSeparator = true;
687 temp.LoadString(IDS_MENUBRANCH);
688 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
691 if(bShowRenameOption)
693 bAddSeparator = true;
694 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
697 else if(selectedLeafs.size() == 2)
699 bAddSeparator = true;
700 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), 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.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
712 else
713 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, 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.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
725 else
726 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, 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.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
738 else
739 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
741 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
742 bAddSeparator = true;
747 if(hTreePos!=NULL && selectedLeafs.empty())
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, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), 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(IDS_PROC_BROWSEREFS_FETCHFROM, 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 CString temp;
775 temp.LoadString(IDS_MENUBRANCH);
776 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
778 if(pTree->IsFrom(L"refs/tags"))
780 if(bAddSeparator)
781 popupMenu.AppendMenu(MF_SEPARATOR);
782 CString temp;
783 temp.LoadString(IDS_MENUTAG);
784 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
789 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
790 switch(cmd)
792 case eCmd_ViewLog:
794 CLogDlg dlg;
795 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
796 dlg.DoModal();
798 break;
799 case eCmd_DeleteBranch:
800 case eCmd_DeleteRemoteBranch:
802 if(ConfirmDeleteRef(selectedLeafs))
803 DoDeleteRefs(selectedLeafs, true);
804 Refresh();
806 break;
807 case eCmd_DeleteTag:
809 if(ConfirmDeleteRef(selectedLeafs))
810 DoDeleteRefs(selectedLeafs, true);
811 Refresh();
813 break;
814 case eCmd_ShowReflog:
816 CRefLogDlg refLogDlg(this);
817 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
818 refLogDlg.DoModal();
820 break;
821 case eCmd_Fetch:
823 CAppUtils::Fetch(remoteName);
824 Refresh();
826 break;
827 case eCmd_Switch:
829 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
831 break;
832 case eCmd_Rename:
834 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
835 if(pos != NULL)
836 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
838 break;
839 case eCmd_AddRemote:
841 CAddRemoteDlg(this).DoModal();
842 Refresh();
844 break;
845 case eCmd_ManageRemotes:
847 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
848 // CSettingGitRemote W_Remotes(m_cmdPath);
849 // W_Remotes.DoModal();
850 Refresh();
852 break;
853 case eCmd_CreateBranch:
855 CString *commitHash = NULL;
856 if (selectedLeafs.size() == 1)
857 commitHash = &(selectedLeafs[0]->m_csRefHash);
858 CAppUtils::CreateBranchTag(false, commitHash);
859 Refresh();
861 break;
862 case eCmd_CreateTag:
864 CAppUtils::CreateBranchTag(true);
865 Refresh();
867 break;
868 case eCmd_Diff:
870 CFileDiffDlg dlg;
871 dlg.SetDiff(
872 NULL,
873 selectedLeafs[0]->m_csRefHash,
874 selectedLeafs[1]->m_csRefHash);
875 dlg.DoModal();
877 break;
881 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
883 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
884 if(!(*i)->IsFrom(from))
885 return false;
886 return true;
889 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
891 if (pMsg->message == WM_KEYDOWN)
893 switch (pMsg->wParam)
895 /* case VK_RETURN:
897 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
899 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
901 PostMessage(WM_COMMAND, IDOK);
903 return TRUE;
906 break;
907 */ case VK_F2:
909 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
911 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
912 if(pos != NULL)
913 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
916 break;
918 case VK_F5:
920 Refresh();
922 break;
927 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
930 class CRefLeafListCompareFunc
932 public:
933 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
935 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
937 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
940 int Compare(LPARAM lParam1, LPARAM lParam2)
942 return Compare(
943 (CShadowTree*)m_pList->GetItemData(lParam1),
944 (CShadowTree*)m_pList->GetItemData(lParam2));
947 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
949 int result=CompareNoDesc(pLeft,pRight);
950 if(m_desc)
951 return -result;
952 return result;
955 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
957 switch(m_col)
959 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
960 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
961 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
962 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
964 return 0;
967 int m_col;
968 bool m_desc;
969 CListCtrl* m_pList;
975 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
977 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
978 *pResult = 0;
980 if(m_currSortCol == pNMLV->iSubItem)
981 m_currSortDesc = !m_currSortDesc;
982 else
984 m_currSortCol = pNMLV->iSubItem;
985 m_currSortDesc = false;
988 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
989 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
991 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
994 void CBrowseRefsDlg::OnDestroy()
996 m_pickedRef = GetSelectedRef(true, false);
998 CResizableStandAloneDialog::OnDestroy();
1001 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1003 UNREFERENCED_PARAMETER(pNMHDR);
1004 *pResult = 0;
1006 EndDialog(IDOK);
1009 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1011 CBrowseRefsDlg dlg(CString(),NULL);
1013 if(initialRef.IsEmpty())
1014 initialRef = L"HEAD";
1015 dlg.m_initialRef = initialRef;
1016 dlg.m_pickRef_Kind = pickRef_Kind;
1018 if(dlg.DoModal() != IDOK)
1019 return CString();
1021 return dlg.m_pickedRef;
1024 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1026 CString origRef;
1027 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1028 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1029 if(resultRef.IsEmpty())
1030 return false;
1031 if(wcsncmp(resultRef,L"refs/",5)==0)
1032 resultRef = resultRef.Mid(5);
1033 // if(wcsncmp(resultRef,L"heads/",6)==0)
1034 // resultRef = resultRef.Mid(6);
1036 //Find closest match of choice in combobox
1037 int ixFound = -1;
1038 int matchLength = 0;
1039 CString comboRefName;
1040 for(int i = 0; i < pComboBox->GetCount(); ++i)
1042 pComboBox->GetLBText(i, comboRefName);
1043 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1044 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1045 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1047 matchLength = comboRefName.GetLength();
1048 ixFound = i;
1051 if(ixFound >= 0)
1052 pComboBox->SetCurSel(ixFound);
1053 else
1054 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)
1056 return true;
1059 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1061 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1062 *pResult = FALSE;
1064 if(pDispInfo->item.pszText == NULL)
1065 return; //User canceled changing
1067 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1069 if(!pTree->IsFrom(L"refs/heads"))
1071 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1072 return;
1075 CString origName = pTree->GetRefName().Mid(11);
1077 CString newName;
1078 if(m_pListCtrlRoot != NULL)
1079 newName = m_pListCtrlRoot->GetRefName() + L'/';
1080 newName += pDispInfo->item.pszText;
1082 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1084 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1085 return;
1088 CString newNameTrunced = newName.Mid(11);
1090 CString errorMsg;
1091 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1093 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1094 return;
1096 //Do as if it failed to rename. Let Refresh() do the job.
1097 //*pResult = TRUE;
1099 Refresh(newName);
1101 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1103 // AfxMessageBox(W_csPopup);
1107 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1109 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1110 *pResult = FALSE;
1112 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1114 if(!pTree->IsFrom(L"refs/heads"))
1116 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1117 return;