Statistics graphs do not show up any more
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobfee6ea78da17f9dea33b00ed61c5a93f5c7595ba
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 if(!selectRef.IsEmpty())
415 if(selectRef == "HEAD")
417 if (g_Git.GetCurrentBranchFromFile(g_Git.m_CurrentDir, selectRef))
418 selectRef.Empty();
419 else
420 selectRef = L"refs/heads/" + selectRef;
423 else
425 selectRef = GetSelectedRef(false, true);
428 m_RefTreeCtrl.DeleteAllItems();
429 m_ListRefLeafs.DeleteAllItems();
430 m_TreeRoot.m_ShadowTree.clear();
431 m_TreeRoot.m_csRefName = "refs";
432 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"refs",NULL,NULL);
433 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
435 CString allRefs, error;
436 if (g_Git.Run(L"git.exe for-each-ref --format="
437 L"%(refname)%04"
438 L"%(objectname)%04"
439 L"%(upstream)%04"
440 L"%(authordate:relative)%04"
441 L"%(subject)%04"
442 L"%(authorname)%04"
443 L"%(authordate:iso8601)%03",
444 &allRefs, &error, CP_UTF8))
446 CMessageBox::Show(NULL, CString(_T("Get refs failed\n")) + error, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
449 int linePos=0;
450 CString singleRef;
452 MAP_STRING_STRING refMap;
454 //First sort on ref name
455 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
457 singleRef.TrimLeft(L"\r\n");
458 int valuePos=0;
459 CString refName=singleRef.Tokenize(L"\04",valuePos);
460 if(refName.IsEmpty())
461 continue;
462 CString refRest=singleRef.Mid(valuePos);
465 //Use ref based on m_pickRef_Kind
466 if (wcsncmp(refName, L"refs/heads/", 11) == 0 && !(m_pickRef_Kind & gPickRef_Head))
467 continue; //Skip
468 if (wcsncmp(refName, L"refs/tags/", 10) == 0 && !(m_pickRef_Kind & gPickRef_Tag))
469 continue; //Skip
470 if (wcsncmp(refName, L"refs/remotes/", 13) == 0 && !(m_pickRef_Kind & gPickRef_Remote))
471 continue; //Skip
473 refMap[refName] = refRest; //Use
476 MAP_STRING_STRING descriptions = GetBranchDescriptions();
478 //Populate ref tree
479 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
481 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
482 CString values=iterRefMap->second;
483 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
485 int valuePos=0;
486 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
487 treeLeaf.m_csUpstream = values.Tokenize(L"\04", valuePos); if (valuePos < 0) continue;
488 CGit::GetShortName(treeLeaf.m_csUpstream, treeLeaf.m_csUpstream, L"refs/remotes/");
489 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
490 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
491 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
492 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
494 if (wcsncmp(iterRefMap->first, L"refs/heads/", 11) == 0)
495 treeLeaf.m_csDescription = descriptions[treeLeaf.m_csRefName];
498 // always expand the tree first
499 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree, TVE_EXPAND);
501 // try exact match first
502 if (!selectRef.IsEmpty() && !SelectRef(selectRef, true))
503 SelectRef(selectRef, false);
506 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
508 if(!bExactMatch)
510 CString newRefName = GetFullRefName(refName);
511 if(!newRefName.IsEmpty())
512 refName = newRefName;
513 //else refName is not a valid ref. Try to select as good as possible.
515 if(_wcsnicmp(refName, L"refs/", 5) != 0)
516 return false; // Not a ref name
518 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
519 if(treeLeafHead.m_hTree != NULL)
521 //Not a leaf. Select tree node and return
522 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
523 return true;
526 if(treeLeafHead.m_pParent==NULL)
527 return false; //Weird... should not occur.
529 //This is the current head.
530 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
532 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
534 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
535 if(pCurrShadowTree == &treeLeafHead)
537 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
538 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
542 return true;
545 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
547 if(pTreePos==NULL)
549 if(_wcsnicmp(refName, L"refs/", 5) == 0)
550 refName=refName.Mid(5);
551 pTreePos=&m_TreeRoot;
553 if(refName.IsEmpty())
554 return *pTreePos;//Found leaf
556 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
557 if(pNextTree==NULL)
559 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
560 ASSERT(!bCreateIfNotExist);
561 return *pTreePos;
564 if(!refName.IsEmpty())
566 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
567 //Leafs are for the list control.
568 if(pNextTree->m_hTree==NULL)
570 //New tree. Create node in control.
571 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
572 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
576 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
580 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
582 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
583 *pResult = 0;
585 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
588 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
590 m_ListRefLeafs.DeleteAllItems();
592 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
593 if(pTree==NULL)
595 ASSERT(FALSE);
596 return;
598 FillListCtrlForShadowTree(pTree,L"",true);
599 m_ColumnManager.SetVisible(eCol_Upstream, (wcsncmp(pTree->GetRefName(), L"refs/heads", 11) == 0));
602 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
604 if(pTree->IsLeaf())
606 CString filter;
607 m_ctrlFilter.GetWindowText(filter);
608 filter.MakeLower();
609 CString ref = refNamePrefix + pTree->m_csRefName;
610 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter))
612 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
614 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
615 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
616 m_ListRefLeafs.SetItemText(indexItem, eCol_Upstream, pTree->m_csUpstream);
617 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
618 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
619 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
620 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
621 int pos = 0;
622 m_ListRefLeafs.SetItemText(indexItem,eCol_Description, pTree->m_csDescription.Tokenize(_T("\n"), pos));
625 else
628 CString csThisName;
629 if(!isFirstLevel)
630 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
631 else
632 m_pListCtrlRoot = pTree;
633 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
635 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
638 if (isFirstLevel)
640 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
641 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
643 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
647 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter)
649 if (m_SelectedFilters & LOGFILTER_REFNAME)
651 CString msg = ref;
652 msg = msg.MakeLower();
654 if (msg.Find(filter) >= 0)
655 return true;
658 if (m_SelectedFilters & LOGFILTER_SUBJECT)
660 CString msg = pTree->m_csSubject;
661 msg = msg.MakeLower();
663 if (msg.Find(filter) >= 0)
664 return true;
667 if (m_SelectedFilters & LOGFILTER_AUTHORS)
669 CString msg = pTree->m_csAuthor;
670 msg = msg.MakeLower();
672 if (msg.Find(filter) >= 0)
673 return true;
676 if (m_SelectedFilters & LOGFILTER_REVS)
678 CString msg = pTree->m_csRefHash;
679 msg = msg.MakeLower();
681 if (msg.Find(filter) >= 0)
682 return true;
684 return false;
687 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
689 ASSERT(!leafs.empty());
691 CString csMessage;
692 UINT mbIcon=MB_ICONQUESTION;
694 bool bIsRemoteBranch = false;
695 bool bIsBranch = false;
696 if (leafs[0]->IsFrom(L"refs/remotes/")) {bIsBranch = true; bIsRemoteBranch = true;}
697 else if (leafs[0]->IsFrom(L"refs/heads/")) {bIsBranch = true;}
699 if(bIsBranch)
701 if(leafs.size() == 1)
703 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
704 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
706 //Check if branch is fully merged in HEAD
707 if (!g_Git.IsFastForward(leafs[0]->GetRefName(), _T("HEAD")))
709 csMessage += L"\r\n\r\n";
710 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
711 mbIcon = MB_ICONWARNING;
714 if(bIsRemoteBranch)
716 csMessage += L"\r\n\r\n";
717 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
718 mbIcon = MB_ICONWARNING;
721 else
723 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
725 csMessage += L"\r\n\r\n";
726 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
727 mbIcon = MB_ICONWARNING;
729 if(bIsRemoteBranch)
731 csMessage += L"\r\n\r\n";
732 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
733 mbIcon = MB_ICONWARNING;
738 else if(leafs[0]->IsFrom(L"refs/tags/"))
740 if(leafs.size() == 1)
742 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
743 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
745 else
747 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
751 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
755 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs)
757 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
758 if(!DoDeleteRef((*i)->GetRefName()))
759 return false;
760 return true;
763 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName)
765 bool bIsRemoteBranch = false;
766 bool bIsBranch = false;
767 if (wcsncmp(completeRefName, L"refs/remotes/",13) == 0) {bIsBranch = true; bIsRemoteBranch = true;}
768 else if (wcsncmp(completeRefName, L"refs/heads/",11) == 0) {bIsBranch = true;}
770 if (bIsRemoteBranch)
772 CString branchToDelete = completeRefName.Mid(13);
773 CString cmd;
774 CString remoteName, remoteBranchToDelete;
775 if (SplitRemoteBranchName(branchToDelete, remoteName, remoteBranchToDelete))
776 return false;
778 if (CAppUtils::IsSSHPutty())
779 CAppUtils::LaunchPAgent(NULL, &remoteName);
781 cmd.Format(L"git.exe push \"%s\" :refs/heads/%s", remoteName, remoteBranchToDelete);
783 CSysProgressDlg sysProgressDlg;
784 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
785 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
786 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
787 sysProgressDlg.SetShowProgressBar(false);
788 sysProgressDlg.ShowModal(this, true);
790 CString errorMsg;
791 if (g_Git.Run(cmd, &errorMsg, CP_UTF8) != 0)
793 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
794 sysProgressDlg.Stop();
795 BringWindowToTop();
796 return false;
798 sysProgressDlg.Stop();
799 BringWindowToTop();
801 else if (bIsBranch)
803 if (g_Git.DeleteRef(completeRefName))
805 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
806 return false;
809 else if (wcsncmp(completeRefName, L"refs/tags/", 10) == 0)
811 if (g_Git.DeleteRef(completeRefName))
813 CMessageBox::Show(m_hWnd, g_Git.GetGitLastErr(L"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
814 return false;
817 return true;
820 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
822 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
823 if(pLeaf == NULL)
824 return CString();
825 return pLeaf->GetRefName();
829 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
831 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
832 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
835 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
837 CPoint clientPoint=point;
838 m_RefTreeCtrl.ScreenToClient(&clientPoint);
840 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
841 if(hTreeItem!=NULL)
842 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
844 VectorPShadowTree tree;
845 ShowContextMenu(point,hTreeItem,tree);
848 void CBrowseRefsDlg::GetSelectedLeaves(VectorPShadowTree& selectedLeafs)
850 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
851 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
852 while (pos)
854 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(m_ListRefLeafs.GetNextSelectedItem(pos)));
858 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
860 std::vector<CShadowTree*> selectedLeafs;
861 GetSelectedLeaves(selectedLeafs);
862 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
865 CString CBrowseRefsDlg::GetTwoSelectedRefs(VectorPShadowTree& selectedLeafs, const CString &lastSelected, const CString &separator)
867 ASSERT(selectedLeafs.size() == 2);
869 if (selectedLeafs.at(0)->GetRefName() == lastSelected)
870 return g_Git.StripRefName(selectedLeafs.at(1)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
871 else
872 return g_Git.StripRefName(selectedLeafs.at(0)->GetRefName()) + separator + g_Git.StripRefName(lastSelected);
875 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
877 CIconMenu popupMenu;
878 popupMenu.CreatePopupMenu();
880 bool bAddSeparator = false;
881 CString remoteName;
883 if(selectedLeafs.size()==1)
885 bAddSeparator = true;
887 bool bShowReflogOption = false;
888 bool bShowFetchOption = false;
889 bool bShowRenameOption = false;
890 bool bShowCreateBranchOption = false;
891 bool bShowEditBranchDescriptionOption = false;
893 CString fetchFromCmd;
895 if(selectedLeafs[0]->IsFrom(L"refs/heads/"))
897 bShowReflogOption = true;
898 bShowRenameOption = true;
899 bShowEditBranchDescriptionOption = true;
901 else if(selectedLeafs[0]->IsFrom(L"refs/remotes/"))
903 bShowReflogOption = true;
904 bShowFetchOption = true;
905 bShowCreateBranchOption = true;
907 CString remoteBranch;
908 if (SplitRemoteBranchName(selectedLeafs[0]->GetRefName(), remoteName, remoteBranch))
909 bShowFetchOption = false;
910 else
911 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
913 else if(selectedLeafs[0]->IsFrom(L"refs/tags/"))
917 CString temp;
918 temp.LoadString(IDS_MENULOG);
919 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
920 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
921 if(bShowReflogOption)
923 temp.LoadString(IDS_MENUREFLOG);
924 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
927 popupMenu.AppendMenu(MF_SEPARATOR);
928 bAddSeparator = false;
930 if(bShowFetchOption)
932 bAddSeparator = true;
933 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
936 if(bAddSeparator)
937 popupMenu.AppendMenu(MF_SEPARATOR);
939 bAddSeparator = false;
940 if (m_bHasWC)
942 CString format, str;
943 if (selectedLeafs[0]->GetRefName() != _T("refs/heads/") + g_Git.GetCurrentBranch())
945 format.LoadString(IDS_LOG_POPUP_MERGEREV);
946 str.Format(format, g_Git.GetCurrentBranch());
947 popupMenu.AppendMenuIcon(eCmd_Merge, str, IDI_MERGE);
949 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
950 popupMenu.AppendMenu(MF_SEPARATOR);
953 if(bShowCreateBranchOption)
955 bAddSeparator = true;
956 temp.LoadString(IDS_MENUBRANCH);
957 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
960 if (bShowEditBranchDescriptionOption)
962 bAddSeparator = true;
963 popupMenu.AppendMenuIcon(eCmd_EditBranchDescription, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION)), IDI_RENAME);
965 if(bShowRenameOption)
967 bAddSeparator = true;
968 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
971 else if(selectedLeafs.size() == 2)
973 bAddSeparator = true;
974 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
975 popupMenu.AppendMenuIcon(eCmd_UnifiedDiff, CString(MAKEINTRESOURCE(IDS_LOG_POPUP_GNUDIFF)), IDI_DIFF);
976 CString menu;
977 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
978 popupMenu.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
979 menu.Format(IDS_SHOWLOG_OF, GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
980 popupMenu.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
983 if(!selectedLeafs.empty())
985 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
987 if(bAddSeparator)
988 popupMenu.AppendMenu(MF_SEPARATOR);
989 CString menuItemName;
990 if(selectedLeafs.size() == 1)
991 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
992 else
993 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
995 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
996 bAddSeparator = true;
998 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
1000 if(bAddSeparator)
1001 popupMenu.AppendMenu(MF_SEPARATOR);
1002 CString menuItemName;
1003 if(selectedLeafs.size() == 1)
1004 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
1005 else
1006 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
1008 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
1009 bAddSeparator = true;
1011 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
1013 if(bAddSeparator)
1014 popupMenu.AppendMenu(MF_SEPARATOR);
1015 CString menuItemName;
1016 if(selectedLeafs.size() == 1)
1017 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
1018 else
1019 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
1021 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
1022 bAddSeparator = true;
1027 if(hTreePos!=NULL && selectedLeafs.empty())
1029 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
1030 if(pTree->IsFrom(L"refs/remotes"))
1032 if(bAddSeparator)
1033 popupMenu.AppendMenu(MF_SEPARATOR);
1034 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
1035 bAddSeparator = true;
1036 if(selectedLeafs.empty())
1038 CString remoteBranch;
1039 if (SplitRemoteBranchName(pTree->GetRefName(), remoteName, remoteBranch))
1040 remoteName = _T("");
1041 if(!remoteName.IsEmpty())
1043 CString temp;
1044 temp.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
1045 popupMenu.AppendMenuIcon(eCmd_Fetch, temp, IDI_PULL);
1047 temp.LoadString(IDS_DELETEREMOTETAG);
1048 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteTag, temp, IDI_DELETE);
1052 if(pTree->IsFrom(L"refs/heads"))
1054 if(bAddSeparator)
1055 popupMenu.AppendMenu(MF_SEPARATOR);
1056 CString temp;
1057 temp.LoadString(IDS_MENUBRANCH);
1058 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
1060 if(pTree->IsFrom(L"refs/tags"))
1062 if(bAddSeparator)
1063 popupMenu.AppendMenu(MF_SEPARATOR);
1064 CString temp;
1065 temp.LoadString(IDS_MENUTAG);
1066 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
1067 temp.LoadString(IDS_PROC_BROWSEREFS_DELETEALLTAGS);
1068 popupMenu.AppendMenuIcon(eCmd_DeleteAllTags, temp, IDI_DELETE);
1073 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
1074 switch(cmd)
1076 case eCmd_ViewLog:
1078 CLogDlg dlg;
1079 dlg.SetRange(g_Git.FixBranchName(selectedLeafs[0]->GetRefName()));
1080 dlg.DoModal();
1082 break;
1083 case eCmd_ViewLogRange:
1085 CLogDlg dlg;
1086 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("..")));
1087 dlg.DoModal();
1089 break;
1090 case eCmd_ViewLogRangeReachableFromOnlyOne:
1092 CLogDlg dlg;
1093 dlg.SetRange(GetTwoSelectedRefs(selectedLeafs, m_sLastSelected, _T("...")));
1094 dlg.DoModal();
1096 break;
1097 case eCmd_RepoBrowser:
1098 CAppUtils::RunTortoiseGitProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
1099 break;
1100 case eCmd_DeleteBranch:
1101 case eCmd_DeleteRemoteBranch:
1103 if(ConfirmDeleteRef(selectedLeafs))
1104 DoDeleteRefs(selectedLeafs);
1105 Refresh();
1107 break;
1108 case eCmd_DeleteTag:
1110 if(ConfirmDeleteRef(selectedLeafs))
1111 DoDeleteRefs(selectedLeafs);
1112 Refresh();
1114 break;
1115 case eCmd_ShowReflog:
1117 CRefLogDlg refLogDlg(this);
1118 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
1119 refLogDlg.DoModal();
1121 break;
1122 case eCmd_Fetch:
1124 CAppUtils::Fetch(remoteName);
1125 Refresh();
1127 break;
1128 case eCmd_DeleteRemoteTag:
1130 CDeleteRemoteTagDlg deleteRemoteTagDlg;
1131 deleteRemoteTagDlg.m_sRemote = remoteName;
1132 deleteRemoteTagDlg.DoModal();
1134 break;
1135 case eCmd_Merge:
1137 CString ref = selectedLeafs[0]->GetRefName();
1138 CAppUtils::Merge(&ref);
1140 break;
1141 case eCmd_Switch:
1143 CAppUtils::Switch(selectedLeafs[0]->GetRefName());
1145 break;
1146 case eCmd_Rename:
1148 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1149 if(pos != NULL)
1150 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1152 break;
1153 case eCmd_AddRemote:
1155 CAddRemoteDlg(this).DoModal();
1156 Refresh();
1158 break;
1159 case eCmd_ManageRemotes:
1161 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
1162 // CSettingGitRemote W_Remotes(m_cmdPath);
1163 // W_Remotes.DoModal();
1164 Refresh();
1166 break;
1167 case eCmd_CreateBranch:
1169 CString *commitHash = NULL;
1170 if (selectedLeafs.size() == 1)
1171 commitHash = &(selectedLeafs[0]->m_csRefHash);
1172 CAppUtils::CreateBranchTag(false, commitHash);
1173 Refresh();
1175 break;
1176 case eCmd_CreateTag:
1178 CAppUtils::CreateBranchTag(true);
1179 Refresh();
1181 break;
1182 case eCmd_DeleteAllTags:
1184 for (int i = 0; i < m_ListRefLeafs.GetItemCount(); ++i)
1186 m_ListRefLeafs.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1187 selectedLeafs.push_back((CShadowTree*)m_ListRefLeafs.GetItemData(i));
1189 if (ConfirmDeleteRef(selectedLeafs))
1190 DoDeleteRefs(selectedLeafs);
1191 Refresh();
1193 break;
1194 case eCmd_Diff:
1196 CFileDiffDlg dlg;
1197 dlg.SetDiff(
1198 NULL,
1199 selectedLeafs[1]->GetRefName() + L"^{}",
1200 selectedLeafs[0]->GetRefName() + L"^{}");
1201 dlg.DoModal();
1203 break;
1204 case eCmd_UnifiedDiff:
1206 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), selectedLeafs[0]->m_csRefHash, CTGitPath(), selectedLeafs[1]->m_csRefHash);
1208 break;
1209 case eCmd_EditBranchDescription:
1211 CInputDlg dlg;
1212 dlg.m_sHintText = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1213 dlg.m_sInputText = selectedLeafs[0]->m_csDescription;
1214 dlg.m_sTitle = CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_EDITDESCRIPTION));
1215 dlg.m_bUseLogWidth = true;
1216 if(dlg.DoModal() == IDOK)
1218 CString key;
1219 key.Format(_T("branch.%s.description"), selectedLeafs[0]->m_csRefName);
1220 dlg.m_sInputText.Replace(_T("\r"), _T(""));
1221 dlg.m_sInputText.Trim();
1222 if (dlg.m_sInputText.IsEmpty())
1223 g_Git.UnsetConfigValue(key);
1224 else
1225 g_Git.SetConfigValue(key, dlg.m_sInputText);
1226 Refresh();
1229 break;
1233 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
1235 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
1236 if(!(*i)->IsFrom(from))
1237 return false;
1238 return true;
1241 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
1243 if (pMsg->message == WM_KEYDOWN)
1245 switch (pMsg->wParam)
1247 /* case VK_RETURN:
1249 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
1251 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
1253 PostMessage(WM_COMMAND, IDOK);
1255 return TRUE;
1258 break;
1259 */ case VK_F2:
1261 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
1263 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
1264 if(pos != NULL)
1265 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
1268 break;
1270 case VK_F5:
1272 Refresh();
1274 break;
1279 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
1282 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1284 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1285 *pResult = 0;
1287 if(m_currSortCol == pNMLV->iSubItem)
1288 m_currSortDesc = !m_currSortDesc;
1289 else
1291 m_currSortCol = pNMLV->iSubItem;
1292 m_currSortDesc = false;
1295 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1296 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1298 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1301 void CBrowseRefsDlg::OnDestroy()
1303 if (!m_bPickedRefSet)
1304 m_pickedRef = GetSelectedRef(true, false);
1306 int maxcol = m_ColumnManager.GetColumnCount();
1307 for (int col = 0; col < maxcol; ++col)
1308 if (m_ColumnManager.IsVisible(col))
1309 m_ColumnManager.ColumnResized(col);
1310 m_ColumnManager.WriteSettings();
1312 CResizableStandAloneDialog::OnDestroy();
1315 void CBrowseRefsDlg::OnItemChangedListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1317 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1318 *pResult = 0;
1320 CShadowTree *item = (CShadowTree*)m_ListRefLeafs.GetItemData(pNMListView->iItem);
1321 if (item && pNMListView->uNewState == LVIS_SELECTED)
1322 m_sLastSelected = item->GetRefName();
1324 UpdateInfoLabel();
1327 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR * /*pNMHDR*/, LRESULT *pResult)
1329 *pResult = 0;
1331 if (!m_ListRefLeafs.GetFirstSelectedItemPosition())
1332 return;
1333 EndDialog(IDOK);
1336 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind, bool pickMultipleRefsOrRange)
1338 CBrowseRefsDlg dlg(CString(),NULL);
1340 if(initialRef.IsEmpty())
1341 initialRef = L"HEAD";
1342 dlg.m_initialRef = initialRef;
1343 dlg.m_pickRef_Kind = pickRef_Kind;
1344 dlg.m_bPickOne = !pickMultipleRefsOrRange;
1346 if(dlg.DoModal() != IDOK)
1347 return CString();
1349 return dlg.m_pickedRef;
1352 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1354 CString origRef;
1355 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1356 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1357 if(resultRef.IsEmpty())
1358 return false;
1359 if(wcsncmp(resultRef,L"refs/",5)==0)
1360 resultRef = resultRef.Mid(5);
1361 // if(wcsncmp(resultRef,L"heads/",6)==0)
1362 // resultRef = resultRef.Mid(6);
1364 //Find closest match of choice in combobox
1365 int ixFound = -1;
1366 int matchLength = 0;
1367 CString comboRefName;
1368 for(int i = 0; i < pComboBox->GetCount(); ++i)
1370 pComboBox->GetLBText(i, comboRefName);
1371 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1372 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1373 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1375 matchLength = comboRefName.GetLength();
1376 ixFound = i;
1379 if(ixFound >= 0)
1380 pComboBox->SetCurSel(ixFound);
1381 else
1382 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)
1384 return true;
1387 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1389 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1390 *pResult = FALSE;
1392 if(pDispInfo->item.pszText == NULL)
1393 return; //User canceled changing
1395 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1397 if(!pTree->IsFrom(L"refs/heads/"))
1399 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1400 return;
1403 CString origName = pTree->GetRefName().Mid(11);
1405 CString newName;
1406 if(m_pListCtrlRoot != NULL)
1407 newName = m_pListCtrlRoot->GetRefName() + L'/';
1408 newName += pDispInfo->item.pszText;
1410 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1412 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1413 return;
1416 CString newNameTrunced = newName.Mid(11);
1418 CString errorMsg;
1419 if(g_Git.Run(L"git.exe branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1421 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1422 return;
1424 //Do as if it failed to rename. Let Refresh() do the job.
1425 //*pResult = TRUE;
1427 Refresh(newName);
1429 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1431 // AfxMessageBox(W_csPopup);
1435 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1437 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1438 *pResult = FALSE;
1440 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1442 if(!pTree->IsFrom(L"refs/heads/"))
1444 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1445 return;
1449 void CBrowseRefsDlg::OnEnChangeEditFilter()
1451 SetTimer(IDT_FILTER, 1000, NULL);
1454 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1456 if (nIDEvent == IDT_FILTER)
1458 KillTimer(IDT_FILTER);
1459 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1462 CResizableStandAloneDialog::OnTimer(nIDEvent);
1465 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1467 // FIXME: x64 version would get this function called with unexpected parameters.
1468 if (!lParam)
1469 return 0;
1471 RECT * rect = (LPRECT)lParam;
1472 CPoint point;
1473 CString temp;
1474 point = CPoint(rect->left, rect->bottom);
1475 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1476 CMenu popup;
1477 if (popup.CreatePopupMenu())
1479 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1480 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1482 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1483 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1485 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1486 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1488 temp.LoadString(IDS_LOG_FILTER_REVS);
1489 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1491 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1492 if (selection != 0)
1494 m_SelectedFilters ^= selection;
1495 SetFilterCueText();
1496 SetTimer(IDT_FILTER, 1000, NULL);
1499 return 0L;
1502 void CBrowseRefsDlg::SetFilterCueText()
1504 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1505 temp += _T(" ");
1507 if (m_SelectedFilters & LOGFILTER_REFNAME)
1508 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1510 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1512 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1513 temp += _T(", ");
1514 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1517 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1519 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1520 temp += _T(", ");
1521 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1524 if (m_SelectedFilters & LOGFILTER_REVS)
1526 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1527 temp += _T(", ");
1528 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1531 // to make the cue banner text appear more to the right of the edit control
1532 temp = _T(" ") + temp;
1533 m_ctrlFilter.SetCueBanner(temp.TrimRight());
1536 void CBrowseRefsDlg::OnBnClickedCurrentbranch()
1538 m_pickedRef = g_Git.GetCurrentBranch(true);
1539 m_bPickedRefSet = true;
1540 OnOK();
1543 void CBrowseRefsDlg::UpdateInfoLabel()
1545 CString temp;
1546 temp.FormatMessage(IDS_REFBROWSE_INFO, m_ListRefLeafs.GetItemCount(), m_ListRefLeafs.GetSelectedCount());
1547 SetDlgItemText(IDC_INFOLABEL, temp);