Add start and pre commit hooks
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blob4389ab50dc0522273a93b777d6b14a128c2390bb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2014 - 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_WM_TIMER()
195 ON_BN_CLICKED(IDC_CURRENTBRANCH, OnBnClickedCurrentbranch)
196 END_MESSAGE_MAP()
199 // CBrowseRefsDlg message handlers
201 void CBrowseRefsDlg::OnBnClickedOk()
203 if (m_bPickOne || m_ListRefLeafs.GetSelectedCount() != 2)
205 OnOK();
206 return;
209 CIconMenu popupMenu;
210 popupMenu.CreatePopupMenu();
212 std::vector<CShadowTree*> selectedLeafs;
213 GetSelectedLeaves(selectedLeafs);
215 popupMenu.AppendMenuIcon(1, GetSelectedRef(true, false), IDI_LOG);
216 popupMenu.SetDefaultItem(1);
217 popupMenu.AppendMenuIcon(2, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")), IDI_LOG);
218 popupMenu.AppendMenuIcon(3, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")), IDI_LOG);
220 RECT rect;
221 GetDlgItem(IDOK)->GetWindowRect(&rect);
222 int selection = popupMenu.TrackPopupMenuEx(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.top, this, 0);
223 switch (selection)
225 case 1:
226 OnOK();
227 break;
228 case 2:
230 m_bPickedRefSet = true;
231 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T(".."));
232 OnOK();
234 break;
235 case 3:
237 m_bPickedRefSet = true;
238 m_pickedRef = GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..."));
239 OnOK();
241 break;
242 default:
243 break;
247 BOOL CBrowseRefsDlg::OnInitDialog()
249 CResizableStandAloneDialog::OnInitDialog();
250 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
252 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
253 m_ctrlFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED);
254 m_ctrlFilter.SetInfoIcon(IDI_FILTEREDIT);
255 SetFilterCueText();
257 AddAnchor(IDC_TREE_REF, TOP_LEFT, BOTTOM_LEFT);
258 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
259 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER, TOP_LEFT);
260 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
261 AddAnchor(IDC_INFOLABEL, BOTTOM_LEFT, BOTTOM_RIGHT);
262 AddAnchor(IDHELP, BOTTOM_RIGHT);
264 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
265 static UINT columnNames[] = { IDS_BRANCHNAME, IDS_TRACKEDBRANCH, IDS_DATELASTCOMMIT, IDS_LASTCOMMIT, IDS_LASTAUTHOR, IDS_HASH, IDS_DESCRIPTION };
266 static int columnWidths[] = { 150, 100, 100, 300, 100, 80, 80 };
267 DWORD dwDefaultColumns = (1 << eCol_Name) | (1 << eCol_Upstream ) | (1 << eCol_Date) | (1 << eCol_Msg) |
268 (1 << eCol_LastAuthor) | (1 << eCol_Hash) | (1 << eCol_Description);
269 m_ColumnManager.SetNames(columnNames, _countof(columnNames));
270 m_ColumnManager.ReadSettings(dwDefaultColumns, 0, _T("BrowseRefs"), _countof(columnNames), columnWidths);
271 m_bPickedRefSet = false;
273 AddAnchor(IDOK,BOTTOM_RIGHT);
274 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
275 AddAnchor(IDC_CURRENTBRANCH, BOTTOM_RIGHT);
277 Refresh(m_initialRef);
279 EnableSaveRestore(L"BrowseRefs");
281 CString sWindowTitle;
282 GetWindowText(sWindowTitle);
283 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
285 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
287 if (m_bPickOne)
288 m_ListRefLeafs.ModifyStyle(0, LVS_SINGLESEL);
290 m_ListRefLeafs.SetFocus();
291 return FALSE;
294 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
296 int posSlash=nameLeft.Find('/');
297 CString nameSub;
298 if(posSlash<0)
300 nameSub=nameLeft;
301 nameLeft.Empty();//Nothing left
303 else
305 nameSub=nameLeft.Left(posSlash);
306 nameLeft=nameLeft.Mid(posSlash+1);
308 if(nameSub.IsEmpty())
309 return NULL;
311 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
312 return NULL;
314 CShadowTree& nextNode=m_ShadowTree[nameSub];
315 nextNode.m_csRefName=nameSub;
316 nextNode.m_pParent=this;
317 return &nextNode;
320 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
322 if(IsLeaf())
324 if(m_csRefName.GetLength() > partialRefName.GetLength())
325 return NULL;
326 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
328 //Match of leaf name. Try match on total name.
329 CString totalRefName = GetRefName();
330 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
331 return this; //Also match. Found.
334 else
336 //Not a leaf. Search all nodes.
337 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
339 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
340 if(pSubtree != NULL)
341 return pSubtree; //Found
344 return NULL;//Not found
348 typedef std::map<CString,CString> MAP_STRING_STRING;
350 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
352 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
353 //List ctrl selection?
354 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
356 //A leaf is selected
357 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
358 m_ListRefLeafs.GetNextSelectedItem(pos));
359 return pTree->GetRefName();
361 else if (pos && !pickFirstSelIfMultiSel)
363 // at least one leaf is selected
364 CString refs;
365 int index;
366 while ((index = m_ListRefLeafs.GetNextSelectedItem(pos)) >= 0)
368 CString ref = ((CShadowTree*)m_ListRefLeafs.GetItemData(index))->GetRefName();
369 if(wcsncmp(ref, L"refs/", 5) == 0)
370 ref = ref.Mid(5);
371 if(wcsncmp(ref, L"heads/", 6) == 0)
372 ref = ref.Mid(6);
373 refs += ref + _T(" ");
375 return refs.Trim();
377 else if(!onlyIfLeaf)
379 //Tree ctrl selection?
380 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
381 if(hTree!=NULL)
383 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
384 return pTree->GetRefName();
387 return CString();//None
390 static int GetBranchDescriptionsCallback(const git_config_entry *entry, void *data)
392 MAP_STRING_STRING *descriptions = (MAP_STRING_STRING *) data;
393 CString key = CUnicodeUtils::GetUnicode(entry->name, CP_UTF8);
394 CString val = CUnicodeUtils::GetUnicode(entry->value, CP_UTF8);
395 descriptions->insert(std::make_pair(key.Mid(7, key.GetLength() - 7 - 12), val)); // 7: branch., 12: .description
396 return 0;
399 MAP_STRING_STRING GetBranchDescriptions()
401 MAP_STRING_STRING descriptions;
402 CAutoConfig config(true);
403 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(g_Git.GetGitLocalConfig()), GIT_CONFIG_LEVEL_LOCAL, FALSE);
404 git_config_foreach_match(config, "branch\\..*\\.description", GetBranchDescriptionsCallback, &descriptions);
405 return descriptions;
408 void CBrowseRefsDlg::Refresh(CString selectRef)
410 // m_RefMap.clear();
411 // g_Git.GetMapHashToFriendName(m_RefMap);
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 allRefs, error;
440 if (g_Git.Run(L"git.exe for-each-ref --format="
441 L"%(refname)%04"
442 L"%(objectname)%04"
443 L"%(upstream)%04"
444 L"%(authordate:relative)%04"
445 L"%(subject)%04"
446 L"%(authorname)%04"
447 L"%(authordate:iso8601)%03",
448 &allRefs, &error, CP_UTF8))
450 CMessageBox::Show(NULL, CString(_T("Get refs failed\n")) + error, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
453 int linePos=0;
454 CString singleRef;
456 MAP_STRING_STRING refMap;
458 //First sort on ref name
459 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
461 singleRef.TrimLeft(L"\r\n");
462 int valuePos=0;
463 CString refName=singleRef.Tokenize(L"\04",valuePos);
464 if(refName.IsEmpty())
465 continue;
466 CString refRest=singleRef.Mid(valuePos);
469 //Use ref based on m_pickRef_Kind
470 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
471 continue; //Skip
472 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
473 continue; //Skip
474 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
475 continue; //Skip
477 refMap[refName] = refRest; //Use
480 MAP_STRING_STRING descriptions = GetBranchDescriptions();
482 //Populate ref tree
483 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
485 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
486 CString values=iterRefMap->second;
487 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
489 int valuePos=0;
490 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
491 treeLeaf.m_csUpstream = values.Tokenize(L"\04", valuePos); if (valuePos < 0) continue;
492 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
493 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
494 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
495 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
496 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
498 if (wcsncmp(iterRefMap->first, L"refs/heads/", 11) == 0)
499 treeLeaf.m_csDescription = descriptions[treeLeaf.m_csRefName];
502 // always expand the tree first
503 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
505 // try exact match first
506 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
507 SelectRef(selectRef, false);
510 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
512 if(!bExactMatch)
514 CString newRefName = GetFullRefName(refName);
515 if(!newRefName.IsEmpty())
516 refName = newRefName;
517 //else refName is not a valid ref. Try to select as good as possible.
519 if(_wcsnicmp(refName, L"refs/", 5) != 0)
520 return false; // Not a ref name
522 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
523 if(treeLeafHead.m_hTree != NULL)
525 //Not a leaf. Select tree node and return
526 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
527 return true;
530 if(treeLeafHead.m_pParent==NULL)
531 return false; //Weird... should not occur.
533 //This is the current head.
534 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
536 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
538 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
539 if(pCurrShadowTree == &treeLeafHead)
541 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
542 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
546 return true;
549 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
551 if(pTreePos==NULL)
553 if(_wcsnicmp(refName, L"refs/", 5) == 0)
554 refName=refName.Mid(5);
555 pTreePos=&m_TreeRoot;
557 if(refName.IsEmpty())
558 return *pTreePos;//Found leaf
560 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
561 if(pNextTree==NULL)
563 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
564 ASSERT(!bCreateIfNotExist);
565 return *pTreePos;
568 if(!refName.IsEmpty())
570 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
571 //Leafs are for the list control.
572 if(pNextTree->m_hTree==NULL)
574 //New tree. Create node in control.
575 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
576 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
580 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
584 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
586 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
587 *pResult = 0;
589 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
592 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
594 m_ListRefLeafs.DeleteAllItems();
596 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
597 if(pTree==NULL)
599 ASSERT(FALSE);
600 return;
602 FillListCtrlForShadowTree(pTree,L"",true);
603 m_ColumnManager.SetVisible(eCol_Upstream, (wcsncmp(pTree->GetRefName(), L"refs/heads", 11) == 0));
606 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
608 if(pTree->IsLeaf())
610 CString filter;
611 m_ctrlFilter.GetWindowText(filter);
612 filter.MakeLower();
613 CString ref = refNamePrefix + pTree->m_csRefName;
614 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter))
616 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
618 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
619 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
620 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
621 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
622 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
623 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
624 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
625 int pos = 0;
626 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
629 else
632 CString csThisName;
633 if(!isFirstLevel)
634 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
635 else
636 m_pListCtrlRoot = pTree;
637 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
639 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
642 if (isFirstLevel)
644 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
645 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
647 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
651 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter)
653 if (m_SelectedFilters & LOGFILTER_REFNAME)
655 CString msg = ref;
656 msg = msg.MakeLower();
658 if (msg.Find(filter) >= 0)
659 return true;
662 if (m_SelectedFilters & LOGFILTER_SUBJECT)
664 CString msg = pTree->m_csSubject;
665 msg = msg.MakeLower();
667 if (msg.Find(filter) >= 0)
668 return true;
671 if (m_SelectedFilters & LOGFILTER_AUTHORS)
673 CString msg = pTree->m_csAuthor;
674 msg = msg.MakeLower();
676 if (msg.Find(filter) >= 0)
677 return true;
680 if (m_SelectedFilters & LOGFILTER_REVS)
682 CString msg = pTree->m_csRefHash;
683 msg = msg.MakeLower();
685 if (msg.Find(filter) >= 0)
686 return true;
688 return false;
691 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
693 ASSERT(!leafs.empty());
695 CString csMessage;
696 UINT mbIcon=MB_ICONQUESTION;
698 bool bIsRemoteBranch = false;
699 bool bIsBranch = false;
700 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
701 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
703 if(bIsBranch)
705 if(leafs.size() == 1)
707 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
708 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
710 //Check if branch is fully merged in HEAD
711 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
713 csMessage += L"\r\n\r\n";
714 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
715 mbIcon = MB_ICONWARNING;
718 if(bIsRemoteBranch)
720 csMessage += L"\r\n\r\n";
721 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
722 mbIcon = MB_ICONWARNING;
725 else
727 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
729 csMessage += L"\r\n\r\n";
730 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
731 mbIcon = MB_ICONWARNING;
733 if(bIsRemoteBranch)
735 csMessage += L"\r\n\r\n";
736 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
737 mbIcon = MB_ICONWARNING;
742 else if(leafs[0]->IsFrom(L"refs/tags/"))
744 if(leafs.size() == 1)
746 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
747 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
749 else
751 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
755 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
759 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
761 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
762 if(!DoDeleteRef((*i)->GetRefName()))
763 return false;
764 return true;
767 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
769 bool bIsRemoteBranch = false;
770 bool bIsBranch = false;
771 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
772 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
774 if (bIsRemoteBranch)
776 CString branchToDelete = completeRefName.Mid(13);
777 CString cmd;
778 CString remoteName, remoteBranchToDelete;
779 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
780 return false;
782 if (CAppUtils::IsSSHPutty())
783 CAppUtils::LaunchPAgent(NULL, &remoteName);
785 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, remoteBranchToDelete);
787 CSysProgressDlg sysProgressDlg;
788 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
789 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
790 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
791 sysProgressDlg.SetShowProgressBar(false);
792 sysProgressDlg.ShowModal(this, true);
794 CString errorMsg;
795 if (g_Git.Run(cmd, &errorMsg, CP_UTF8) != 0)
797 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
798 sysProgressDlg.Stop();
799 BringWindowToTop();
800 return false;
802 sysProgressDlg.Stop();
803 BringWindowToTop();
805 else if (bIsBranch)
807 if (g_Git.DeleteRef(completeRefName))
809 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
810 return false;
813 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
815 if (g_Git.DeleteRef(completeRefName))
817 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
818 return false;
821 return true;
824 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
826 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
827 if(pLeaf == NULL)
828 return CString();
829 return pLeaf->GetRefName();
833 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
835 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
836 else if (pWndFrom == &m_ListRefLeafs)
838 CRect headerPosition;
839 m_ListRefLeafs.GetHeaderCtrl()->GetWindowRect(headerPosition);
840 if (!headerPosition.PtInRect(point))
841 OnContextMenu_ListRefLeafs(point);
845 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
847 CPoint clientPoint=point;
848 m_RefTreeCtrl.ScreenToClient(&clientPoint);
850 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
851 if(hTreeItem!=NULL)
852 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
854 VectorPShadowTree tree;
855 ShowContextMenu(point,hTreeItem,tree);
858 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
860 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
861 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
862 while (pos)
864 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
868 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
870 std::vector<CShadowTree*> selectedLeafs;
871 GetSelectedLeaves(selectedLeafs);
872 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
875 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
877 ASSERT(selectedLeafs.size() == 2);
879 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
880 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
881 else
882 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
885 int findVectorPosition(const STRING_VECTOR& vector, const CString& entry)
887 int i = 0;
888 for (auto it = vector.cbegin(); it != vector.cend(); ++it, ++i)
890 if (*it == entry)
891 return i;
893 return -1;
896 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
898 CIconMenu popupMenu;
899 popupMenu.CreatePopupMenu();
901 bool bAddSeparator = false;
902 CString remoteName;
904 if(selectedLeafs.size()==1)
906 bAddSeparator = true;
908 bool bShowReflogOption = false;
909 bool bShowFetchOption = false;
910 bool bShowRenameOption = false;
911 bool bShowCreateBranchOption = false;
912 bool bShowEditBranchDescriptionOption = false;
914 CString fetchFromCmd;
916 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
918 bShowReflogOption = true;
919 bShowRenameOption = true;
920 bShowEditBranchDescriptionOption = true;
922 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
924 bShowReflogOption = true;
925 bShowFetchOption = true;
926 bShowCreateBranchOption = true;
928 CString remoteBranch;
929 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
930 bShowFetchOption = false;
931 else
932 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
934 else if(selectedLeafs[0]->IsFrom(L"refs/tags/"))
938 CString temp;
939 temp.LoadString(IDS_MENULOG);
940 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
941 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
942 if(bShowReflogOption)
944 temp.LoadString(IDS_MENUREFLOG);
945 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
948 popupMenu.AppendMenu(MF_SEPARATOR);
949 bAddSeparator = false;
951 if(bShowFetchOption)
953 bAddSeparator = true;
954 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
957 if(bAddSeparator)
958 popupMenu.AppendMenu(MF_SEPARATOR);
960 bAddSeparator = false;
961 if (m_bHasWC)
963 CString format, str;
964 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
966 format.LoadString(IDS_LOG_POPUP_MERGEREV);
967 str.Format(format, g_Git.GetCurrentBranch());
968 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
970 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
971 popupMenu.AppendMenu(MF_SEPARATOR);
974 if(bShowCreateBranchOption)
976 bAddSeparator = true;
977 temp.LoadString(IDS_MENUBRANCH);
978 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
981 if (bShowEditBranchDescriptionOption)
983 bAddSeparator = true;
984 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
986 if(bShowRenameOption)
988 bAddSeparator = true;
989 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
992 else if(selectedLeafs.size() == 2)
994 bAddSeparator = true;
995 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
996 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
997 CString menu;
998 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
999 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
1000 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1001 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
1004 if(!selectedLeafs.empty())
1006 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
1008 if(bAddSeparator)
1009 popupMenu.AppendMenu(MF_SEPARATOR);
1010 CString menuItemName;
1011 if(selectedLeafs.size() == 1)
1012 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
1013 else
1014 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
1016 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
1017 bAddSeparator = true;
1019 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1021 if(bAddSeparator)
1022 popupMenu.AppendMenu(MF_SEPARATOR);
1023 CString menuItemName;
1024 if(selectedLeafs.size() == 1)
1025 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1026 else
1027 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1029 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1030 bAddSeparator = true;
1032 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1034 if(bAddSeparator)
1035 popupMenu.AppendMenu(MF_SEPARATOR);
1036 CString menuItemName;
1037 if(selectedLeafs.size() == 1)
1038 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1039 else
1040 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1042 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1043 bAddSeparator = true;
1048 if(hTreePos!=NULL && selectedLeafs.empty())
1050 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1051 if(pTree->IsFrom(L"refs/remotes"))
1053 if(bAddSeparator)
1054 popupMenu.AppendMenu(MF_SEPARATOR);
1055 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1056 bAddSeparator = true;
1057 if(selectedLeafs.empty())
1059 CString remoteBranch;
1060 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1061 remoteName = _T("");
1062 int pos = findVectorPosition(remotes, remoteName);
1063 if (pos >= 0)
1065 CString temp;
1066 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
1067 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1069 temp.LoadString(IDS_DELETEREMOTETAG);
1070 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (pos << 16), temp, IDI_DELETE);
1074 if(pTree->IsFrom(L"refs/heads"))
1076 if(bAddSeparator)
1077 popupMenu.AppendMenu(MF_SEPARATOR);
1078 CString temp;
1079 temp.LoadString(IDS_MENUBRANCH);
1080 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1082 if(pTree->IsFrom(L"refs/tags"))
1084 if(bAddSeparator)
1085 popupMenu.AppendMenu(MF_SEPARATOR);
1086 CString temp;
1087 temp.LoadString(IDS_MENUTAG);
1088 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1089 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1090 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1091 if (!remotes.empty())
1093 popupMenu.AppendMenu(MF_SEPARATOR);
1094 int i = 0;
1095 for (auto it = remotes.cbegin(); it != remotes.cend(); ++it, ++i)
1097 temp.Format(IDS_DELETEREMOTETAGON, *it);
1098 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag | (i << 16), temp, IDI_DELETE);
1105 int selection = popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0);
1106 switch ((eCmd)(selection & 0xFFFF))
1108 case eCmd_ViewLog:
1110 CLogDlg dlg;
1111 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1112 dlg.DoModal();
1114 break;
1115 case eCmd_ViewLogRange:
1117 CLogDlg dlg;
1118 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1119 dlg.DoModal();
1121 break;
1122 case eCmd_ViewLogRangeReachableFromOnlyOne:
1124 CLogDlg dlg;
1125 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1126 dlg.DoModal();
1128 break;
1129 case eCmd_RepoBrowser:
1130 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1131 break;
1132 case eCmd_DeleteBranch:
1133 case eCmd_DeleteRemoteBranch:
1135 if(ConfirmDeleteRef(selectedLeafs))
1136 DoDeleteRefs(selectedLeafs);
1137 Refresh();
1139 break;
1140 case eCmd_DeleteTag:
1142 if(ConfirmDeleteRef(selectedLeafs))
1143 DoDeleteRefs(selectedLeafs);
1144 Refresh();
1146 break;
1147 case eCmd_ShowReflog:
1149 CRefLogDlg refLogDlg(this);
1150 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1151 refLogDlg.DoModal();
1153 break;
1154 case eCmd_Fetch:
1156 CAppUtils::Fetch(remoteName);
1157 Refresh();
1159 break;
1160 case eCmd_DeleteRemoteTag:
1162 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1163 int remoteInx = selection >> 16;
1164 if (remoteInx < 0 || remoteInx >= remotes.size())
1165 return;
1166 deleteRemoteTagDlg.m_sRemote = remotes[remoteInx];
1167 deleteRemoteTagDlg.DoModal();
1169 break;
1170 case eCmd_Merge:
1172 CString ref = selectedLeafs[0]->GetRefName();
1173 CAppUtils::Merge(&ref);
1175 break;
1176 case eCmd_Switch:
1178 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1180 break;
1181 case eCmd_Rename:
1183 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1184 if(pos != NULL)
1185 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1187 break;
1188 case eCmd_AddRemote:
1190 CAddRemoteDlg(this).DoModal();
1191 Refresh();
1193 break;
1194 case eCmd_ManageRemotes:
1196 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
1197 // CSettingGitRemote W_Remotes(m_cmdPath);
1198 // W_Remotes.DoModal();
1199 Refresh();
1201 break;
1202 case eCmd_CreateBranch:
1204 CString *commitHash = NULL;
1205 if (selectedLeafs.size() == 1)
1206 commitHash = &(selectedLeafs[0]->m_csRefHash);
1207 CAppUtils::CreateBranchTag(false, commitHash);
1208 Refresh();
1210 break;
1211 case eCmd_CreateTag:
1213 CAppUtils::CreateBranchTag(true);
1214 Refresh();
1216 break;
1217 case eCmd_DeleteAllTags:
1219 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1221 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1222 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1224 if (ConfirmDeleteRef(selectedLeafs))
1225 DoDeleteRefs(selectedLeafs);
1226 Refresh();
1228 break;
1229 case eCmd_Diff:
1231 CFileDiffDlg dlg;
1232 dlg.SetDiff(
1233 NULL,
1234 selectedLeafs[1]->GetRefName() + L"^{}",
1235 selectedLeafs[0]->GetRefName() + L"^{}");
1236 dlg.DoModal();
1238 break;
1239 case eCmd_UnifiedDiff:
1241 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1243 break;
1244 case eCmd_EditBranchDescription:
1246 CInputDlg dlg;
1247 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1248 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1249 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1250 dlg.m_bUseLogWidth = true;
1251 if(dlg.DoModal() == IDOK)
1253 CString key;
1254 key.Format(_T("branch.%s.description"), selectedLeafs[0]->m_csRefName);
1255 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1256 dlg.m_sInputText.Trim();
1257 if (dlg.m_sInputText.IsEmpty())
1258 g_Git.UnsetConfigValue(key);
1259 else
1260 g_Git.SetConfigValue(key, dlg.m_sInputText);
1261 Refresh();
1264 break;
1268 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1270 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1271 if(!(*i)->IsFrom(from))
1272 return false;
1273 return true;
1276 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1278 if (pMsg->message == WM_KEYDOWN)
1280 switch (pMsg->wParam)
1282 /* case VK_RETURN:
1284 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1286 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1288 PostMessage(WM_COMMAND, IDOK);
1290 return TRUE;
1293 break;
1294 */ case VK_F2:
1296 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1298 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1299 if(pos != NULL)
1300 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1303 break;
1305 case VK_F5:
1307 Refresh();
1309 break;
1314 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1317 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1319 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1320 *pResult = 0;
1322 if(m_currSortCol == pNMLV->iSubItem)
1323 m_currSortDesc = !m_currSortDesc;
1324 else
1326 m_currSortCol = pNMLV->iSubItem;
1327 m_currSortDesc = false;
1330 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1331 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1333 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1336 void CBrowseRefsDlg::OnDestroy()
1338 if (!m_bPickedRefSet)
1339 m_pickedRef = GetSelectedRef(true, false);
1341 int maxcol = m_ColumnManager.GetColumnCount();
1342 for (int col = 0; col < maxcol; ++col)
1343 if (m_ColumnManager.IsVisible(col))
1344 m_ColumnManager.ColumnResized(col);
1345 m_ColumnManager.WriteSettings();
1347 CResizableStandAloneDialog::OnDestroy();
1350 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1352 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1353 *pResult = 0;
1355 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1356 if (item && pNMListView->uNewState == LVIS_SELECTED)
1357 m_sLastSelected = item->GetRefName();
1359 UpdateInfoLabel();
1362 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1364 *pResult = 0;
1366 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1367 return;
1368 EndDialog(IDOK);
1371 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1373 CBrowseRefsDlg dlg(CString(),NULL);
1375 if(initialRef.IsEmpty())
1376 initialRef = L"HEAD";
1377 dlg.m_initialRef = initialRef;
1378 dlg.m_pickRef_Kind = pickRef_Kind;
1379 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1381 if(dlg.DoModal() != IDOK)
1382 return CString();
1384 return dlg.m_pickedRef;
1387 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1389 CString origRef;
1390 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1391 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1392 if(resultRef.IsEmpty())
1393 return false;
1394 if(wcsncmp(resultRef,L"refs/",5)==0)
1395 resultRef = resultRef.Mid(5);
1396 // if(wcsncmp(resultRef,L"heads/",6)==0)
1397 // resultRef = resultRef.Mid(6);
1399 //Find closest match of choice in combobox
1400 int ixFound = -1;
1401 int matchLength = 0;
1402 CString comboRefName;
1403 for(int i = 0; i < pComboBox->GetCount(); ++i)
1405 pComboBox->GetLBText(i, comboRefName);
1406 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1407 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1408 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1410 matchLength = comboRefName.GetLength();
1411 ixFound = i;
1414 if(ixFound >= 0)
1415 pComboBox->SetCurSel(ixFound);
1416 else
1417 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)
1419 return true;
1422 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1424 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1425 *pResult = FALSE;
1427 if(pDispInfo->item.pszText == NULL)
1428 return; //User canceled changing
1430 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1432 if(!pTree->IsFrom(L"refs/heads/"))
1434 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1435 return;
1438 CString origName = pTree->GetRefName().Mid(11);
1440 CString newName;
1441 if(m_pListCtrlRoot != NULL)
1442 newName = m_pListCtrlRoot->GetRefName() + L'/';
1443 newName += pDispInfo->item.pszText;
1445 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1447 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1448 return;
1451 CString newNameTrunced = newName.Mid(11);
1453 CString errorMsg;
1454 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1456 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1457 return;
1459 //Do as if it failed to rename. Let Refresh() do the job.
1460 //*pResult = TRUE;
1462 Refresh(newName);
1464 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1466 // AfxMessageBox(W_csPopup);
1470 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1472 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1473 *pResult = FALSE;
1475 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1477 if(!pTree->IsFrom(L"refs/heads/"))
1479 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1480 return;
1484 void CBrowseRefsDlg::OnEnChangeEditFilter()
1486 SetTimer(IDT_FILTER, 1000, NULL);
1489 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1491 if (nIDEvent == IDT_FILTER)
1493 KillTimer(IDT_FILTER);
1494 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1497 CResizableStandAloneDialog::OnTimer(nIDEvent);
1500 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1502 // FIXME: x64 version would get this function called with unexpected parameters.
1503 if (!lParam)
1504 return 0;
1506 RECT * rect = (LPRECT)lParam;
1507 CPoint point;
1508 CString temp;
1509 point = CPoint(rect->left, rect->bottom);
1510 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1511 CMenu popup;
1512 if (popup.CreatePopupMenu())
1514 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1515 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1517 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1518 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1520 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1521 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1523 temp.LoadString(IDS_LOG_FILTER_REVS);
1524 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1526 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1527 if (selection != 0)
1529 m_SelectedFilters ^= selection;
1530 SetFilterCueText();
1531 SetTimer(IDT_FILTER, 1000, NULL);
1534 return 0L;
1537 void CBrowseRefsDlg::SetFilterCueText()
1539 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1540 temp += _T(" ");
1542 if (m_SelectedFilters & LOGFILTER_REFNAME)
1543 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1545 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1547 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1548 temp += _T(", ");
1549 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1552 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1554 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1555 temp += _T(", ");
1556 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1559 if (m_SelectedFilters & LOGFILTER_REVS)
1561 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1562 temp += _T(", ");
1563 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1566 // to make the cue banner text appear more to the right of the edit control
1567 temp = _T(" ") + temp;
1568 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1571 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1573 m_pickedRef = g_Git.GetCurrentBranch(true);
1574 m_bPickedRefSet = true;
1575 OnOK();
1578 void CBrowseRefsDlg::UpdateInfoLabel()
1580 CString temp;
1581 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1582 SetDlgItemText(IDC_INFOLABEL, temp);