Fixed issue #1542: Can send pull request email
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob1415dcba5451e66def5c6d84fd5f62fbe92541d1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2013 - 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"
34 #include "DeleteRemoteTagDlg.h"
35 #include "UnicodeUtils.h"
36 #include "InputDlg.h"
37 #include "SysProgressDlg.h"
39 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
41 if (control == NULL)
42 return;
43 // set the sort arrow
44 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
45 HDITEM HeaderItem = {0};
46 HeaderItem.mask = HDI_FORMAT;
47 for (int i=0; i<pHeader->GetItemCount(); ++i)
49 pHeader->GetItem(i, &HeaderItem);
50 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
51 pHeader->SetItem(i, &HeaderItem);
53 if (nColumn >= 0)
55 pHeader->GetItem(nColumn, &HeaderItem);
56 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
57 pHeader->SetItem(nColumn, &HeaderItem);
61 class CRefLeafListCompareFunc
63 public:
64 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
65 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
66 if (m_bSortLogical)
67 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
70 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
72 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
75 int Compare(LPARAM lParam1, LPARAM lParam2)
77 return Compare(
78 (CShadowTree*)m_pList->GetItemData((int)lParam1),
79 (CShadowTree*)m_pList->GetItemData((int)lParam2));
82 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
84 int result=CompareNoDesc(pLeft,pRight);
85 if(m_desc)
86 return -result;
87 return result;
90 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
92 switch(m_col)
94 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
95 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
96 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
97 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
98 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
99 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
101 return 0;
103 int SortStrCmp(CString left, CString right)
105 if (m_bSortLogical)
106 return StrCmpLogicalW(left, right);
107 return StrCmpI(left, right);
110 int m_col;
111 bool m_desc;
112 CListCtrl* m_pList;
113 bool m_bSortLogical;
116 // CBrowseRefsDlg dialog
118 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
120 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
121 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
122 m_cmdPath(cmdPath),
123 m_currSortCol(0),
124 m_currSortDesc(false),
125 m_initialRef(L"HEAD"),
126 m_pickRef_Kind(gPickRef_All),
127 m_pListCtrlRoot(NULL),
128 m_bHasWC(true),
129 m_SelectedFilters(LOGFILTER_ALL),
130 m_bPickOne(false)
135 CBrowseRefsDlg::~CBrowseRefsDlg()
139 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
141 CDialog::DoDataExchange(pDX);
142 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
143 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
144 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
148 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
149 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
150 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
151 ON_WM_CONTEXTMENU()
152 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
153 ON_WM_DESTROY()
154 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
155 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
156 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
157 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
158 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
159 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
160 ON_WM_TIMER()
161 END_MESSAGE_MAP()
164 // CBrowseRefsDlg message handlers
166 void CBrowseRefsDlg::OnBnClickedOk()
168 OnOK();
171 BOOL CBrowseRefsDlg::OnInitDialog()
173 CResizableStandAloneDialog::OnInitDialog();
174 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
176 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
177 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
178 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
179 SetFilterCueText();
181 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
182 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
183 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
184 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
185 AddAnchor(IDHELP, BOTTOM_RIGHT);
187 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
188 CString temp;
189 temp.LoadString(IDS_BRANCHNAME);
190 m_ListRefLeafs.InsertColumn(eCol_Name, temp, 0, 150);
191 temp.LoadString(IDS_DATELASTCOMMIT);
192 m_ListRefLeafs.InsertColumn(eCol_Date, temp, 0, 100);
193 temp.LoadString(IDS_LASTCOMMIT);
194 m_ListRefLeafs.InsertColumn(eCol_Msg, temp, 0, 300);
195 temp.LoadString(IDS_LASTAUTHOR);
196 m_ListRefLeafs.InsertColumn(eCol_LastAuthor, temp, 0, 100);
197 temp.LoadString(IDS_HASH);
198 m_ListRefLeafs.InsertColumn(eCol_Hash, temp, 0, 80);
199 temp.LoadString(IDS_DESCRIPTION);
200 m_ListRefLeafs.InsertColumn(eCol_Description, temp, 0, 80);
202 AddAnchor(IDOK,BOTTOM_RIGHT);
203 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
205 Refresh(m_initialRef);
207 EnableSaveRestore(L"BrowseRefs");
209 CString sWindowTitle;
210 GetWindowText(sWindowTitle);
211 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
213 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
215 if (m_bPickOne)
216 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
218 m_ListRefLeafs.SetFocus();
219 return FALSE;
222 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
224 int posSlash=nameLeft.Find('/');
225 CString nameSub;
226 if(posSlash<0)
228 nameSub=nameLeft;
229 nameLeft.Empty();//Nothing left
231 else
233 nameSub=nameLeft.Left(posSlash);
234 nameLeft=nameLeft.Mid(posSlash+1);
236 if(nameSub.IsEmpty())
237 return NULL;
239 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
240 return NULL;
242 CShadowTree& nextNode=m_ShadowTree[nameSub];
243 nextNode.m_csRefName=nameSub;
244 nextNode.m_pParent=this;
245 return &nextNode;
248 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
250 if(IsLeaf())
252 if(m_csRefName.GetLength() > partialRefName.GetLength())
253 return NULL;
254 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
256 //Match of leaf name. Try match on total name.
257 CString totalRefName = GetRefName();
258 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
259 return this; //Also match. Found.
262 else
264 //Not a leaf. Search all nodes.
265 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
267 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
268 if(pSubtree != NULL)
269 return pSubtree; //Found
272 return NULL;//Not found
276 typedef std::map<CString,CString> MAP_STRING_STRING;
278 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
280 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
281 //List ctrl selection?
282 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
284 //A leaf is selected
285 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
286 m_ListRefLeafs.GetNextSelectedItem(pos));
287 return pTree->GetRefName();
289 else if (pos && !pickFirstSelIfMultiSel)
291 // at least one leaf is selected
292 CString refs;
293 int index;
294 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
296 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
297 if(wcsncmp(ref, L"refs/", 5) == 0)
298 ref = ref.Mid(5);
299 if(wcsncmp(ref, L"heads/", 6) == 0)
300 ref = ref.Mid(6);
301 refs += ref + _T(" ");
303 return refs.Trim();
305 else if(!onlyIfLeaf)
307 //Tree ctrl selection?
308 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
309 if(hTree!=NULL)
311 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
312 return pTree->GetRefName();
315 return CString();//None
318 static int GetBranchDescriptionsCallback(const git_config_entry *entry, void *data)
320 MAP_STRING_STRING *descriptions = (MAP_STRING_STRING *) data;
321 CString key = CUnicodeUtils::GetUnicode(entry->name, CP_UTF8);
322 CString val = CUnicodeUtils::GetUnicode(entry->value, CP_UTF8);
323 descriptions->insert(make_pair(key.Mid(7, key.GetLength() - 7 - 12), val)); // 7: branch., 12: .description
324 return 0;
327 MAP_STRING_STRING GetBranchDescriptions()
329 MAP_STRING_STRING descriptions;
330 git_config * config;
331 git_config_new(&config);
332 CStringA projectConfigA = CUnicodeUtils::GetMulti(g_Git.GetGitLocalConfig(), CP_UTF8);
333 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 3, FALSE);
334 projectConfigA.ReleaseBuffer();
335 git_config_foreach_match(config, "branch\\..*\\.description", GetBranchDescriptionsCallback, &descriptions);
336 git_config_free(config);
337 return descriptions;
340 void CBrowseRefsDlg::Refresh(CString selectRef)
342 // m_RefMap.clear();
343 // g_Git.GetMapHashToFriendName(m_RefMap);
345 if(!selectRef.IsEmpty())
347 if(selectRef == "HEAD")
349 selectRef = g_Git.GetSymbolicRef(selectRef, false);
352 else
354 selectRef = GetSelectedRef(false, true);
357 m_RefTreeCtrl.DeleteAllItems();
358 m_ListRefLeafs.DeleteAllItems();
359 m_TreeRoot.m_ShadowTree.clear();
360 m_TreeRoot.m_csRefName = "refs";
361 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
362 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
364 CString allRefs, error;
365 if (g_Git.Run(L"git for-each-ref --format="
366 L"%(refname)%04"
367 L"%(objectname)%04"
368 L"%(authordate:relative)%04"
369 L"%(subject)%04"
370 L"%(authorname)%04"
371 L"%(authordate:iso8601)%03",
372 &allRefs, &error, CP_UTF8))
374 CMessageBox::Show(NULL, CString(_T("Get refs failed\n")) + error, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
377 int linePos=0;
378 CString singleRef;
380 MAP_STRING_STRING refMap;
382 //First sort on ref name
383 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
385 singleRef.TrimLeft(L"\r\n");
386 int valuePos=0;
387 CString refName=singleRef.Tokenize(L"\04",valuePos);
388 if(refName.IsEmpty())
389 continue;
390 CString refRest=singleRef.Mid(valuePos);
393 //Use ref based on m_pickRef_Kind
394 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
395 continue; //Skip
396 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
397 continue; //Skip
398 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
399 continue; //Skip
401 refMap[refName] = refRest; //Use
404 MAP_STRING_STRING descriptions = GetBranchDescriptions();
406 //Populate ref tree
407 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
409 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
410 CString values=iterRefMap->second;
411 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
413 int valuePos=0;
414 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
415 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
416 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
417 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
418 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
420 if (wcsncmp(iterRefMap->first, L"refs/heads", 10) == 0)
421 treeLeaf.m_csDescription = descriptions[treeLeaf.m_csRefName];
425 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
426 //Probably not on a branch. Select root node.
427 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
431 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
433 if(!bExactMatch)
435 CString newRefName = GetFullRefName(refName);
436 if(!newRefName.IsEmpty())
437 refName = newRefName;
438 //else refName is not a valid ref. Try to select as good as possible.
440 if(_wcsnicmp(refName, L"refs/", 5) != 0)
441 return false; // Not a ref name
443 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
444 if(treeLeafHead.m_hTree != NULL)
446 //Not a leaf. Select tree node and return
447 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
448 return true;
451 if(treeLeafHead.m_pParent==NULL)
452 return false; //Weird... should not occur.
454 //This is the current head.
455 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
457 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
459 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
460 if(pCurrShadowTree == &treeLeafHead)
462 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
463 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
467 return true;
470 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
472 if(pTreePos==NULL)
474 if(_wcsnicmp(refName, L"refs/", 5) == 0)
475 refName=refName.Mid(5);
476 pTreePos=&m_TreeRoot;
478 if(refName.IsEmpty())
479 return *pTreePos;//Found leaf
481 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
482 if(pNextTree==NULL)
484 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
485 ASSERT(!bCreateIfNotExist);
486 return *pTreePos;
489 if(!refName.IsEmpty())
491 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
492 //Leafs are for the list control.
493 if(pNextTree->m_hTree==NULL)
495 //New tree. Create node in control.
496 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
497 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
501 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
505 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
507 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
508 *pResult = 0;
510 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
513 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
515 m_ListRefLeafs.DeleteAllItems();
517 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
518 if(pTree==NULL)
520 ASSERT(FALSE);
521 return;
523 FillListCtrlForShadowTree(pTree,L"",true);
526 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
528 if(pTree->IsLeaf())
530 CString filter;
531 m_ctrlFilter.GetWindowText(filter);
532 filter.MakeLower();
533 CString ref = refNamePrefix + pTree->m_csRefName;
534 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter))
536 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
538 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
539 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
540 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
541 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
542 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
543 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
544 int pos = 0;
545 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
548 else
551 CString csThisName;
552 if(!isFirstLevel)
553 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
554 else
555 m_pListCtrlRoot = pTree;
556 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
558 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
561 if (isFirstLevel)
563 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
564 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
566 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
570 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter)
572 if (m_SelectedFilters & LOGFILTER_REFNAME)
574 CString msg = ref;
575 msg = msg.MakeLower();
577 if (msg.Find(filter) >= 0)
578 return true;
581 if (m_SelectedFilters & LOGFILTER_SUBJECT)
583 CString msg = pTree->m_csSubject;
584 msg = msg.MakeLower();
586 if (msg.Find(filter) >= 0)
587 return true;
590 if (m_SelectedFilters & LOGFILTER_AUTHORS)
592 CString msg = pTree->m_csAuthor;
593 msg = msg.MakeLower();
595 if (msg.Find(filter) >= 0)
596 return true;
599 if (m_SelectedFilters & LOGFILTER_REVS)
601 CString msg = pTree->m_csRefHash;
602 msg = msg.MakeLower();
604 if (msg.Find(filter) >= 0)
605 return true;
607 return false;
610 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
612 ASSERT(!leafs.empty());
614 CString csMessage;
615 UINT mbIcon=MB_ICONQUESTION;
617 bool bIsRemoteBranch = false;
618 bool bIsBranch = false;
619 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
620 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
622 if(bIsBranch)
624 if(leafs.size() == 1)
626 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
627 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
629 //Check if branch is fully merged in HEAD
630 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
632 csMessage += L"\r\n\r\n";
633 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
634 mbIcon = MB_ICONWARNING;
637 if(bIsRemoteBranch)
639 csMessage += L"\r\n\r\n";
640 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
641 mbIcon = MB_ICONWARNING;
644 else
646 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
648 csMessage += L"\r\n\r\n";
649 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
650 mbIcon = MB_ICONWARNING;
652 if(bIsRemoteBranch)
654 csMessage += L"\r\n\r\n";
655 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
656 mbIcon = MB_ICONWARNING;
661 else if(leafs[0]->IsFrom(L"refs/tags"))
663 if(leafs.size() == 1)
665 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
666 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
668 else
670 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
674 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
678 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
680 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
681 if(!DoDeleteRef((*i)->GetRefName(), bForce))
682 return false;
683 return true;
686 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
688 bool bIsRemoteBranch = false;
689 bool bIsBranch = false;
690 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
691 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
693 if(bIsBranch)
695 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
696 CString cmd;
697 if(bIsRemoteBranch)
699 int slash = branchToDelete.Find(L'/');
700 if(slash < 0)
701 return false;
702 CString remoteName = branchToDelete.Left(slash);
703 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
705 if(CAppUtils::IsSSHPutty())
707 CAppUtils::LaunchPAgent(NULL, &remoteName);
710 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, remoteBranchToDelete);
712 else
713 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
714 CSysProgressDlg sysProgressDlg;
715 if (bIsRemoteBranch)
717 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
718 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
719 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
720 sysProgressDlg.SetShowProgressBar(false);
721 sysProgressDlg.ShowModal(this, true);
723 CString errorMsg;
724 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
726 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
727 if (bIsRemoteBranch)
728 sysProgressDlg.Stop();
729 return false;
731 if (bIsRemoteBranch)
732 sysProgressDlg.Stop();
734 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
736 CString tagToDelete = completeRefName.Mid(10);
737 CString cmd;
738 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
739 CString errorMsg;
740 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
742 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
743 return false;
746 return true;
749 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
751 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
752 if(pLeaf == NULL)
753 return CString();
754 return pLeaf->GetRefName();
758 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
760 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
761 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
764 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
766 CPoint clientPoint=point;
767 m_RefTreeCtrl.ScreenToClient(&clientPoint);
769 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
770 if(hTreeItem!=NULL)
771 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
773 VectorPShadowTree tree;
774 ShowContextMenu(point,hTreeItem,tree);
778 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
780 std::vector<CShadowTree*> selectedLeafs;
781 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
782 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
783 while(pos)
785 selectedLeafs.push_back(
786 (CShadowTree*)m_ListRefLeafs.GetItemData(
787 m_ListRefLeafs.GetNextSelectedItem(pos)));
790 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
793 CString GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
795 ASSERT(selectedLeafs.size() == 2);
797 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
798 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
799 else
800 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
803 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
805 CIconMenu popupMenu;
806 popupMenu.CreatePopupMenu();
808 bool bAddSeparator = false;
809 CString remoteName;
811 if(selectedLeafs.size()==1)
813 bAddSeparator = true;
815 bool bShowReflogOption = false;
816 bool bShowFetchOption = false;
817 bool bShowSwitchOption = false;
818 bool bShowRenameOption = false;
819 bool bShowCreateBranchOption = false;
820 bool bShowEditBranchDescriptionOption = false;
822 CString fetchFromCmd;
824 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
826 bShowReflogOption = true;
827 bShowSwitchOption = true;
828 bShowRenameOption = true;
829 bShowEditBranchDescriptionOption = true;
831 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
833 bShowReflogOption = true;
834 bShowFetchOption = true;
835 bShowCreateBranchOption = true;
837 int dummy = 0;//Needed for tokenize
838 remoteName = selectedLeafs[0]->GetRefName();
839 remoteName = remoteName.Mid(13);
840 remoteName = remoteName.Tokenize(L"/", dummy);
841 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
843 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
847 CString temp;
848 temp.LoadString(IDS_MENULOG);
849 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
850 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
851 if(bShowReflogOption)
853 temp.LoadString(IDS_MENUREFLOG);
854 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
857 popupMenu.AppendMenu(MF_SEPARATOR);
858 bAddSeparator = false;
860 if(bShowFetchOption)
862 bAddSeparator = true;
863 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
866 if(bAddSeparator)
867 popupMenu.AppendMenu(MF_SEPARATOR);
869 bAddSeparator = false;
870 if (m_bHasWC)
872 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
873 popupMenu.AppendMenu(MF_SEPARATOR);
876 if(bShowCreateBranchOption)
878 bAddSeparator = true;
879 temp.LoadString(IDS_MENUBRANCH);
880 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
883 if (bShowEditBranchDescriptionOption)
885 bAddSeparator = true;
886 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
888 if(bShowRenameOption)
890 bAddSeparator = true;
891 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
894 else if(selectedLeafs.size() == 2)
896 bAddSeparator = true;
897 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
898 CString menu;
899 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
900 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
901 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
902 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
905 if(!selectedLeafs.empty())
907 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
909 if(bAddSeparator)
910 popupMenu.AppendMenu(MF_SEPARATOR);
911 CString menuItemName;
912 if(selectedLeafs.size() == 1)
913 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
914 else
915 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
917 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
918 bAddSeparator = true;
920 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
922 if(bAddSeparator)
923 popupMenu.AppendMenu(MF_SEPARATOR);
924 CString menuItemName;
925 if(selectedLeafs.size() == 1)
926 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
927 else
928 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
930 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
931 bAddSeparator = true;
933 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
935 if(bAddSeparator)
936 popupMenu.AppendMenu(MF_SEPARATOR);
937 CString menuItemName;
938 if(selectedLeafs.size() == 1)
939 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
940 else
941 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
943 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
944 bAddSeparator = true;
949 if(hTreePos!=NULL && selectedLeafs.empty())
951 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
952 if(pTree->IsFrom(L"refs/remotes"))
954 if(bAddSeparator)
955 popupMenu.AppendMenu(MF_SEPARATOR);
956 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
957 bAddSeparator = true;
958 if(selectedLeafs.empty())
960 int dummy = 0;//Needed for tokenize
961 remoteName = pTree->GetRefName();
962 remoteName = remoteName.Mid(13);
963 remoteName = remoteName.Tokenize(L"/", dummy);
964 if(!remoteName.IsEmpty())
966 CString temp;
967 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
968 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
970 temp.LoadString(IDS_DELETEREMOTETAG);
971 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag, temp, IDI_DELETE);
975 if(pTree->IsFrom(L"refs/heads"))
977 if(bAddSeparator)
978 popupMenu.AppendMenu(MF_SEPARATOR);
979 CString temp;
980 temp.LoadString(IDS_MENUBRANCH);
981 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
983 if(pTree->IsFrom(L"refs/tags"))
985 if(bAddSeparator)
986 popupMenu.AppendMenu(MF_SEPARATOR);
987 CString temp;
988 temp.LoadString(IDS_MENUTAG);
989 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
990 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
991 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
996 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
997 switch(cmd)
999 case eCmd_ViewLog:
1001 CLogDlg dlg;
1002 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1003 dlg.DoModal();
1005 break;
1006 case eCmd_ViewLogRange:
1008 CLogDlg dlg;
1009 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1010 dlg.DoModal();
1012 break;
1013 case eCmd_ViewLogRangeReachableFromOnlyOne:
1015 CLogDlg dlg;
1016 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1017 dlg.DoModal();
1019 break;
1020 case eCmd_RepoBrowser:
1021 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1022 break;
1023 case eCmd_DeleteBranch:
1024 case eCmd_DeleteRemoteBranch:
1026 if(ConfirmDeleteRef(selectedLeafs))
1027 DoDeleteRefs(selectedLeafs, true);
1028 Refresh();
1030 break;
1031 case eCmd_DeleteTag:
1033 if(ConfirmDeleteRef(selectedLeafs))
1034 DoDeleteRefs(selectedLeafs, true);
1035 Refresh();
1037 break;
1038 case eCmd_ShowReflog:
1040 CRefLogDlg refLogDlg(this);
1041 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1042 refLogDlg.DoModal();
1044 break;
1045 case eCmd_Fetch:
1047 CAppUtils::Fetch(remoteName);
1048 Refresh();
1050 break;
1051 case eCmd_DeleteRemoteTag:
1053 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1054 deleteRemoteTagDlg.m_sRemote = remoteName;
1055 deleteRemoteTagDlg.DoModal();
1057 break;
1058 case eCmd_Switch:
1060 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1062 break;
1063 case eCmd_Rename:
1065 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1066 if(pos != NULL)
1067 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1069 break;
1070 case eCmd_AddRemote:
1072 CAddRemoteDlg(this).DoModal();
1073 Refresh();
1075 break;
1076 case eCmd_ManageRemotes:
1078 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
1079 // CSettingGitRemote W_Remotes(m_cmdPath);
1080 // W_Remotes.DoModal();
1081 Refresh();
1083 break;
1084 case eCmd_CreateBranch:
1086 CString *commitHash = NULL;
1087 if (selectedLeafs.size() == 1)
1088 commitHash = &(selectedLeafs[0]->m_csRefHash);
1089 CAppUtils::CreateBranchTag(false, commitHash);
1090 Refresh();
1092 break;
1093 case eCmd_CreateTag:
1095 CAppUtils::CreateBranchTag(true);
1096 Refresh();
1098 break;
1099 case eCmd_DeleteAllTags:
1101 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1103 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1104 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1106 if (ConfirmDeleteRef(selectedLeafs))
1107 DoDeleteRefs(selectedLeafs, true);
1108 Refresh();
1110 break;
1111 case eCmd_Diff:
1113 CFileDiffDlg dlg;
1114 dlg.SetDiff(
1115 NULL,
1116 selectedLeafs[0]->m_csRefHash,
1117 selectedLeafs[1]->m_csRefHash);
1118 dlg.DoModal();
1120 break;
1121 case eCmd_EditBranchDescription:
1123 CInputDlg dlg;
1124 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1125 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1126 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1127 dlg.m_bUseLogWidth = true;
1128 if(dlg.DoModal() == IDOK)
1130 CString key;
1131 key.Format(_T("branch.%s.description"), selectedLeafs[0]->m_csRefName);
1132 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1133 dlg.m_sInputText.Trim();
1134 if (dlg.m_sInputText.IsEmpty())
1135 g_Git.UnsetConfigValue(key);
1136 else
1137 g_Git.SetConfigValue(key, dlg.m_sInputText);
1138 Refresh();
1141 break;
1145 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1147 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1148 if(!(*i)->IsFrom(from))
1149 return false;
1150 return true;
1153 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1155 if (pMsg->message == WM_KEYDOWN)
1157 switch (pMsg->wParam)
1159 /* case VK_RETURN:
1161 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1163 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1165 PostMessage(WM_COMMAND, IDOK);
1167 return TRUE;
1170 break;
1171 */ case VK_F2:
1173 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1175 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1176 if(pos != NULL)
1177 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1180 break;
1182 case VK_F5:
1184 Refresh();
1186 break;
1191 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1194 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1196 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1197 *pResult = 0;
1199 if(m_currSortCol == pNMLV->iSubItem)
1200 m_currSortDesc = !m_currSortDesc;
1201 else
1203 m_currSortCol = pNMLV->iSubItem;
1204 m_currSortDesc = false;
1207 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1208 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1210 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1213 void CBrowseRefsDlg::OnDestroy()
1215 m_pickedRef = GetSelectedRef(true, false);
1217 CResizableStandAloneDialog::OnDestroy();
1220 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1222 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1223 *pResult = 0;
1225 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1226 if (item && pNMListView->uNewState == 2)
1227 m_sLastSelected = item->GetRefName();
1230 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1232 *pResult = 0;
1234 EndDialog(IDOK);
1237 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefs)
1239 CBrowseRefsDlg dlg(CString(),NULL);
1241 if(initialRef.IsEmpty())
1242 initialRef = L"HEAD";
1243 dlg.m_initialRef = initialRef;
1244 dlg.m_pickRef_Kind = pickRef_Kind;
1245 dlg.m_bPickOne = !pickMultipleRefs;
1247 if(dlg.DoModal() != IDOK)
1248 return CString();
1250 return dlg.m_pickedRef;
1253 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1255 CString origRef;
1256 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1257 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1258 if(resultRef.IsEmpty())
1259 return false;
1260 if(wcsncmp(resultRef,L"refs/",5)==0)
1261 resultRef = resultRef.Mid(5);
1262 // if(wcsncmp(resultRef,L"heads/",6)==0)
1263 // resultRef = resultRef.Mid(6);
1265 //Find closest match of choice in combobox
1266 int ixFound = -1;
1267 int matchLength = 0;
1268 CString comboRefName;
1269 for(int i = 0; i < pComboBox->GetCount(); ++i)
1271 pComboBox->GetLBText(i, comboRefName);
1272 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1273 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1274 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1276 matchLength = comboRefName.GetLength();
1277 ixFound = i;
1280 if(ixFound >= 0)
1281 pComboBox->SetCurSel(ixFound);
1282 else
1283 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)
1285 return true;
1288 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1290 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1291 *pResult = FALSE;
1293 if(pDispInfo->item.pszText == NULL)
1294 return; //User canceled changing
1296 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1298 if(!pTree->IsFrom(L"refs/heads"))
1300 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1301 return;
1304 CString origName = pTree->GetRefName().Mid(11);
1306 CString newName;
1307 if(m_pListCtrlRoot != NULL)
1308 newName = m_pListCtrlRoot->GetRefName() + L'/';
1309 newName += pDispInfo->item.pszText;
1311 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1313 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1314 return;
1317 CString newNameTrunced = newName.Mid(11);
1319 CString errorMsg;
1320 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1322 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1323 return;
1325 //Do as if it failed to rename. Let Refresh() do the job.
1326 //*pResult = TRUE;
1328 Refresh(newName);
1330 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1332 // AfxMessageBox(W_csPopup);
1336 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1338 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1339 *pResult = FALSE;
1341 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1343 if(!pTree->IsFrom(L"refs/heads"))
1345 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1346 return;
1350 void CBrowseRefsDlg::OnEnChangeEditFilter()
1352 SetTimer(IDT_FILTER, 1000, NULL);
1355 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1357 if (nIDEvent == IDT_FILTER)
1359 KillTimer(IDT_FILTER);
1360 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1363 CResizableStandAloneDialog::OnTimer(nIDEvent);
1366 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1368 // FIXME: x64 version would get this function called with unexpected parameters.
1369 if (!lParam)
1370 return 0;
1372 RECT * rect = (LPRECT)lParam;
1373 CPoint point;
1374 CString temp;
1375 point = CPoint(rect->left, rect->bottom);
1376 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1377 CMenu popup;
1378 if (popup.CreatePopupMenu())
1380 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1381 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1383 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1384 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1386 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1387 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1389 temp.LoadString(IDS_LOG_FILTER_REVS);
1390 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1392 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1393 if (selection != 0)
1395 m_SelectedFilters ^= selection;
1396 SetFilterCueText();
1397 SetTimer(IDT_FILTER, 1000, NULL);
1400 return 0L;
1403 void CBrowseRefsDlg::SetFilterCueText()
1405 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1406 temp += _T(" ");
1408 if (m_SelectedFilters & LOGFILTER_REFNAME)
1409 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1411 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1413 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1414 temp += _T(", ");
1415 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1418 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1420 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1421 temp += _T(", ");
1422 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1425 if (m_SelectedFilters & LOGFILTER_REVS)
1427 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1428 temp += _T(", ");
1429 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1432 // to make the cue banner text appear more to the right of the edit control
1433 temp = _T(" ") + temp;
1434 m_ctrlFilter.SetCueBanner(temp.TrimRight());