Do not use GitAdminDir objects
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob6796503709a72b74e3c6e7aa81188ed093efb176
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"
39 static int SplitRemoteBranchName(CString ref, CString &remote, CString &branch)
41 if (ref.Left(13) == _T("refs/remotes/"))
42 ref = ref.Mid(13);
43 else if (ref.Left(8) == _T("remotes/"))
44 ref = ref.Mid(8);
46 STRING_VECTOR list;
47 int result = g_Git.GetRemoteList(list);
48 if (result != 0)
49 return result;
51 for (size_t i = 0; i < list.size(); ++i)
53 if (ref.Left(list[i].GetLength() + 1) == list[i] + _T("/"))
55 remote = list[i];
56 branch = ref.Mid(list[i].GetLength() + 1);
57 return 0;
59 if (ref == list[i])
61 remote = list[i];
62 branch = _T("");
63 return 0;
67 return -1;
70 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
72 if (control == NULL)
73 return;
74 // set the sort arrow
75 CHeaderCtrl * pHeader = control->GetHeaderCtrl();
76 HDITEM HeaderItem = {0};
77 HeaderItem.mask = HDI_FORMAT;
78 for (int i=0; i<pHeader->GetItemCount(); ++i)
80 pHeader->GetItem(i, &HeaderItem);
81 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
82 pHeader->SetItem(i, &HeaderItem);
84 if (nColumn >= 0)
86 pHeader->GetItem(nColumn, &HeaderItem);
87 HeaderItem.fmt |= (bAscending ? HDF_SORTUP : HDF_SORTDOWN);
88 pHeader->SetItem(nColumn, &HeaderItem);
92 class CRefLeafListCompareFunc
94 public:
95 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){
96 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
97 if (m_bSortLogical)
98 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
101 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
103 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
106 int Compare(LPARAM lParam1, LPARAM lParam2)
108 return Compare(
109 (CShadowTree*)m_pList->GetItemData((int)lParam1),
110 (CShadowTree*)m_pList->GetItemData((int)lParam2));
113 int Compare(const CShadowTree* pLeft, const CShadowTree* pRight)
115 int result=CompareNoDesc(pLeft,pRight);
116 if(m_desc)
117 return -result;
118 return result;
121 int CompareNoDesc(const CShadowTree* pLeft, const CShadowTree* pRight)
123 switch(m_col)
125 case CBrowseRefsDlg::eCol_Name: return SortStrCmp(pLeft->GetRefName(), pRight->GetRefName());
126 case CBrowseRefsDlg::eCol_Upstream: return SortStrCmp(pLeft->m_csUpstream, pRight->m_csUpstream);
127 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
128 case CBrowseRefsDlg::eCol_Msg: return SortStrCmp(pLeft->m_csSubject, pRight->m_csSubject);
129 case CBrowseRefsDlg::eCol_LastAuthor: return SortStrCmp(pLeft->m_csAuthor, pRight->m_csAuthor);
130 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
131 case CBrowseRefsDlg::eCol_Description: return SortStrCmp(pLeft->m_csDescription, pRight->m_csDescription);
133 return 0;
135 int SortStrCmp(const CString& left, const CString& right)
137 if (m_bSortLogical)
138 return StrCmpLogicalW(left, right);
139 return StrCmpI(left, right);
142 int m_col;
143 bool m_desc;
144 CListCtrl* m_pList;
145 bool m_bSortLogical;
148 // CBrowseRefsDlg dialog
150 IMPLEMENT_DYNAMIC(CBrowseRefsDlg, CResizableStandAloneDialog)
152 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath, CWnd* pParent /*=NULL*/)
153 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD, pParent),
154 m_cmdPath(cmdPath),
155 m_currSortCol(0),
156 m_currSortDesc(false),
157 m_initialRef(L"HEAD"),
158 m_pickRef_Kind(gPickRef_All),
159 m_pListCtrlRoot(NULL),
160 m_bHasWC(true),
161 m_SelectedFilters(LOGFILTER_ALL),
162 m_ColumnManager(&m_ListRefLeafs),
163 m_bPickOne(false),
164 m_bPickedRefSet(false)
169 CBrowseRefsDlg::~CBrowseRefsDlg()
173 void CBrowseRefsDlg::DoDataExchange(CDataExchange* pDX)
175 CDialog::DoDataExchange(pDX);
176 DDX_Control(pDX, IDC_TREE_REF, m_RefTreeCtrl);
177 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_ListRefLeafs);
178 DDX_Control(pDX, IDC_BROWSEREFS_EDIT_FILTER, m_ctrlFilter);
182 BEGIN_MESSAGE_MAP(CBrowseRefsDlg, CResizableStandAloneDialog)
183 ON_BN_CLICKED(IDOK, &CBrowseRefsDlg::OnBnClickedOk)
184 ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_REF, &CBrowseRefsDlg::OnTvnSelchangedTreeRef)
185 ON_WM_CONTEXTMENU()
186 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
187 ON_WM_DESTROY()
188 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnNMDblclkListRefLeafs)
189 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnItemChangedListRefLeafs)
190 ON_NOTIFY(LVN_ENDLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs)
191 ON_NOTIFY(LVN_BEGINLABELEDIT, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs)
192 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER, &CBrowseRefsDlg::OnEnChangeEditFilter)
193 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED, OnClickedInfoIcon)
194 ON_MESSAGE(WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
195 ON_WM_TIMER()
196 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
197 END_MESSAGE_MAP()
200 // CBrowseRefsDlg message handlers
202 void CBrowseRefsDlg::OnBnClickedOk()
204 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
206 OnOK();
207 return;
210 CIconMenu popupMenu;
211 popupMenu.CreatePopupMenu();
213 std::vector<CShadowTree*> selectedLeafs;
214 GetSelectedLeaves(selectedLeafs);
216 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
217 popupMenu.SetDefaultItem(1);
218 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
219 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
221 RECT rect;
222 GetDlgItem(IDOK)->GetWindowRect(&rect);
223 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
224 switch (selection)
226 case 1:
227 OnOK();
228 break;
229 case 2:
231 m_bPickedRefSet = true;
232 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
233 OnOK();
235 break;
236 case 3:
238 m_bPickedRefSet = true;
239 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
240 OnOK();
242 break;
243 default:
244 break;
248 BOOL CBrowseRefsDlg::OnInitDialog()
250 CResizableStandAloneDialog::OnInitDialog();
251 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
253 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
254 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
255 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
256 SetFilterCueText();
258 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
259 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
260 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
261 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
262 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
263 AddAnchor(IDHELP, BOTTOM_RIGHT);
265 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
266 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
267 static int columnWidths[] = { 150, 100, 100, 300, 100, 80, 80 };
268 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
269 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
270 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
271 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
272 m_bPickedRefSet = false;
274 AddAnchor(IDOK,BOTTOM_RIGHT);
275 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
276 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
278 Refresh(m_initialRef);
280 EnableSaveRestore(L"BrowseRefs");
282 CString sWindowTitle;
283 GetWindowText(sWindowTitle);
284 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
286 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
288 if (m_bPickOne)
289 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
291 m_ListRefLeafs.SetFocus();
292 return FALSE;
295 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
297 int posSlash=nameLeft.Find('/');
298 CString nameSub;
299 if(posSlash<0)
301 nameSub=nameLeft;
302 nameLeft.Empty();//Nothing left
304 else
306 nameSub=nameLeft.Left(posSlash);
307 nameLeft=nameLeft.Mid(posSlash+1);
309 if(nameSub.IsEmpty())
310 return NULL;
312 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
313 return NULL;
315 CShadowTree& nextNode=m_ShadowTree[nameSub];
316 nextNode.m_csRefName=nameSub;
317 nextNode.m_pParent=this;
318 return &nextNode;
321 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
323 if(IsLeaf())
325 if(m_csRefName.GetLength() > partialRefName.GetLength())
326 return NULL;
327 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
329 //Match of leaf name. Try match on total name.
330 CString totalRefName = GetRefName();
331 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
332 return this; //Also match. Found.
335 else
337 //Not a leaf. Search all nodes.
338 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
340 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
341 if(pSubtree != NULL)
342 return pSubtree; //Found
345 return NULL;//Not found
349 typedef std::map<CString,CString> MAP_STRING_STRING;
351 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
353 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
354 //List ctrl selection?
355 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
357 //A leaf is selected
358 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
359 m_ListRefLeafs.GetNextSelectedItem(pos));
360 return pTree->GetRefName();
362 else if (pos && !pickFirstSelIfMultiSel)
364 // at least one leaf is selected
365 CString refs;
366 int index;
367 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
369 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
370 if(wcsncmp(ref, L"refs/", 5) == 0)
371 ref = ref.Mid(5);
372 if(wcsncmp(ref, L"heads/", 6) == 0)
373 ref = ref.Mid(6);
374 refs += ref + _T(" ");
376 return refs.Trim();
378 else if(!onlyIfLeaf)
380 //Tree ctrl selection?
381 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
382 if(hTree!=NULL)
384 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
385 return pTree->GetRefName();
388 return CString();//None
391 static int GetBranchDescriptionsCallback(const git_config_entry *entry, void *data)
393 MAP_STRING_STRING *descriptions = (MAP_STRING_STRING *) data;
394 CString key = CUnicodeUtils::GetUnicode(entry->name, CP_UTF8);
395 CString val = CUnicodeUtils::GetUnicode(entry->value, CP_UTF8);
396 descriptions->insert(std::make_pair(key.Mid(7, key.GetLength() - 7 - 12), val)); // 7: branch., 12: .description
397 return 0;
400 MAP_STRING_STRING GetBranchDescriptions()
402 MAP_STRING_STRING descriptions;
403 CAutoConfig config(true);
404 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(g_Git.GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, FALSE);
405 git_config_foreach_match(config, "branch\\..*\\.description", GetBranchDescriptionsCallback, &descriptions);
406 return descriptions;
409 void CBrowseRefsDlg::Refresh(CString selectRef)
411 // m_RefMap.clear();
412 // g_Git.GetMapHashToFriendName(m_RefMap);
414 remotes.clear();
415 if (g_Git.GetRemoteList(remotes))
416 MessageBox(CGit::GetLibGit2LastErr(_T("Could not get a list of remotes.")), _T("TortoiseGit"), MB_ICONERROR);
418 if(!selectRef.IsEmpty())
420 if(selectRef == "HEAD")
422 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
423 selectRef.Empty();
424 else
425 selectRef = L"refs/heads/" + selectRef;
428 else
430 selectRef = GetSelectedRef(false, true);
433 m_RefTreeCtrl.DeleteAllItems();
434 m_ListRefLeafs.DeleteAllItems();
435 m_TreeRoot.m_ShadowTree.clear();
436 m_TreeRoot.m_csRefName = "refs";
437 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"refs",NULL,NULL);
438 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
440 CString allRefs, error;
441 if (g_Git.Run(L"git.exe for-each-ref --format="
442 L"%(refname)%04"
443 L"%(objectname)%04"
444 L"%(upstream)%04"
445 L"%(authordate:relative)%04"
446 L"%(subject)%04"
447 L"%(authorname)%04"
448 L"%(authordate:iso8601)%03",
449 &allRefs, &error, CP_UTF8))
451 CMessageBox::Show(NULL, CString(_T("Get refs failed\n")) + error, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
454 int linePos=0;
455 CString singleRef;
457 MAP_STRING_STRING refMap;
459 //First sort on ref name
460 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
462 singleRef.TrimLeft(L"\r\n");
463 int valuePos=0;
464 CString refName=singleRef.Tokenize(L"\04",valuePos);
465 if(refName.IsEmpty())
466 continue;
467 CString refRest=singleRef.Mid(valuePos);
470 //Use ref based on m_pickRef_Kind
471 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
472 continue; //Skip
473 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
474 continue; //Skip
475 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
476 continue; //Skip
478 refMap[refName] = refRest; //Use
481 MAP_STRING_STRING descriptions = GetBranchDescriptions();
483 //Populate ref tree
484 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
486 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
487 CString values=iterRefMap->second;
488 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
490 int valuePos=0;
491 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
492 treeLeaf.m_csUpstream = values.Tokenize(L"\04", valuePos); if (valuePos < 0) continue;
493 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
494 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
495 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
496 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
497 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
499 if (wcsncmp(iterRefMap->first, L"refs/heads/", 11) == 0)
500 treeLeaf.m_csDescription = descriptions[treeLeaf.m_csRefName];
503 // always expand the tree first
504 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
506 // try exact match first
507 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
508 SelectRef(selectRef, false);
511 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
513 if(!bExactMatch)
515 CString newRefName = GetFullRefName(refName);
516 if(!newRefName.IsEmpty())
517 refName = newRefName;
518 //else refName is not a valid ref. Try to select as good as possible.
520 if(_wcsnicmp(refName, L"refs/", 5) != 0)
521 return false; // Not a ref name
523 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
524 if(treeLeafHead.m_hTree != NULL)
526 //Not a leaf. Select tree node and return
527 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
528 return true;
531 if(treeLeafHead.m_pParent==NULL)
532 return false; //Weird... should not occur.
534 //This is the current head.
535 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
537 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
539 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
540 if(pCurrShadowTree == &treeLeafHead)
542 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
543 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
547 return true;
550 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
552 if(pTreePos==NULL)
554 if(_wcsnicmp(refName, L"refs/", 5) == 0)
555 refName=refName.Mid(5);
556 pTreePos=&m_TreeRoot;
558 if(refName.IsEmpty())
559 return *pTreePos;//Found leaf
561 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
562 if(pNextTree==NULL)
564 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
565 ASSERT(!bCreateIfNotExist);
566 return *pTreePos;
569 if(!refName.IsEmpty())
571 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
572 //Leafs are for the list control.
573 if(pNextTree->m_hTree==NULL)
575 //New tree. Create node in control.
576 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
577 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
581 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
585 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
587 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
588 *pResult = 0;
590 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
593 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
595 m_ListRefLeafs.DeleteAllItems();
597 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
598 if(pTree==NULL)
600 ASSERT(FALSE);
601 return;
603 FillListCtrlForShadowTree(pTree,L"",true);
604 m_ColumnManager.SetVisible(eCol_Upstream, (wcsncmp(pTree->GetRefName(), L"refs/heads", 11) == 0));
607 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
609 if(pTree->IsLeaf())
611 CString filter;
612 m_ctrlFilter.GetWindowText(filter);
613 filter.MakeLower();
614 bool positive = filter[0] != '!';
615 if (!positive)
616 filter = filter.Mid(1);
617 CString ref = refNamePrefix + pTree->m_csRefName;
618 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter, positive))
620 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
622 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
623 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
624 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
625 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
626 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
627 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
628 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
629 int pos = 0;
630 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
633 else
636 CString csThisName;
637 if(!isFirstLevel)
638 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
639 else
640 m_pListCtrlRoot = pTree;
641 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
643 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
646 if (isFirstLevel)
648 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
649 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
651 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
655 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter, bool positive)
657 if (m_SelectedFilters & LOGFILTER_REFNAME)
659 CString msg = ref;
660 msg = msg.MakeLower();
662 if (msg.Find(filter) >= 0)
663 return positive;
666 if (m_SelectedFilters & LOGFILTER_SUBJECT)
668 CString msg = pTree->m_csSubject;
669 msg = msg.MakeLower();
671 if (msg.Find(filter) >= 0)
672 return positive;
675 if (m_SelectedFilters & LOGFILTER_AUTHORS)
677 CString msg = pTree->m_csAuthor;
678 msg = msg.MakeLower();
680 if (msg.Find(filter) >= 0)
681 return positive;
684 if (m_SelectedFilters & LOGFILTER_REVS)
686 CString msg = pTree->m_csRefHash;
687 msg = msg.MakeLower();
689 if (msg.Find(filter) >= 0)
690 return positive;
692 return !positive;
695 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
697 ASSERT(!leafs.empty());
699 CString csMessage;
700 UINT mbIcon=MB_ICONQUESTION;
702 bool bIsRemoteBranch = false;
703 bool bIsBranch = false;
704 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
705 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
707 if(bIsBranch)
709 if(leafs.size() == 1)
711 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
712 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
714 //Check if branch is fully merged in HEAD
715 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
717 csMessage += L"\r\n\r\n";
718 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
719 mbIcon = MB_ICONWARNING;
722 if(bIsRemoteBranch)
724 csMessage += L"\r\n\r\n";
725 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
726 mbIcon = MB_ICONWARNING;
729 else
731 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
733 csMessage += L"\r\n\r\n";
734 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
735 mbIcon = MB_ICONWARNING;
737 if(bIsRemoteBranch)
739 csMessage += L"\r\n\r\n";
740 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
741 mbIcon = MB_ICONWARNING;
746 else if(leafs[0]->IsFrom(L"refs/tags/"))
748 if(leafs.size() == 1)
750 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
751 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
753 else
755 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
759 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
763 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
765 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
766 if(!DoDeleteRef((*i)->GetRefName()))
767 return false;
768 return true;
771 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
773 bool bIsRemoteBranch = false;
774 bool bIsBranch = false;
775 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
776 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
778 if (bIsRemoteBranch)
780 CString branchToDelete = completeRefName.Mid(13);
781 CString remoteName, remoteBranchToDelete;
782 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
783 return false;
785 if (CAppUtils::IsSSHPutty())
786 CAppUtils::LaunchPAgent(NULL, &remoteName);
788 CSysProgressDlg sysProgressDlg;
789 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
790 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
791 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
792 sysProgressDlg.SetShowProgressBar(false);
793 sysProgressDlg.ShowModal(this, true);
795 STRING_VECTOR list;
796 list.push_back(_T("refs/heads/") + remoteBranchToDelete);
797 if (g_Git.DeleteRemoteRefs(remoteName, list))
799 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
800 sysProgressDlg.Stop();
801 BringWindowToTop();
802 return false;
804 sysProgressDlg.Stop();
805 BringWindowToTop();
807 else if (bIsBranch)
809 if (g_Git.DeleteRef(completeRefName))
811 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
812 return false;
815 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
817 if (g_Git.DeleteRef(completeRefName))
819 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
820 return false;
823 return true;
826 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
828 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
829 if(pLeaf == NULL)
830 return CString();
831 return pLeaf->GetRefName();
835 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
837 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
838 else if (pWndFrom == &m_ListRefLeafs)
840 CRect headerPosition;
841 m_ListRefLeafs.GetHeaderCtrl()->GetWindowRect(headerPosition);
842 if (!headerPosition.PtInRect(point))
843 OnContextMenu_ListRefLeafs(point);
847 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
849 CPoint clientPoint=point;
850 m_RefTreeCtrl.ScreenToClient(&clientPoint);
852 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
853 if(hTreeItem!=NULL)
854 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
856 VectorPShadowTree tree;
857 ShowContextMenu(point,hTreeItem,tree);
860 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
862 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
863 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
864 while (pos)
866 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
870 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
872 std::vector<CShadowTree*> selectedLeafs;
873 GetSelectedLeaves(selectedLeafs);
874 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
877 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
879 ASSERT(selectedLeafs.size() == 2);
881 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
882 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
883 else
884 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
887 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
889 int i = 0;
890 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
892 if (*it == entry)
893 return i;
895 return -1;
898 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
900 CIconMenu popupMenu;
901 popupMenu.CreatePopupMenu();
903 bool bAddSeparator = false;
904 CString remoteName;
906 if(selectedLeafs.size()==1)
908 bAddSeparator = true;
910 bool bShowReflogOption = false;
911 bool bShowFetchOption = false;
912 bool bShowRenameOption = false;
913 bool bShowCreateBranchOption = false;
914 bool bShowEditBranchDescriptionOption = false;
916 CString fetchFromCmd;
918 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
920 bShowReflogOption = true;
921 bShowRenameOption = true;
922 bShowEditBranchDescriptionOption = true;
924 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
926 bShowReflogOption = true;
927 bShowFetchOption = true;
928 bShowCreateBranchOption = true;
930 CString remoteBranch;
931 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
932 bShowFetchOption = false;
933 else
934 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
936 else if(selectedLeafs[0]->IsFrom(L"refs/tags/"))
940 CString temp;
941 temp.LoadString(IDS_MENULOG);
942 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
943 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
944 if(bShowReflogOption)
946 temp.LoadString(IDS_MENUREFLOG);
947 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
950 popupMenu.AppendMenu(MF_SEPARATOR);
951 bAddSeparator = false;
953 if(bShowFetchOption)
955 bAddSeparator = true;
956 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
959 if(bAddSeparator)
960 popupMenu.AppendMenu(MF_SEPARATOR);
962 bAddSeparator = false;
963 if (m_bHasWC)
965 CString format, str;
966 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
968 format.LoadString(IDS_LOG_POPUP_MERGEREV);
969 str.Format(format, g_Git.GetCurrentBranch());
970 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
972 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
973 popupMenu.AppendMenu(MF_SEPARATOR);
976 if(bShowCreateBranchOption)
978 bAddSeparator = true;
979 temp.LoadString(IDS_MENUBRANCH);
980 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
983 if (bShowEditBranchDescriptionOption)
985 bAddSeparator = true;
986 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
988 if(bShowRenameOption)
990 bAddSeparator = true;
991 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
994 else if(selectedLeafs.size() == 2)
996 bAddSeparator = true;
997 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
998 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
999 CString menu;
1000 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1001 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
1002 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1003 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
1006 if(!selectedLeafs.empty())
1008 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
1010 if(bAddSeparator)
1011 popupMenu.AppendMenu(MF_SEPARATOR);
1012 CString menuItemName;
1013 if(selectedLeafs.size() == 1)
1014 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1015 else
1016 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1018 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1019 bAddSeparator = true;
1021 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1023 if(bAddSeparator)
1024 popupMenu.AppendMenu(MF_SEPARATOR);
1025 CString menuItemName;
1026 if(selectedLeafs.size() == 1)
1027 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1028 else
1029 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1031 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1032 bAddSeparator = true;
1034 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1036 if(bAddSeparator)
1037 popupMenu.AppendMenu(MF_SEPARATOR);
1038 CString menuItemName;
1039 if(selectedLeafs.size() == 1)
1040 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1041 else
1042 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1044 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1045 bAddSeparator = true;
1050 if(hTreePos!=NULL && selectedLeafs.empty())
1052 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1053 if(pTree->IsFrom(L"refs/remotes"))
1055 if(bAddSeparator)
1056 popupMenu.AppendMenu(MF_SEPARATOR);
1057 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1058 bAddSeparator = true;
1059 if(selectedLeafs.empty())
1061 CString remoteBranch;
1062 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1063 remoteName = _T("");
1064 int pos = findVectorPosition(remotes, remoteName);
1065 if (pos >= 0)
1067 CString temp;
1068 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
1069 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1071 temp.LoadString(IDS_DELETEREMOTETAG);
1072 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1076 if(pTree->IsFrom(L"refs/heads"))
1078 if(bAddSeparator)
1079 popupMenu.AppendMenu(MF_SEPARATOR);
1080 CString temp;
1081 temp.LoadString(IDS_MENUBRANCH);
1082 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1084 if(pTree->IsFrom(L"refs/tags"))
1086 if(bAddSeparator)
1087 popupMenu.AppendMenu(MF_SEPARATOR);
1088 CString temp;
1089 temp.LoadString(IDS_MENUTAG);
1090 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1091 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1092 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1093 if (!remotes.empty())
1095 popupMenu.AppendMenu(MF_SEPARATOR);
1096 int i = 0;
1097 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1099 temp.Format(IDS_DELETEREMOTETAGON, *it);
1100 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1107 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1108 switch ((eCmd)(selection & 0xFFFF))
1110 case eCmd_ViewLog:
1112 CLogDlg dlg;
1113 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1114 dlg.DoModal();
1116 break;
1117 case eCmd_ViewLogRange:
1119 CLogDlg dlg;
1120 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1121 dlg.DoModal();
1123 break;
1124 case eCmd_ViewLogRangeReachableFromOnlyOne:
1126 CLogDlg dlg;
1127 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1128 dlg.DoModal();
1130 break;
1131 case eCmd_RepoBrowser:
1132 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1133 break;
1134 case eCmd_DeleteBranch:
1135 case eCmd_DeleteRemoteBranch:
1137 if(ConfirmDeleteRef(selectedLeafs))
1138 DoDeleteRefs(selectedLeafs);
1139 Refresh();
1141 break;
1142 case eCmd_DeleteTag:
1144 if(ConfirmDeleteRef(selectedLeafs))
1145 DoDeleteRefs(selectedLeafs);
1146 Refresh();
1148 break;
1149 case eCmd_ShowReflog:
1151 CRefLogDlg refLogDlg(this);
1152 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1153 refLogDlg.DoModal();
1155 break;
1156 case eCmd_Fetch:
1158 CAppUtils::Fetch(remoteName);
1159 Refresh();
1161 break;
1162 case eCmd_DeleteRemoteTag:
1164 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1165 int remoteInx = selection >> 16;
1166 if (remoteInx < 0 || remoteInx >= remotes.size())
1167 return;
1168 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1169 deleteRemoteTagDlg.DoModal();
1171 break;
1172 case eCmd_Merge:
1174 CString ref = selectedLeafs[0]->GetRefName();
1175 CAppUtils::Merge(&ref);
1177 break;
1178 case eCmd_Switch:
1180 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1182 break;
1183 case eCmd_Rename:
1185 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1186 if(pos != NULL)
1187 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1189 break;
1190 case eCmd_AddRemote:
1192 CAddRemoteDlg(this).DoModal();
1193 Refresh();
1195 break;
1196 case eCmd_ManageRemotes:
1198 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(), this).DoModal();
1199 // CSettingGitRemote W_Remotes(m_cmdPath);
1200 // W_Remotes.DoModal();
1201 Refresh();
1203 break;
1204 case eCmd_CreateBranch:
1206 CString *commitHash = NULL;
1207 if (selectedLeafs.size() == 1)
1208 commitHash = &(selectedLeafs[0]->m_csRefHash);
1209 CAppUtils::CreateBranchTag(false, commitHash);
1210 Refresh();
1212 break;
1213 case eCmd_CreateTag:
1215 CAppUtils::CreateBranchTag(true);
1216 Refresh();
1218 break;
1219 case eCmd_DeleteAllTags:
1221 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1223 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1224 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1226 if (ConfirmDeleteRef(selectedLeafs))
1227 DoDeleteRefs(selectedLeafs);
1228 Refresh();
1230 break;
1231 case eCmd_Diff:
1233 CFileDiffDlg dlg;
1234 dlg.SetDiff(
1235 NULL,
1236 selectedLeafs[1]->GetRefName() + L"^{}",
1237 selectedLeafs[0]->GetRefName() + L"^{}");
1238 dlg.DoModal();
1240 break;
1241 case eCmd_UnifiedDiff:
1243 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1245 break;
1246 case eCmd_EditBranchDescription:
1248 CInputDlg dlg;
1249 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1250 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1251 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1252 dlg.m_bUseLogWidth = true;
1253 if(dlg.DoModal() == IDOK)
1255 CString key;
1256 key.Format(_T("branch.%s.description"), selectedLeafs[0]->m_csRefName);
1257 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1258 dlg.m_sInputText.Trim();
1259 if (dlg.m_sInputText.IsEmpty())
1260 g_Git.UnsetConfigValue(key);
1261 else
1262 g_Git.SetConfigValue(key, dlg.m_sInputText);
1263 Refresh();
1266 break;
1270 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1272 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1273 if(!(*i)->IsFrom(from))
1274 return false;
1275 return true;
1278 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1280 if (pMsg->message == WM_KEYDOWN)
1282 switch (pMsg->wParam)
1284 /* case VK_RETURN:
1286 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1288 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1290 PostMessage(WM_COMMAND, IDOK);
1292 return TRUE;
1295 break;
1296 */ case VK_F2:
1298 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1300 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1301 if(pos != NULL)
1302 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1305 break;
1307 case VK_F5:
1309 Refresh();
1311 break;
1312 case L'E':
1314 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
1316 m_ctrlFilter.SetSel(0, -1, FALSE);
1317 m_ctrlFilter.SetFocus();
1318 return TRUE;
1321 break;
1326 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1329 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1331 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1332 *pResult = 0;
1334 if(m_currSortCol == pNMLV->iSubItem)
1335 m_currSortDesc = !m_currSortDesc;
1336 else
1338 m_currSortCol = pNMLV->iSubItem;
1339 m_currSortDesc = false;
1342 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1343 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1345 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1348 void CBrowseRefsDlg::OnDestroy()
1350 if (!m_bPickedRefSet)
1351 m_pickedRef = GetSelectedRef(true, false);
1353 int maxcol = m_ColumnManager.GetColumnCount();
1354 for (int col = 0; col < maxcol; ++col)
1355 if (m_ColumnManager.IsVisible(col))
1356 m_ColumnManager.ColumnResized(col);
1357 m_ColumnManager.WriteSettings();
1359 CResizableStandAloneDialog::OnDestroy();
1362 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1364 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1365 *pResult = 0;
1367 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1368 if (item && pNMListView->uNewState == LVIS_SELECTED)
1369 m_sLastSelected = item->GetRefName();
1371 UpdateInfoLabel();
1374 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1376 *pResult = 0;
1378 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1379 return;
1380 EndDialog(IDOK);
1383 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1385 CBrowseRefsDlg dlg(CString(),NULL);
1387 if(initialRef.IsEmpty())
1388 initialRef = L"HEAD";
1389 dlg.m_initialRef = initialRef;
1390 dlg.m_pickRef_Kind = pickRef_Kind;
1391 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1393 if(dlg.DoModal() != IDOK)
1394 return CString();
1396 return dlg.m_pickedRef;
1399 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1401 CString origRef;
1402 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1403 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1404 if(resultRef.IsEmpty())
1405 return false;
1406 if(wcsncmp(resultRef,L"refs/",5)==0)
1407 resultRef = resultRef.Mid(5);
1408 // if(wcsncmp(resultRef,L"heads/",6)==0)
1409 // resultRef = resultRef.Mid(6);
1411 //Find closest match of choice in combobox
1412 int ixFound = -1;
1413 int matchLength = 0;
1414 CString comboRefName;
1415 for(int i = 0; i < pComboBox->GetCount(); ++i)
1417 pComboBox->GetLBText(i, comboRefName);
1418 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1419 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1420 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1422 matchLength = comboRefName.GetLength();
1423 ixFound = i;
1426 if(ixFound >= 0)
1427 pComboBox->SetCurSel(ixFound);
1428 else
1429 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)
1431 return true;
1434 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1436 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1437 *pResult = FALSE;
1439 if(pDispInfo->item.pszText == NULL)
1440 return; //User canceled changing
1442 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1444 if(!pTree->IsFrom(L"refs/heads/"))
1446 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1447 return;
1450 CString origName = pTree->GetRefName().Mid(11);
1452 CString newName;
1453 if(m_pListCtrlRoot != NULL)
1454 newName = m_pListCtrlRoot->GetRefName() + L'/';
1455 newName += pDispInfo->item.pszText;
1457 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1459 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1460 return;
1463 CString newNameTrunced = newName.Mid(11);
1465 CString errorMsg;
1466 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1468 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1469 return;
1471 //Do as if it failed to rename. Let Refresh() do the job.
1472 //*pResult = TRUE;
1474 Refresh(newName);
1476 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1478 // AfxMessageBox(W_csPopup);
1482 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1484 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1485 *pResult = FALSE;
1487 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1489 if(!pTree->IsFrom(L"refs/heads/"))
1491 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1492 return;
1496 void CBrowseRefsDlg::OnEnChangeEditFilter()
1498 SetTimer(IDT_FILTER, 1000, NULL);
1501 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1503 if (nIDEvent == IDT_FILTER)
1505 KillTimer(IDT_FILTER);
1506 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1509 CResizableStandAloneDialog::OnTimer(nIDEvent);
1512 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1514 // FIXME: x64 version would get this function called with unexpected parameters.
1515 if (!lParam)
1516 return 0;
1518 RECT * rect = (LPRECT)lParam;
1519 CPoint point;
1520 CString temp;
1521 point = CPoint(rect->left, rect->bottom);
1522 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1523 CMenu popup;
1524 if (popup.CreatePopupMenu())
1526 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1527 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1529 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1530 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1532 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1533 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1535 temp.LoadString(IDS_LOG_FILTER_REVS);
1536 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1538 temp.LoadString(IDS_LOG_FILTER_TOGGLE);
1539 popup.AppendMenu(MF_STRING | MF_ENABLED, LOGFILTER_TOGGLE, temp);
1541 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1542 if (selection != 0)
1544 if (selection == LOGFILTER_TOGGLE)
1545 m_SelectedFilters = (~m_SelectedFilters) & LOGFILTER_ALL;
1546 else
1547 m_SelectedFilters ^= selection;
1548 SetFilterCueText();
1549 SetTimer(IDT_FILTER, 1000, NULL);
1552 return 0L;
1555 LRESULT CBrowseRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1557 KillTimer(LOGFILTER_TIMER);
1558 m_ctrlFilter.SetWindowText(_T(""));
1559 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1560 return 0L;
1563 void CBrowseRefsDlg::SetFilterCueText()
1565 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1566 temp += _T(" ");
1568 if (m_SelectedFilters & LOGFILTER_REFNAME)
1569 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1571 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1573 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1574 temp += _T(", ");
1575 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1578 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1580 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1581 temp += _T(", ");
1582 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1585 if (m_SelectedFilters & LOGFILTER_REVS)
1587 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1588 temp += _T(", ");
1589 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1592 // to make the cue banner text appear more to the right of the edit control
1593 temp = _T(" ") + temp;
1594 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1597 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1599 m_pickedRef = g_Git.GetCurrentBranch(true);
1600 m_bPickedRefSet = true;
1601 OnOK();
1604 void CBrowseRefsDlg::UpdateInfoLabel()
1606 CString temp;
1607 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1608 SetDlgItemText(IDC_INFOLABEL, temp);