No need to explicitly initialize CString and use Empty() for clearing CString
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob8f4547ff62d9b97bac1f7b2843ab016879b4c6c0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2015 - 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"
38 #include "LoglistUtils.h"
39 #include "GitRevRefBrowser.h"
41 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
43 if (ref.Left(13) == _T("refs/remotes/"))
44 ref = ref.Mid(13);
45 else if (ref.Left(8) == _T("remotes/"))
46 ref = ref.Mid(8);
48 STRING_VECTOR list;
49 int result = g_Git.GetRemoteList(list);
50 if (result != 0)
51 return result;
53 for (size_t i = 0; i < list.size(); ++i)
55 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
57 remote = list[i];
58 branch = ref.Mid(list[i].GetLength() + 1);
59 return 0;
61 if (ref == list[i])
63 remote = list[i];
64 branch.Empty();
65 return 0;
69 return -1;
72 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
74 if (control == NULL)
75 return;
76 // set the sort arrow
77 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
78 HDITEM HeaderItem = {0};
79 HeaderItem.mask = HDI_FORMAT;
80 for (int i=0; i<pHeader->GetItemCount(); ++i)
82 pHeader->GetItem(i, &HeaderItem);
83 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
84 pHeader->SetItem(i, &HeaderItem);
86 if (nColumn >= 0)
88 pHeader->GetItem(nColumn, &HeaderItem);
89 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
90 pHeader->SetItem(nColumn, &HeaderItem);
94 class CRefLeafListCompareFunc
96 public:
97 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
98 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
99 if (m_bSortLogical)
100 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
103 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
105 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
108 int Compare(LPARAM lParam1, LPARAM lParam2)
110 return Compare(
111 (CShadowTree*)m_pList->GetItemData((int)lParam1),
112 (CShadowTree*)m_pList->GetItemData((int)lParam2));
115 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
117 int result=CompareNoDesc(pLeft,pRight);
118 if(m_desc)
119 return -result;
120 return result;
123 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
125 switch(m_col)
127 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
128 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
129 case CBrowseRefsDlg::eCol_Date: return ((pLeft->m_csDate == pRight->m_csDate) ? 0 : ((pLeft->m_csDate > pRight->m_csDate) ? 1 : -1));
130 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
131 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
132 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
133 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
135 return 0;
137 int SortStrCmp(const CString& left, const CString& right)
139 if (m_bSortLogical)
140 return StrCmpLogicalW(left, right);
141 return StrCmpI(left, right);
144 int m_col;
145 bool m_desc;
146 CListCtrl* m_pList;
147 bool m_bSortLogical;
150 // CBrowseRefsDlg dialog
152 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
154 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
155 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
156 m_cmdPath(cmdPath),
157 m_currSortCol(0),
158 m_currSortDesc(false),
159 m_initialRef(L"HEAD"),
160 m_pickRef_Kind(gPickRef_All),
161 m_pListCtrlRoot(NULL),
162 m_bHasWC(true),
163 m_SelectedFilters(LOGFILTER_ALL),
164 m_ColumnManager(&m_ListRefLeafs),
165 m_bPickOne(false),
166 m_bIncludeNestedRefs(TRUE),
167 m_bPickedRefSet(false)
169 // get short/long datetime setting from registry
170 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
171 if (RegUseShortDateFormat)
172 m_DateFormat = DATE_SHORTDATE;
173 else
174 m_DateFormat = DATE_LONGDATE;
175 // get relative time display setting from registry
176 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
177 m_bRelativeTimes = (regRelativeTimes != 0);
179 m_regIncludeNestedRefs = CRegDWORD(_T("Software\\TortoiseGit\\RefBrowserIncludeNestedRefs"), TRUE);
182 CBrowseRefsDlg::~CBrowseRefsDlg()
186 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
188 CDialog::DoDataExchange(pDX);
189 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
190 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
191 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
192 DDX_Check(pDX, IDC_INCLUDENESTEDREFS, m_bIncludeNestedRefs);
196 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
197 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
198 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
199 ON_WM_CONTEXTMENU()
200 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
201 ON_WM_DESTROY()
202 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
203 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
204 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
205 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
206 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
207 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
208 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
209 ON_WM_TIMER()
210 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
211 ON_BN_CLICKED(IDC_INCLUDENESTEDREFS, &CBrowseRefsDlg::OnBnClickedIncludeNestedRefs)
212 END_MESSAGE_MAP()
215 // CBrowseRefsDlg message handlers
217 void CBrowseRefsDlg::OnBnClickedOk()
219 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
221 OnOK();
222 return;
225 CIconMenu popupMenu;
226 popupMenu.CreatePopupMenu();
228 std::vector<CShadowTree*> selectedLeafs;
229 GetSelectedLeaves(selectedLeafs);
231 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
232 popupMenu.SetDefaultItem(1);
233 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
234 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
236 RECT rect;
237 GetDlgItem(IDOK)->GetWindowRect(&rect);
238 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
239 switch (selection)
241 case 1:
242 OnOK();
243 break;
244 case 2:
246 m_bPickedRefSet = true;
247 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
248 OnOK();
250 break;
251 case 3:
253 m_bPickedRefSet = true;
254 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
255 OnOK();
257 break;
258 default:
259 break;
263 BOOL CBrowseRefsDlg::OnInitDialog()
265 CResizableStandAloneDialog::OnInitDialog();
266 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
268 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
269 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
270 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
271 SetFilterCueText();
273 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
274 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
275 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
276 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
277 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
278 AddAnchor(IDC_INCLUDENESTEDREFS, BOTTOM_LEFT);
279 AddAnchor(IDHELP, BOTTOM_RIGHT);
281 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
282 DWORD exStyle = LVS_EX_INFOTIP;
283 if (DWORD(regFullRowSelect))
284 exStyle |= LVS_EX_FULLROWSELECT;
285 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle() | exStyle);
286 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
287 static int columnWidths[] = { 150, 100, 100, 300, 100, 80, 80 };
288 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
289 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
290 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
291 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
292 m_bPickedRefSet = false;
294 AddAnchor(IDOK,BOTTOM_RIGHT);
295 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
296 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
298 m_bIncludeNestedRefs = !!m_regIncludeNestedRefs;
299 UpdateData();
301 Refresh(m_initialRef);
303 EnableSaveRestore(L"BrowseRefs");
305 CString sWindowTitle;
306 GetWindowText(sWindowTitle);
307 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
309 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
311 if (m_bPickOne)
312 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
314 m_ListRefLeafs.SetFocus();
315 return FALSE;
318 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
320 int posSlash=nameLeft.Find('/');
321 CString nameSub;
322 if(posSlash<0)
324 nameSub=nameLeft;
325 nameLeft.Empty();//Nothing left
327 else
329 nameSub=nameLeft.Left(posSlash);
330 nameLeft=nameLeft.Mid(posSlash+1);
332 if(nameSub.IsEmpty())
333 return NULL;
335 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
336 return NULL;
338 CShadowTree& nextNode=m_ShadowTree[nameSub];
339 nextNode.m_csRefName=nameSub;
340 nextNode.m_pParent=this;
341 return &nextNode;
344 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
346 if(IsLeaf())
348 if(m_csRefName.GetLength() > partialRefName.GetLength())
349 return NULL;
350 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
352 //Match of leaf name. Try match on total name.
353 CString totalRefName = GetRefName();
354 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
355 return this; //Also match. Found.
358 else
360 //Not a leaf. Search all nodes.
361 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
363 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
364 if(pSubtree != NULL)
365 return pSubtree; //Found
368 return NULL;//Not found
371 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
373 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
374 //List ctrl selection?
375 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
377 //A leaf is selected
378 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
379 m_ListRefLeafs.GetNextSelectedItem(pos));
380 return pTree->GetRefName();
382 else if (pos && !pickFirstSelIfMultiSel)
384 // at least one leaf is selected
385 CString refs;
386 int index;
387 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
389 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
390 if(wcsncmp(ref, L"refs/", 5) == 0)
391 ref = ref.Mid(5);
392 if(wcsncmp(ref, L"heads/", 6) == 0)
393 ref = ref.Mid(6);
394 refs += ref + _T(" ");
396 return refs.Trim();
398 else if(!onlyIfLeaf)
400 //Tree ctrl selection?
401 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
402 if(hTree!=NULL)
404 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
405 return pTree->GetRefName();
408 return CString();//None
411 void CBrowseRefsDlg::Refresh(CString selectRef)
413 remotes.clear();
414 if (g_Git.GetRemoteList(remotes))
415 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
417 if(!selectRef.IsEmpty())
419 if(selectRef == "HEAD")
421 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
422 selectRef.Empty();
423 else
424 selectRef = L"refs/heads/" + selectRef;
427 else
429 selectRef = GetSelectedRef(false, true);
432 m_RefTreeCtrl.DeleteAllItems();
433 m_ListRefLeafs.DeleteAllItems();
434 m_TreeRoot.m_ShadowTree.clear();
435 m_TreeRoot.m_csRefName = "refs";
436 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"refs",NULL,NULL);
437 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
439 CString err;
440 MAP_REF_GITREVREFBROWSER refMap;
441 if (GitRevRefBrowser::GetGitRevRefMap(refMap, err, [&](const CString& refName)
443 //Use ref based on m_pickRef_Kind
444 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
445 return false; //Skip
446 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
447 return false; //Skip
448 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
449 return false; //Skip
450 if (m_pickRef_Kind == gPickRef_Remote && wcsncmp(refName, L"refs/remotes/", 13) != 0) // do not show refs/stash if only remote branches are requested
451 return false;
452 return true;
455 MessageBox(_T("Get refs failed:") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
458 //Populate ref tree
459 for (auto iterRefMap = refMap.cbegin(); iterRefMap != refMap.cend(); ++iterRefMap)
461 CShadowTree& treeLeaf = GetTreeNode(iterRefMap->first, nullptr, true);
462 GitRevRefBrowser ref = iterRefMap->second;
464 treeLeaf.m_csRefHash = ref.m_CommitHash.ToString();
465 treeLeaf.m_csUpstream = ref.m_UpstreamRef;
466 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
467 treeLeaf.m_csSubject = ref.GetSubject();
468 treeLeaf.m_csAuthor = ref.GetAuthorName();
469 treeLeaf.m_csDate = ref.GetAuthorDate();
470 treeLeaf.m_csDescription = ref.m_Description;
473 // always expand the tree first
474 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
476 // try exact match first
477 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
478 SelectRef(selectRef, false);
481 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
483 if(!bExactMatch)
485 CString newRefName = GetFullRefName(refName);
486 if(!newRefName.IsEmpty())
487 refName = newRefName;
488 //else refName is not a valid ref. Try to select as good as possible.
490 if(_wcsnicmp(refName, L"refs/", 5) != 0)
491 return false; // Not a ref name
493 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
494 if(treeLeafHead.m_hTree != NULL)
496 //Not a leaf. Select tree node and return
497 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
498 return true;
501 if(treeLeafHead.m_pParent==NULL)
502 return false; //Weird... should not occur.
504 //This is the current head.
505 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
507 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
509 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
510 if(pCurrShadowTree == &treeLeafHead)
512 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
513 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
517 return true;
520 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
522 if(pTreePos==NULL)
524 if(_wcsnicmp(refName, L"refs/", 5) == 0)
525 refName=refName.Mid(5);
526 pTreePos=&m_TreeRoot;
528 if(refName.IsEmpty())
529 return *pTreePos;//Found leaf
531 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
532 if(pNextTree==NULL)
534 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
535 ASSERT(!bCreateIfNotExist);
536 return *pTreePos;
539 if(!refName.IsEmpty())
541 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
542 //Leafs are for the list control.
543 if(pNextTree->m_hTree==NULL)
545 //New tree. Create node in control.
546 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
547 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
551 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
555 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
557 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
558 *pResult = 0;
560 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
563 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
565 m_ListRefLeafs.DeleteAllItems();
567 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
568 if(pTree==NULL)
570 ASSERT(FALSE);
571 return;
573 FillListCtrlForShadowTree(pTree,L"",true);
574 m_ColumnManager.SetVisible(eCol_Upstream, pTree->IsFrom(L"refs/heads"));
577 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
579 if(pTree->IsLeaf())
581 CString filter;
582 m_ctrlFilter.GetWindowText(filter);
583 filter.MakeLower();
584 bool positive = filter[0] != '!';
585 if (!positive)
586 filter = filter.Mid(1);
587 CString ref = refNamePrefix + pTree->m_csRefName;
588 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter, positive))
590 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
592 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
593 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
594 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
595 m_ListRefLeafs.SetItemText(indexItem, eCol_Date, pTree->m_csDate != 0 ? CLoglistUtils::FormatDateAndTime(pTree->m_csDate, m_DateFormat, true, m_bRelativeTimes) : _T(""));
596 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
597 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
598 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
599 int pos = 0;
600 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
603 else
606 CString csThisName;
607 if (!isFirstLevel && !m_bIncludeNestedRefs)
608 return;
609 else if (!isFirstLevel)
610 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
611 else
612 m_pListCtrlRoot = pTree;
613 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
615 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
618 if (isFirstLevel)
620 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
621 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
623 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
627 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
629 if (m_SelectedFilters & LOGFILTER_REFNAME)
631 CString msg = ref;
632 msg = msg.MakeLower();
634 if (msg.Find(filter) >= 0)
635 return positive;
638 if (m_SelectedFilters & LOGFILTER_SUBJECT)
640 CString msg = pTree->m_csSubject;
641 msg = msg.MakeLower();
643 if (msg.Find(filter) >= 0)
644 return positive;
647 if (m_SelectedFilters & LOGFILTER_AUTHORS)
649 CString msg = pTree->m_csAuthor;
650 msg = msg.MakeLower();
652 if (msg.Find(filter) >= 0)
653 return positive;
656 if (m_SelectedFilters & LOGFILTER_REVS)
658 CString msg = pTree->m_csRefHash;
659 msg = msg.MakeLower();
661 if (msg.Find(filter) >= 0)
662 return positive;
664 return !positive;
667 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
669 ASSERT(!leafs.empty());
671 CString csMessage;
672 UINT mbIcon=MB_ICONQUESTION;
674 bool bIsRemoteBranch = false;
675 bool bIsBranch = false;
676 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
677 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
679 if(bIsBranch)
681 if(leafs.size() == 1)
683 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
684 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)branchToDelete);
686 //Check if branch is fully merged in HEAD
687 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
689 csMessage += L"\r\n\r\n";
690 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
691 mbIcon = MB_ICONWARNING;
694 if(bIsRemoteBranch)
696 csMessage += L"\r\n\r\n";
697 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
698 mbIcon = MB_ICONWARNING;
701 else
703 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
705 csMessage += L"\r\n\r\n";
706 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
707 mbIcon = MB_ICONWARNING;
709 if(bIsRemoteBranch)
711 csMessage += L"\r\n\r\n";
712 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
713 mbIcon = MB_ICONWARNING;
718 else if(leafs[0]->IsFrom(L"refs/tags/"))
720 if(leafs.size() == 1)
722 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
723 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tagToDelete);
725 else
727 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
731 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
735 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
737 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
738 if(!DoDeleteRef((*i)->GetRefName()))
739 return false;
740 return true;
743 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
745 bool bIsRemoteBranch = false;
746 bool bIsBranch = false;
747 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
748 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
750 if (bIsRemoteBranch)
752 CString branchToDelete = completeRefName.Mid(13);
753 CString remoteName, remoteBranchToDelete;
754 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
755 return false;
757 if (CAppUtils::IsSSHPutty())
758 CAppUtils::LaunchPAgent(NULL, &remoteName);
760 CSysProgressDlg sysProgressDlg;
761 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
762 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
763 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
764 sysProgressDlg.SetShowProgressBar(false);
765 sysProgressDlg.ShowModal(this, true);
767 STRING_VECTOR list;
768 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
769 if (g_Git.DeleteRemoteRefs(remoteName, list))
771 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
772 sysProgressDlg.Stop();
773 BringWindowToTop();
774 return false;
776 sysProgressDlg.Stop();
777 BringWindowToTop();
779 else if (bIsBranch)
781 if (g_Git.DeleteRef(completeRefName))
783 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
784 return false;
787 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
789 if (g_Git.DeleteRef(completeRefName))
791 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
792 return false;
795 return true;
798 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
800 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
801 if(pLeaf == NULL)
802 return CString();
803 return pLeaf->GetRefName();
807 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
809 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
810 else if (pWndFrom == &m_ListRefLeafs)
812 CRect headerPosition;
813 m_ListRefLeafs.GetHeaderCtrl()->GetWindowRect(headerPosition);
814 if (!headerPosition.PtInRect(point))
815 OnContextMenu_ListRefLeafs(point);
819 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
821 CPoint clientPoint=point;
822 m_RefTreeCtrl.ScreenToClient(&clientPoint);
824 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
825 if(hTreeItem!=NULL)
826 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
828 VectorPShadowTree tree;
829 ShowContextMenu(point,hTreeItem,tree);
832 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
834 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
835 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
836 while (pos)
838 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
842 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
844 std::vector<CShadowTree*> selectedLeafs;
845 GetSelectedLeaves(selectedLeafs);
846 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
849 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
851 ASSERT(selectedLeafs.size() == 2);
853 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
854 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
855 else
856 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
859 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
861 int i = 0;
862 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
864 if (*it == entry)
865 return i;
867 return -1;
870 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
872 CIconMenu popupMenu;
873 popupMenu.CreatePopupMenu();
875 bool bAddSeparator = false;
876 CString remoteName;
878 if(selectedLeafs.size()==1)
880 bAddSeparator = true;
882 bool bShowReflogOption = false;
883 bool bShowFetchOption = false;
884 bool bShowRenameOption = false;
885 bool bShowCreateBranchOption = false;
886 bool bShowEditBranchDescriptionOption = false;
888 CString fetchFromCmd;
890 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
892 bShowReflogOption = true;
893 bShowRenameOption = true;
894 bShowEditBranchDescriptionOption = true;
896 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
898 bShowReflogOption = true;
899 bShowFetchOption = true;
900 bShowCreateBranchOption = true;
902 CString remoteBranch;
903 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
904 bShowFetchOption = false;
905 else
906 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
908 else if(selectedLeafs[0]->IsFrom(L"refs/tags/"))
912 CString temp;
913 temp.LoadString(IDS_MENULOG);
914 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
915 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
916 if(bShowReflogOption)
918 temp.LoadString(IDS_MENUREFLOG);
919 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
922 popupMenu.AppendMenu(MF_SEPARATOR);
923 bAddSeparator = false;
925 if(bShowFetchOption)
927 bAddSeparator = true;
928 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
931 if(bAddSeparator)
932 popupMenu.AppendMenu(MF_SEPARATOR);
934 bAddSeparator = false;
935 if (m_bHasWC)
937 CString str;
938 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
940 str.Format(IDS_LOG_POPUP_MERGEREV, (LPCTSTR)g_Git.GetCurrentBranch());
941 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
943 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
944 popupMenu.AppendMenu(MF_SEPARATOR);
947 if(bShowCreateBranchOption)
949 bAddSeparator = true;
950 temp.LoadString(IDS_MENUBRANCH);
951 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
954 if (bShowEditBranchDescriptionOption)
956 bAddSeparator = true;
957 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
959 if(bShowRenameOption)
961 bAddSeparator = true;
962 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
965 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
967 if (bAddSeparator)
968 popupMenu.AppendMenu(MF_SEPARATOR);
969 bAddSeparator = true;
970 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
971 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH)));
972 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH)));
975 else if(selectedLeafs.size() == 2)
977 bAddSeparator = true;
978 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
979 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
980 CString menu;
981 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
982 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
983 menu.Format(IDS_SHOWLOG_OF, (LPCTSTR)GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
984 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
987 if(!selectedLeafs.empty())
989 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
991 if(bAddSeparator)
992 popupMenu.AppendMenu(MF_SEPARATOR);
993 CString menuItemName;
994 if(selectedLeafs.size() == 1)
995 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
996 else
997 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
999 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1000 bAddSeparator = true;
1002 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1004 if(bAddSeparator)
1005 popupMenu.AppendMenu(MF_SEPARATOR);
1006 CString menuItemName;
1007 if(selectedLeafs.size() == 1)
1008 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1009 else
1010 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1012 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1013 bAddSeparator = true;
1015 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1017 if(bAddSeparator)
1018 popupMenu.AppendMenu(MF_SEPARATOR);
1019 CString menuItemName;
1020 if(selectedLeafs.size() == 1)
1021 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1022 else
1023 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1025 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1026 bAddSeparator = true;
1031 if(hTreePos!=NULL && selectedLeafs.empty())
1033 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1034 if(pTree->IsFrom(L"refs/remotes"))
1036 if(bAddSeparator)
1037 popupMenu.AppendMenu(MF_SEPARATOR);
1038 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1039 bAddSeparator = true;
1040 if(selectedLeafs.empty())
1042 CString remoteBranch;
1043 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1044 remoteName.Empty();
1045 int pos = findVectorPosition(remotes, remoteName);
1046 if (pos >= 0)
1048 CString temp;
1049 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, (LPCTSTR)remoteName);
1050 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1052 temp.LoadString(IDS_DELETEREMOTETAG);
1053 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1057 if(pTree->IsFrom(L"refs/heads"))
1059 if(bAddSeparator)
1060 popupMenu.AppendMenu(MF_SEPARATOR);
1061 CString temp;
1062 temp.LoadString(IDS_MENUBRANCH);
1063 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1065 if(pTree->IsFrom(L"refs/tags"))
1067 if(bAddSeparator)
1068 popupMenu.AppendMenu(MF_SEPARATOR);
1069 CString temp;
1070 temp.LoadString(IDS_MENUTAG);
1071 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1072 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1073 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1074 if (!remotes.empty())
1076 popupMenu.AppendMenu(MF_SEPARATOR);
1077 int i = 0;
1078 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1080 temp.Format(IDS_DELETEREMOTETAGON, (LPCTSTR)*it);
1081 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1088 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1089 switch ((eCmd)(selection & 0xFFFF))
1091 case eCmd_ViewLog:
1093 CLogDlg dlg;
1094 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1095 dlg.DoModal();
1097 break;
1098 case eCmd_ViewLogRange:
1100 CLogDlg dlg;
1101 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1102 dlg.DoModal();
1104 break;
1105 case eCmd_ViewLogRangeReachableFromOnlyOne:
1107 CLogDlg dlg;
1108 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1109 dlg.DoModal();
1111 break;
1112 case eCmd_RepoBrowser:
1113 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1114 break;
1115 case eCmd_DeleteBranch:
1116 case eCmd_DeleteRemoteBranch:
1118 if(ConfirmDeleteRef(selectedLeafs))
1119 DoDeleteRefs(selectedLeafs);
1120 Refresh();
1122 break;
1123 case eCmd_DeleteTag:
1125 if(ConfirmDeleteRef(selectedLeafs))
1126 DoDeleteRefs(selectedLeafs);
1127 Refresh();
1129 break;
1130 case eCmd_ShowReflog:
1132 CRefLogDlg refLogDlg(this);
1133 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1134 refLogDlg.DoModal();
1136 break;
1137 case eCmd_Fetch:
1139 CAppUtils::Fetch(remoteName);
1140 Refresh();
1142 break;
1143 case eCmd_DeleteRemoteTag:
1145 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1146 int remoteInx = selection >> 16;
1147 if (remoteInx < 0 || remoteInx >= remotes.size())
1148 return;
1149 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1150 deleteRemoteTagDlg.DoModal();
1152 break;
1153 case eCmd_Merge:
1155 CString ref = selectedLeafs[0]->GetRefName();
1156 CAppUtils::Merge(&ref);
1158 break;
1159 case eCmd_Switch:
1161 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1163 break;
1164 case eCmd_Rename:
1166 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1167 if(pos != NULL)
1168 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1170 break;
1171 case eCmd_AddRemote:
1173 CAddRemoteDlg(this).DoModal();
1174 Refresh();
1176 break;
1177 case eCmd_ManageRemotes:
1179 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1180 // CSettingGitRemote W_Remotes(m_cmdPath);
1181 // W_Remotes.DoModal();
1182 Refresh();
1184 break;
1185 case eCmd_CreateBranch:
1187 CString *commitHash = NULL;
1188 if (selectedLeafs.size() == 1)
1189 commitHash = &(selectedLeafs[0]->m_csRefHash);
1190 CAppUtils::CreateBranchTag(false, commitHash);
1191 Refresh();
1193 break;
1194 case eCmd_CreateTag:
1196 CAppUtils::CreateBranchTag(true);
1197 Refresh();
1199 break;
1200 case eCmd_DeleteAllTags:
1202 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1204 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1205 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1207 if (ConfirmDeleteRef(selectedLeafs))
1208 DoDeleteRefs(selectedLeafs);
1209 Refresh();
1211 break;
1212 case eCmd_Diff:
1214 CFileDiffDlg dlg;
1215 dlg.SetDiff(
1216 NULL,
1217 selectedLeafs[1]->GetRefName() + L"^{}",
1218 selectedLeafs[0]->GetRefName() + L"^{}");
1219 dlg.DoModal();
1221 break;
1222 case eCmd_UnifiedDiff:
1224 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1226 break;
1227 case eCmd_EditBranchDescription:
1229 CInputDlg dlg;
1230 dlg.m_sHintText.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1231 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1232 dlg.m_sTitle.LoadString(IDS_PROC_BROWSEREFS_EDITDESCRIPTION);
1233 dlg.m_bUseLogWidth = true;
1234 if(dlg.DoModal() == IDOK)
1236 CString key;
1237 key.Format(_T("branch.%s.description"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1238 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1239 dlg.m_sInputText.Trim();
1240 if (dlg.m_sInputText.IsEmpty())
1241 g_Git.UnsetConfigValue(key);
1242 else
1243 g_Git.SetConfigValue(key, dlg.m_sInputText);
1244 Refresh();
1247 break;
1248 case eCmd_UpstreamDrop:
1250 CString key;
1251 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1252 g_Git.UnsetConfigValue(key);
1253 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1254 g_Git.UnsetConfigValue(key);
1256 Refresh();
1257 break;
1258 case eCmd_UpstreamSet:
1260 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1261 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1262 return;
1263 CString remote, branch;
1264 if (SplitRemoteBranchName(newRef, remote, branch))
1265 return;
1266 CString key;
1267 key.Format(_T("branch.%s.remote"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1268 g_Git.SetConfigValue(key, remote);
1269 key.Format(_T("branch.%s.merge"), (LPCTSTR)selectedLeafs[0]->GetRefsHeadsName());
1270 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1271 Refresh();
1273 break;
1277 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1279 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1280 if(!(*i)->IsFrom(from))
1281 return false;
1282 return true;
1285 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1287 if (pMsg->message == WM_KEYDOWN)
1289 switch (pMsg->wParam)
1291 /* case VK_RETURN:
1293 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1295 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1297 PostMessage(WM_COMMAND, IDOK);
1299 return TRUE;
1302 break;
1303 */ case VK_F2:
1305 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1307 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1308 if(pos != NULL)
1309 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1312 break;
1314 case VK_F5:
1316 Refresh();
1318 break;
1319 case L'E':
1321 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1323 m_ctrlFilter.SetSel(0, -1, FALSE);
1324 m_ctrlFilter.SetFocus();
1325 return TRUE;
1328 break;
1333 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1336 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1338 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1339 *pResult = 0;
1341 if(m_currSortCol == pNMLV->iSubItem)
1342 m_currSortDesc = !m_currSortDesc;
1343 else
1345 m_currSortCol = pNMLV->iSubItem;
1346 m_currSortDesc = false;
1349 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1350 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1352 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1355 void CBrowseRefsDlg::OnDestroy()
1357 if (!m_bPickedRefSet)
1358 m_pickedRef = GetSelectedRef(true, false);
1360 int maxcol = m_ColumnManager.GetColumnCount();
1361 for (int col = 0; col < maxcol; ++col)
1362 if (m_ColumnManager.IsVisible(col))
1363 m_ColumnManager.ColumnResized(col);
1364 m_ColumnManager.WriteSettings();
1366 CResizableStandAloneDialog::OnDestroy();
1369 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1371 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1372 *pResult = 0;
1374 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1375 if (item && pNMListView->uNewState == LVIS_SELECTED)
1376 m_sLastSelected = item->GetRefName();
1378 UpdateInfoLabel();
1381 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1383 *pResult = 0;
1385 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1386 return;
1387 EndDialog(IDOK);
1390 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1392 CBrowseRefsDlg dlg(CString(),NULL);
1394 if(initialRef.IsEmpty())
1395 initialRef = L"HEAD";
1396 dlg.m_initialRef = initialRef;
1397 dlg.m_pickRef_Kind = pickRef_Kind;
1398 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1400 if(dlg.DoModal() != IDOK)
1401 return CString();
1403 return dlg.m_pickedRef;
1406 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1408 CString origRef;
1409 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1410 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1411 if(resultRef.IsEmpty())
1412 return false;
1413 if(wcsncmp(resultRef,L"refs/",5)==0)
1414 resultRef = resultRef.Mid(5);
1415 // if(wcsncmp(resultRef,L"heads/",6)==0)
1416 // resultRef = resultRef.Mid(6);
1418 //Find closest match of choice in combobox
1419 int ixFound = -1;
1420 int matchLength = 0;
1421 CString comboRefName;
1422 for(int i = 0; i < pComboBox->GetCount(); ++i)
1424 pComboBox->GetLBText(i, comboRefName);
1425 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1426 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1427 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1429 matchLength = comboRefName.GetLength();
1430 ixFound = i;
1433 if(ixFound >= 0)
1434 pComboBox->SetCurSel(ixFound);
1435 else
1436 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)
1438 return true;
1441 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1443 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1444 *pResult = FALSE;
1446 if(pDispInfo->item.pszText == NULL)
1447 return; //User canceled changing
1449 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1451 if(!pTree->IsFrom(L"refs/heads/"))
1453 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1454 return;
1457 CString origName = pTree->GetRefName().Mid(11);
1459 CString newName;
1460 if(m_pListCtrlRoot != NULL)
1461 newName = m_pListCtrlRoot->GetRefName() + L'/';
1462 newName += pDispInfo->item.pszText;
1464 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1466 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1467 return;
1470 CString newNameTrunced = newName.Mid(11);
1472 CString errorMsg;
1473 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1475 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1476 return;
1478 //Do as if it failed to rename. Let Refresh() do the job.
1479 //*pResult = TRUE;
1481 Refresh(newName);
1483 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1485 // AfxMessageBox(W_csPopup);
1489 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1491 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1492 *pResult = FALSE;
1494 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1496 if(!pTree->IsFrom(L"refs/heads/"))
1498 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1499 return;
1503 void CBrowseRefsDlg::OnEnChangeEditFilter()
1505 SetTimer(IDT_FILTER, 1000, NULL);
1508 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1510 if (nIDEvent == IDT_FILTER)
1512 KillTimer(IDT_FILTER);
1513 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1516 CResizableStandAloneDialog::OnTimer(nIDEvent);
1519 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1521 // FIXME: x64 version would get this function called with unexpected parameters.
1522 if (!lParam)
1523 return 0;
1525 RECT * rect = (LPRECT)lParam;
1526 CPoint point;
1527 CString temp;
1528 point = CPoint(rect->left, rect->bottom);
1529 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1530 CMenu popup;
1531 if (popup.CreatePopupMenu())
1533 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1534 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1536 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1537 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1539 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1540 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1542 temp.LoadString(IDS_LOG_FILTER_REVS);
1543 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1545 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1546 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1548 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1549 if (selection != 0)
1551 if (selection == LOGFILTER_TOGGLE)
1552 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1553 else
1554 m_SelectedFilters ^= selection;
1555 SetFilterCueText();
1556 SetTimer(IDT_FILTER, 1000, NULL);
1559 return 0L;
1562 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1564 KillTimer(LOGFILTER_TIMER);
1565 m_ctrlFilter.SetWindowText(_T(""));
1566 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1567 return 0L;
1570 void CBrowseRefsDlg::SetFilterCueText()
1572 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1573 temp += _T(" ");
1575 if (m_SelectedFilters & LOGFILTER_REFNAME)
1576 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1578 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1580 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1581 temp += _T(", ");
1582 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1585 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1587 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1588 temp += _T(", ");
1589 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1592 if (m_SelectedFilters & LOGFILTER_REVS)
1594 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1595 temp += _T(", ");
1596 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1599 // to make the cue banner text appear more to the right of the edit control
1600 temp = _T(" ") + temp;
1601 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1604 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1606 m_pickedRef = g_Git.GetCurrentBranch(true);
1607 m_bPickedRefSet = true;
1608 OnOK();
1611 void CBrowseRefsDlg::UpdateInfoLabel()
1613 CString temp;
1614 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1615 SetDlgItemText(IDC_INFOLABEL, temp);
1619 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1621 UpdateData(TRUE);
1622 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1623 Refresh();