Make sure buffer is large enough for the nul terminator
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob6b8353a073e3fdefdd4ce45bde57f751f375044b
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 = _T("");
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, 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, 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, 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 format, str;
938 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
940 format.LoadString(IDS_LOG_POPUP_MERGEREV);
941 str.Format(format, g_Git.GetCurrentBranch());
942 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
944 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
945 popupMenu.AppendMenu(MF_SEPARATOR);
948 if(bShowCreateBranchOption)
950 bAddSeparator = true;
951 temp.LoadString(IDS_MENUBRANCH);
952 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
955 if (bShowEditBranchDescriptionOption)
957 bAddSeparator = true;
958 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
960 if(bShowRenameOption)
962 bAddSeparator = true;
963 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
966 if (m_bHasWC && selectedLeafs[0]->IsFrom(L"refs/heads/"))
968 if (bAddSeparator)
969 popupMenu.AppendMenu(MF_SEPARATOR);
970 bAddSeparator = true;
971 if (!selectedLeafs[0]->m_csUpstream.IsEmpty())
972 popupMenu.AppendMenuIcon(eCmd_UpstreamDrop, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_DROPTRACKEDBRANCH)));
973 popupMenu.AppendMenuIcon(eCmd_UpstreamSet, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_SETTRACKEDBRANCH)));
976 else if(selectedLeafs.size() == 2)
978 bAddSeparator = true;
979 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
980 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
981 CString menu;
982 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
983 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
984 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
985 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
988 if(!selectedLeafs.empty())
990 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
992 if(bAddSeparator)
993 popupMenu.AppendMenu(MF_SEPARATOR);
994 CString menuItemName;
995 if(selectedLeafs.size() == 1)
996 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
997 else
998 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1000 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1001 bAddSeparator = true;
1003 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1005 if(bAddSeparator)
1006 popupMenu.AppendMenu(MF_SEPARATOR);
1007 CString menuItemName;
1008 if(selectedLeafs.size() == 1)
1009 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1010 else
1011 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1013 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1014 bAddSeparator = true;
1016 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1018 if(bAddSeparator)
1019 popupMenu.AppendMenu(MF_SEPARATOR);
1020 CString menuItemName;
1021 if(selectedLeafs.size() == 1)
1022 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1023 else
1024 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1026 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1027 bAddSeparator = true;
1032 if(hTreePos!=NULL && selectedLeafs.empty())
1034 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1035 if(pTree->IsFrom(L"refs/remotes"))
1037 if(bAddSeparator)
1038 popupMenu.AppendMenu(MF_SEPARATOR);
1039 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1040 bAddSeparator = true;
1041 if(selectedLeafs.empty())
1043 CString remoteBranch;
1044 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1045 remoteName = _T("");
1046 int pos = findVectorPosition(remotes, remoteName);
1047 if (pos >= 0)
1049 CString temp;
1050 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
1051 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1053 temp.LoadString(IDS_DELETEREMOTETAG);
1054 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1058 if(pTree->IsFrom(L"refs/heads"))
1060 if(bAddSeparator)
1061 popupMenu.AppendMenu(MF_SEPARATOR);
1062 CString temp;
1063 temp.LoadString(IDS_MENUBRANCH);
1064 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1066 if(pTree->IsFrom(L"refs/tags"))
1068 if(bAddSeparator)
1069 popupMenu.AppendMenu(MF_SEPARATOR);
1070 CString temp;
1071 temp.LoadString(IDS_MENUTAG);
1072 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1073 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1074 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1075 if (!remotes.empty())
1077 popupMenu.AppendMenu(MF_SEPARATOR);
1078 int i = 0;
1079 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1081 temp.Format(IDS_DELETEREMOTETAGON, *it);
1082 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1089 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1090 switch ((eCmd)(selection & 0xFFFF))
1092 case eCmd_ViewLog:
1094 CLogDlg dlg;
1095 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1096 dlg.DoModal();
1098 break;
1099 case eCmd_ViewLogRange:
1101 CLogDlg dlg;
1102 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1103 dlg.DoModal();
1105 break;
1106 case eCmd_ViewLogRangeReachableFromOnlyOne:
1108 CLogDlg dlg;
1109 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1110 dlg.DoModal();
1112 break;
1113 case eCmd_RepoBrowser:
1114 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1115 break;
1116 case eCmd_DeleteBranch:
1117 case eCmd_DeleteRemoteBranch:
1119 if(ConfirmDeleteRef(selectedLeafs))
1120 DoDeleteRefs(selectedLeafs);
1121 Refresh();
1123 break;
1124 case eCmd_DeleteTag:
1126 if(ConfirmDeleteRef(selectedLeafs))
1127 DoDeleteRefs(selectedLeafs);
1128 Refresh();
1130 break;
1131 case eCmd_ShowReflog:
1133 CRefLogDlg refLogDlg(this);
1134 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1135 refLogDlg.DoModal();
1137 break;
1138 case eCmd_Fetch:
1140 CAppUtils::Fetch(remoteName);
1141 Refresh();
1143 break;
1144 case eCmd_DeleteRemoteTag:
1146 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1147 int remoteInx = selection >> 16;
1148 if (remoteInx < 0 || remoteInx >= remotes.size())
1149 return;
1150 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1151 deleteRemoteTagDlg.DoModal();
1153 break;
1154 case eCmd_Merge:
1156 CString ref = selectedLeafs[0]->GetRefName();
1157 CAppUtils::Merge(&ref);
1159 break;
1160 case eCmd_Switch:
1162 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1164 break;
1165 case eCmd_Rename:
1167 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1168 if(pos != NULL)
1169 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1171 break;
1172 case eCmd_AddRemote:
1174 CAddRemoteDlg(this).DoModal();
1175 Refresh();
1177 break;
1178 case eCmd_ManageRemotes:
1180 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1181 // CSettingGitRemote W_Remotes(m_cmdPath);
1182 // W_Remotes.DoModal();
1183 Refresh();
1185 break;
1186 case eCmd_CreateBranch:
1188 CString *commitHash = NULL;
1189 if (selectedLeafs.size() == 1)
1190 commitHash = &(selectedLeafs[0]->m_csRefHash);
1191 CAppUtils::CreateBranchTag(false, commitHash);
1192 Refresh();
1194 break;
1195 case eCmd_CreateTag:
1197 CAppUtils::CreateBranchTag(true);
1198 Refresh();
1200 break;
1201 case eCmd_DeleteAllTags:
1203 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1205 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1206 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1208 if (ConfirmDeleteRef(selectedLeafs))
1209 DoDeleteRefs(selectedLeafs);
1210 Refresh();
1212 break;
1213 case eCmd_Diff:
1215 CFileDiffDlg dlg;
1216 dlg.SetDiff(
1217 NULL,
1218 selectedLeafs[1]->GetRefName() + L"^{}",
1219 selectedLeafs[0]->GetRefName() + L"^{}");
1220 dlg.DoModal();
1222 break;
1223 case eCmd_UnifiedDiff:
1225 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1227 break;
1228 case eCmd_EditBranchDescription:
1230 CInputDlg dlg;
1231 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1232 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1233 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1234 dlg.m_bUseLogWidth = true;
1235 if(dlg.DoModal() == IDOK)
1237 CString key;
1238 key.Format(_T("branch.%s.description"), selectedLeafs[0]->GetRefsHeadsName());
1239 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1240 dlg.m_sInputText.Trim();
1241 if (dlg.m_sInputText.IsEmpty())
1242 g_Git.UnsetConfigValue(key);
1243 else
1244 g_Git.SetConfigValue(key, dlg.m_sInputText);
1245 Refresh();
1248 break;
1249 case eCmd_UpstreamDrop:
1251 CString key;
1252 key.Format(_T("branch.%s.remote"), selectedLeafs[0]->GetRefsHeadsName());
1253 g_Git.UnsetConfigValue(key);
1254 key.Format(_T("branch.%s.merge"), selectedLeafs[0]->GetRefsHeadsName());
1255 g_Git.UnsetConfigValue(key);
1257 Refresh();
1258 break;
1259 case eCmd_UpstreamSet:
1261 CString newRef = CBrowseRefsDlg::PickRef(false, _T(""), gPickRef_Remote, false);
1262 if (newRef.IsEmpty() || newRef.Find(_T("refs/remotes/")) != 0)
1263 return;
1264 CString remote, branch;
1265 if (SplitRemoteBranchName(newRef, remote, branch))
1266 return;
1267 CString key;
1268 key.Format(_T("branch.%s.remote"), selectedLeafs[0]->GetRefsHeadsName());
1269 g_Git.SetConfigValue(key, remote);
1270 key.Format(_T("branch.%s.merge"), selectedLeafs[0]->GetRefsHeadsName());
1271 g_Git.SetConfigValue(key, _T("refs/heads/") + branch);
1272 Refresh();
1274 break;
1278 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1280 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1281 if(!(*i)->IsFrom(from))
1282 return false;
1283 return true;
1286 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1288 if (pMsg->message == WM_KEYDOWN)
1290 switch (pMsg->wParam)
1292 /* case VK_RETURN:
1294 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1296 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1298 PostMessage(WM_COMMAND, IDOK);
1300 return TRUE;
1303 break;
1304 */ case VK_F2:
1306 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1308 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1309 if(pos != NULL)
1310 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1313 break;
1315 case VK_F5:
1317 Refresh();
1319 break;
1320 case L'E':
1322 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1324 m_ctrlFilter.SetSel(0, -1, FALSE);
1325 m_ctrlFilter.SetFocus();
1326 return TRUE;
1329 break;
1334 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1337 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1339 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1340 *pResult = 0;
1342 if(m_currSortCol == pNMLV->iSubItem)
1343 m_currSortDesc = !m_currSortDesc;
1344 else
1346 m_currSortCol = pNMLV->iSubItem;
1347 m_currSortDesc = false;
1350 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1351 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1353 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1356 void CBrowseRefsDlg::OnDestroy()
1358 if (!m_bPickedRefSet)
1359 m_pickedRef = GetSelectedRef(true, false);
1361 int maxcol = m_ColumnManager.GetColumnCount();
1362 for (int col = 0; col < maxcol; ++col)
1363 if (m_ColumnManager.IsVisible(col))
1364 m_ColumnManager.ColumnResized(col);
1365 m_ColumnManager.WriteSettings();
1367 CResizableStandAloneDialog::OnDestroy();
1370 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1372 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1373 *pResult = 0;
1375 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1376 if (item && pNMListView->uNewState == LVIS_SELECTED)
1377 m_sLastSelected = item->GetRefName();
1379 UpdateInfoLabel();
1382 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1384 *pResult = 0;
1386 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1387 return;
1388 EndDialog(IDOK);
1391 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1393 CBrowseRefsDlg dlg(CString(),NULL);
1395 if(initialRef.IsEmpty())
1396 initialRef = L"HEAD";
1397 dlg.m_initialRef = initialRef;
1398 dlg.m_pickRef_Kind = pickRef_Kind;
1399 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1401 if(dlg.DoModal() != IDOK)
1402 return CString();
1404 return dlg.m_pickedRef;
1407 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1409 CString origRef;
1410 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1411 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1412 if(resultRef.IsEmpty())
1413 return false;
1414 if(wcsncmp(resultRef,L"refs/",5)==0)
1415 resultRef = resultRef.Mid(5);
1416 // if(wcsncmp(resultRef,L"heads/",6)==0)
1417 // resultRef = resultRef.Mid(6);
1419 //Find closest match of choice in combobox
1420 int ixFound = -1;
1421 int matchLength = 0;
1422 CString comboRefName;
1423 for(int i = 0; i < pComboBox->GetCount(); ++i)
1425 pComboBox->GetLBText(i, comboRefName);
1426 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1427 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1428 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1430 matchLength = comboRefName.GetLength();
1431 ixFound = i;
1434 if(ixFound >= 0)
1435 pComboBox->SetCurSel(ixFound);
1436 else
1437 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)
1439 return true;
1442 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1444 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1445 *pResult = FALSE;
1447 if(pDispInfo->item.pszText == NULL)
1448 return; //User canceled changing
1450 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1452 if(!pTree->IsFrom(L"refs/heads/"))
1454 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1455 return;
1458 CString origName = pTree->GetRefName().Mid(11);
1460 CString newName;
1461 if(m_pListCtrlRoot != NULL)
1462 newName = m_pListCtrlRoot->GetRefName() + L'/';
1463 newName += pDispInfo->item.pszText;
1465 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1467 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1468 return;
1471 CString newNameTrunced = newName.Mid(11);
1473 CString errorMsg;
1474 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1476 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1477 return;
1479 //Do as if it failed to rename. Let Refresh() do the job.
1480 //*pResult = TRUE;
1482 Refresh(newName);
1484 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1486 // AfxMessageBox(W_csPopup);
1490 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1492 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1493 *pResult = FALSE;
1495 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1497 if(!pTree->IsFrom(L"refs/heads/"))
1499 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1500 return;
1504 void CBrowseRefsDlg::OnEnChangeEditFilter()
1506 SetTimer(IDT_FILTER, 1000, NULL);
1509 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1511 if (nIDEvent == IDT_FILTER)
1513 KillTimer(IDT_FILTER);
1514 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1517 CResizableStandAloneDialog::OnTimer(nIDEvent);
1520 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1522 // FIXME: x64 version would get this function called with unexpected parameters.
1523 if (!lParam)
1524 return 0;
1526 RECT * rect = (LPRECT)lParam;
1527 CPoint point;
1528 CString temp;
1529 point = CPoint(rect->left, rect->bottom);
1530 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1531 CMenu popup;
1532 if (popup.CreatePopupMenu())
1534 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1535 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1537 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1538 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1540 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1541 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1543 temp.LoadString(IDS_LOG_FILTER_REVS);
1544 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1546 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1547 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1549 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1550 if (selection != 0)
1552 if (selection == LOGFILTER_TOGGLE)
1553 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1554 else
1555 m_SelectedFilters ^= selection;
1556 SetFilterCueText();
1557 SetTimer(IDT_FILTER, 1000, NULL);
1560 return 0L;
1563 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1565 KillTimer(LOGFILTER_TIMER);
1566 m_ctrlFilter.SetWindowText(_T(""));
1567 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1568 return 0L;
1571 void CBrowseRefsDlg::SetFilterCueText()
1573 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1574 temp += _T(" ");
1576 if (m_SelectedFilters & LOGFILTER_REFNAME)
1577 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1579 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1581 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1582 temp += _T(", ");
1583 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1586 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1588 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1589 temp += _T(", ");
1590 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1593 if (m_SelectedFilters & LOGFILTER_REVS)
1595 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1596 temp += _T(", ");
1597 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1600 // to make the cue banner text appear more to the right of the edit control
1601 temp = _T(" ") + temp;
1602 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1605 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1607 m_pickedRef = g_Git.GetCurrentBranch(true);
1608 m_bPickedRefSet = true;
1609 OnOK();
1612 void CBrowseRefsDlg::UpdateInfoLabel()
1614 CString temp;
1615 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1616 SetDlgItemText(IDC_INFOLABEL, temp);
1620 void CBrowseRefsDlg::OnBnClickedIncludeNestedRefs()
1622 UpdateData(TRUE);
1623 m_regIncludeNestedRefs = m_bIncludeNestedRefs;
1624 Refresh();