1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2012 - 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
23 #include "TortoiseProc.h"
24 #include "BrowseRefsDlg.h"
26 #include "AddRemoteDlg.h"
28 #include "Settings\SettingGitRemote.h"
29 #include "SinglePropSheetDlg.h"
30 #include "MessageBox.h"
31 #include "RefLogDlg.h"
33 #include "FileDiffDlg.h"
35 void SetSortArrow(CListCtrl
* control
, int nColumn
, bool bAscending
)
40 CHeaderCtrl
* pHeader
= control
->GetHeaderCtrl();
41 HDITEM HeaderItem
= {0};
42 HeaderItem
.mask
= HDI_FORMAT
;
43 for (int i
=0; i
<pHeader
->GetItemCount(); ++i
)
45 pHeader
->GetItem(i
, &HeaderItem
);
46 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
47 pHeader
->SetItem(i
, &HeaderItem
);
51 pHeader
->GetItem(nColumn
, &HeaderItem
);
52 HeaderItem
.fmt
|= (bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
53 pHeader
->SetItem(nColumn
, &HeaderItem
);
57 // CBrowseRefsDlg dialog
59 IMPLEMENT_DYNAMIC(CBrowseRefsDlg
, CResizableStandAloneDialog
)
61 CBrowseRefsDlg::CBrowseRefsDlg(CString cmdPath
, CWnd
* pParent
/*=NULL*/)
62 : CResizableStandAloneDialog(CBrowseRefsDlg::IDD
, pParent
),
65 m_currSortDesc(false),
66 m_initialRef(L
"HEAD"),
67 m_pickRef_Kind(gPickRef_All
),
68 m_pListCtrlRoot(NULL
),
70 m_SelectedFilters(LOGFILTER_ALL
)
75 CBrowseRefsDlg::~CBrowseRefsDlg()
79 void CBrowseRefsDlg::DoDataExchange(CDataExchange
* pDX
)
81 CDialog::DoDataExchange(pDX
);
82 DDX_Control(pDX
, IDC_TREE_REF
, m_RefTreeCtrl
);
83 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_ListRefLeafs
);
84 DDX_Control(pDX
, IDC_BROWSEREFS_EDIT_FILTER
, m_ctrlFilter
);
88 BEGIN_MESSAGE_MAP(CBrowseRefsDlg
, CResizableStandAloneDialog
)
89 ON_BN_CLICKED(IDOK
, &CBrowseRefsDlg::OnBnClickedOk
)
90 ON_NOTIFY(TVN_SELCHANGED
, IDC_TREE_REF
, &CBrowseRefsDlg::OnTvnSelchangedTreeRef
)
92 ON_NOTIFY(LVN_COLUMNCLICK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs
)
94 ON_NOTIFY(NM_DBLCLK
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnNMDblclkListRefLeafs
)
95 ON_NOTIFY(LVN_ENDLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs
)
96 ON_NOTIFY(LVN_BEGINLABELEDIT
, IDC_LIST_REF_LEAFS
, &CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs
)
97 ON_EN_CHANGE(IDC_BROWSEREFS_EDIT_FILTER
, &CBrowseRefsDlg::OnEnChangeEditFilter
)
98 ON_MESSAGE(WM_FILTEREDIT_INFOCLICKED
, OnClickedInfoIcon
)
103 // CBrowseRefsDlg message handlers
105 void CBrowseRefsDlg::OnBnClickedOk()
110 BOOL
CBrowseRefsDlg::OnInitDialog()
112 CResizableStandAloneDialog::OnInitDialog();
113 CAppUtils::MarkWindowAsUnpinnable(m_hWnd
);
115 // the filter control has a 'cancel' button (the red 'X'), we need to load its bitmap
116 m_ctrlFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
);
117 m_ctrlFilter
.SetInfoIcon(IDI_FILTEREDIT
);
120 AddAnchor(IDC_TREE_REF
, TOP_LEFT
, BOTTOM_LEFT
);
121 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
122 AddAnchor(IDC_BROWSEREFS_STATIC_FILTER
, BOTTOM_LEFT
);
123 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
124 AddAnchor(IDHELP
, BOTTOM_RIGHT
);
126 m_ListRefLeafs
.SetExtendedStyle(m_ListRefLeafs
.GetExtendedStyle()|LVS_EX_FULLROWSELECT
);
128 temp
.LoadString(IDS_BRANCHNAME
);
129 m_ListRefLeafs
.InsertColumn(eCol_Name
, temp
, 0, 150);
130 temp
.LoadString(IDS_DATELASTCOMMIT
);
131 m_ListRefLeafs
.InsertColumn(eCol_Date
, temp
, 0, 100);
132 temp
.LoadString(IDS_LASTCOMMIT
);
133 m_ListRefLeafs
.InsertColumn(eCol_Msg
, temp
, 0, 300);
134 temp
.LoadString(IDS_HASH
);
135 m_ListRefLeafs
.InsertColumn(eCol_Hash
, temp
, 0, 80);
137 AddAnchor(IDOK
,BOTTOM_RIGHT
);
138 AddAnchor(IDCANCEL
,BOTTOM_RIGHT
);
140 Refresh(m_initialRef
);
142 EnableSaveRestore(L
"BrowseRefs");
144 CString sWindowTitle
;
145 GetWindowText(sWindowTitle
);
146 CAppUtils::SetWindowTitle(m_hWnd
, g_Git
.m_CurrentDir
, sWindowTitle
);
148 m_bHasWC
= !g_GitAdminDir
.IsBareRepo(g_Git
.m_CurrentDir
);
150 m_ListRefLeafs
.SetFocus();
154 CShadowTree
* CShadowTree::GetNextSub(CString
& nameLeft
, bool bCreateIfNotExist
)
156 int posSlash
=nameLeft
.Find('/');
161 nameLeft
.Empty();//Nothing left
165 nameSub
=nameLeft
.Left(posSlash
);
166 nameLeft
=nameLeft
.Mid(posSlash
+1);
168 if(nameSub
.IsEmpty())
171 if(!bCreateIfNotExist
&& m_ShadowTree
.find(nameSub
)==m_ShadowTree
.end())
174 CShadowTree
& nextNode
=m_ShadowTree
[nameSub
];
175 nextNode
.m_csRefName
=nameSub
;
176 nextNode
.m_pParent
=this;
180 CShadowTree
* CShadowTree::FindLeaf(CString partialRefName
)
184 if(m_csRefName
.GetLength() > partialRefName
.GetLength())
186 if(partialRefName
.Right(m_csRefName
.GetLength()) == m_csRefName
)
188 //Match of leaf name. Try match on total name.
189 CString totalRefName
= GetRefName();
190 if(totalRefName
.Right(partialRefName
.GetLength()) == partialRefName
)
191 return this; //Also match. Found.
196 //Not a leaf. Search all nodes.
197 for(TShadowTreeMap::iterator itShadowTree
= m_ShadowTree
.begin(); itShadowTree
!= m_ShadowTree
.end(); ++itShadowTree
)
199 CShadowTree
* pSubtree
= itShadowTree
->second
.FindLeaf(partialRefName
);
201 return pSubtree
; //Found
204 return NULL
;//Not found
208 typedef std::map
<CString
,CString
> MAP_STRING_STRING
;
210 CString
CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf
, bool pickFirstSelIfMultiSel
)
212 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
213 //List ctrl selection?
214 if(pos
&& (pickFirstSelIfMultiSel
|| m_ListRefLeafs
.GetSelectedCount() == 1))
217 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(
218 m_ListRefLeafs
.GetNextSelectedItem(pos
));
219 return pTree
->GetRefName();
223 //Tree ctrl selection?
224 HTREEITEM hTree
=m_RefTreeCtrl
.GetSelectedItem();
227 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTree
);
228 return pTree
->GetRefName();
231 return CString();//None
234 void CBrowseRefsDlg::Refresh(CString selectRef
)
237 // g_Git.GetMapHashToFriendName(m_RefMap);
239 if(!selectRef
.IsEmpty())
241 if(selectRef
== "HEAD")
243 selectRef
= g_Git
.GetSymbolicRef(selectRef
, false);
248 selectRef
= GetSelectedRef(false, true);
251 m_RefTreeCtrl
.DeleteAllItems();
252 m_ListRefLeafs
.DeleteAllItems();
253 m_TreeRoot
.m_ShadowTree
.clear();
254 m_TreeRoot
.m_csRefName
= "refs";
255 m_TreeRoot
.m_hTree
=m_RefTreeCtrl
.InsertItem(L
"Refs",NULL
,NULL
);
256 m_RefTreeCtrl
.SetItemData(m_TreeRoot
.m_hTree
,(DWORD_PTR
)&m_TreeRoot
);
259 g_Git
.Run(L
"git for-each-ref --format="
262 L
"%(authordate:relative)%04"
265 L
"%(authordate:iso8601)%03",
266 &allRefs
, NULL
, CP_UTF8
);
271 MAP_STRING_STRING refMap
;
273 //First sort on ref name
274 while(!(singleRef
=allRefs
.Tokenize(L
"\03",linePos
)).IsEmpty())
276 singleRef
.TrimLeft(L
"\r\n");
278 CString refName
=singleRef
.Tokenize(L
"\04",valuePos
);
279 if(refName
.IsEmpty())
281 CString refRest
=singleRef
.Mid(valuePos
);
284 //Use ref based on m_pickRef_Kind
285 if(wcsncmp(refName
,L
"refs/heads",10)==0 && !(m_pickRef_Kind
& gPickRef_Head
) )
287 if(wcsncmp(refName
,L
"refs/tags",9)==0 && !(m_pickRef_Kind
& gPickRef_Tag
) )
289 if(wcsncmp(refName
,L
"refs/remotes",12)==0 && !(m_pickRef_Kind
& gPickRef_Remote
) )
292 refMap
[refName
] = refRest
; //Use
298 for(MAP_STRING_STRING::iterator iterRefMap
=refMap
.begin();iterRefMap
!=refMap
.end();++iterRefMap
)
300 CShadowTree
& treeLeaf
=GetTreeNode(iterRefMap
->first
,NULL
,true);
301 CString values
=iterRefMap
->second
;
302 values
.Replace(L
"\04" L
"\04",L
"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
305 treeLeaf
.m_csRefHash
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
306 treeLeaf
.m_csDate
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
307 treeLeaf
.m_csSubject
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
308 treeLeaf
.m_csAuthor
= values
.Tokenize(L
"\04",valuePos
); if(valuePos
< 0) continue;
309 treeLeaf
.m_csDate_Iso8601
= values
.Tokenize(L
"\04",valuePos
);
313 if(selectRef
.IsEmpty() || !SelectRef(selectRef
, false))
314 //Probably not on a branch. Select root node.
315 m_RefTreeCtrl
.Expand(m_TreeRoot
.m_hTree
,TVE_EXPAND
);
319 bool CBrowseRefsDlg::SelectRef(CString refName
, bool bExactMatch
)
323 CString newRefName
= GetFullRefName(refName
);
324 if(!newRefName
.IsEmpty())
325 refName
= newRefName
;
326 //else refName is not a valid ref. Try to select as good as possible.
328 if(_wcsnicmp(refName
, L
"refs/", 5) != 0)
329 return false; // Not a ref name
331 CShadowTree
& treeLeafHead
=GetTreeNode(refName
,NULL
,false);
332 if(treeLeafHead
.m_hTree
!= NULL
)
334 //Not a leaf. Select tree node and return
335 m_RefTreeCtrl
.Select(treeLeafHead
.m_hTree
,TVGN_CARET
);
339 if(treeLeafHead
.m_pParent
==NULL
)
340 return false; //Weird... should not occur.
342 //This is the current head.
343 m_RefTreeCtrl
.Select(treeLeafHead
.m_pParent
->m_hTree
,TVGN_CARET
);
345 for(int indexPos
= 0; indexPos
< m_ListRefLeafs
.GetItemCount(); ++indexPos
)
347 CShadowTree
* pCurrShadowTree
= (CShadowTree
*)m_ListRefLeafs
.GetItemData(indexPos
);
348 if(pCurrShadowTree
== &treeLeafHead
)
350 m_ListRefLeafs
.SetItemState(indexPos
,LVIS_SELECTED
,LVIS_SELECTED
);
351 m_ListRefLeafs
.EnsureVisible(indexPos
,FALSE
);
358 CShadowTree
& CBrowseRefsDlg::GetTreeNode(CString refName
, CShadowTree
* pTreePos
, bool bCreateIfNotExist
)
362 if(_wcsnicmp(refName
, L
"refs/", 5) == 0)
363 refName
=refName
.Mid(5);
364 pTreePos
=&m_TreeRoot
;
366 if(refName
.IsEmpty())
367 return *pTreePos
;//Found leaf
369 CShadowTree
* pNextTree
=pTreePos
->GetNextSub(refName
,bCreateIfNotExist
);
372 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
373 ASSERT(!bCreateIfNotExist
);
377 if(!refName
.IsEmpty())
379 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
380 //Leafs are for the list control.
381 if(pNextTree
->m_hTree
==NULL
)
383 //New tree. Create node in control.
384 pNextTree
->m_hTree
=m_RefTreeCtrl
.InsertItem(pNextTree
->m_csRefName
,pTreePos
->m_hTree
,NULL
);
385 m_RefTreeCtrl
.SetItemData(pNextTree
->m_hTree
,(DWORD_PTR
)pNextTree
);
389 return GetTreeNode(refName
, pNextTree
, bCreateIfNotExist
);
393 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR
*pNMHDR
, LRESULT
*pResult
)
395 LPNMTREEVIEW pNMTreeView
= reinterpret_cast<LPNMTREEVIEW
>(pNMHDR
);
398 FillListCtrlForTreeNode(pNMTreeView
->itemNew
.hItem
);
401 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode
)
403 m_ListRefLeafs
.DeleteAllItems();
405 m_currSortDesc
= false;
406 SetSortArrow(&m_ListRefLeafs
,-1,false);
408 CShadowTree
* pTree
=(CShadowTree
*)(m_RefTreeCtrl
.GetItemData(treeNode
));
414 FillListCtrlForShadowTree(pTree
,L
"",true);
417 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree
* pTree
, CString refNamePrefix
, bool isFirstLevel
)
422 m_ctrlFilter
.GetWindowText(filter
);
424 CString ref
= refNamePrefix
+ pTree
->m_csRefName
;
425 if (!(pTree
->m_csRefName
.IsEmpty() || pTree
->m_csRefName
== "refs" && pTree
->m_pParent
== NULL
) && IsMatchFilter(pTree
, ref
, filter
))
427 int indexItem
= m_ListRefLeafs
.InsertItem(m_ListRefLeafs
.GetItemCount(), L
"");
429 m_ListRefLeafs
.SetItemData(indexItem
,(DWORD_PTR
)pTree
);
430 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Name
, ref
);
431 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Date
, pTree
->m_csDate
);
432 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Msg
, pTree
->m_csSubject
);
433 m_ListRefLeafs
.SetItemText(indexItem
,eCol_Hash
, pTree
->m_csRefHash
);
441 csThisName
=refNamePrefix
+pTree
->m_csRefName
+L
"/";
443 m_pListCtrlRoot
= pTree
;
444 for(CShadowTree::TShadowTreeMap::iterator itSubTree
=pTree
->m_ShadowTree
.begin(); itSubTree
!=pTree
->m_ShadowTree
.end(); ++itSubTree
)
446 FillListCtrlForShadowTree(&itSubTree
->second
,csThisName
,false);
451 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree
* pTree
, const CString
&ref
, const CString
&filter
)
453 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
456 msg
= msg
.MakeLower();
458 if (msg
.Find(filter
) >= 0)
462 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
464 CString msg
= pTree
->m_csSubject
;
465 msg
= msg
.MakeLower();
467 if (msg
.Find(filter
) >= 0)
471 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
473 CString msg
= pTree
->m_csAuthor
;
474 msg
= msg
.MakeLower();
476 if (msg
.Find(filter
) >= 0)
480 if (m_SelectedFilters
& LOGFILTER_REVS
)
482 CString msg
= pTree
->m_csRefHash
;
483 msg
= msg
.MakeLower();
485 if (msg
.Find(filter
) >= 0)
491 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree
& leafs
)
493 ASSERT(!leafs
.empty());
496 UINT mbIcon
=MB_ICONQUESTION
;
498 bool bIsRemoteBranch
= false;
499 bool bIsBranch
= false;
500 if (leafs
[0]->IsFrom(L
"refs/remotes")) {bIsBranch
= true; bIsRemoteBranch
= true;}
501 else if (leafs
[0]->IsFrom(L
"refs/heads")) {bIsBranch
= true;}
505 if(leafs
.size() == 1)
507 CString branchToDelete
= leafs
[0]->GetRefName().Mid(bIsRemoteBranch
? 13 : 11);
508 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, branchToDelete
);
510 //Check if branch is fully merged in HEAD
511 CGitHash branchHash
= g_Git
.GetHash(leafs
[0]->GetRefName());
512 CGitHash commonAncestor
;
513 CString commonAncestorstr
;
515 cmd
.Format(L
"git.exe merge-base HEAD %s", leafs
[0]->GetRefName());
516 g_Git
.Run(cmd
, &commonAncestorstr
, NULL
, CP_UTF8
);
518 commonAncestor
=commonAncestorstr
;
520 if(commonAncestor
!= branchHash
)
522 csMessage
+= L
"\r\n\r\n";
523 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED
));
524 mbIcon
= MB_ICONWARNING
;
529 csMessage
+= L
"\r\n\r\n";
530 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
531 mbIcon
= MB_ICONWARNING
;
536 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
538 csMessage
+= L
"\r\n\r\n";
539 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK
));
540 mbIcon
= MB_ICONWARNING
;
544 csMessage
+= L
"\r\n\r\n";
545 csMessage
+= CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES
));
546 mbIcon
= MB_ICONWARNING
;
551 else if(leafs
[0]->IsFrom(L
"refs/tags"))
553 if(leafs
.size() == 1)
555 CString tagToDelete
= leafs
[0]->GetRefName().Mid(10);
556 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, tagToDelete
);
560 csMessage
.Format(IDS_PROC_DELETENREFS
, leafs
.size());
564 return CMessageBox::Show(m_hWnd
, csMessage
, _T("TortoiseGit"), MB_YESNO
| mbIcon
) == IDYES
;
568 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree
& leafs
, bool bForce
)
570 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
571 if(!DoDeleteRef((*i
)->GetRefName(), bForce
))
576 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName
, bool bForce
)
578 bool bIsRemoteBranch
= false;
579 bool bIsBranch
= false;
580 if (wcsncmp(completeRefName
, L
"refs/remotes",12)==0) {bIsBranch
= true; bIsRemoteBranch
= true;}
581 else if (wcsncmp(completeRefName
, L
"refs/heads",10)==0) {bIsBranch
= true;}
585 CString branchToDelete
= completeRefName
.Mid(bIsRemoteBranch
? 13 : 11);
589 int slash
= branchToDelete
.Find(L
'/');
592 CString remoteName
= branchToDelete
.Left(slash
);
593 CString remoteBranchToDelete
= branchToDelete
.Mid(slash
+ 1);
595 if(CAppUtils::IsSSHPutty())
597 CAppUtils::LaunchPAgent(NULL
, &remoteName
);
600 cmd
.Format(L
"git.exe push \"%s\" :%s", remoteName
, remoteBranchToDelete
);
603 cmd
.Format(L
"git.exe branch -%c -- %s",bForce
?L
'D':L
'd',branchToDelete
);
605 if(g_Git
.Run(cmd
,&errorMsg
,CP_UTF8
)!=0)
607 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
611 else if(wcsncmp(completeRefName
,L
"refs/tags",9)==0)
613 CString tagToDelete
= completeRefName
.Mid(10);
615 cmd
.Format(L
"git.exe tag -d -- %s",tagToDelete
);
617 if(g_Git
.Run(cmd
,&errorMsg
,CP_UTF8
)!=0)
619 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
626 CString
CBrowseRefsDlg::GetFullRefName(CString partialRefName
)
628 CShadowTree
* pLeaf
= m_TreeRoot
.FindLeaf(partialRefName
);
631 return pLeaf
->GetRefName();
635 void CBrowseRefsDlg::OnContextMenu(CWnd
* pWndFrom
, CPoint point
)
637 if(pWndFrom
==&m_RefTreeCtrl
) OnContextMenu_RefTreeCtrl(point
);
638 else if(pWndFrom
==&m_ListRefLeafs
) OnContextMenu_ListRefLeafs(point
);
641 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point
)
643 CPoint clientPoint
=point
;
644 m_RefTreeCtrl
.ScreenToClient(&clientPoint
);
646 HTREEITEM hTreeItem
=m_RefTreeCtrl
.HitTest(clientPoint
);
648 m_RefTreeCtrl
.Select(hTreeItem
,TVGN_CARET
);
650 VectorPShadowTree tree
;
651 ShowContextMenu(point
,hTreeItem
,tree
);
655 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point
)
657 std::vector
<CShadowTree
*> selectedLeafs
;
658 selectedLeafs
.reserve(m_ListRefLeafs
.GetSelectedCount());
659 POSITION pos
=m_ListRefLeafs
.GetFirstSelectedItemPosition();
662 selectedLeafs
.push_back(
663 (CShadowTree
*)m_ListRefLeafs
.GetItemData(
664 m_ListRefLeafs
.GetNextSelectedItem(pos
)));
667 ShowContextMenu(point
,m_RefTreeCtrl
.GetSelectedItem(),selectedLeafs
);
670 void CBrowseRefsDlg::ShowContextMenu(CPoint point
, HTREEITEM hTreePos
, VectorPShadowTree
& selectedLeafs
)
673 popupMenu
.CreatePopupMenu();
675 bool bAddSeparator
= false;
678 if(selectedLeafs
.size()==1)
680 bAddSeparator
= true;
682 bool bShowReflogOption
= false;
683 bool bShowFetchOption
= false;
684 bool bShowSwitchOption
= false;
685 bool bShowRenameOption
= false;
686 bool bShowCreateBranchOption
= false;
688 CString fetchFromCmd
;
690 if(selectedLeafs
[0]->IsFrom(L
"refs/heads"))
692 bShowReflogOption
= true;
693 bShowSwitchOption
= true;
694 bShowRenameOption
= true;
696 else if(selectedLeafs
[0]->IsFrom(L
"refs/remotes"))
698 bShowReflogOption
= true;
699 bShowFetchOption
= true;
700 bShowCreateBranchOption
= true;
702 int dummy
= 0;//Needed for tokenize
703 remoteName
= selectedLeafs
[0]->GetRefName();
704 remoteName
= remoteName
.Mid(13);
705 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
706 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, remoteName
);
708 else if(selectedLeafs
[0]->IsFrom(L
"refs/tags"))
713 temp
.LoadString(IDS_MENULOG
);
714 popupMenu
.AppendMenuIcon(eCmd_ViewLog
, temp
, IDI_LOG
);
715 popupMenu
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
716 if(bShowReflogOption
)
718 temp
.LoadString(IDS_MENUREFLOG
);
719 popupMenu
.AppendMenuIcon(eCmd_ShowReflog
, temp
, IDI_LOG
);
722 popupMenu
.AppendMenu(MF_SEPARATOR
);
723 bAddSeparator
= false;
727 bAddSeparator
= true;
728 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
732 popupMenu
.AppendMenu(MF_SEPARATOR
);
734 bAddSeparator
= false;
737 popupMenu
.AppendMenuIcon(eCmd_Switch
, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS
)), IDI_SWITCH
);
738 popupMenu
.AppendMenu(MF_SEPARATOR
);
741 if(bShowCreateBranchOption
)
743 bAddSeparator
= true;
744 temp
.LoadString(IDS_MENUBRANCH
);
745 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
748 if(bShowRenameOption
)
750 bAddSeparator
= true;
751 popupMenu
.AppendMenuIcon(eCmd_Rename
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME
)), IDI_RENAME
);
754 else if(selectedLeafs
.size() == 2)
756 bAddSeparator
= true;
757 popupMenu
.AppendMenuIcon(eCmd_Diff
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS
)), IDI_DIFF
);
760 if(!selectedLeafs
.empty())
762 if(AreAllFrom(selectedLeafs
, L
"refs/remotes/"))
765 popupMenu
.AppendMenu(MF_SEPARATOR
);
766 CString menuItemName
;
767 if(selectedLeafs
.size() == 1)
768 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH
);
770 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES
, selectedLeafs
.size());
772 popupMenu
.AppendMenuIcon(eCmd_DeleteRemoteBranch
, menuItemName
, IDI_DELETE
);
773 bAddSeparator
= true;
775 else if(AreAllFrom(selectedLeafs
, L
"refs/heads/"))
778 popupMenu
.AppendMenu(MF_SEPARATOR
);
779 CString menuItemName
;
780 if(selectedLeafs
.size() == 1)
781 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH
);
783 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES
, selectedLeafs
.size());
785 popupMenu
.AppendMenuIcon(eCmd_DeleteBranch
, menuItemName
, IDI_DELETE
);
786 bAddSeparator
= true;
788 else if(AreAllFrom(selectedLeafs
, L
"refs/tags/"))
791 popupMenu
.AppendMenu(MF_SEPARATOR
);
792 CString menuItemName
;
793 if(selectedLeafs
.size() == 1)
794 menuItemName
.LoadString(IDS_PROC_BROWSEREFS_DELETETAG
);
796 menuItemName
.Format(IDS_PROC_BROWSEREFS_DELETETAGS
, selectedLeafs
.size());
798 popupMenu
.AppendMenuIcon(eCmd_DeleteTag
, menuItemName
, IDI_DELETE
);
799 bAddSeparator
= true;
804 if(hTreePos
!=NULL
&& selectedLeafs
.empty())
806 CShadowTree
* pTree
=(CShadowTree
*)m_RefTreeCtrl
.GetItemData(hTreePos
);
807 if(pTree
->IsFrom(L
"refs/remotes"))
810 popupMenu
.AppendMenu(MF_SEPARATOR
);
811 popupMenu
.AppendMenuIcon(eCmd_ManageRemotes
, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES
)), IDI_SETTINGS
);
812 bAddSeparator
= true;
813 if(selectedLeafs
.empty())
815 int dummy
= 0;//Needed for tokenize
816 remoteName
= pTree
->GetRefName();
817 remoteName
= remoteName
.Mid(13);
818 remoteName
= remoteName
.Tokenize(L
"/", dummy
);
819 if(!remoteName
.IsEmpty())
821 CString fetchFromCmd
;
822 fetchFromCmd
.Format(IDS_PROC_BROWSEREFS_FETCHFROM
, remoteName
);
823 popupMenu
.AppendMenuIcon(eCmd_Fetch
, fetchFromCmd
, IDI_PULL
);
827 if(pTree
->IsFrom(L
"refs/heads"))
830 popupMenu
.AppendMenu(MF_SEPARATOR
);
832 temp
.LoadString(IDS_MENUBRANCH
);
833 popupMenu
.AppendMenuIcon(eCmd_CreateBranch
, temp
, IDI_COPY
);
835 if(pTree
->IsFrom(L
"refs/tags"))
838 popupMenu
.AppendMenu(MF_SEPARATOR
);
840 temp
.LoadString(IDS_MENUTAG
);
841 popupMenu
.AppendMenuIcon(eCmd_CreateTag
, temp
, IDI_TAG
);
846 eCmd cmd
=(eCmd
)popupMenu
.TrackPopupMenuEx(TPM_LEFTALIGN
|TPM_RETURNCMD
, point
.x
, point
.y
, this, 0);
852 dlg
.SetStartRef(selectedLeafs
[0]->GetRefName());
856 case eCmd_RepoBrowser
:
857 CAppUtils::RunTortoiseProc(_T("/command:repobrowser /path:\"") + g_Git
.m_CurrentDir
+ _T("\" /rev:") + selectedLeafs
[0]->GetRefName());
859 case eCmd_DeleteBranch
:
860 case eCmd_DeleteRemoteBranch
:
862 if(ConfirmDeleteRef(selectedLeafs
))
863 DoDeleteRefs(selectedLeafs
, true);
869 if(ConfirmDeleteRef(selectedLeafs
))
870 DoDeleteRefs(selectedLeafs
, true);
874 case eCmd_ShowReflog
:
876 CRefLogDlg
refLogDlg(this);
877 refLogDlg
.m_CurrentBranch
= selectedLeafs
[0]->GetRefName();
883 CAppUtils::Fetch(remoteName
);
889 CAppUtils::Switch(NULL
, selectedLeafs
[0]->GetRefName());
894 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
896 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
901 CAddRemoteDlg(this).DoModal();
905 case eCmd_ManageRemotes
:
907 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS
)), new CSettingGitRemote(g_Git
.m_CurrentDir
), this).DoModal();
908 // CSettingGitRemote W_Remotes(m_cmdPath);
909 // W_Remotes.DoModal();
913 case eCmd_CreateBranch
:
915 CString
*commitHash
= NULL
;
916 if (selectedLeafs
.size() == 1)
917 commitHash
= &(selectedLeafs
[0]->m_csRefHash
);
918 CAppUtils::CreateBranchTag(false, commitHash
);
924 CAppUtils::CreateBranchTag(true);
933 selectedLeafs
[0]->m_csRefHash
,
934 selectedLeafs
[1]->m_csRefHash
);
941 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree
& leafs
, const wchar_t* from
)
943 for(VectorPShadowTree::iterator i
= leafs
.begin(); i
!= leafs
.end(); ++i
)
944 if(!(*i
)->IsFrom(from
))
949 BOOL
CBrowseRefsDlg::PreTranslateMessage(MSG
* pMsg
)
951 if (pMsg
->message
== WM_KEYDOWN
)
953 switch (pMsg
->wParam
)
957 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
959 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
961 PostMessage(WM_COMMAND, IDOK);
969 if(pMsg
->hwnd
== m_ListRefLeafs
.m_hWnd
)
971 POSITION pos
= m_ListRefLeafs
.GetFirstSelectedItemPosition();
973 m_ListRefLeafs
.EditLabel(m_ListRefLeafs
.GetNextSelectedItem(pos
));
987 return CResizableStandAloneDialog::PreTranslateMessage(pMsg
);
990 class CRefLeafListCompareFunc
993 CRefLeafListCompareFunc(CListCtrl
* pList
, int col
, bool desc
):m_col(col
),m_desc(desc
),m_pList(pList
){}
995 static int CALLBACK
StaticCompare(LPARAM lParam1
, LPARAM lParam2
, LPARAM lParamSort
)
997 return ((CRefLeafListCompareFunc
*)lParamSort
)->Compare(lParam1
,lParam2
);
1000 int Compare(LPARAM lParam1
, LPARAM lParam2
)
1003 (CShadowTree
*)m_pList
->GetItemData(lParam1
),
1004 (CShadowTree
*)m_pList
->GetItemData(lParam2
));
1007 int Compare(CShadowTree
* pLeft
, CShadowTree
* pRight
)
1009 int result
=CompareNoDesc(pLeft
,pRight
);
1015 int CompareNoDesc(CShadowTree
* pLeft
, CShadowTree
* pRight
)
1019 case CBrowseRefsDlg::eCol_Name
: return pLeft
->GetRefName().CompareNoCase(pRight
->GetRefName());
1020 case CBrowseRefsDlg::eCol_Date
: return pLeft
->m_csDate_Iso8601
.CompareNoCase(pRight
->m_csDate_Iso8601
);
1021 case CBrowseRefsDlg::eCol_Msg
: return pLeft
->m_csSubject
.CompareNoCase(pRight
->m_csSubject
);
1022 case CBrowseRefsDlg::eCol_Hash
: return pLeft
->m_csRefHash
.CompareNoCase(pRight
->m_csRefHash
);
1035 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1037 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
1040 if(m_currSortCol
== pNMLV
->iSubItem
)
1041 m_currSortDesc
= !m_currSortDesc
;
1044 m_currSortCol
= pNMLV
->iSubItem
;
1045 m_currSortDesc
= false;
1048 CRefLeafListCompareFunc
compareFunc(&m_ListRefLeafs
, m_currSortCol
, m_currSortDesc
);
1049 m_ListRefLeafs
.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare
, (DWORD_PTR
)&compareFunc
);
1051 SetSortArrow(&m_ListRefLeafs
,m_currSortCol
,!m_currSortDesc
);
1054 void CBrowseRefsDlg::OnDestroy()
1056 m_pickedRef
= GetSelectedRef(true, false);
1058 CResizableStandAloneDialog::OnDestroy();
1061 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1063 UNREFERENCED_PARAMETER(pNMHDR
);
1069 CString
CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef
, int pickRef_Kind
)
1071 CBrowseRefsDlg
dlg(CString(),NULL
);
1073 if(initialRef
.IsEmpty())
1074 initialRef
= L
"HEAD";
1075 dlg
.m_initialRef
= initialRef
;
1076 dlg
.m_pickRef_Kind
= pickRef_Kind
;
1078 if(dlg
.DoModal() != IDOK
)
1081 return dlg
.m_pickedRef
;
1084 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx
* pComboBox
, int pickRef_Kind
)
1087 pComboBox
->GetLBText(pComboBox
->GetCurSel(), origRef
);
1088 CString resultRef
= PickRef(false,origRef
,pickRef_Kind
);
1089 if(resultRef
.IsEmpty())
1091 if(wcsncmp(resultRef
,L
"refs/",5)==0)
1092 resultRef
= resultRef
.Mid(5);
1093 // if(wcsncmp(resultRef,L"heads/",6)==0)
1094 // resultRef = resultRef.Mid(6);
1096 //Find closest match of choice in combobox
1098 int matchLength
= 0;
1099 CString comboRefName
;
1100 for(int i
= 0; i
< pComboBox
->GetCount(); ++i
)
1102 pComboBox
->GetLBText(i
, comboRefName
);
1103 if(comboRefName
.Find(L
'/') < 0 && !comboRefName
.IsEmpty())
1104 comboRefName
.Insert(0,L
"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1105 if(matchLength
< comboRefName
.GetLength() && resultRef
.Right(comboRefName
.GetLength()) == comboRefName
)
1107 matchLength
= comboRefName
.GetLength();
1112 pComboBox
->SetCurSel(ixFound
);
1114 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)
1119 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1121 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1124 if(pDispInfo
->item
.pszText
== NULL
)
1125 return; //User canceled changing
1127 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1129 if(!pTree
->IsFrom(L
"refs/heads"))
1131 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1135 CString origName
= pTree
->GetRefName().Mid(11);
1138 if(m_pListCtrlRoot
!= NULL
)
1139 newName
= m_pListCtrlRoot
->GetRefName() + L
'/';
1140 newName
+= pDispInfo
->item
.pszText
;
1142 if(wcsncmp(newName
,L
"refs/heads/",11)!=0)
1144 CMessageBox::Show(m_hWnd
, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1148 CString newNameTrunced
= newName
.Mid(11);
1151 if(g_Git
.Run(L
"git branch -m \"" + origName
+ L
"\" \"" + newNameTrunced
+ L
"\"", &errorMsg
, CP_UTF8
) != 0)
1153 CMessageBox::Show(m_hWnd
, errorMsg
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1156 //Do as if it failed to rename. Let Refresh() do the job.
1161 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1163 // AfxMessageBox(W_csPopup);
1167 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR
*pNMHDR
, LRESULT
*pResult
)
1169 NMLVDISPINFO
*pDispInfo
= reinterpret_cast<NMLVDISPINFO
*>(pNMHDR
);
1172 CShadowTree
* pTree
=(CShadowTree
*)m_ListRefLeafs
.GetItemData(pDispInfo
->item
.iItem
);
1174 if(!pTree
->IsFrom(L
"refs/heads"))
1176 *pResult
= TRUE
; //Dont allow renaming any other things then branches at the moment.
1181 void CBrowseRefsDlg::OnEnChangeEditFilter()
1183 SetTimer(IDT_FILTER
, 1000, NULL
);
1186 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent
)
1188 if (nIDEvent
== IDT_FILTER
)
1190 KillTimer(IDT_FILTER
);
1191 FillListCtrlForTreeNode(m_RefTreeCtrl
.GetSelectedItem());
1194 CResizableStandAloneDialog::OnTimer(nIDEvent
);
1197 LRESULT
CBrowseRefsDlg::OnClickedInfoIcon(WPARAM
/*wParam*/, LPARAM lParam
)
1199 // FIXME: x64 version would get this function called with unexpected parameters.
1203 RECT
* rect
= (LPRECT
)lParam
;
1206 point
= CPoint(rect
->left
, rect
->bottom
);
1207 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1209 if (popup
.CreatePopupMenu())
1211 temp
.LoadString(IDS_LOG_FILTER_REFNAME
);
1212 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME
), LOGFILTER_REFNAME
, temp
);
1214 temp
.LoadString(IDS_LOG_FILTER_SUBJECT
);
1215 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT
), LOGFILTER_SUBJECT
, temp
);
1217 temp
.LoadString(IDS_LOG_FILTER_AUTHORS
);
1218 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS
), LOGFILTER_AUTHORS
, temp
);
1220 temp
.LoadString(IDS_LOG_FILTER_REVS
);
1221 popup
.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS
), LOGFILTER_REVS
, temp
);
1223 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this, 0);
1226 m_SelectedFilters
^= selection
;
1228 SetTimer(IDT_FILTER
, 1000, NULL
);
1234 void CBrowseRefsDlg::SetFilterCueText()
1236 CString
temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY
));
1239 if (m_SelectedFilters
& LOGFILTER_REFNAME
)
1240 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME
));
1242 if (m_SelectedFilters
& LOGFILTER_SUBJECT
)
1244 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1246 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT
));
1249 if (m_SelectedFilters
& LOGFILTER_AUTHORS
)
1251 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1253 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS
));
1256 if (m_SelectedFilters
& LOGFILTER_REVS
)
1258 if (temp
.ReverseFind(_T(' ')) != temp
.GetLength() - 1)
1260 temp
+= CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS
));
1263 // to make the cue banner text appear more to the right of the edit control
1264 temp
= _T(" ") + temp
;
1265 m_ctrlFilter
.SetCueBanner(temp
.TrimRight());