Fixed issue #440: Don't enable 'Apply' button until the data are ok in Settings/Git...
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobfc2801ac60a70773cb495f1e7a2ba0a87ee99e59
1 // BrowseRefsDlg.cpp : implementation file
2 //
4 #include "stdafx.h"
5 #include "TortoiseProc.h"
6 #include "BrowseRefsDlg.h"
7 #include "LogDlg.h"
8 #include "AddRemoteDlg.h"
9 #include "AppUtils.h"
10 #include "Settings\SettingGitRemote.h"
11 #include "SinglePropSheetDlg.h"
12 #include "MessageBox.h"
13 #include "RefLogDlg.h"
14 #include "IconMenu.h"
15 #include "FileDiffDlg.h"
17 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
19 if (control == NULL)
20 return;
21 // set the sort arrow
22 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
23 HDITEM HeaderItem = {0};
24 HeaderItem.mask = HDI_FORMAT;
25 for (int i=0; i<pHeader->GetItemCount(); ++i)
27 pHeader->GetItem(i, &HeaderItem);
28 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
29 pHeader->SetItem(i, &HeaderItem);
31 if (nColumn >= 0)
33 pHeader->GetItem(nColumn, &HeaderItem);
34 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
35 pHeader->SetItem(nColumn, &HeaderItem);
39 // CBrowseRefsDlg dialog
41 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
43 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
44 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
45 m_cmdPath(cmdPath),
46 m_currSortCol(-1),
47 m_currSortDesc(false),
48 m_initialRef(L"HEAD"),
49 m_pickRef_Kind(gPickRef_All)
54 CBrowseRefsDlg::~CBrowseRefsDlg()
58 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
60 CDialog::DoDataExchange(pDX);
61 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
62 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
66 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
67 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
68 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
69 ON_WM_CONTEXTMENU()
70 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
71 ON_WM_DESTROY()
72 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
73 END_MESSAGE_MAP()
76 // CBrowseRefsDlg message handlers
78 void CBrowseRefsDlg::OnBnClickedOk()
80 OnOK();
83 BOOL CBrowseRefsDlg::OnInitDialog()
85 CResizableStandAloneDialog::OnInitDialog();
87 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
88 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
89 AddAnchor(IDHELP, BOTTOM_RIGHT);
91 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
92 m_ListRefLeafs.InsertColumn(eCol_Name, L"Name",0,150);
93 m_ListRefLeafs.InsertColumn(eCol_Date, L"Date Last Commit",0,100);
94 m_ListRefLeafs.InsertColumn(eCol_Msg, L"Last Commit",0,300);
95 m_ListRefLeafs.InsertColumn(eCol_Hash, L"Hash",0,80);
97 AddAnchor(IDOK,BOTTOM_RIGHT);
98 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
100 Refresh(m_initialRef);
102 EnableSaveRestore(L"BrowseRefs");
105 m_ListRefLeafs.SetFocus();
106 return FALSE;
109 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
111 int posSlash=nameLeft.Find('/');
112 CString nameSub;
113 if(posSlash<0)
115 nameSub=nameLeft;
116 nameLeft.Empty();//Nothing left
118 else
120 nameSub=nameLeft.Left(posSlash);
121 nameLeft=nameLeft.Mid(posSlash+1);
123 if(nameSub.IsEmpty())
124 return NULL;
126 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
127 return NULL;
129 CShadowTree& nextNode=m_ShadowTree[nameSub];
130 nextNode.m_csRefName=nameSub;
131 nextNode.m_pParent=this;
132 return &nextNode;
135 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
137 if(IsLeaf())
139 if(m_csRefName.GetLength() > partialRefName.GetLength())
140 return NULL;
141 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
143 //Match of leaf name. Try match on total name.
144 CString totalRefName = GetRefName();
145 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
146 return this; //Also match. Found.
149 else
151 //Not a leaf. Search all nodes.
152 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
154 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
155 if(pSubtree != NULL)
156 return pSubtree; //Found
159 return NULL;//Not found
163 typedef std::map<CString,CString> MAP_STRING_STRING;
165 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
167 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
168 //List ctrl selection?
169 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
171 //A leaf is selected
172 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
173 m_ListRefLeafs.GetNextSelectedItem(pos));
174 return pTree->GetRefName();
176 else if(!onlyIfLeaf)
178 //Tree ctrl selection?
179 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
180 if(hTree!=NULL)
182 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
183 return pTree->GetRefName();
186 return CString();//None
189 void CBrowseRefsDlg::Refresh(CString selectRef)
191 // m_RefMap.clear();
192 // g_Git.GetMapHashToFriendName(m_RefMap);
194 if(!selectRef.IsEmpty())
196 if(selectRef == "HEAD")
198 selectRef = g_Git.GetSymbolicRef(selectRef, false);
201 else
203 selectRef = GetSelectedRef(false, true);
206 m_RefTreeCtrl.DeleteAllItems();
207 m_ListRefLeafs.DeleteAllItems();
208 m_TreeRoot.m_ShadowTree.clear();
209 m_TreeRoot.m_csRefName="refs";
210 // m_TreeRoot.m_csShowName="Refs";
211 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
212 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
214 CString allRefs;
215 g_Git.Run(L"git for-each-ref --format="
216 L"%(refname)%04"
217 L"%(objectname)%04"
218 L"%(authordate:relative)%04"
219 L"%(subject)%04"
220 L"%(authorname)%04"
221 L"%(authordate:iso8601)",
222 &allRefs,CP_UTF8);
224 int linePos=0;
225 CString singleRef;
227 MAP_STRING_STRING refMap;
229 //First sort on ref name
230 while(!(singleRef=allRefs.Tokenize(L"\r\n",linePos)).IsEmpty())
232 int valuePos=0;
233 CString refName=singleRef.Tokenize(L"\04",valuePos);
234 CString refRest=singleRef.Mid(valuePos);
236 //Use ref based on m_pickRef_Kind
237 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
238 continue; //Skip
239 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
240 continue; //Skip
241 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
242 continue; //Skip
244 refMap[refName] = refRest; //Use
249 //Populate ref tree
250 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
252 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
253 CString values=iterRefMap->second;
254 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
256 int valuePos=0;
257 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos);
258 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos);
259 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos);
260 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos);
261 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
265 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
266 //Probably not on a branch. Select root node.
267 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
271 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
273 if(!bExactMatch)
274 refName = GetFullRefName(refName);
275 if(wcsnicmp(refName,L"refs/",5)!=0)
276 return false; // Not a ref name
278 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
279 if(treeLeafHead.m_hTree != NULL)
281 //Not a leaf. Select tree node and return
282 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
283 return true;
286 if(treeLeafHead.m_pParent==NULL)
287 return false; //Weird... should not occur.
289 //This is the current head.
290 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
292 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
294 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
295 if(pCurrShadowTree == &treeLeafHead)
297 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
298 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
302 return true;
305 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
307 if(pTreePos==NULL)
309 if(wcsnicmp(refName,L"refs/",5)==0)
310 refName=refName.Mid(5);
311 pTreePos=&m_TreeRoot;
313 if(refName.IsEmpty())
314 return *pTreePos;//Found leaf
316 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
317 if(pNextTree==NULL)
319 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
320 ASSERT(!bCreateIfNotExist);
321 return *pTreePos;
324 if(!refName.IsEmpty())
326 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
327 //Leafs are for the list control.
328 if(pNextTree->m_hTree==NULL)
330 //New tree. Create node in control.
331 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
332 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
336 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
340 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
342 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
343 *pResult = 0;
345 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
348 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
350 m_ListRefLeafs.DeleteAllItems();
351 m_currSortCol = -1;
352 m_currSortDesc = false;
353 SetSortArrow(&m_ListRefLeafs,-1,false);
355 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
356 if(pTree==NULL)
358 ASSERT(FALSE);
359 return;
361 FillListCtrlForShadowTree(pTree,L"",true);
364 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
366 if(pTree->IsLeaf())
368 int indexItem=m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(),L"");
370 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
371 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, refNamePrefix+pTree->m_csRefName);
372 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
373 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
374 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
376 else
379 CString csThisName;
380 if(!isFirstLevel)
381 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
382 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
384 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
389 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
391 ASSERT(!leafs.empty());
393 CString csMessage;
394 CString csTitle;
396 UINT mbIcon=MB_ICONQUESTION;
397 csMessage = L"Are you sure you want to delete ";
399 bool bIsRemoteBranch = false;
400 bool bIsBranch = false;
401 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
402 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
404 if(bIsBranch)
406 if(leafs.size() == 1)
408 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
409 csTitle.Format(L"Confirm deletion of %sbranch %s",
410 bIsRemoteBranch? L"remote ": L"",
411 branchToDelete);
413 csMessage += "the ";
414 if(bIsRemoteBranch)
415 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
416 csMessage += L"branch:\r\n\r\n<b>";
417 csMessage += branchToDelete;
418 csMessage += L"</b>";
420 //Check if branch is fully merged in HEAD
421 CString branchHash = g_Git.GetHash(leafs[0]->GetRefName());
422 CString commonAncestor;
423 CString cmd;
424 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
425 g_Git.Run(cmd,&commonAncestor,CP_UTF8);
427 branchHash=branchHash.Left(40);
428 commonAncestor=commonAncestor.Left(40);
430 if(commonAncestor != branchHash)
432 csMessage += L"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
433 mbIcon = MB_ICONWARNING;
435 if(bIsRemoteBranch)
437 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branch on the remote.</b>";
438 mbIcon = MB_ICONWARNING;
441 else
443 csTitle.Format(L"Confirm deletion of %d %sbranches",
444 leafs.size(),
445 bIsRemoteBranch? L"remote ": L"");
447 CString csMoreMsgText;
448 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
449 csMessage += csMoreMsgText;
450 if(bIsRemoteBranch)
451 csMessage += L"<ct=0x0000FF><i>remote</i></ct> ";
452 csMessage += L"branches";
454 csMessage += L"\r\n\r\n<b>Warning:\r\nIt has not been checked if these branches have been fully merged into HEAD.</b>";
455 mbIcon = MB_ICONWARNING;
457 if(bIsRemoteBranch)
459 csMessage += L"\r\n\r\n<b>Warning:\r\nThis action will remove the branches on the remote.</b>";
460 mbIcon = MB_ICONWARNING;
465 else if(leafs[0]->IsFrom(L"refs/tags"))
467 if(leafs.size() == 1)
469 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
470 csTitle.Format(L"Confirm deletion of tag %s", tagToDelete);
471 csMessage += "the tag:\r\n\r\n<b>";
472 csMessage += tagToDelete;
473 csMessage += "</b>";
475 else
477 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
478 csTitle.Format(L"Confirm deletion of %d tags", leafs.size());
479 CString csMoreMsgText;
480 csMoreMsgText.Format(L"<b>%d</b> ", leafs.size());
481 csMessage += csMoreMsgText;
482 csMessage += L"tags";
486 return CMessageBox::Show(m_hWnd,csMessage,csTitle,MB_YESNO|mbIcon)==IDYES;
490 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
492 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
493 if(!DoDeleteRef((*i)->GetRefName(), bForce))
494 return false;
495 return true;
498 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
500 bool bIsRemoteBranch = false;
501 bool bIsBranch = false;
502 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
503 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
505 if(bIsBranch)
507 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
508 CString cmd;
509 if(bIsRemoteBranch)
511 int slash = branchToDelete.Find(L'/');
512 if(slash < 0)
513 return false;
514 CString remoteName = branchToDelete.Left(slash);
515 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
516 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
518 else
519 cmd.Format(L"git.exe branch -%c %s",bForce?L'D':L'd',branchToDelete);
520 CString resultDummy;
521 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
523 CString errorMsg;
524 errorMsg.Format(L"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete,resultDummy);
525 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting branch",MB_OK|MB_ICONERROR);
526 return false;
529 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
531 CString tagToDelete = completeRefName.Mid(10);
532 CString cmd;
533 cmd.Format(L"git.exe tag -d %s",tagToDelete);
534 CString resultDummy;
535 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
537 CString errorMsg;
538 errorMsg.Format(L"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete,resultDummy);
539 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting tag",MB_OK|MB_ICONERROR);
540 return false;
543 return true;
546 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
548 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
549 if(pLeaf == NULL)
550 return CString();
551 return pLeaf->GetRefName();
555 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
557 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
558 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
561 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
563 CPoint clientPoint=point;
564 m_RefTreeCtrl.ScreenToClient(&clientPoint);
566 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
567 if(hTreeItem!=NULL)
568 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
570 ShowContextMenu(point,hTreeItem,VectorPShadowTree());
574 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
576 std::vector<CShadowTree*> selectedLeafs;
577 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
578 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
579 while(pos)
581 selectedLeafs.push_back(
582 (CShadowTree*)m_ListRefLeafs.GetItemData(
583 m_ListRefLeafs.GetNextSelectedItem(pos)));
586 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
589 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
591 CIconMenu popupMenu;
592 popupMenu.CreatePopupMenu();
594 bool bAddSeparator = false;
595 CString remoteName;
597 if(selectedLeafs.size()==1)
599 bAddSeparator = true;
601 bool bShowReflogOption = false;
602 bool bShowFetchOption = false;
603 bool bShowSwitchOption = false;
605 CString fetchFromCmd;
607 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
609 bShowReflogOption = true;
610 bShowSwitchOption = true;
612 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
614 bShowReflogOption = true;
615 bShowFetchOption = true;
617 int dummy = 0;//Needed for tokenize
618 remoteName = selectedLeafs[0]->GetRefName();
619 remoteName = remoteName.Mid(13);
620 remoteName = remoteName.Tokenize(L"/", dummy);
621 fetchFromCmd.Format(L"Fetch from %s", remoteName);
623 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
627 popupMenu.AppendMenuIcon(eCmd_ViewLog, L"Show Log", IDI_LOG);
628 if(bShowReflogOption) popupMenu.AppendMenuIcon(eCmd_ShowReflog, L"Show Reflog", IDI_LOG);
629 if(bShowFetchOption) popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
630 if(bShowSwitchOption) popupMenu.AppendMenuIcon(eCmd_Switch, L"Switch to this Ref", IDI_SWITCH);
633 else if(selectedLeafs.size() == 2)
635 bAddSeparator = true;
637 popupMenu.AppendMenuIcon(eCmd_Diff, L"Compare These Refs", IDI_DIFF);
640 if(!selectedLeafs.empty())
642 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
644 CString menuItemName;
645 if(selectedLeafs.size() == 1)
646 menuItemName = L"Delete Remote Branch";
647 else
648 menuItemName.Format(L"Delete %d Remote Branches", selectedLeafs.size());
650 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
653 if(AreAllFrom(selectedLeafs, L"refs/heads/"))
655 CString menuItemName;
656 if(selectedLeafs.size() == 1)
657 menuItemName = L"Delete Branch";
658 else
659 menuItemName.Format(L"Delete %d Branches", selectedLeafs.size());
661 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
664 if(AreAllFrom(selectedLeafs, L"refs/tags/"))
666 CString menuItemName;
667 if(selectedLeafs.size() == 1)
668 menuItemName = L"Delete Tag";
669 else
670 menuItemName.Format(L"Delete %d Tags", selectedLeafs.size());
672 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
676 if(bAddSeparator) popupMenu.AppendMenu(MF_SEPARATOR);
678 if(hTreePos!=NULL)
680 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
681 if(pTree->IsFrom(L"refs/remotes"))
683 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
684 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, L"Manage Remotes", IDI_SETTINGS);
685 if(selectedLeafs.empty())
687 int dummy = 0;//Needed for tokenize
688 remoteName = pTree->GetRefName();
689 remoteName = remoteName.Mid(13);
690 remoteName = remoteName.Tokenize(L"/", dummy);
691 if(!remoteName.IsEmpty())
693 CString fetchFromCmd;
694 fetchFromCmd.Format(L"Fetch from %s", remoteName);
695 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
699 else if(pTree->IsFrom(L"refs/heads"))
700 popupMenu.AppendMenuIcon(eCmd_CreateBranch, L"Create Branch", IDI_COPY);
701 else if(pTree->IsFrom(L"refs/tags"))
702 popupMenu.AppendMenuIcon(eCmd_CreateTag, L"Create Tag", IDI_TAG);
706 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
707 switch(cmd)
709 case eCmd_ViewLog:
711 CLogDlg dlg;
712 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
713 dlg.DoModal();
715 break;
716 case eCmd_DeleteBranch:
717 case eCmd_DeleteRemoteBranch:
719 if(ConfirmDeleteRef(selectedLeafs))
720 DoDeleteRefs(selectedLeafs, true);
721 Refresh();
723 break;
724 case eCmd_DeleteTag:
726 if(ConfirmDeleteRef(selectedLeafs))
727 DoDeleteRefs(selectedLeafs, true);
728 Refresh();
730 break;
731 case eCmd_ShowReflog:
733 CRefLogDlg refLogDlg(this);
734 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
735 refLogDlg.DoModal();
737 break;
738 case eCmd_Fetch:
740 CString cmd;
741 cmd.Format(_T("git.exe fetch %s"), remoteName);
742 CProgressDlg progress;
743 progress.m_GitCmd=cmd;
744 progress.DoModal();
745 Refresh();
747 break;
748 case eCmd_Switch:
750 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
752 break;
753 case eCmd_AddRemote:
755 CAddRemoteDlg(this).DoModal();
756 Refresh();
758 break;
759 case eCmd_ManageRemotes:
761 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(g_Git.m_CurrentDir),this).DoModal();
762 // CSettingGitRemote W_Remotes(m_cmdPath);
763 // W_Remotes.DoModal();
764 Refresh();
766 break;
767 case eCmd_CreateBranch:
769 CAppUtils::CreateBranchTag(false);
770 Refresh();
772 break;
773 case eCmd_CreateTag:
775 CAppUtils::CreateBranchTag(true);
776 Refresh();
778 break;
779 case eCmd_Diff:
781 CFileDiffDlg dlg;
782 dlg.SetDiff(
783 NULL,
784 selectedLeafs[0]->m_csRefHash,
785 selectedLeafs[1]->m_csRefHash);
786 dlg.DoModal();
788 break;
792 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
794 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
795 if(!(*i)->IsFrom(from))
796 return false;
797 return true;
800 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
802 if (pMsg->message == WM_KEYDOWN)
804 switch (pMsg->wParam)
806 /* case VK_RETURN:
808 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
810 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
812 PostMessage(WM_COMMAND, IDOK);
814 return TRUE;
817 break;
818 */ case VK_F5:
820 Refresh();
822 break;
827 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
830 class CRefLeafListCompareFunc
832 public:
833 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
835 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
837 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
840 int Compare(LPARAM lParam1, LPARAM lParam2)
842 return Compare(
843 (CShadowTree*)m_pList->GetItemData(lParam1),
844 (CShadowTree*)m_pList->GetItemData(lParam2));
847 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
849 int result=CompareNoDesc(pLeft,pRight);
850 if(m_desc)
851 return -result;
852 return result;
855 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
857 switch(m_col)
859 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
860 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
861 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
862 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
864 return 0;
867 int m_col;
868 bool m_desc;
869 CListCtrl* m_pList;
875 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
877 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
878 *pResult = 0;
880 if(m_currSortCol == pNMLV->iSubItem)
881 m_currSortDesc = !m_currSortDesc;
882 else
884 m_currSortCol = pNMLV->iSubItem;
885 m_currSortDesc = false;
888 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
889 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
891 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
894 void CBrowseRefsDlg::OnDestroy()
896 m_pickedRef = GetSelectedRef(true, false);
898 CResizableStandAloneDialog::OnDestroy();
901 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
903 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
904 *pResult = 0;
906 EndDialog(IDOK);
909 CString CBrowseRefsDlg::PickRef(bool returnAsHash, CString initialRef, int pickRef_Kind)
911 CBrowseRefsDlg dlg(CString(),NULL);
913 if(initialRef.IsEmpty())
914 initialRef = L"HEAD";
915 dlg.m_initialRef = initialRef;
916 dlg.m_pickRef_Kind = pickRef_Kind;
918 if(dlg.DoModal() != IDOK)
919 return CString();
921 return dlg.m_pickedRef;
924 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
926 CString origRef;
927 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
928 CString resultRef = PickRef(false,origRef,pickRef_Kind);
929 if(resultRef.IsEmpty())
930 return false;
931 if(wcsncmp(resultRef,L"refs/",5)==0)
932 resultRef = resultRef.Mid(5);
933 // if(wcsncmp(resultRef,L"heads/",6)==0)
934 // resultRef = resultRef.Mid(6);
936 //Find closest match of choice in combobox
937 int ixFound = -1;
938 int matchLength = 0;
939 CString comboRefName;
940 for(int i = 0; i < pComboBox->GetCount(); ++i)
942 pComboBox->GetLBText(i, comboRefName);
943 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
944 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
945 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
947 matchLength = comboRefName.GetLength();
948 ixFound = i;
951 if(ixFound >= 0)
952 pComboBox->SetCurSel(ixFound);
953 else
954 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)
956 return true;