Fixed issue #1264: TortoiseProc might crash if commands are executed w/o a working...
[TortoiseGit.git] / src / TortoiseProc / BrowseRefsDlg.cpp
blobf5d490c537130fbb5ff102ea0e3ca78b8fda6d2f
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
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"
35 void SetSortArrow(CListCtrl * control, int nColumn, bool bAscending)
37 if (control == NULL)
38 return;
39 // set the sort arrow
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);
49 if (nColumn >= 0)
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),
63 m_cmdPath(cmdPath),
64 m_currSortCol(-1),
65 m_currSortDesc(false),
66 m_initialRef(L"HEAD"),
67 m_pickRef_Kind(gPickRef_All),
68 m_pListCtrlRoot(NULL),
69 m_bHasWC(true),
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)
91 ON_WM_CONTEXTMENU()
92 ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST_REF_LEAFS, &CBrowseRefsDlg::OnLvnColumnclickListRefLeafs)
93 ON_WM_DESTROY()
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)
99 ON_WM_TIMER()
100 END_MESSAGE_MAP()
103 // CBrowseRefsDlg message handlers
105 void CBrowseRefsDlg::OnBnClickedOk()
107 OnOK();
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);
118 SetFilterCueText();
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, TOP_LEFT);
123 AddAnchor(IDC_BROWSEREFS_EDIT_FILTER, TOP_LEFT, TOP_RIGHT);
124 AddAnchor(IDHELP, BOTTOM_RIGHT);
126 m_ListRefLeafs.SetExtendedStyle(m_ListRefLeafs.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
127 CString temp;
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_LASTAUTHOR);
135 m_ListRefLeafs.InsertColumn(eCol_LastAuthor, temp, 0, 100);
136 temp.LoadString(IDS_HASH);
137 m_ListRefLeafs.InsertColumn(eCol_Hash, temp, 0, 80);
139 AddAnchor(IDOK,BOTTOM_RIGHT);
140 AddAnchor(IDCANCEL,BOTTOM_RIGHT);
142 Refresh(m_initialRef);
144 EnableSaveRestore(L"BrowseRefs");
146 CString sWindowTitle;
147 GetWindowText(sWindowTitle);
148 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
150 m_bHasWC = !g_GitAdminDir.IsBareRepo(g_Git.m_CurrentDir);
152 m_ListRefLeafs.SetFocus();
153 return FALSE;
156 CShadowTree* CShadowTree::GetNextSub(CString& nameLeft, bool bCreateIfNotExist)
158 int posSlash=nameLeft.Find('/');
159 CString nameSub;
160 if(posSlash<0)
162 nameSub=nameLeft;
163 nameLeft.Empty();//Nothing left
165 else
167 nameSub=nameLeft.Left(posSlash);
168 nameLeft=nameLeft.Mid(posSlash+1);
170 if(nameSub.IsEmpty())
171 return NULL;
173 if(!bCreateIfNotExist && m_ShadowTree.find(nameSub)==m_ShadowTree.end())
174 return NULL;
176 CShadowTree& nextNode=m_ShadowTree[nameSub];
177 nextNode.m_csRefName=nameSub;
178 nextNode.m_pParent=this;
179 return &nextNode;
182 CShadowTree* CShadowTree::FindLeaf(CString partialRefName)
184 if(IsLeaf())
186 if(m_csRefName.GetLength() > partialRefName.GetLength())
187 return NULL;
188 if(partialRefName.Right(m_csRefName.GetLength()) == m_csRefName)
190 //Match of leaf name. Try match on total name.
191 CString totalRefName = GetRefName();
192 if(totalRefName.Right(partialRefName.GetLength()) == partialRefName)
193 return this; //Also match. Found.
196 else
198 //Not a leaf. Search all nodes.
199 for(TShadowTreeMap::iterator itShadowTree = m_ShadowTree.begin(); itShadowTree != m_ShadowTree.end(); ++itShadowTree)
201 CShadowTree* pSubtree = itShadowTree->second.FindLeaf(partialRefName);
202 if(pSubtree != NULL)
203 return pSubtree; //Found
206 return NULL;//Not found
210 typedef std::map<CString,CString> MAP_STRING_STRING;
212 CString CBrowseRefsDlg::GetSelectedRef(bool onlyIfLeaf, bool pickFirstSelIfMultiSel)
214 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
215 //List ctrl selection?
216 if(pos && (pickFirstSelIfMultiSel || m_ListRefLeafs.GetSelectedCount() == 1))
218 //A leaf is selected
219 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(
220 m_ListRefLeafs.GetNextSelectedItem(pos));
221 return pTree->GetRefName();
223 else if(!onlyIfLeaf)
225 //Tree ctrl selection?
226 HTREEITEM hTree=m_RefTreeCtrl.GetSelectedItem();
227 if(hTree!=NULL)
229 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTree);
230 return pTree->GetRefName();
233 return CString();//None
236 void CBrowseRefsDlg::Refresh(CString selectRef)
238 // m_RefMap.clear();
239 // g_Git.GetMapHashToFriendName(m_RefMap);
241 if(!selectRef.IsEmpty())
243 if(selectRef == "HEAD")
245 selectRef = g_Git.GetSymbolicRef(selectRef, false);
248 else
250 selectRef = GetSelectedRef(false, true);
253 m_RefTreeCtrl.DeleteAllItems();
254 m_ListRefLeafs.DeleteAllItems();
255 m_TreeRoot.m_ShadowTree.clear();
256 m_TreeRoot.m_csRefName = "refs";
257 m_TreeRoot.m_hTree=m_RefTreeCtrl.InsertItem(L"Refs",NULL,NULL);
258 m_RefTreeCtrl.SetItemData(m_TreeRoot.m_hTree,(DWORD_PTR)&m_TreeRoot);
260 CString allRefs;
261 g_Git.Run(L"git for-each-ref --format="
262 L"%(refname)%04"
263 L"%(objectname)%04"
264 L"%(authordate:relative)%04"
265 L"%(subject)%04"
266 L"%(authorname)%04"
267 L"%(authordate:iso8601)%03",
268 &allRefs, NULL, CP_UTF8);
270 int linePos=0;
271 CString singleRef;
273 MAP_STRING_STRING refMap;
275 //First sort on ref name
276 while(!(singleRef=allRefs.Tokenize(L"\03",linePos)).IsEmpty())
278 singleRef.TrimLeft(L"\r\n");
279 int valuePos=0;
280 CString refName=singleRef.Tokenize(L"\04",valuePos);
281 if(refName.IsEmpty())
282 continue;
283 CString refRest=singleRef.Mid(valuePos);
286 //Use ref based on m_pickRef_Kind
287 if(wcsncmp(refName,L"refs/heads",10)==0 && !(m_pickRef_Kind & gPickRef_Head) )
288 continue; //Skip
289 if(wcsncmp(refName,L"refs/tags",9)==0 && !(m_pickRef_Kind & gPickRef_Tag) )
290 continue; //Skip
291 if(wcsncmp(refName,L"refs/remotes",12)==0 && !(m_pickRef_Kind & gPickRef_Remote) )
292 continue; //Skip
294 refMap[refName] = refRest; //Use
299 //Populate ref tree
300 for(MAP_STRING_STRING::iterator iterRefMap=refMap.begin();iterRefMap!=refMap.end();++iterRefMap)
302 CShadowTree& treeLeaf=GetTreeNode(iterRefMap->first,NULL,true);
303 CString values=iterRefMap->second;
304 values.Replace(L"\04" L"\04",L"\04 \04");//Workaround Tokenize problem (treating 2 tokens as one)
306 int valuePos=0;
307 treeLeaf.m_csRefHash= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
308 treeLeaf.m_csDate= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
309 treeLeaf.m_csSubject= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
310 treeLeaf.m_csAuthor= values.Tokenize(L"\04",valuePos); if(valuePos < 0) continue;
311 treeLeaf.m_csDate_Iso8601= values.Tokenize(L"\04",valuePos);
315 if(selectRef.IsEmpty() || !SelectRef(selectRef, false))
316 //Probably not on a branch. Select root node.
317 m_RefTreeCtrl.Expand(m_TreeRoot.m_hTree,TVE_EXPAND);
321 bool CBrowseRefsDlg::SelectRef(CString refName, bool bExactMatch)
323 if(!bExactMatch)
325 CString newRefName = GetFullRefName(refName);
326 if(!newRefName.IsEmpty())
327 refName = newRefName;
328 //else refName is not a valid ref. Try to select as good as possible.
330 if(_wcsnicmp(refName, L"refs/", 5) != 0)
331 return false; // Not a ref name
333 CShadowTree& treeLeafHead=GetTreeNode(refName,NULL,false);
334 if(treeLeafHead.m_hTree != NULL)
336 //Not a leaf. Select tree node and return
337 m_RefTreeCtrl.Select(treeLeafHead.m_hTree,TVGN_CARET);
338 return true;
341 if(treeLeafHead.m_pParent==NULL)
342 return false; //Weird... should not occur.
344 //This is the current head.
345 m_RefTreeCtrl.Select(treeLeafHead.m_pParent->m_hTree,TVGN_CARET);
347 for(int indexPos = 0; indexPos < m_ListRefLeafs.GetItemCount(); ++indexPos)
349 CShadowTree* pCurrShadowTree = (CShadowTree*)m_ListRefLeafs.GetItemData(indexPos);
350 if(pCurrShadowTree == &treeLeafHead)
352 m_ListRefLeafs.SetItemState(indexPos,LVIS_SELECTED,LVIS_SELECTED);
353 m_ListRefLeafs.EnsureVisible(indexPos,FALSE);
357 return true;
360 CShadowTree& CBrowseRefsDlg::GetTreeNode(CString refName, CShadowTree* pTreePos, bool bCreateIfNotExist)
362 if(pTreePos==NULL)
364 if(_wcsnicmp(refName, L"refs/", 5) == 0)
365 refName=refName.Mid(5);
366 pTreePos=&m_TreeRoot;
368 if(refName.IsEmpty())
369 return *pTreePos;//Found leaf
371 CShadowTree* pNextTree=pTreePos->GetNextSub(refName,bCreateIfNotExist);
372 if(pNextTree==NULL)
374 //Should not occur when all ref-names are valid and bCreateIfNotExist is true.
375 ASSERT(!bCreateIfNotExist);
376 return *pTreePos;
379 if(!refName.IsEmpty())
381 //When the refName is not empty, this node is not a leaf, so lets add it to the tree control.
382 //Leafs are for the list control.
383 if(pNextTree->m_hTree==NULL)
385 //New tree. Create node in control.
386 pNextTree->m_hTree=m_RefTreeCtrl.InsertItem(pNextTree->m_csRefName,pTreePos->m_hTree,NULL);
387 m_RefTreeCtrl.SetItemData(pNextTree->m_hTree,(DWORD_PTR)pNextTree);
391 return GetTreeNode(refName, pNextTree, bCreateIfNotExist);
395 void CBrowseRefsDlg::OnTvnSelchangedTreeRef(NMHDR *pNMHDR, LRESULT *pResult)
397 LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
398 *pResult = 0;
400 FillListCtrlForTreeNode(pNMTreeView->itemNew.hItem);
403 void CBrowseRefsDlg::FillListCtrlForTreeNode(HTREEITEM treeNode)
405 m_ListRefLeafs.DeleteAllItems();
406 m_currSortCol = -1;
407 m_currSortDesc = false;
408 SetSortArrow(&m_ListRefLeafs,-1,false);
410 CShadowTree* pTree=(CShadowTree*)(m_RefTreeCtrl.GetItemData(treeNode));
411 if(pTree==NULL)
413 ASSERT(FALSE);
414 return;
416 FillListCtrlForShadowTree(pTree,L"",true);
419 void CBrowseRefsDlg::FillListCtrlForShadowTree(CShadowTree* pTree, CString refNamePrefix, bool isFirstLevel)
421 if(pTree->IsLeaf())
423 CString filter;
424 m_ctrlFilter.GetWindowText(filter);
425 filter.MakeLower();
426 CString ref = refNamePrefix + pTree->m_csRefName;
427 if (!(pTree->m_csRefName.IsEmpty() || pTree->m_csRefName == "refs" && pTree->m_pParent == NULL) && IsMatchFilter(pTree, ref, filter))
429 int indexItem = m_ListRefLeafs.InsertItem(m_ListRefLeafs.GetItemCount(), L"");
431 m_ListRefLeafs.SetItemData(indexItem,(DWORD_PTR)pTree);
432 m_ListRefLeafs.SetItemText(indexItem,eCol_Name, ref);
433 m_ListRefLeafs.SetItemText(indexItem,eCol_Date, pTree->m_csDate);
434 m_ListRefLeafs.SetItemText(indexItem,eCol_Msg, pTree->m_csSubject);
435 m_ListRefLeafs.SetItemText(indexItem,eCol_LastAuthor, pTree->m_csAuthor);
436 m_ListRefLeafs.SetItemText(indexItem,eCol_Hash, pTree->m_csRefHash);
439 else
442 CString csThisName;
443 if(!isFirstLevel)
444 csThisName=refNamePrefix+pTree->m_csRefName+L"/";
445 else
446 m_pListCtrlRoot = pTree;
447 for(CShadowTree::TShadowTreeMap::iterator itSubTree=pTree->m_ShadowTree.begin(); itSubTree!=pTree->m_ShadowTree.end(); ++itSubTree)
449 FillListCtrlForShadowTree(&itSubTree->second,csThisName,false);
454 bool CBrowseRefsDlg::IsMatchFilter(const CShadowTree* pTree, const CString &ref, const CString &filter)
456 if (m_SelectedFilters & LOGFILTER_REFNAME)
458 CString msg = ref;
459 msg = msg.MakeLower();
461 if (msg.Find(filter) >= 0)
462 return true;
465 if (m_SelectedFilters & LOGFILTER_SUBJECT)
467 CString msg = pTree->m_csSubject;
468 msg = msg.MakeLower();
470 if (msg.Find(filter) >= 0)
471 return true;
474 if (m_SelectedFilters & LOGFILTER_AUTHORS)
476 CString msg = pTree->m_csAuthor;
477 msg = msg.MakeLower();
479 if (msg.Find(filter) >= 0)
480 return true;
483 if (m_SelectedFilters & LOGFILTER_REVS)
485 CString msg = pTree->m_csRefHash;
486 msg = msg.MakeLower();
488 if (msg.Find(filter) >= 0)
489 return true;
491 return false;
494 bool CBrowseRefsDlg::ConfirmDeleteRef(VectorPShadowTree& leafs)
496 ASSERT(!leafs.empty());
498 CString csMessage;
499 UINT mbIcon=MB_ICONQUESTION;
501 bool bIsRemoteBranch = false;
502 bool bIsBranch = false;
503 if (leafs[0]->IsFrom(L"refs/remotes")) {bIsBranch = true; bIsRemoteBranch = true;}
504 else if (leafs[0]->IsFrom(L"refs/heads")) {bIsBranch = true;}
506 if(bIsBranch)
508 if(leafs.size() == 1)
510 CString branchToDelete = leafs[0]->GetRefName().Mid(bIsRemoteBranch ? 13 : 11);
511 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, branchToDelete);
513 //Check if branch is fully merged in HEAD
514 CGitHash branchHash = g_Git.GetHash(leafs[0]->GetRefName());
515 CGitHash commonAncestor;
516 CString commonAncestorstr;
517 CString cmd;
518 cmd.Format(L"git.exe merge-base HEAD %s", leafs[0]->GetRefName());
519 g_Git.Run(cmd, &commonAncestorstr, NULL, CP_UTF8);
521 commonAncestor=commonAncestorstr;
523 if(commonAncestor != branchHash)
525 csMessage += L"\r\n\r\n";
526 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGUNMERGED));
527 mbIcon = MB_ICONWARNING;
530 if(bIsRemoteBranch)
532 csMessage += L"\r\n\r\n";
533 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
534 mbIcon = MB_ICONWARNING;
537 else
539 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
541 csMessage += L"\r\n\r\n";
542 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGNOMERGECHECK));
543 mbIcon = MB_ICONWARNING;
545 if(bIsRemoteBranch)
547 csMessage += L"\r\n\r\n";
548 csMessage += CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_WARNINGDELETEREMOTEBRANCHES));
549 mbIcon = MB_ICONWARNING;
554 else if(leafs[0]->IsFrom(L"refs/tags"))
556 if(leafs.size() == 1)
558 CString tagToDelete = leafs[0]->GetRefName().Mid(10);
559 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, tagToDelete);
561 else
563 csMessage.Format(IDS_PROC_DELETENREFS, leafs.size());
567 return CMessageBox::Show(m_hWnd, csMessage, _T("TortoiseGit"), MB_YESNO | mbIcon) == IDYES;
571 bool CBrowseRefsDlg::DoDeleteRefs(VectorPShadowTree& leafs, bool bForce)
573 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
574 if(!DoDeleteRef((*i)->GetRefName(), bForce))
575 return false;
576 return true;
579 bool CBrowseRefsDlg::DoDeleteRef(CString completeRefName, bool bForce)
581 bool bIsRemoteBranch = false;
582 bool bIsBranch = false;
583 if (wcsncmp(completeRefName, L"refs/remotes",12)==0) {bIsBranch = true; bIsRemoteBranch = true;}
584 else if (wcsncmp(completeRefName, L"refs/heads",10)==0) {bIsBranch = true;}
586 if(bIsBranch)
588 CString branchToDelete = completeRefName.Mid(bIsRemoteBranch ? 13 : 11);
589 CString cmd;
590 if(bIsRemoteBranch)
592 int slash = branchToDelete.Find(L'/');
593 if(slash < 0)
594 return false;
595 CString remoteName = branchToDelete.Left(slash);
596 CString remoteBranchToDelete = branchToDelete.Mid(slash + 1);
598 if(CAppUtils::IsSSHPutty())
600 CAppUtils::LaunchPAgent(NULL, &remoteName);
603 cmd.Format(L"git.exe push \"%s\" :%s", remoteName, remoteBranchToDelete);
605 else
606 cmd.Format(L"git.exe branch -%c -- %s",bForce?L'D':L'd',branchToDelete);
607 CString errorMsg;
608 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
610 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
611 return false;
614 else if(wcsncmp(completeRefName,L"refs/tags",9)==0)
616 CString tagToDelete = completeRefName.Mid(10);
617 CString cmd;
618 cmd.Format(L"git.exe tag -d -- %s",tagToDelete);
619 CString errorMsg;
620 if(g_Git.Run(cmd,&errorMsg,CP_UTF8)!=0)
622 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
623 return false;
626 return true;
629 CString CBrowseRefsDlg::GetFullRefName(CString partialRefName)
631 CShadowTree* pLeaf = m_TreeRoot.FindLeaf(partialRefName);
632 if(pLeaf == NULL)
633 return CString();
634 return pLeaf->GetRefName();
638 void CBrowseRefsDlg::OnContextMenu(CWnd* pWndFrom, CPoint point)
640 if(pWndFrom==&m_RefTreeCtrl) OnContextMenu_RefTreeCtrl(point);
641 else if(pWndFrom==&m_ListRefLeafs) OnContextMenu_ListRefLeafs(point);
644 void CBrowseRefsDlg::OnContextMenu_RefTreeCtrl(CPoint point)
646 CPoint clientPoint=point;
647 m_RefTreeCtrl.ScreenToClient(&clientPoint);
649 HTREEITEM hTreeItem=m_RefTreeCtrl.HitTest(clientPoint);
650 if(hTreeItem!=NULL)
651 m_RefTreeCtrl.Select(hTreeItem,TVGN_CARET);
653 VectorPShadowTree tree;
654 ShowContextMenu(point,hTreeItem,tree);
658 void CBrowseRefsDlg::OnContextMenu_ListRefLeafs(CPoint point)
660 std::vector<CShadowTree*> selectedLeafs;
661 selectedLeafs.reserve(m_ListRefLeafs.GetSelectedCount());
662 POSITION pos=m_ListRefLeafs.GetFirstSelectedItemPosition();
663 while(pos)
665 selectedLeafs.push_back(
666 (CShadowTree*)m_ListRefLeafs.GetItemData(
667 m_ListRefLeafs.GetNextSelectedItem(pos)));
670 ShowContextMenu(point,m_RefTreeCtrl.GetSelectedItem(),selectedLeafs);
673 void CBrowseRefsDlg::ShowContextMenu(CPoint point, HTREEITEM hTreePos, VectorPShadowTree& selectedLeafs)
675 CIconMenu popupMenu;
676 popupMenu.CreatePopupMenu();
678 bool bAddSeparator = false;
679 CString remoteName;
681 if(selectedLeafs.size()==1)
683 bAddSeparator = true;
685 bool bShowReflogOption = false;
686 bool bShowFetchOption = false;
687 bool bShowSwitchOption = false;
688 bool bShowRenameOption = false;
689 bool bShowCreateBranchOption = false;
691 CString fetchFromCmd;
693 if(selectedLeafs[0]->IsFrom(L"refs/heads"))
695 bShowReflogOption = true;
696 bShowSwitchOption = true;
697 bShowRenameOption = true;
699 else if(selectedLeafs[0]->IsFrom(L"refs/remotes"))
701 bShowReflogOption = true;
702 bShowFetchOption = true;
703 bShowCreateBranchOption = true;
705 int dummy = 0;//Needed for tokenize
706 remoteName = selectedLeafs[0]->GetRefName();
707 remoteName = remoteName.Mid(13);
708 remoteName = remoteName.Tokenize(L"/", dummy);
709 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
711 else if(selectedLeafs[0]->IsFrom(L"refs/tags"))
715 CString temp;
716 temp.LoadString(IDS_MENULOG);
717 popupMenu.AppendMenuIcon(eCmd_ViewLog, temp, IDI_LOG);
718 popupMenu.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
719 if(bShowReflogOption)
721 temp.LoadString(IDS_MENUREFLOG);
722 popupMenu.AppendMenuIcon(eCmd_ShowReflog, temp, IDI_LOG);
725 popupMenu.AppendMenu(MF_SEPARATOR);
726 bAddSeparator = false;
728 if(bShowFetchOption)
730 bAddSeparator = true;
731 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
734 if(bAddSeparator)
735 popupMenu.AppendMenu(MF_SEPARATOR);
737 bAddSeparator = false;
738 if (m_bHasWC)
740 popupMenu.AppendMenuIcon(eCmd_Switch, CString(MAKEINTRESOURCE(IDS_SWITCH_TO_THIS)), IDI_SWITCH);
741 popupMenu.AppendMenu(MF_SEPARATOR);
744 if(bShowCreateBranchOption)
746 bAddSeparator = true;
747 temp.LoadString(IDS_MENUBRANCH);
748 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
751 if(bShowRenameOption)
753 bAddSeparator = true;
754 popupMenu.AppendMenuIcon(eCmd_Rename, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_RENAME)), IDI_RENAME);
757 else if(selectedLeafs.size() == 2)
759 bAddSeparator = true;
760 popupMenu.AppendMenuIcon(eCmd_Diff, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_COMPAREREFS)), IDI_DIFF);
763 if(!selectedLeafs.empty())
765 if(AreAllFrom(selectedLeafs, L"refs/remotes/"))
767 if(bAddSeparator)
768 popupMenu.AppendMenu(MF_SEPARATOR);
769 CString menuItemName;
770 if(selectedLeafs.size() == 1)
771 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCH);
772 else
773 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEREMOTEBRANCHES, selectedLeafs.size());
775 popupMenu.AppendMenuIcon(eCmd_DeleteRemoteBranch, menuItemName, IDI_DELETE);
776 bAddSeparator = true;
778 else if(AreAllFrom(selectedLeafs, L"refs/heads/"))
780 if(bAddSeparator)
781 popupMenu.AppendMenu(MF_SEPARATOR);
782 CString menuItemName;
783 if(selectedLeafs.size() == 1)
784 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETEBRANCH);
785 else
786 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETEBRANCHES, selectedLeafs.size());
788 popupMenu.AppendMenuIcon(eCmd_DeleteBranch, menuItemName, IDI_DELETE);
789 bAddSeparator = true;
791 else if(AreAllFrom(selectedLeafs, L"refs/tags/"))
793 if(bAddSeparator)
794 popupMenu.AppendMenu(MF_SEPARATOR);
795 CString menuItemName;
796 if(selectedLeafs.size() == 1)
797 menuItemName.LoadString(IDS_PROC_BROWSEREFS_DELETETAG);
798 else
799 menuItemName.Format(IDS_PROC_BROWSEREFS_DELETETAGS, selectedLeafs.size());
801 popupMenu.AppendMenuIcon(eCmd_DeleteTag, menuItemName, IDI_DELETE);
802 bAddSeparator = true;
807 if(hTreePos!=NULL && selectedLeafs.empty())
809 CShadowTree* pTree=(CShadowTree*)m_RefTreeCtrl.GetItemData(hTreePos);
810 if(pTree->IsFrom(L"refs/remotes"))
812 if(bAddSeparator)
813 popupMenu.AppendMenu(MF_SEPARATOR);
814 popupMenu.AppendMenuIcon(eCmd_ManageRemotes, CString(MAKEINTRESOURCE(IDS_PROC_BROWSEREFS_MANAGEREMOTES)), IDI_SETTINGS);
815 bAddSeparator = true;
816 if(selectedLeafs.empty())
818 int dummy = 0;//Needed for tokenize
819 remoteName = pTree->GetRefName();
820 remoteName = remoteName.Mid(13);
821 remoteName = remoteName.Tokenize(L"/", dummy);
822 if(!remoteName.IsEmpty())
824 CString fetchFromCmd;
825 fetchFromCmd.Format(IDS_PROC_BROWSEREFS_FETCHFROM, remoteName);
826 popupMenu.AppendMenuIcon(eCmd_Fetch, fetchFromCmd, IDI_PULL);
830 if(pTree->IsFrom(L"refs/heads"))
832 if(bAddSeparator)
833 popupMenu.AppendMenu(MF_SEPARATOR);
834 CString temp;
835 temp.LoadString(IDS_MENUBRANCH);
836 popupMenu.AppendMenuIcon(eCmd_CreateBranch, temp, IDI_COPY);
838 if(pTree->IsFrom(L"refs/tags"))
840 if(bAddSeparator)
841 popupMenu.AppendMenu(MF_SEPARATOR);
842 CString temp;
843 temp.LoadString(IDS_MENUTAG);
844 popupMenu.AppendMenuIcon(eCmd_CreateTag, temp, IDI_TAG);
849 eCmd cmd=(eCmd)popupMenu.TrackPopupMenuEx(TPM_LEFTALIGN|TPM_RETURNCMD, point.x, point.y, this, 0);
850 switch(cmd)
852 case eCmd_ViewLog:
854 CLogDlg dlg;
855 dlg.SetStartRef(selectedLeafs[0]->GetRefName());
856 dlg.DoModal();
858 break;
859 case eCmd_RepoBrowser:
860 CAppUtils::RunTortoiseProc(_T("/command:repobrowser /path:\"") + g_Git.m_CurrentDir + _T("\" /rev:") + selectedLeafs[0]->GetRefName());
861 break;
862 case eCmd_DeleteBranch:
863 case eCmd_DeleteRemoteBranch:
865 if(ConfirmDeleteRef(selectedLeafs))
866 DoDeleteRefs(selectedLeafs, true);
867 Refresh();
869 break;
870 case eCmd_DeleteTag:
872 if(ConfirmDeleteRef(selectedLeafs))
873 DoDeleteRefs(selectedLeafs, true);
874 Refresh();
876 break;
877 case eCmd_ShowReflog:
879 CRefLogDlg refLogDlg(this);
880 refLogDlg.m_CurrentBranch = selectedLeafs[0]->GetRefName();
881 refLogDlg.DoModal();
883 break;
884 case eCmd_Fetch:
886 CAppUtils::Fetch(remoteName);
887 Refresh();
889 break;
890 case eCmd_Switch:
892 CAppUtils::Switch(NULL, selectedLeafs[0]->GetRefName());
894 break;
895 case eCmd_Rename:
897 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
898 if(pos != NULL)
899 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
901 break;
902 case eCmd_AddRemote:
904 CAddRemoteDlg(this).DoModal();
905 Refresh();
907 break;
908 case eCmd_ManageRemotes:
910 CSinglePropSheetDlg(CString(MAKEINTRESOURCE(IDS_PROCS_TITLE_GITREMOTESETTINGS)), new CSettingGitRemote(g_Git.m_CurrentDir), this).DoModal();
911 // CSettingGitRemote W_Remotes(m_cmdPath);
912 // W_Remotes.DoModal();
913 Refresh();
915 break;
916 case eCmd_CreateBranch:
918 CString *commitHash = NULL;
919 if (selectedLeafs.size() == 1)
920 commitHash = &(selectedLeafs[0]->m_csRefHash);
921 CAppUtils::CreateBranchTag(false, commitHash);
922 Refresh();
924 break;
925 case eCmd_CreateTag:
927 CAppUtils::CreateBranchTag(true);
928 Refresh();
930 break;
931 case eCmd_Diff:
933 CFileDiffDlg dlg;
934 dlg.SetDiff(
935 NULL,
936 selectedLeafs[0]->m_csRefHash,
937 selectedLeafs[1]->m_csRefHash);
938 dlg.DoModal();
940 break;
944 bool CBrowseRefsDlg::AreAllFrom(VectorPShadowTree& leafs, const wchar_t* from)
946 for(VectorPShadowTree::iterator i = leafs.begin(); i != leafs.end(); ++i)
947 if(!(*i)->IsFrom(from))
948 return false;
949 return true;
952 BOOL CBrowseRefsDlg::PreTranslateMessage(MSG* pMsg)
954 if (pMsg->message == WM_KEYDOWN)
956 switch (pMsg->wParam)
958 /* case VK_RETURN:
960 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
962 if ( GetDlgItem(IDOK)->IsWindowEnabled() )
964 PostMessage(WM_COMMAND, IDOK);
966 return TRUE;
969 break;
970 */ case VK_F2:
972 if(pMsg->hwnd == m_ListRefLeafs.m_hWnd)
974 POSITION pos = m_ListRefLeafs.GetFirstSelectedItemPosition();
975 if(pos != NULL)
976 m_ListRefLeafs.EditLabel(m_ListRefLeafs.GetNextSelectedItem(pos));
979 break;
981 case VK_F5:
983 Refresh();
985 break;
990 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
993 class CRefLeafListCompareFunc
995 public:
996 CRefLeafListCompareFunc(CListCtrl* pList, int col, bool desc):m_col(col),m_desc(desc),m_pList(pList){}
998 static int CALLBACK StaticCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
1000 return ((CRefLeafListCompareFunc*)lParamSort)->Compare(lParam1,lParam2);
1003 int Compare(LPARAM lParam1, LPARAM lParam2)
1005 return Compare(
1006 (CShadowTree*)m_pList->GetItemData(lParam1),
1007 (CShadowTree*)m_pList->GetItemData(lParam2));
1010 int Compare(CShadowTree* pLeft, CShadowTree* pRight)
1012 int result=CompareNoDesc(pLeft,pRight);
1013 if(m_desc)
1014 return -result;
1015 return result;
1018 int CompareNoDesc(CShadowTree* pLeft, CShadowTree* pRight)
1020 switch(m_col)
1022 case CBrowseRefsDlg::eCol_Name: return pLeft->GetRefName().CompareNoCase(pRight->GetRefName());
1023 case CBrowseRefsDlg::eCol_Date: return pLeft->m_csDate_Iso8601.CompareNoCase(pRight->m_csDate_Iso8601);
1024 case CBrowseRefsDlg::eCol_Msg: return pLeft->m_csSubject.CompareNoCase(pRight->m_csSubject);
1025 case CBrowseRefsDlg::eCol_LastAuthor: return pLeft->m_csAuthor.CompareNoCase(pRight->m_csAuthor);
1026 case CBrowseRefsDlg::eCol_Hash: return pLeft->m_csRefHash.CompareNoCase(pRight->m_csRefHash);
1028 return 0;
1031 int m_col;
1032 bool m_desc;
1033 CListCtrl* m_pList;
1039 void CBrowseRefsDlg::OnLvnColumnclickListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1041 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1042 *pResult = 0;
1044 if(m_currSortCol == pNMLV->iSubItem)
1045 m_currSortDesc = !m_currSortDesc;
1046 else
1048 m_currSortCol = pNMLV->iSubItem;
1049 m_currSortDesc = false;
1052 CRefLeafListCompareFunc compareFunc(&m_ListRefLeafs, m_currSortCol, m_currSortDesc);
1053 m_ListRefLeafs.SortItemsEx(&CRefLeafListCompareFunc::StaticCompare, (DWORD_PTR)&compareFunc);
1055 SetSortArrow(&m_ListRefLeafs,m_currSortCol,!m_currSortDesc);
1058 void CBrowseRefsDlg::OnDestroy()
1060 m_pickedRef = GetSelectedRef(true, false);
1062 CResizableStandAloneDialog::OnDestroy();
1065 void CBrowseRefsDlg::OnNMDblclkListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1067 UNREFERENCED_PARAMETER(pNMHDR);
1068 *pResult = 0;
1070 EndDialog(IDOK);
1073 CString CBrowseRefsDlg::PickRef(bool /*returnAsHash*/, CString initialRef, int pickRef_Kind)
1075 CBrowseRefsDlg dlg(CString(),NULL);
1077 if(initialRef.IsEmpty())
1078 initialRef = L"HEAD";
1079 dlg.m_initialRef = initialRef;
1080 dlg.m_pickRef_Kind = pickRef_Kind;
1082 if(dlg.DoModal() != IDOK)
1083 return CString();
1085 return dlg.m_pickedRef;
1088 bool CBrowseRefsDlg::PickRefForCombo(CComboBoxEx* pComboBox, int pickRef_Kind)
1090 CString origRef;
1091 pComboBox->GetLBText(pComboBox->GetCurSel(), origRef);
1092 CString resultRef = PickRef(false,origRef,pickRef_Kind);
1093 if(resultRef.IsEmpty())
1094 return false;
1095 if(wcsncmp(resultRef,L"refs/",5)==0)
1096 resultRef = resultRef.Mid(5);
1097 // if(wcsncmp(resultRef,L"heads/",6)==0)
1098 // resultRef = resultRef.Mid(6);
1100 //Find closest match of choice in combobox
1101 int ixFound = -1;
1102 int matchLength = 0;
1103 CString comboRefName;
1104 for(int i = 0; i < pComboBox->GetCount(); ++i)
1106 pComboBox->GetLBText(i, comboRefName);
1107 if(comboRefName.Find(L'/') < 0 && !comboRefName.IsEmpty())
1108 comboRefName.Insert(0,L"heads/"); // If combo contains single level ref name, it is usualy from 'heads/'
1109 if(matchLength < comboRefName.GetLength() && resultRef.Right(comboRefName.GetLength()) == comboRefName)
1111 matchLength = comboRefName.GetLength();
1112 ixFound = i;
1115 if(ixFound >= 0)
1116 pComboBox->SetCurSel(ixFound);
1117 else
1118 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)
1120 return true;
1123 void CBrowseRefsDlg::OnLvnEndlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1125 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1126 *pResult = FALSE;
1128 if(pDispInfo->item.pszText == NULL)
1129 return; //User canceled changing
1131 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1133 if(!pTree->IsFrom(L"refs/heads"))
1135 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_RENAMEONLYBRANCHES, IDS_APPNAME, MB_OK | MB_ICONERROR);
1136 return;
1139 CString origName = pTree->GetRefName().Mid(11);
1141 CString newName;
1142 if(m_pListCtrlRoot != NULL)
1143 newName = m_pListCtrlRoot->GetRefName() + L'/';
1144 newName += pDispInfo->item.pszText;
1146 if(wcsncmp(newName,L"refs/heads/",11)!=0)
1148 CMessageBox::Show(m_hWnd, IDS_PROC_BROWSEREFS_NOCHANGEOFTYPE, IDS_APPNAME, MB_OK | MB_ICONERROR);
1149 return;
1152 CString newNameTrunced = newName.Mid(11);
1154 CString errorMsg;
1155 if(g_Git.Run(L"git branch -m \"" + origName + L"\" \"" + newNameTrunced + L"\"", &errorMsg, CP_UTF8) != 0)
1157 CMessageBox::Show(m_hWnd, errorMsg, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1158 return;
1160 //Do as if it failed to rename. Let Refresh() do the job.
1161 //*pResult = TRUE;
1163 Refresh(newName);
1165 // CString W_csPopup;W_csPopup.Format8(L"Ref: %s. New name: %s. With path: %s", pTree->GetRefName(), pDispInfo->item.pszText, newName);
1167 // AfxMessageBox(W_csPopup);
1171 void CBrowseRefsDlg::OnLvnBeginlabeleditListRefLeafs(NMHDR *pNMHDR, LRESULT *pResult)
1173 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1174 *pResult = FALSE;
1176 CShadowTree* pTree=(CShadowTree*)m_ListRefLeafs.GetItemData(pDispInfo->item.iItem);
1178 if(!pTree->IsFrom(L"refs/heads"))
1180 *pResult = TRUE; //Dont allow renaming any other things then branches at the moment.
1181 return;
1185 void CBrowseRefsDlg::OnEnChangeEditFilter()
1187 SetTimer(IDT_FILTER, 1000, NULL);
1190 void CBrowseRefsDlg::OnTimer(UINT_PTR nIDEvent)
1192 if (nIDEvent == IDT_FILTER)
1194 KillTimer(IDT_FILTER);
1195 FillListCtrlForTreeNode(m_RefTreeCtrl.GetSelectedItem());
1198 CResizableStandAloneDialog::OnTimer(nIDEvent);
1201 LRESULT CBrowseRefsDlg::OnClickedInfoIcon(WPARAM /*wParam*/, LPARAM lParam)
1203 // FIXME: x64 version would get this function called with unexpected parameters.
1204 if (!lParam)
1205 return 0;
1207 RECT * rect = (LPRECT)lParam;
1208 CPoint point;
1209 CString temp;
1210 point = CPoint(rect->left, rect->bottom);
1211 #define LOGMENUFLAGS(x) (MF_STRING | MF_ENABLED | (m_SelectedFilters & x ? MF_CHECKED : MF_UNCHECKED))
1212 CMenu popup;
1213 if (popup.CreatePopupMenu())
1215 temp.LoadString(IDS_LOG_FILTER_REFNAME);
1216 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REFNAME), LOGFILTER_REFNAME, temp);
1218 temp.LoadString(IDS_LOG_FILTER_SUBJECT);
1219 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_SUBJECT), LOGFILTER_SUBJECT, temp);
1221 temp.LoadString(IDS_LOG_FILTER_AUTHORS);
1222 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_AUTHORS), LOGFILTER_AUTHORS, temp);
1224 temp.LoadString(IDS_LOG_FILTER_REVS);
1225 popup.AppendMenu(LOGMENUFLAGS(LOGFILTER_REVS), LOGFILTER_REVS, temp);
1227 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1228 if (selection != 0)
1230 m_SelectedFilters ^= selection;
1231 SetFilterCueText();
1232 SetTimer(IDT_FILTER, 1000, NULL);
1235 return 0L;
1238 void CBrowseRefsDlg::SetFilterCueText()
1240 CString temp(MAKEINTRESOURCE(IDS_LOG_FILTER_BY));
1241 temp += _T(" ");
1243 if (m_SelectedFilters & LOGFILTER_REFNAME)
1244 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REFNAME));
1246 if (m_SelectedFilters & LOGFILTER_SUBJECT)
1248 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1249 temp += _T(", ");
1250 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_SUBJECT));
1253 if (m_SelectedFilters & LOGFILTER_AUTHORS)
1255 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1256 temp += _T(", ");
1257 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_AUTHORS));
1260 if (m_SelectedFilters & LOGFILTER_REVS)
1262 if (temp.ReverseFind(_T(' ')) != temp.GetLength() - 1)
1263 temp += _T(", ");
1264 temp += CString(MAKEINTRESOURCE(IDS_LOG_FILTER_REVS));
1267 // to make the cue banner text appear more to the right of the edit control
1268 temp = _T(" ") + temp;
1269 m_ctrlFilter.SetCueBanner(temp.TrimRight());