BrowseRefs: Sorting: Also backward sorting
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob8e80a4c09738b55eaf59e87a74f871a4f39a8ccc
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"
14 // CBrowseRefsDlg dialog
16 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
18 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
19 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
20 m_cmdPath(cmdPath),
21 m_currSortCol(-1),
22 m_currSortDesc(false)
27 CBrowseRefsDlg::~CBrowseRefsDlg()
31 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
33 CDialog::DoDataExchange(pDX);
34 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
35 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
39 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
40 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
41 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
42 ON_WM_CONTEXTMENU()
43 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
44 END_MESSAGE_MAP()
47 // CBrowseRefsDlg message handlers
49 void CBrowseRefsDlg::OnBnClickedOk()
51 OnOK();
54 BOOL CBrowseRefsDlg::OnInitDialog()
56 CResizableStandAloneDialog::OnInitDialog();
58 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
59 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
61 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
62 m_ListRefLeafs.InsertColumn(eCol_Name, L"Name",0,150);
63 m_ListRefLeafs.InsertColumn(eCol_Date, L"Date Last Commit",0,100);
64 m_ListRefLeafs.InsertColumn(eCol_Msg, L"Last Commit",0,300);
65 m_ListRefLeafs.InsertColumn(eCol_Hash, L"Hash",0,80);
67 AddAnchor(IDOK,BOTTOM_RIGHT);
68 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
70 Refresh(true);
73 return TRUE;
76 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
78 int posSlash=nameLeft.Find('/');
79 CString nameSub;
80 if(posSlash<0)
82 nameSub=nameLeft;
83 nameLeft.Empty();//Nothing left
85 else
87 nameSub=nameLeft.Left(posSlash);
88 nameLeft=nameLeft.Mid(posSlash+1);
90 if(nameSub.IsEmpty())
91 return NULL;
93 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
94 return NULL;
96 CShadowTree& nextNode=m_ShadowTree[nameSub];
97 nextNode.m_csRefName=nameSub;
98 nextNode.m_pParent=this;
99 return &nextNode;
102 typedef std::map<CString,CString> MAP_STRING_STRING;
104 void CBrowseRefsDlg::Refresh(bool bSelectCurHead)
106 // m_RefMap.clear();
107 // g_Git.GetMapHashToFriendName(m_RefMap);
109 CString selectRef;
110 if(bSelectCurHead)
112 g_Git.Run(L"git symbolic-ref HEAD",&selectRef,CP_UTF8);
113 selectRef.Trim(L"\r\n\t ");
115 else
117 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
118 //List ctrl selection?
119 if(pos)
121 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
122 m_ListRefLeafs.GetNextSelectedItem(pos));
123 selectRef=pTree->GetRefName();
125 else
127 //Tree ctrl selection?
128 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
129 if(hTree!=NULL)
131 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
132 selectRef=pTree->GetRefName();
137 m_RefTreeCtrl.DeleteAllItems();
138 m_ListRefLeafs.DeleteAllItems();
139 m_TreeRoot.m_ShadowTree.clear();
140 m_TreeRoot.m_csRefName="refs";
141 // m_TreeRoot.m_csShowName="Refs";
142 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
143 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
145 CString allRefs;
146 g_Git.Run(L"git for-each-ref --format="
147 L"%(refname)%04"
148 L"%(objectname)%04"
149 L"%(authordate:relative)%04"
150 L"%(subject)%04"
151 L"%(authorname)%04"
152 L"%(authordate:iso8601)",
153 &allRefs,CP_UTF8);
155 int linePos=0;
156 CString singleRef;
158 MAP_STRING_STRING refMap;
160 //First sort on ref name
161 while(!(singleRef=allRefs.Tokenize(L"\r\n",linePos)).IsEmpty())
163 int valuePos=0;
164 CString refName=singleRef.Tokenize(L"\04",valuePos);
165 CString refRest=singleRef.Mid(valuePos);
166 refMap[refName]=refRest;
171 // for(MAP_HASH_NAME::iterator iterRef=m_RefMap.begin();iterRef!=m_RefMap.end();++iterRef)
172 // for(STRING_VECTOR::iterator iterRefName=iterRef->second.begin();iterRefName!=iterRef->second.end();++iterRefName)
173 // refName[*iterRefName]=iterRef->first;
175 //Populate ref tree
176 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
178 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
179 CString values=iterRefMap->second;
181 int valuePos=0;
182 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos);
183 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos);
184 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos);
185 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos);
186 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
190 if(selectRef.IsEmpty() || !SelectRef(selectRef))
191 //Probably not on a branch. Select root node.
192 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
196 bool CBrowseRefsDlg::SelectRef(CString refName)
198 if(wcsnicmp(refName,L"refs/",5)!=0)
199 return false; // Not a ref name
201 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
202 if(treeLeafHead.m_hTree != NULL)
204 //Not a leaf. Select tree node and return
205 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
206 return true;
209 if(treeLeafHead.m_pParent==NULL)
210 return false; //Weird... should not occur.
212 //This is the current head.
213 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
215 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
217 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
218 if(pCurrShadowTree == &treeLeafHead)
220 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
221 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
225 return true;
228 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
230 if(pTreePos==NULL)
232 if(wcsnicmp(refName,L"refs/",5)==0)
233 refName=refName.Mid(5);
234 pTreePos=&m_TreeRoot;
236 if(refName.IsEmpty())
237 return *pTreePos;//Found leaf
239 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
240 if(pNextTree==NULL)
242 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
243 ASSERT(!bCreateIfNotExist);
244 return *pTreePos;
247 if(!refName.IsEmpty())
249 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
250 //Leafs are for the list control.
251 if(pNextTree->m_hTree==NULL)
253 //New tree. Create node in control.
254 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
255 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
259 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
263 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
265 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
266 *pResult = 0;
268 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
271 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
273 m_ListRefLeafs.DeleteAllItems();
275 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
276 if(pTree==NULL)
278 ASSERT(FALSE);
279 return;
281 FillListCtrlForShadowTree(pTree,L"",true);
284 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
286 if(pTree->IsLeaf())
288 int indexItem=m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(),L"");
290 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
291 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, refNamePrefix+pTree->m_csRefName);
292 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
293 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
294 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
296 else
299 CString csThisName;
300 if(!isFirstLevel)
301 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
302 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
304 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
309 bool CBrowseRefsDlg::ConfirmDeleteRef(CString completeRefName)
311 CString csMessage;
312 CString csTitle;
314 UINT mbIcon=MB_ICONQUESTION;
315 csMessage=L"Are you sure you want to delete the ";
316 if(wcsncmp(completeRefName,L"refs/heads",10)==0)
318 CString branchToDelete = completeRefName.Mid(11);
319 csTitle.Format(L"Confirm deletion of branch %s", branchToDelete);
320 csMessage += "branch:\r\n\r\n<b>";
321 csMessage += branchToDelete;
322 csMessage += "</b>";
324 //Check if branch is fully merged in HEAD
325 CString branchHash = g_Git.GetHash(completeRefName);
326 CString commonAncestor;
327 CString cmd;
328 cmd.Format(L"git.exe merge-base HEAD %s",completeRefName);
329 g_Git.Run(cmd,&commonAncestor,CP_UTF8);
331 branchHash=branchHash.Left(40);
332 commonAncestor=commonAncestor.Left(40);
334 if(commonAncestor != branchHash)
336 csMessage += L"\r\n\r\n<b>Warning:\r\nThis branch is not fully merged into HEAD.</b>";
337 mbIcon=MB_ICONWARNING;
340 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
342 CString tagToDelete = completeRefName.Mid(10);
343 csTitle.Format(L"Confirm deletion of tag %s", tagToDelete);
344 csMessage += "tag:\r\n\r\n<b>";
345 csMessage += tagToDelete;
346 csMessage += "</b>";
349 return CMessageBox::Show(m_hWnd,csMessage,csTitle,MB_YESNO|mbIcon)==IDYES;
354 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
356 if(wcsncmp(completeRefName,L"refs/heads",10)==0)
358 CString branchToDelete = completeRefName.Mid(11);
359 CString cmd;
360 cmd.Format(L"git.exe branch -%c %s",bForce?L'D':L'd',branchToDelete);
361 CString resultDummy;
362 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
364 CString errorMsg;
365 errorMsg.Format(L"Could not delete branch %s. Message from git:\r\n\r\n%s",branchToDelete,resultDummy);
366 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting branch",MB_OK|MB_ICONERROR);
367 return false;
370 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
372 CString tagToDelete = completeRefName.Mid(10);
373 CString cmd;
374 cmd.Format(L"git.exe tag -d %s",tagToDelete);
375 CString resultDummy;
376 if(g_Git.Run(cmd,&resultDummy,CP_UTF8)!=0)
378 CString errorMsg;
379 errorMsg.Format(L"Could not delete tag %s. Message from git:\r\n\r\n%s",tagToDelete,resultDummy);
380 CMessageBox::Show(m_hWnd,errorMsg,L"Error deleting tag",MB_OK|MB_ICONERROR);
381 return false;
384 return true;
387 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
389 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
390 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
393 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
395 CPoint clientPoint=point;
396 m_RefTreeCtrl.ScreenToClient(&clientPoint);
398 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
399 if(hTreeItem!=NULL)
400 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
402 ShowContextMenu(point,hTreeItem,VectorPShadowTree());
406 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
408 std::vector<CShadowTree*> selectedLeafs;
409 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
410 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
411 while(pos)
413 selectedLeafs.push_back(
414 (CShadowTree*)m_ListRefLeafs.GetItemData(
415 m_ListRefLeafs.GetNextSelectedItem(pos)));
418 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
421 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
423 CMenu popupMenu;
424 popupMenu.CreatePopupMenu();
426 if(selectedLeafs.size()==1)
428 popupMenu.AppendMenu(MF_STRING,eCmd_ViewLog,L"View log");
429 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
430 popupMenu.AppendMenu(MF_STRING,eCmd_DeleteBranch,L"Delete Branch");
431 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
432 popupMenu.AppendMenu(MF_STRING,eCmd_DeleteTag,L"Delete Tag");
434 // CShadowTree* pTree = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMHDR->idFrom);
435 // if(pTree==NULL)
436 // return;
439 if(hTreePos!=NULL)
441 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
442 if(pTree->IsFrom(L"refs/remotes"))
444 // popupMenu.AppendMenu(MF_STRING,eCmd_AddRemote,L"Add Remote");
445 popupMenu.AppendMenu(MF_STRING,eCmd_ManageRemotes,L"Manage Remotes");
447 else if(pTree->IsFrom(L"refs/heads"))
448 popupMenu.AppendMenu(MF_STRING,eCmd_CreateBranch,L"Create Branch");
449 else if(pTree->IsFrom(L"refs/tags"))
450 popupMenu.AppendMenu(MF_STRING,eCmd_CreateTag,L"Create Tag");
454 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
455 switch(cmd)
457 case eCmd_ViewLog:
459 CLogDlg dlg;
460 dlg.SetStartRef(selectedLeafs[0]->m_csRefHash);
461 dlg.DoModal();
463 break;
464 case eCmd_DeleteBranch:
466 if(ConfirmDeleteRef(selectedLeafs[0]->GetRefName()))
467 DoDeleteRef(selectedLeafs[0]->GetRefName(), true);
468 Refresh();
470 break;
471 case eCmd_DeleteTag:
473 if(ConfirmDeleteRef(selectedLeafs[0]->GetRefName()))
474 DoDeleteRef(selectedLeafs[0]->GetRefName(), true);
475 Refresh();
477 break;
478 case eCmd_AddRemote:
480 CAddRemoteDlg(this).DoModal();
481 Refresh();
483 break;
484 case eCmd_ManageRemotes:
486 CSinglePropSheetDlg(L"Git Remote Settings",new CSettingGitRemote(m_cmdPath),this).DoModal();
487 // CSettingGitRemote W_Remotes(m_cmdPath);
488 // W_Remotes.DoModal();
489 Refresh();
491 break;
492 case eCmd_CreateBranch:
494 CAppUtils::CreateBranchTag(false);
495 Refresh();
497 break;
498 case eCmd_CreateTag:
500 CAppUtils::CreateBranchTag(true);
501 Refresh();
503 break;
507 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
509 if (pMsg->message == WM_KEYDOWN)
511 switch (pMsg->wParam)
513 /* case VK_RETURN:
515 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
517 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
519 PostMessage(WM_COMMAND, IDOK);
521 return TRUE;
524 break;
525 */ case VK_F5:
527 Refresh();
529 break;
534 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
537 class CRefLeafListCompareFunc
539 public:
540 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
542 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
544 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
547 int Compare(LPARAM lParam1, LPARAM lParam2)
549 return Compare(
550 (CShadowTree*)m_pList->GetItemData(lParam1),
551 (CShadowTree*)m_pList->GetItemData(lParam2));
554 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
556 int result=CompareNoDesc(pLeft,pRight);
557 if(m_desc)
558 return -result;
559 return result;
562 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
564 switch(m_col)
566 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
567 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
568 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
569 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
571 return 0;
574 int m_col;
575 bool m_desc;
576 CListCtrl* m_pList;
581 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
583 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
584 *pResult = 0;
586 if(m_currSortCol == pNMLV->iSubItem)
587 m_currSortDesc = !m_currSortDesc;
588 else
590 m_currSortCol = pNMLV->iSubItem;
591 m_currSortDesc = false;
594 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
595 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);