Improved fix for issue #1618
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
blob152250da1259e5395dbd2a68c40d1ec9335eba46
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
5 // Copyright (C) 2011-2013 - Sven Strickroth <email@cs-ware.de>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "resource.h"
25 #include "GitLogListBase.h"
26 #include "GitRev.h"
27 #include "IconMenu.h"
28 #include "cursor.h"
29 #include "InputDlg.h"
30 #include "GitProgressDlg.h"
31 #include "ProgressDlg.h"
32 #include "LogDlg.h"
33 #include "MessageBox.h"
34 #include "registry.h"
35 #include "LoglistUtils.h"
36 #include "PathUtils.h"
37 #include "StringUtils.h"
38 #include "UnicodeUtils.h"
39 #include "TempFile.h"
40 #include "IconMenu.h"
41 #include "GitStatus.h"
42 #include "..\\TortoiseShell\\Resource.h"
43 #include "FindDlg.h"
44 #include "SysInfo.h"
46 const UINT CGitLogListBase::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
47 const UINT CGitLogListBase::m_ScrollToMessage = RegisterWindowMessage(_T("TORTOISEGIT_LOG_SCROLLTO"));
48 const UINT CGitLogListBase::m_RebaseActionMessage = RegisterWindowMessage(_T("TORTOISEGIT_LOG_REBASEACTION"));
50 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintListCtrl)
52 CGitLogListBase::CGitLogListBase():CHintListCtrl()
53 ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
54 ,m_nSearchIndex(0)
55 ,m_bNoDispUpdates(FALSE)
56 , m_bThreadRunning(FALSE)
57 , m_bStrictStopped(false)
58 , m_pStoreSelection(NULL)
59 , m_SelectedFilters(LOGFILTER_ALL)
60 , m_ShowFilter(FILTERSHOW_ALL)
61 , m_bShowWC(false)
62 , m_logEntries(&m_LogCache)
63 , m_pFindDialog(NULL)
64 , m_ColumnManager(this)
65 , m_dwDefaultColumns(0)
66 , m_arShownList(&m_critSec)
67 , m_hasWC(true)
68 , m_bNoHightlightHead(FALSE)
69 , m_ShowRefMask(LOGLIST_SHOWALLREFS)
71 // use the default GUI font, create a copy of it and
72 // change the copy to BOLD (leave the rest of the font
73 // the same)
74 HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
75 LOGFONT lf = {0};
76 GetObject(hFont, sizeof(LOGFONT), &lf);
77 lf.lfWeight = FW_BOLD;
78 m_boldFont = CreateFontIndirect(&lf);
80 m_bShowBugtraqColumn=false;
82 m_IsIDReplaceAction=FALSE;
84 this->m_critSec.Init();
85 m_critSec_AsyncDiff.Init();
86 m_wcRev.m_CommitHash.Empty();
87 m_wcRev.GetSubject() = CString(MAKEINTRESOURCE(IDS_LOG_WORKINGDIRCHANGES));
88 m_wcRev.m_ParentHash.clear();
89 m_wcRev.m_Mark=_T('-');
90 m_wcRev.m_IsUpdateing=FALSE;
91 m_wcRev.m_IsFull = TRUE;
93 m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
94 m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
95 m_hAddedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
96 m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
97 m_hFetchIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONFETCHING), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
99 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
101 m_From = -1;
102 m_To = -1;
104 m_ShowMask = 0;
105 m_LoadingThread = NULL;
107 InterlockedExchange(&m_bExitThread,FALSE);
108 m_IsOldFirst = FALSE;
109 m_IsRebaseReplaceGraph = FALSE;
111 for (int i = 0; i < Lanes::COLORS_NUM; ++i)
113 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));
115 // get short/long datetime setting from registry
116 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
117 if ( RegUseShortDateFormat )
119 m_DateFormat = DATE_SHORTDATE;
121 else
123 m_DateFormat = DATE_LONGDATE;
125 // get relative time display setting from registry
126 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
127 m_bRelativeTimes = (regRelativeTimes != 0);
128 m_ContextMenuMask = 0xFFFFFFFFFFFFFFFF;
130 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_PICK);
131 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SQUASH);
132 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_EDIT);
133 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SKIP);
134 m_ContextMenuMask &= ~GetContextMenuBit(ID_LOG);
135 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAME);
137 m_ColumnRegKey=_T("log");
139 m_bTagsBranchesOnRightSide = !!CRegDWORD(_T("Software\\TortoiseGit\\DrawTagsBranchesOnRightSide"), FALSE);
140 m_bSymbolizeRefNames = !!CRegDWORD(_T("Software\\TortoiseGit\\SymbolizeRefNames"), FALSE);
141 m_bIncludeBoundaryCommits = !!CRegDWORD(_T("Software\\TortoiseGit\\LogIncludeBoundaryCommits"), FALSE);
143 m_LineWidth = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogLineWidth"), 2));
144 m_NodeSize = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogNodeSize"), 10));
146 m_AsyncThreadExit = FALSE;
147 m_AsyncDiffEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
148 m_AsynDiffListLock.Init();
150 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
151 if (m_DiffingThread ==NULL)
153 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
154 return;
159 int CGitLogListBase::AsyncDiffThread()
161 m_AsyncThreadExited = false;
162 while(!m_AsyncThreadExit)
164 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
166 GitRev *pRev = NULL;
167 while(!m_AsyncThreadExit && !m_AsynDiffList.empty())
169 m_AsynDiffListLock.Lock();
170 pRev = m_AsynDiffList.back();
171 m_AsynDiffList.pop_back();
172 m_AsynDiffListLock.Unlock();
174 if( pRev->m_CommitHash.IsEmpty() )
176 if(pRev->m_IsDiffFiles)
177 continue;
179 pRev->GetFiles(this).Clear();
180 pRev->m_ParentHash.clear();
181 pRev->m_ParentHash.push_back(m_HeadHash);
182 if(g_Git.IsInitRepos())
184 if (g_Git.GetInitAddList(pRev->GetFiles(this)))
185 CMessageBox::Show(NULL, _T("Run ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
188 else
190 g_Git.GetCommitDiffList(pRev->m_CommitHash.ToString(),this->m_HeadHash.ToString(), pRev->GetFiles(this));
192 int dummyAction = 0;
193 int *action = &dummyAction;
194 SafeGetAction(pRev, &action);
195 *action = 0;
197 for (int j = 0; j < pRev->GetFiles(this).GetCount(); ++j)
198 *action |= pRev->GetFiles(this)[j].m_Action;
200 pRev->GetUnRevFiles().FillUnRev(CTGitPath::LOGACTIONS_UNVER);
202 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
203 InterlockedExchange(&pRev->m_IsFull, TRUE);
205 pRev->GetBody().Format(IDS_FILESCHANGES, pRev->GetFiles(this).GetCount());
206 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
207 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
210 m_critSec_AsyncDiff.Lock();
211 int ret = pRev->CheckAndDiff();
212 m_critSec_AsyncDiff.Unlock();
213 if (!ret)
214 { // fetch change file list
215 for (int i = GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex() + GetCountPerPage(); ++i)
217 if(i < m_arShownList.GetCount())
219 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(i);
220 if(data->m_CommitHash == pRev->m_CommitHash)
222 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
223 break;
228 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
230 POSITION pos = GetFirstSelectedItemPosition();
231 int nItem = GetNextSelectedItem(pos);
233 if(nItem>=0)
235 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(nItem);
236 if(data)
237 if(data->m_CommitHash == pRev->m_CommitHash)
239 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
246 m_AsyncThreadExited = true;
247 return 0;
249 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
251 if (exclusivelyShow)
253 m_ContextMenuMask &= hideMask;
255 else
257 m_ContextMenuMask &= ~hideMask;
261 CGitLogListBase::~CGitLogListBase()
263 InterlockedExchange(&m_bNoDispUpdates, TRUE);
264 this->m_arShownList.SafeRemoveAll();
266 DestroyIcon(m_hModifiedIcon);
267 DestroyIcon(m_hReplacedIcon);
268 DestroyIcon(m_hAddedIcon);
269 DestroyIcon(m_hDeletedIcon);
270 m_logEntries.ClearAll();
272 if (m_boldFont)
273 DeleteObject(m_boldFont);
275 if ( m_pStoreSelection )
277 delete m_pStoreSelection;
278 m_pStoreSelection = NULL;
281 SafeTerminateThread();
282 SafeTerminateAsyncDiffThread();
284 if(m_AsyncDiffEvent)
285 CloseHandle(m_AsyncDiffEvent);
289 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)
290 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
291 ON_REGISTERED_MESSAGE(m_ScrollToMessage, OnScrollToMessage)
292 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
293 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
294 ON_WM_CONTEXTMENU()
295 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
296 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
297 ON_WM_CREATE()
298 ON_WM_DESTROY()
299 ON_MESSAGE(MSG_LOADED,OnLoad)
300 ON_WM_MEASUREITEM()
301 ON_WM_MEASUREITEM_REFLECT()
302 ON_NOTIFY(HDN_BEGINTRACKA, 0, OnHdnBegintrack)
303 ON_NOTIFY(HDN_BEGINTRACKW, 0, OnHdnBegintrack)
304 ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
305 ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
306 ON_NOTIFY(HDN_ENDTRACK, 0, OnColumnResized)
307 ON_NOTIFY(HDN_ENDDRAG, 0, OnColumnMoved)
308 END_MESSAGE_MAP()
310 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
312 //if (m_nRowHeight>0)
314 lpMeasureItemStruct->itemHeight = 50;
318 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
320 PreSubclassWindow();
321 return CHintListCtrl::OnCreate(lpCreateStruct);
324 void CGitLogListBase::PreSubclassWindow()
326 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
327 // load the icons for the action columns
328 // m_Theme.Open(m_hWnd, L"ListView");
329 SetWindowTheme(m_hWnd, L"Explorer", NULL);
330 CHintListCtrl::PreSubclassWindow();
333 void CGitLogListBase::InsertGitColumn()
335 CString temp;
337 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
338 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
339 if (DWORD(regFullRowSelect))
340 exStyle |= LVS_EX_FULLROWSELECT;
341 SetExtendedStyle(exStyle);
343 // only load properties if we have a repository
344 if (GitAdminDir().HasAdminDir(g_Git.m_CurrentDir) || GitAdminDir().IsBareRepo(g_Git.m_CurrentDir))
345 UpdateProjectProperties();
347 static UINT normal[] =
349 IDS_LOG_GRAPH,
350 IDS_LOG_REBASE,
351 IDS_LOG_ID,
352 IDS_LOG_HASH,
353 IDS_LOG_ACTIONS,
354 IDS_LOG_MESSAGE,
355 IDS_LOG_AUTHOR,
356 IDS_LOG_DATE,
357 IDS_LOG_EMAIL,
358 IDS_LOG_COMMIT_NAME,
359 IDS_LOG_COMMIT_EMAIL,
360 IDS_LOG_COMMIT_DATE,
361 IDS_LOG_BUGIDS,
364 static int with[] =
366 ICONITEMBORDER+16*4,
367 ICONITEMBORDER+16*4,
368 ICONITEMBORDER+16*4,
369 ICONITEMBORDER+16*4,
370 ICONITEMBORDER+16*4,
371 LOGLIST_MESSAGE_MIN,
372 ICONITEMBORDER+16*4,
373 ICONITEMBORDER+16*4,
374 ICONITEMBORDER+16*4,
375 ICONITEMBORDER+16*4,
376 ICONITEMBORDER+16*4,
377 ICONITEMBORDER+16*4,
378 ICONITEMBORDER+16*4,
380 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
382 DWORD hideColumns = 0;
383 if(this->m_IsRebaseReplaceGraph)
385 hideColumns |= GIT_LOG_GRAPH;
386 m_dwDefaultColumns |= GIT_LOG_REBASE;
388 else
390 hideColumns |= GIT_LOG_REBASE;
393 if(this->m_IsIDReplaceAction)
395 hideColumns |= GIT_LOG_ACTIONS;
396 m_dwDefaultColumns |= GIT_LOG_ID;
397 m_dwDefaultColumns |= GIT_LOG_HASH;
399 else
401 hideColumns |= GIT_LOG_ID;
403 if(this->m_bShowBugtraqColumn)
405 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
407 else
409 hideColumns |= GIT_LOGLIST_BUG;
411 SetRedraw(false);
413 m_ColumnManager.SetNames(normal, _countof(normal));
414 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey+_T("loglist"), _countof(normal), with);
416 SetRedraw(true);
420 * Resizes all columns in a list control to values in registry.
422 void CGitLogListBase::ResizeAllListCtrlCols()
424 // column max and min widths to allow
425 static const int nMinimumWidth = 10;
426 static const int nMaximumWidth = 1000;
427 CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
428 if (pHdrCtrl)
430 int numcols = pHdrCtrl->GetItemCount();
431 for (int col = 0; col < numcols; ++col)
433 // get width for this col last time from registry
434 CString regentry;
435 regentry.Format( _T("Software\\TortoiseGit\\%s\\ColWidth%d"),m_ColumnRegKey, col);
436 CRegDWORD regwidth(regentry, 0);
437 int cx = regwidth;
438 if ( cx == 0 )
440 // no saved value, setup sensible defaults
441 if (col == this->LOGLIST_MESSAGE)
443 cx = LOGLIST_MESSAGE_MIN;
445 else
447 cx = ICONITEMBORDER+16*4;
450 if (cx < nMinimumWidth)
452 cx = nMinimumWidth;
454 else if (cx > nMaximumWidth)
456 cx = nMaximumWidth;
459 SetColumnWidth(col, cx);
466 void CGitLogListBase::FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect)
468 LVITEM rItem;
469 SecureZeroMemory(&rItem, sizeof(LVITEM));
470 rItem.mask = LVIF_STATE;
471 rItem.iItem = (int)Index;
472 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
473 GetItem(&rItem);
475 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(Index);
476 HBRUSH brush = NULL;
478 if (!(rItem.state & LVIS_SELECTED))
480 int action = SafeGetAction(pLogEntry);
481 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
482 brush = ::CreateSolidBrush(RGB(156,156,156));
483 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
484 brush = ::CreateSolidBrush(RGB(200,200,128));
486 else if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
488 if (rItem.state & LVIS_SELECTED)
490 if (::GetFocus() == m_hWnd)
491 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
492 else
493 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
496 if (brush != NULL)
498 ::FillRect(hdc, &rect, brush);
499 ::DeleteObject(brush);
503 void DrawTrackingRoundRect(HDC hdc, CRect rect, HBRUSH brush, COLORREF darkColor)
505 POINT point = { 4, 4 };
506 CRect rt2 = rect;
507 rt2.DeflateRect(1, 1);
508 rt2.OffsetRect(2, 2);
510 HPEN nullPen = ::CreatePen(PS_NULL, 0, 0);
511 HPEN oldpen = (HPEN)::SelectObject(hdc, nullPen);
512 HBRUSH darkBrush = (HBRUSH)::CreateSolidBrush(darkColor);
513 HBRUSH oldbrush = (HBRUSH)::SelectObject(hdc, darkBrush);
514 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
516 ::SelectObject(hdc, brush);
517 rt2.OffsetRect(-2, -2);
518 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
519 ::SelectObject(hdc, oldbrush);
520 ::SelectObject(hdc, oldpen);
521 ::DeleteObject(nullPen);
522 ::DeleteObject(darkBrush);
525 void DrawLightning(HDC hdc, CRect rect, COLORREF color, int bold)
527 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
528 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
529 ::MoveToEx(hdc, rect.left + 7, rect.top, NULL);
530 ::LineTo(hdc, rect.left + 1, (rect.top + rect.bottom) / 2);
531 ::LineTo(hdc, rect.left + 6, (rect.top + rect.bottom) / 2);
532 ::LineTo(hdc, rect.left, rect.bottom);
533 ::SelectObject(hdc, oldpen);
534 ::DeleteObject(pen);
537 void DrawUpTriangle(HDC hdc, CRect rect, COLORREF color, int bold)
539 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
540 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
541 ::MoveToEx(hdc, (rect.left + rect.right) / 2, rect.top, NULL);
542 ::LineTo(hdc, rect.left, rect.bottom);
543 ::LineTo(hdc, rect.right, rect.bottom);
544 ::LineTo(hdc, (rect.left + rect.right) / 2, rect.top);
545 ::SelectObject(hdc, oldpen);
546 ::DeleteObject(pen);
549 void CGitLogListBase::DrawTagBranchMessage(HDC hdc, CRect &rect, INT_PTR index, std::vector<REFLABEL> &refList)
551 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
552 CRect rt=rect;
553 LVITEM rItem;
554 SecureZeroMemory(&rItem, sizeof(LVITEM));
555 rItem.mask = LVIF_STATE;
556 rItem.iItem = (int)index;
557 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
558 GetItem(&rItem);
560 CDC W_Dc;
561 W_Dc.Attach(hdc);
563 HTHEME hTheme = NULL;
564 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
565 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
567 if (!m_bTagsBranchesOnRightSide)
568 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
570 SIZE oneSpaceSize;
571 GetTextExtentPoint32(hdc, L" ", 1, &oneSpaceSize);
572 rt.left += oneSpaceSize.cx;
574 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
576 int txtState = LISS_NORMAL;
577 if (rItem.state & LVIS_SELECTED)
578 txtState = LISS_SELECTED;
580 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, data->GetSubject(), -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS, 0, &rt);
582 else
584 if (rItem.state & LVIS_SELECTED)
586 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
587 ::DrawText(hdc,data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
588 ::SetTextColor(hdc, clrOld);
590 else
592 ::DrawText(hdc, data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
596 if (m_bTagsBranchesOnRightSide)
598 SIZE size;
599 GetTextExtentPoint32(hdc, data->GetSubject(), data->GetSubject().GetLength(), &size);
601 rt.left += oneSpaceSize.cx + size.cx;
603 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
606 if (hTheme)
607 CloseThemeData(hTheme);
609 W_Dc.Detach();
612 void CGitLogListBase::DrawTagBranch(HDC hdc, CDC &W_Dc, HTHEME hTheme, CRect &rect, CRect &rt, LVITEM &rItem, GitRev* data, std::vector<REFLABEL> &refList)
614 for (unsigned int i = 0; i < refList.size(); ++i)
616 CString shortname = !refList[i].simplifiedName.IsEmpty() ? refList[i].simplifiedName : refList[i].name;
617 HBRUSH brush = 0;
618 COLORREF colRef = refList[i].color;
619 bool singleRemote = refList[i].singleRemote;
620 bool hasTracking = refList[i].hasTracking;
621 bool sameName = refList[i].sameName;
622 bool annotatedTag = refList[i].annotatedTag;
624 //When row selected, ajust label color
625 if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
627 if (rItem.state & LVIS_SELECTED)
628 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
631 brush = ::CreateSolidBrush(colRef);
633 if (!shortname.IsEmpty() && (rt.left < rect.right))
635 SIZE size;
636 memset(&size,0,sizeof(SIZE));
637 GetTextExtentPoint32(hdc, shortname, shortname.GetLength(), &size);
639 rt.SetRect(rt.left, rt.top, rt.left + size.cx, rt.bottom);
640 rt.right += 8;
642 int textpos = DT_CENTER;
644 if (rt.right > rect.right)
646 rt.right = rect.right;
647 textpos = 0;
650 CRect textRect = rt;
652 if (singleRemote)
654 rt.right += 8;
655 textRect.OffsetRect(8, 0);
658 if (sameName)
659 rt.right += 8;
661 if (hasTracking)
663 DrawTrackingRoundRect(hdc, rt, brush, m_Colors.Darken(colRef, 100));
665 else
667 //Fill interior of ref label
668 ::FillRect(hdc, &rt, brush);
671 //Draw edge of label
672 CRect rectEdge = rt;
674 if (!hasTracking)
676 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 100), m_Colors.Darken(colRef, 100));
677 rectEdge.DeflateRect(1, 1);
678 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 50), m_Colors.Darken(colRef, 50));
681 if (annotatedTag)
683 rt.right += 8;
684 POINT trianglept[3] = { { rt.right - 8, rt.top }, { rt.right, (rt.top + rt.bottom) / 2 }, { rt.right - 8, rt.bottom } };
685 HRGN hrgn = ::CreatePolygonRgn(trianglept, 3, ALTERNATE);
686 ::FillRgn(hdc, hrgn, brush);
687 ::DeleteObject(hrgn);
688 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[0].y, NULL);
689 HPEN pen;
690 HPEN oldpen = (HPEN)SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Lighten(colRef, 50)));
691 ::LineTo(hdc, trianglept[1].x - 1, trianglept[1].y - 1);
692 ::DeleteObject(pen);
693 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Darken(colRef, 50)));
694 ::LineTo(hdc, trianglept[2].x - 1, trianglept[2].y - 1);
695 ::DeleteObject(pen);
696 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, colRef));
697 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[2].y - 3, NULL);
698 ::LineTo(hdc, trianglept[0].x - 1, trianglept[0].y);
699 ::DeleteObject(pen);
700 SelectObject(hdc, oldpen);
703 //Draw text inside label
704 bool customColor = (colRef & 0xff) * 30 + ((colRef >> 8) & 0xff) * 59 + ((colRef >> 16) & 0xff) * 11 <= 12800; // check if dark background
705 if (!customColor && IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
707 int txtState = LISS_NORMAL;
708 if (rItem.state & LVIS_SELECTED)
709 txtState = LISS_SELECTED;
711 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, 0, &textRect);
713 else
715 W_Dc.SetBkMode(TRANSPARENT);
716 if (customColor || (rItem.state & LVIS_SELECTED))
718 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
719 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
720 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
721 ::SetTextColor(hdc,clrOld);
723 else
725 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
729 if (singleRemote)
731 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
732 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
733 CRect newRect;
734 newRect.SetRect(rt.left + 4, rt.top + 4, rt.left + 8, rt.bottom - 4);
735 DrawLightning(hdc, newRect, color, bold);
738 if (sameName)
740 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
741 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
742 CRect newRect;
743 newRect.SetRect(rt.right - 12, rt.top + 4, rt.right - 4, rt.bottom - 4);
744 DrawUpTriangle(hdc, newRect, color, bold);
747 rt.left = rt.right + 1;
749 if (brush)
750 ::DeleteObject(brush);
752 rt.right = rect.right;
755 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
757 // Returns ((256 - amount)*col1 + amount*col2) / 256;
758 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
759 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
760 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
763 Gdiplus::Color GetGdiColor(COLORREF col)
765 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
767 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
768 const COLORREF& col,const COLORREF& activeColor, int top
771 int h = laneHeight / 2;
772 int m = (x1 + x2) / 2;
773 int r = (x2 - x1) * m_NodeSize / 30;
774 int d = 2 * r;
776 #define P_CENTER m , h+top
777 #define P_0 x2, h+top
778 #define P_90 m , 0+top-1
779 #define P_180 x1, h+top
780 #define P_270 m , 2 * h+top +1
781 #define R_CENTER m - r, h - r+top, d, d
784 #define DELTA_UR_B 2*(x1 - m), 2*h +top
785 #define DELTA_UR_E 0*16, 90*16 +top // -,
787 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
788 #define DELTA_DR_E 270*16, 90*16 +top // -'
790 #define DELTA_UL_B 2*(x2 - m), 2*h +top
791 #define DELTA_UL_E 90*16, 90*16 +top // ,-
793 #define DELTA_DL_B 2*(x2 - m),2*-h +top
794 #define DELTA_DL_E 180*16, 90*16 // '-
796 #define CENTER_UR x1, 2*h, 225
797 #define CENTER_DR x1, 0 , 135
798 #define CENTER_UL x2, 2*h, 315
799 #define CENTER_DL x2, 0 , 45
802 Gdiplus::Graphics graphics( hdc );
804 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
806 // arc
807 switch (type) {
808 case Lanes::JOIN:
809 case Lanes::JOIN_R:
810 case Lanes::HEAD:
811 case Lanes::HEAD_R:
813 Gdiplus::LinearGradientBrush gradient(
814 Gdiplus::Point(x1-2, h+top-2),
815 Gdiplus::Point(P_270),
816 GetGdiColor(activeColor),GetGdiColor(col));
819 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
820 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
822 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
823 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
824 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
826 break;
828 case Lanes::JOIN_L:
831 Gdiplus::LinearGradientBrush gradient(
832 Gdiplus::Point(P_270),
833 Gdiplus::Point(x2+1, h+top-1),
834 GetGdiColor(col),GetGdiColor(activeColor));
837 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
838 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
840 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
841 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
842 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
845 break;
847 case Lanes::TAIL:
848 case Lanes::TAIL_R:
851 Gdiplus::LinearGradientBrush gradient(
852 Gdiplus::Point(x1-2, h+top-2),
853 Gdiplus::Point(P_90),
854 GetGdiColor(activeColor),GetGdiColor(col));
856 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
858 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
860 #if 0
861 QConicalGradient gradient(CENTER_DR);
862 gradient.setColorAt(0.375, activeCol);
863 gradient.setColorAt(0.625, col);
864 myPen.setBrush(gradient);
865 p->setPen(myPen);
866 p->drawArc(P_CENTER, DELTA_DR);
867 #endif
868 break;
870 default:
871 break;
875 //static QPen myPen(Qt::black, 2); // fast path here
876 CPen pen;
877 pen.CreatePen(PS_SOLID,2,col);
878 //myPen.setColor(col);
879 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
881 Gdiplus::Pen myPen(GetGdiColor(col), (Gdiplus::REAL)m_LineWidth);
883 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
885 //p->setPen(myPen);
887 // vertical line
888 switch (type) {
889 case Lanes::ACTIVE:
890 case Lanes::NOT_ACTIVE:
891 case Lanes::MERGE_FORK:
892 case Lanes::MERGE_FORK_R:
893 case Lanes::MERGE_FORK_L:
894 case Lanes::JOIN:
895 case Lanes::JOIN_R:
896 case Lanes::JOIN_L:
897 case Lanes::CROSS:
898 //DrawLine(hdc,P_90,P_270);
899 graphics.DrawLine(&myPen,P_90,P_270);
900 //p->drawLine(P_90, P_270);
901 break;
902 case Lanes::HEAD_L:
903 case Lanes::BRANCH:
904 //DrawLine(hdc,P_CENTER,P_270);
905 graphics.DrawLine(&myPen,P_CENTER,P_270);
906 //p->drawLine(P_CENTER, P_270);
907 break;
908 case Lanes::TAIL_L:
909 case Lanes::INITIAL:
910 case Lanes::BOUNDARY:
911 case Lanes::BOUNDARY_C:
912 case Lanes::BOUNDARY_R:
913 case Lanes::BOUNDARY_L:
914 //DrawLine(hdc,P_90, P_CENTER);
915 graphics.DrawLine(&myPen,P_90,P_CENTER);
916 //p->drawLine(P_90, P_CENTER);
917 break;
918 default:
919 break;
922 myPen.SetColor(GetGdiColor(activeColor));
924 // horizontal line
925 switch (type) {
926 case Lanes::MERGE_FORK:
927 case Lanes::JOIN:
928 case Lanes::HEAD:
929 case Lanes::TAIL:
930 case Lanes::CROSS:
931 case Lanes::CROSS_EMPTY:
932 case Lanes::BOUNDARY_C:
933 //DrawLine(hdc,P_180,P_0);
934 graphics.DrawLine(&myPen,P_180,P_0);
935 //p->drawLine(P_180, P_0);
936 break;
937 case Lanes::MERGE_FORK_R:
938 case Lanes::BOUNDARY_R:
939 //DrawLine(hdc,P_180,P_CENTER);
940 graphics.DrawLine(&myPen,P_180,P_CENTER);
941 //p->drawLine(P_180, P_CENTER);
942 break;
943 case Lanes::MERGE_FORK_L:
944 case Lanes::HEAD_L:
945 case Lanes::TAIL_L:
946 case Lanes::BOUNDARY_L:
947 //DrawLine(hdc,P_CENTER,P_0);
948 graphics.DrawLine(&myPen,P_CENTER,P_0);
949 //p->drawLine(P_CENTER, P_0);
950 break;
951 default:
952 break;
955 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
957 CBrush brush;
958 brush.CreateSolidBrush(col);
959 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
961 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
962 // center symbol, e.g. rect or ellipse
963 switch (type) {
964 case Lanes::ACTIVE:
965 case Lanes::INITIAL:
966 case Lanes::BRANCH:
968 //p->setPen(Qt::NoPen);
969 //p->setBrush(col);
970 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
971 graphics.FillEllipse(&myBrush, R_CENTER);
972 //p->drawEllipse(R_CENTER);
973 break;
974 case Lanes::MERGE_FORK:
975 case Lanes::MERGE_FORK_R:
976 case Lanes::MERGE_FORK_L:
977 //p->setPen(Qt::NoPen);
978 //p->setBrush(col);
979 //p->drawRect(R_CENTER);
980 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
981 graphics.FillRectangle(&myBrush, R_CENTER);
982 break;
983 case Lanes::UNAPPLIED:
984 // Red minus sign
985 //p->setPen(Qt::NoPen);
986 //p->setBrush(Qt::red);
987 //p->drawRect(m - r, h - 1, d, 2);
988 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
989 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
990 break;
991 case Lanes::APPLIED:
992 // Green plus sign
993 //p->setPen(Qt::NoPen);
994 //p->setBrush(DARK_GREEN);
995 //p->drawRect(m - r, h - 1, d, 2);
996 //p->drawRect(m - 1, h - r, 2, d);
997 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
998 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
999 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
1000 break;
1001 case Lanes::BOUNDARY:
1002 //p->setBrush(back);
1003 //p->drawEllipse(R_CENTER);
1004 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
1005 graphics.DrawEllipse(&myPen, R_CENTER);
1006 break;
1007 case Lanes::BOUNDARY_C:
1008 case Lanes::BOUNDARY_R:
1009 case Lanes::BOUNDARY_L:
1010 //p->setBrush(back);
1011 //p->drawRect(R_CENTER);
1012 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
1013 graphics.FillRectangle(&myBrush,R_CENTER);
1014 break;
1015 default:
1016 break;
1019 ::SelectObject(hdc,oldpen);
1020 ::SelectObject(hdc,oldbrush);
1021 #undef P_CENTER
1022 #undef P_0
1023 #undef P_90
1024 #undef P_180
1025 #undef P_270
1026 #undef R_CENTER
1029 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
1031 // TODO: unfinished
1032 // return;
1033 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
1034 if(data->m_CommitHash.IsEmpty())
1035 return;
1037 CRect rt=rect;
1038 LVITEM rItem;
1039 SecureZeroMemory(&rItem, sizeof(LVITEM));
1040 rItem.mask = LVIF_STATE;
1041 rItem.iItem = (int)index;
1042 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
1043 GetItem(&rItem);
1045 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1047 if (data->m_Lanes.empty())
1048 m_logEntries.setLane(data->m_CommitHash);
1050 std::vector<int>& lanes=data->m_Lanes;
1051 size_t laneNum = lanes.size();
1052 UINT activeLane = 0;
1053 for (UINT i = 0; i < laneNum; ++i)
1054 if (Lanes::isMerge(lanes[i])) {
1055 activeLane = i;
1056 break;
1059 int x2 = 0;
1060 int maxWidth = rect.Width();
1061 int lw = 3 * rect.Height() / 4; //laneWidth()
1063 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1064 //if (opt.state & QStyle::State_Selected)
1065 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1067 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; ++i)
1070 int x1 = x2;
1071 x2 += lw;
1073 int ln = lanes[i];
1074 if (ln == Lanes::EMPTY)
1075 continue;
1077 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1078 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1081 #if 0
1082 for (UINT i = 0; i < laneNum && x2 < maxWidth; ++i) {
1084 int x1 = x2;
1085 x2 += lw;
1087 int ln = lanes[i];
1088 if (ln == Lanes::EMPTY)
1089 continue;
1091 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1092 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1094 if (ln == Lanes::CROSS)
1096 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1097 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1099 else
1100 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1102 #endif
1106 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1109 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1110 // Take the default processing unless we set this to something else below.
1111 *pResult = CDRF_DODEFAULT;
1113 if (m_bNoDispUpdates)
1114 return;
1116 switch (pLVCD->nmcd.dwDrawStage)
1118 case CDDS_PREPAINT:
1120 *pResult = CDRF_NOTIFYITEMDRAW;
1121 return;
1123 break;
1124 case CDDS_ITEMPREPAINT:
1126 // This is the prepaint stage for an item. Here's where we set the
1127 // item's text color.
1129 // Tell Windows to send draw notifications for each subitem.
1130 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1132 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1134 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1136 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1137 if (data)
1139 int action = SafeGetAction(data);
1140 if (action & (CTGitPath::LOGACTIONS_REBASE_DONE | CTGitPath::LOGACTIONS_REBASE_SKIP))
1141 crText = RGB(128,128,128);
1143 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
1144 pLVCD->clrTextBk = RGB(156,156,156);
1145 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
1146 pLVCD->clrTextBk = RGB(200,200,128);
1147 else
1148 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1150 if (action & CTGitPath::LOGACTIONS_REBASE_CURRENT)
1152 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1153 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1156 if (data->m_CommitHash == m_HeadHash && m_bNoHightlightHead == FALSE)
1158 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1159 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1162 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1163 // crText = GetSysColor(COLOR_GRAYTEXT);
1165 if (data->m_CommitHash.IsEmpty())
1167 //crText = GetSysColor(RGB(200,200,0));
1168 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1169 // We changed the font, so we're returning CDRF_NEWFONT. This
1170 // tells the control to recalculate the extent of the text.
1171 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1175 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
1177 if (m_bStrictStopped)
1178 crText = GetSysColor(COLOR_GRAYTEXT);
1180 // Store the color back in the NMLVCUSTOMDRAW struct.
1181 pLVCD->clrText = crText;
1182 return;
1184 break;
1185 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
1187 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
1189 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
1192 if (pLVCD->iSubItem == LOGLIST_GRAPH && m_sFilterText.IsEmpty() && (m_ShowFilter & FILTERSHOW_MERGEPOINTS))
1194 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )
1196 CRect rect;
1197 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1199 //TRACE(_T("A Graphic left %d right %d\r\n"),rect.left,rect.right);
1200 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1202 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1203 if( !data ->m_CommitHash.IsEmpty())
1204 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1206 *pResult = CDRF_SKIPDEFAULT;
1207 return;
1211 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
1213 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1215 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1216 //if(!data->m_IsFull)
1218 //if(data->SafeFetchFullInfo(&g_Git))
1219 // this->Invalidate();
1220 //TRACE(_T("Update ... %d\r\n"),pLVCD->nmcd.dwItemSpec);
1223 if (!m_HashMap[data->m_CommitHash].empty())
1225 CRect rect;
1226 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1228 // BEGIN: extended redraw, HACK for issue #1618 and #2014
1229 // not in FillBackGround method, because this only affected the message subitem
1230 if (0 != pLVCD->iStateId) // don't know why, but this helps against loosing the focus rect
1231 return;
1233 int index = (int)pLVCD->nmcd.dwItemSpec;
1234 int state = GetItemState(index, LVIS_SELECTED);
1235 int txtState = LISS_NORMAL;
1236 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater() && GetHotItem() == (int)index)
1238 if (state & LVIS_SELECTED)
1239 txtState = LISS_HOTSELECTED;
1240 else
1241 txtState = LISS_HOT;
1243 else if (state & LVIS_SELECTED)
1245 if (::GetFocus() == m_hWnd)
1246 txtState = LISS_SELECTED;
1247 else
1248 txtState = LISS_SELECTEDNOTFOCUS;
1251 HTHEME hTheme = nullptr;
1252 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
1253 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
1255 if (hTheme && IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTDETAIL, txtState))
1256 DrawThemeParentBackground(m_hWnd, pLVCD->nmcd.hdc, &rect);
1257 else
1259 HBRUSH brush = ::CreateSolidBrush(pLVCD->clrTextBk);
1260 ::FillRect(pLVCD->nmcd.hdc, rect, brush);
1261 ::DeleteObject(brush);
1263 if (hTheme)
1265 CRect rt;
1266 // get rect of whole line
1267 GetItemRect(index, rt, LVIR_BOUNDS);
1268 CRect rect2 = rect;
1269 if (txtState == LISS_NORMAL) // avoid drawing of grey borders
1270 rect2.DeflateRect(1, 1, 1, 1);
1272 // calculate background for rect of whole line, but limit redrawing to SubItem rect
1273 DrawThemeBackground(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, rt, rect2);
1275 CloseThemeData(hTheme);
1277 // END: extended redraw
1279 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1281 std::vector<REFLABEL> refsToShow;
1282 STRING_VECTOR remoteTrackingList;
1283 STRING_VECTOR refList = m_HashMap[data->m_CommitHash];
1284 for (unsigned int i = 0; i < refList.size(); ++i)
1286 CString str = refList[i];
1288 REFLABEL refLabel;
1289 refLabel.color = RGB(255, 255, 255);
1290 refLabel.singleRemote = false;
1291 refLabel.hasTracking = false;
1292 refLabel.sameName = false;
1293 refLabel.annotatedTag = false;
1294 if (CGit::GetShortName(str, refLabel.name, _T("refs/heads/")))
1296 if (!(m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES))
1297 continue;
1298 if (refLabel.name == m_CurrentBranch )
1299 refLabel.color = m_Colors.GetColor(CColors::CurrentBranch);
1300 else
1301 refLabel.color = m_Colors.GetColor(CColors::LocalBranch);
1303 std::pair<CString, CString> trackingEntry = m_TrackingMap[refLabel.name];
1304 CString pullRemote = trackingEntry.first;
1305 CString pullBranch = trackingEntry.second;
1306 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
1308 CString defaultUpstream;
1309 defaultUpstream.Format(_T("refs/remotes/%s/%s"), pullRemote, pullBranch);
1310 refLabel.hasTracking = true;
1311 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
1313 bool found = false;
1314 for (size_t j = i + 1; j < refList.size(); ++j)
1316 if (refList[j] == defaultUpstream)
1318 found = true;
1319 break;
1323 if (found)
1325 bool sameName = pullBranch == refLabel.name;
1326 refsToShow.push_back(refLabel);
1327 CGit::GetShortName(defaultUpstream, refLabel.name, _T("refs/remotes/"));
1328 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1329 if (m_bSymbolizeRefNames)
1331 if (!m_SingleRemote.IsEmpty() && m_SingleRemote == pullRemote)
1333 refLabel.simplifiedName = _T("/") + (sameName ? CString() : pullBranch);
1334 refLabel.singleRemote = true;
1336 else if (sameName)
1337 refLabel.simplifiedName = pullRemote + _T("/");
1338 refLabel.sameName = sameName;
1340 refsToShow.push_back(refLabel);
1341 remoteTrackingList.push_back(defaultUpstream);
1342 continue;
1347 else if (CGit::GetShortName(str, refLabel.name, _T("refs/remotes/")))
1349 if (!(m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES))
1350 continue;
1352 bool found = false;
1353 for (size_t j = 0; j < remoteTrackingList.size(); ++j)
1355 if (remoteTrackingList[j] == str)
1357 found = true;
1358 break;
1361 if (found)
1362 continue;
1364 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1365 if (m_bSymbolizeRefNames)
1367 if (!m_SingleRemote.IsEmpty() && refLabel.name.Left(m_SingleRemote.GetLength() + 1) == m_SingleRemote + _T("/"))
1369 refLabel.simplifiedName = _T("/") + refLabel.name.Mid(m_SingleRemote.GetLength() + 1);
1370 refLabel.singleRemote = true;
1374 else if (CGit::GetShortName(str, refLabel.name, _T("refs/tags/")))
1376 if (!(m_ShowRefMask & LOGLIST_SHOWTAGS))
1377 continue;
1378 refLabel.color = m_Colors.GetColor(CColors::Tag);
1379 refLabel.annotatedTag = str.Right(3) == _T("^{}");
1381 else if (CGit::GetShortName(str, refLabel.name, _T("refs/stash")))
1383 if (!(m_ShowRefMask & LOGLIST_SHOWSTASH))
1384 continue;
1385 refLabel.color = m_Colors.GetColor(CColors::Stash);
1386 refLabel.name = _T("stash");
1388 else if (CGit::GetShortName(str, refLabel.name, _T("refs/bisect/")))
1390 if (!(m_ShowRefMask & LOGLIST_SHOWBISECT))
1391 continue;
1392 if (refLabel.name.Find(_T("good")) == 0)
1394 refLabel.color = m_Colors.GetColor(CColors::BisectGood);
1395 refLabel.name = _T("good");
1397 if (refLabel.name.Find(_T("bad")) == 0)
1399 refLabel.color = m_Colors.GetColor(CColors::BisectBad);
1400 refLabel.name = _T("bad");
1403 else
1404 continue;
1406 refsToShow.push_back(refLabel);
1409 if (refsToShow.empty())
1411 *pResult = CDRF_DODEFAULT;
1412 return;
1415 DrawTagBranchMessage(pLVCD->nmcd.hdc, rect, pLVCD->nmcd.dwItemSpec, refsToShow);
1417 *pResult = CDRF_SKIPDEFAULT;
1418 return;
1425 if (pLVCD->iSubItem == LOGLIST_ACTION)
1427 if(this->m_IsIDReplaceAction)
1429 *pResult = CDRF_DODEFAULT;
1430 return;
1432 *pResult = CDRF_DODEFAULT;
1434 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
1435 return;
1437 int nIcons = 0;
1438 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1439 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1441 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec));
1442 CRect rect;
1443 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1444 //TRACE(_T("Action left %d right %d\r\n"),rect.left,rect.right);
1445 // Get the selected state of the
1446 // item being drawn.
1448 // Fill the background if necessary
1449 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec, rect);
1451 // Draw the icon(s) into the compatible DC
1452 int action = SafeGetAction(pLogEntry);
1454 if (!pLogEntry->m_IsDiffFiles)
1455 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1457 if (action & CTGitPath::LOGACTIONS_MODIFIED)
1458 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1459 ++nIcons;
1461 if (action & (CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_COPY))
1462 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1463 ++nIcons;
1465 if (action & CTGitPath::LOGACTIONS_DELETED)
1466 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1467 ++nIcons;
1469 if (action & CTGitPath::LOGACTIONS_REPLACED)
1470 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1471 ++nIcons;
1472 *pResult = CDRF_SKIPDEFAULT;
1473 return;
1476 break;
1478 *pResult = CDRF_DODEFAULT;
1481 // CGitLogListBase message handlers
1483 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1485 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1487 // Create a pointer to the item
1488 LV_ITEM* pItem = &(pDispInfo)->item;
1490 // Do the list need text information?
1491 if (!(pItem->mask & LVIF_TEXT))
1492 return;
1494 // By default, clear text buffer.
1495 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
1497 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
1499 *pResult = 0;
1500 if (m_bNoDispUpdates || bOutOfRange)
1501 return;
1503 // Which item number?
1504 int itemid = pItem->iItem;
1505 GitRev * pLogEntry = NULL;
1506 if (itemid < m_arShownList.GetCount())
1507 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(pItem->iItem));
1509 CString temp;
1510 if(m_IsOldFirst)
1512 temp.Format(_T("%d"),pItem->iItem+1);
1515 else
1517 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);
1520 // Which column?
1521 switch (pItem->iSubItem)
1523 case this->LOGLIST_GRAPH: //Graphic
1524 break;
1525 case this->LOGLIST_REBASE:
1527 if (this->m_IsRebaseReplaceGraph && pLogEntry)
1529 CTGitPath path;
1530 path.m_Action = SafeGetAction(pLogEntry) & CTGitPath::LOGACTIONS_REBASE_MODE_MASK;
1531 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);
1534 break;
1535 case this->LOGLIST_ACTION: //action -- no text in the column
1536 break;
1537 case this->LOGLIST_HASH:
1538 if(pLogEntry)
1539 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax);
1540 break;
1541 case this->LOGLIST_ID:
1542 if(this->m_IsIDReplaceAction)
1543 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);
1544 break;
1545 case this->LOGLIST_MESSAGE: //Message
1546 if (pLogEntry)
1547 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetSubject(), pItem->cchTextMax);
1548 break;
1549 case this->LOGLIST_AUTHOR: //Author
1550 if (pLogEntry)
1551 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorName(), pItem->cchTextMax);
1552 break;
1553 case this->LOGLIST_DATE: //Date
1554 if ( pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()) )
1555 lstrcpyn(pItem->pszText,
1556 CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
1557 pItem->cchTextMax);
1558 break;
1560 case this->LOGLIST_EMAIL:
1561 if (pLogEntry)
1562 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorEmail(), pItem->cchTextMax);
1563 break;
1565 case this->LOGLIST_COMMIT_NAME: //Commit
1566 if (pLogEntry)
1567 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterName(), pItem->cchTextMax);
1568 break;
1570 case this->LOGLIST_COMMIT_EMAIL: //Commit Email
1571 if (pLogEntry)
1572 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterEmail(), pItem->cchTextMax);
1573 break;
1575 case this->LOGLIST_COMMIT_DATE: //Commit Date
1576 if (pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()))
1577 lstrcpyn(pItem->pszText,
1578 CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes),
1579 pItem->cchTextMax);
1580 break;
1581 case this->LOGLIST_BUG: //Bug ID
1582 if(pLogEntry)
1583 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubject() + _T("\r\n\r\n") + pLogEntry->GetBody()), pItem->cchTextMax);
1584 break;
1586 default:
1587 ASSERT(false);
1591 bool CGitLogListBase::IsOnStash(int index)
1593 GitRev *rev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index));
1594 if (IsStash(rev))
1595 return true;
1596 if (index > 0)
1598 GitRev *preRev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index - 1));
1599 if (IsStash(preRev))
1600 return preRev->m_ParentHash.size() == 2 && preRev->m_ParentHash[1] == rev->m_CommitHash;
1602 return false;
1605 bool CGitLogListBase::IsStash(const GitRev * pSelLogEntry)
1607 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1609 if (m_HashMap[pSelLogEntry->m_CommitHash][i] == _T("refs/stash"))
1610 return true;
1612 return false;
1615 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1618 if (pWnd == GetHeaderCtrl())
1620 return m_ColumnManager.OnContextMenuHeader(pWnd,point,!!IsGroupViewEnabled());
1623 int selIndex = GetSelectionMark();
1624 if (selIndex < 0)
1625 return; // nothing selected, nothing to do with a context menu
1627 // if the user selected the info text telling about not all revisions shown due to
1628 // the "stop on copy/rename" option, we also don't show the context menu
1629 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
1630 return;
1632 // if the context menu is invoked through the keyboard, we have to use
1633 // a calculated position on where to anchor the menu on
1634 if ((point.x == -1) && (point.y == -1))
1636 CRect rect;
1637 GetItemRect(selIndex, &rect, LVIR_LABEL);
1638 ClientToScreen(&rect);
1639 point = rect.CenterPoint();
1641 m_nSearchIndex = selIndex;
1642 m_bCancelled = FALSE;
1644 // calculate some information the context menu commands can use
1645 // CString pathURL = GetURLFromPath(m_path);
1647 POSITION pos = GetFirstSelectedItemPosition();
1648 int indexNext = GetNextSelectedItem(pos);
1649 if (indexNext < 0)
1650 return;
1652 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(indexNext));
1653 #if 0
1654 GitRev revSelected = pSelLogEntry->Rev;
1655 GitRev revPrevious = git_revnum_t(revSelected)-1;
1656 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
1658 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
1660 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->SafeGetAt(i);
1661 if (changedpath->lCopyFromRev)
1662 revPrevious = changedpath->lCopyFromRev;
1665 GitRev revSelected2;
1666 if (pos)
1668 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1669 revSelected2 = pLogEntry->Rev;
1671 bool bAllFromTheSameAuthor = true;
1672 CString firstAuthor;
1673 CLogDataVector selEntries;
1674 GitRev revLowest, revHighest;
1675 GitRevRangeArray revisionRanges;
1677 POSITION pos = GetFirstSelectedItemPosition();
1678 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1679 revisionRanges.AddRevision(pLogEntry->Rev);
1680 selEntries.push_back(pLogEntry);
1681 firstAuthor = pLogEntry->sAuthor;
1682 revLowest = pLogEntry->Rev;
1683 revHighest = pLogEntry->Rev;
1684 while (pos)
1686 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1687 revisionRanges.AddRevision(pLogEntry->Rev);
1688 selEntries.push_back(pLogEntry);
1689 if (firstAuthor.Compare(pLogEntry->sAuthor))
1690 bAllFromTheSameAuthor = false;
1691 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
1692 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
1696 #endif
1698 int FirstSelect=-1, LastSelect=-1;
1699 pos = GetFirstSelectedItemPosition();
1700 FirstSelect = GetNextSelectedItem(pos);
1701 while(pos)
1703 LastSelect = GetNextSelectedItem(pos);
1705 //entry is selected, now show the popup menu
1706 CIconMenu popup;
1707 CIconMenu subbranchmenu, submenu, gnudiffmenu, diffmenu, revertmenu;
1709 if (popup.CreatePopupMenu())
1711 bool isHeadCommit = (pSelLogEntry->m_CommitHash == m_HeadHash);
1712 CString currentBranch = _T("refs/heads/") + g_Git.GetCurrentBranch();
1713 bool isMergeActive = CTGitPath(g_Git.m_CurrentDir).IsMergeActive();
1714 bool isStash = IsOnStash(indexNext);
1716 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_PICK))
1717 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1719 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SQUASH))
1720 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1722 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_EDIT))
1723 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1725 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SKIP))
1726 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1728 if(m_ContextMenuMask&(GetContextMenuBit(ID_REBASE_SKIP)|GetContextMenuBit(ID_REBASE_EDIT)|
1729 GetContextMenuBit(ID_REBASE_SQUASH)|GetContextMenuBit(ID_REBASE_PICK)))
1730 popup.AppendMenu(MF_SEPARATOR, NULL);
1732 if (GetSelectedCount() == 1)
1736 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1738 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE) && m_hasWC) // compare revision with WC
1739 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1741 else
1743 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1744 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1745 if (isMergeActive && (m_ContextMenuMask & GetContextMenuBit(ID_MERGE_ABORT)))
1746 popup.AppendMenuIcon(ID_MERGE_ABORT, IDS_MENUMERGEABORT, IDI_MERGEABORT);
1748 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1) && m_hasWC) // compare with WC, unified
1750 GitRev *pRev=pSelLogEntry;
1751 if (pSelLogEntry->m_ParentHash.empty())
1755 pRev->GetParentFromHash(pRev->m_CommitHash);
1757 catch (const char* msg)
1759 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1762 if(pRev->m_ParentHash.size()<=1)
1764 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1767 else
1769 gnudiffmenu.CreatePopupMenu();
1770 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1772 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1773 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1775 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1777 CString str;
1778 str.Format(IDS_PARENT, i + 1);
1779 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1784 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1787 GitRev *pRev=pSelLogEntry;
1788 if (pSelLogEntry->m_ParentHash.empty())
1792 pRev->GetParentFromHash(pRev->m_CommitHash);
1794 catch (const char* msg)
1796 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1799 if(pRev->m_ParentHash.size()<=1)
1801 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1802 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1803 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1805 else
1807 diffmenu.CreatePopupMenu();
1808 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1809 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1811 CString str;
1812 str.Format(IDS_PARENT, i + 1);
1813 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1814 if (i == 0 && CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1816 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1817 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1823 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1824 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1826 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
1827 popup.AppendMenu(MF_SEPARATOR, NULL);
1829 if (pSelLogEntry->m_CommitHash.IsEmpty() && !isMergeActive)
1831 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1832 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_COMMIT);
1835 if (CTGitPath(g_Git.m_CurrentDir).HasStashDir() && (pSelLogEntry->m_CommitHash.IsEmpty() || isStash))
1837 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1838 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_RELOCATE);
1840 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1841 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1843 popup.AppendMenu(MF_SEPARATOR, NULL);
1846 if (pSelLogEntry->m_CommitHash.IsEmpty())
1848 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
1849 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_PULL);
1851 popup.AppendMenu(MF_SEPARATOR, NULL);
1855 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1856 // {
1857 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1858 // }
1859 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1860 // {
1861 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1862 // }
1863 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1864 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1865 // {
1866 // popup.AppendMenu(MF_SEPARATOR, NULL);
1867 // }
1869 CString str,format;
1870 //if (m_hasWC)
1871 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1873 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1875 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1876 GetSelectedCount() == 1)
1877 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1879 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
1880 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
1882 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1883 str.Format(format,g_Git.GetCurrentBranch());
1885 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC && !isMergeActive && !isStash)
1886 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1888 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1889 str.Format(format,g_Git.GetCurrentBranch());
1891 if (m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC && !isStash)
1892 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1895 // Add Switch Branch express Menu
1896 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1897 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC && !isStash)
1900 std::vector<CString *> branchs;
1901 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1903 CString ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1904 if(ref.Find(_T("refs/heads/")) == 0 && ref != currentBranch)
1906 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1910 CString str;
1911 str.LoadString(IDS_SWITCH_BRANCH);
1913 if(branchs.size() == 1)
1915 str+=_T(" ");
1916 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1917 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1919 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1922 else if(branchs.size() > 1)
1924 subbranchmenu.CreatePopupMenu();
1925 for (size_t i = 0 ; i < branchs.size(); ++i)
1927 if (*branchs[i] != currentBranch)
1929 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1930 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1934 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1938 if (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC && !isStash)
1939 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1941 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH) && !isStash)
1942 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1944 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG) && !isStash)
1945 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1947 format.LoadString(IDS_REBASE_THIS_FORMAT);
1948 str.Format(format,g_Git.GetCurrentBranch());
1950 if (pSelLogEntry->m_CommitHash != m_HeadHash && m_hasWC && !isMergeActive && !isStash)
1951 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1952 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1954 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1955 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1957 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive && !isStash)
1959 GitRev *pRev = pSelLogEntry;
1960 if (pSelLogEntry->m_ParentHash.empty())
1964 pRev->GetParentFromHash(pRev->m_CommitHash);
1966 catch (const char* msg)
1968 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1971 if (pRev->m_ParentHash.size() == 1)
1973 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1975 else
1977 revertmenu.CreatePopupMenu();
1978 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
1980 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1982 CString str;
1983 str.Format(IDS_PARENT, i + 1);
1984 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str);
1989 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE) && !isStash)
1990 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
1992 popup.AppendMenu(MF_SEPARATOR, NULL);
1997 if(!pSelLogEntry->m_Ref.IsEmpty())
1999 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
2000 if (GetSelectedCount() == 1 && pSelLogEntry->m_Ref.Find(_T("refs/stash")) == 0)
2001 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
2002 popup.AppendMenu(MF_SEPARATOR, NULL);
2005 if (GetSelectedCount() >= 2)
2007 bool bAddSeparator = false;
2008 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
2010 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
2011 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
2014 if (GetSelectedCount() == 2)
2016 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
2017 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
2019 if (!pSelLogEntry->m_CommitHash.IsEmpty())
2021 CString firstSelHash = pSelLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2022 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2023 CString lastSelHash = pLastEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2024 CString menu;
2025 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("..") + firstSelHash);
2026 popup.AppendMenuIcon(ID_LOG_VIEWRANGE, menu, IDI_LOG);
2027 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("...") + firstSelHash);
2028 popup.AppendMenuIcon(ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE, menu, IDI_LOG);
2031 bAddSeparator = true;
2034 if (m_hasWC)
2036 bAddSeparator = true;
2039 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive)
2040 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
2042 if (bAddSeparator)
2043 popup.AppendMenu(MF_SEPARATOR, NULL);
2046 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
2048 bool bAddSeparator = false;
2049 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
2051 if (m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC && !isMergeActive)
2053 CString head;
2054 int headindex;
2055 headindex = this->GetHeadIndex();
2056 if(headindex>=0 && LastSelect >= headindex)
2058 head.Format(_T("HEAD~%d"),LastSelect-headindex);
2059 CGitHash hash;
2060 if (g_Git.GetHash(hash, head))
2061 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + head + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
2062 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2063 if(pLastEntry->m_CommitHash == hash) {
2064 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
2065 bAddSeparator = true;
2070 if (m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC && !isMergeActive) {
2071 if (GetSelectedCount() >= 2)
2072 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_EXPORT);
2073 else
2074 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
2075 bAddSeparator = true;
2078 if (GetSelectedCount() <= 2 || (IsSelectionContinuous() && GetSelectedCount() > 0 && !isStash))
2079 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
2080 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
2081 bAddSeparator = true;
2084 if (bAddSeparator)
2085 popup.AppendMenu(MF_SEPARATOR, NULL);
2088 if (m_hasWC && !isMergeActive && !isStash && (m_ContextMenuMask & GetContextMenuBit(ID_BISECTSTART)) && GetSelectedCount() == 2 && !reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(FirstSelect))->m_CommitHash.IsEmpty() && !CTGitPath(g_Git.m_CurrentDir).IsBisectActive())
2090 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART);
2091 popup.AppendMenu(MF_SEPARATOR, NULL);
2094 if (GetSelectedCount() == 1)
2096 bool bAddSeparator = false;
2097 if (m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && !isStash && !m_HashMap[pSelLogEntry->m_CommitHash].empty())
2099 // show the push-option only if the log entry has an associated local branch
2100 bool isLocal = false;
2101 for (size_t i = 0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2103 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
2104 isLocal = true;
2106 if (isLocal)
2108 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
2109 bAddSeparator = true;
2113 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2115 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2117 std::vector<CString *> branchs;
2118 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2120 if(m_HashMap[pSelLogEntry->m_CommitHash][i] != currentBranch)
2121 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
2123 CString str;
2124 if (branchs.size() == 1)
2126 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2127 str+=_T(" ");
2128 str += *branchs[0];
2129 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2130 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2131 bAddSeparator = true;
2133 else if (branchs.size() > 1)
2135 str.LoadString(IDS_DELETE_BRANCHTAG);
2136 submenu.CreatePopupMenu();
2137 for (size_t i = 0; i < branchs.size(); ++i)
2139 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2140 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2143 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2144 bAddSeparator = true;
2147 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2148 if (bAddSeparator)
2149 popup.AppendMenu(MF_SEPARATOR, NULL);
2150 } // GetSelectedCount() == 1
2152 if (GetSelectedCount() != 0)
2154 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
2155 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
2156 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
2157 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
2158 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARDMESSAGES))
2159 popup.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_COPYTOCLIPBOARDMESSAGES, IDI_COPYCLIP);
2162 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2163 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2165 if (GetSelectedCount() == 1 && m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES) && !pSelLogEntry->m_CommitHash.IsEmpty())
2166 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2168 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2169 // DialogEnableWindow(IDOK, FALSE);
2170 // SetPromptApp(&theApp);
2172 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2174 // EnableOKButton();
2175 } // if (popup.CreatePopupMenu())
2179 bool CGitLogListBase::IsSelectionContinuous()
2181 if ( GetSelectedCount()==1 )
2183 // if only one revision is selected, the selection is of course
2184 // continuous
2185 return true;
2188 POSITION pos = GetFirstSelectedItemPosition();
2189 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
2190 if (bContinuous)
2192 int itemindex = GetNextSelectedItem(pos);
2193 while (pos)
2195 int nextindex = GetNextSelectedItem(pos);
2196 if (nextindex - itemindex > 1)
2198 bContinuous = false;
2199 break;
2201 itemindex = nextindex;
2204 return bContinuous;
2207 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2210 CString sClipdata;
2211 POSITION pos = GetFirstSelectedItemPosition();
2212 if (pos != NULL)
2214 CString sRev;
2215 sRev.LoadString(IDS_LOG_REVISION);
2216 CString sAuthor;
2217 sAuthor.LoadString(IDS_LOG_AUTHOR);
2218 CString sDate;
2219 sDate.LoadString(IDS_LOG_DATE);
2220 CString sMessage;
2221 sMessage.LoadString(IDS_LOG_MESSAGE);
2222 bool first = true;
2223 while (pos)
2225 CString sLogCopyText;
2226 CString sPaths;
2227 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
2229 if (toCopy == ID_COPY_ALL)
2231 //pLogEntry->GetFiles(this)
2232 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2234 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2235 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2237 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(": ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2238 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED|CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2240 CString rename;
2241 rename.Format(from, ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2242 sPaths += _T(" ") + rename;
2244 sPaths += _T("\r\n");
2246 sPaths.Trim();
2247 CString body = pLogEntry->GetBody();
2248 body.Replace(_T("\n"), _T("\r\n"));
2249 sLogCopyText.Format(_T("%s: %s\r\n%s: %s <%s>\r\n%s: %s\r\n%s:\r\n%s\r\n----\r\n%s\r\n\r\n"),
2250 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
2251 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2252 (LPCTSTR)sDate,
2253 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2254 (LPCTSTR)sMessage, (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim(),
2255 (LPCTSTR)sPaths);
2256 sClipdata += sLogCopyText;
2258 else if (toCopy == ID_COPY_MESSAGE)
2260 CString body = pLogEntry->GetBody();
2261 body.Replace(_T("\n"), _T("\r\n"));
2262 sClipdata += _T("* ") + (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim() + _T("\r\n\r\n");
2264 else if (toCopy == ID_COPY_SUBJECT)
2266 sClipdata += _T("* ") + pLogEntry->GetSubject().Trim() + _T("\r\n\r\n");
2268 else
2270 if (!first)
2271 sClipdata += _T("\r\n");
2272 sClipdata += pLogEntry->m_CommitHash;
2275 first = false;
2277 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2282 void CGitLogListBase::DiffSelectedRevWithPrevious()
2284 if (m_bThreadRunning)
2285 return;
2287 int FirstSelect=-1, LastSelect=-1;
2288 POSITION pos = GetFirstSelectedItemPosition();
2289 FirstSelect = GetNextSelectedItem(pos);
2290 while(pos)
2292 LastSelect = GetNextSelectedItem(pos);
2295 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
2297 #if 0
2298 UpdateLogInfoLabel();
2299 int selIndex = m_LogList.GetSelectionMark();
2300 if (selIndex < 0)
2301 return;
2302 int selCount = m_LogList.GetSelectedCount();
2303 if (selCount != 1)
2304 return;
2306 // Find selected entry in the log list
2307 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2308 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2309 long rev1 = pLogEntry->Rev;
2310 long rev2 = rev1-1;
2311 CTGitPath path = m_path;
2313 // See how many files under the relative root were changed in selected revision
2314 int nChanged = 0;
2315 LogChangedPath * changed = NULL;
2316 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
2318 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
2319 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2321 ++nChanged;
2322 changed = cpath;
2326 if (m_path.IsDirectory() && nChanged == 1)
2328 // We're looking at the log for a directory and only one file under dir was changed in the revision
2329 // Do diff on that file instead of whole directory
2330 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
2333 m_bCancelled = FALSE;
2334 DialogEnableWindow(IDOK, FALSE);
2335 SetPromptApp(&theApp);
2336 theApp.DoWaitCursor(1);
2338 if (PromptShown())
2340 GitDiff diff(this, m_hWnd, true);
2341 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2342 diff.SetHEADPeg(m_LogRevision);
2343 diff.ShowCompare(path, rev2, path, rev1);
2345 else
2347 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2350 theApp.DoWaitCursor(-1);
2351 EnableOKButton();
2352 #endif
2355 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2357 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2358 *pResult = -1;
2360 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2361 return;
2362 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
2363 return;
2364 if (pFindInfo->lvfi.psz == 0)
2365 return;
2366 #if 0
2367 CString sCmp = pFindInfo->lvfi.psz;
2368 CString sRev;
2369 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2371 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2372 sRev.Format(_T("%ld"), pLogEntry->Rev);
2373 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2375 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2377 *pResult = i;
2378 return;
2381 else
2383 if (sCmp.Compare(sRev)==0)
2385 *pResult = i;
2386 return;
2390 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2392 for (int i=0; i<pFindInfo->iStart; ++i)
2394 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2395 sRev.Format(_T("%ld"), pLogEntry->Rev);
2396 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2398 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2400 *pResult = i;
2401 return;
2404 else
2406 if (sCmp.Compare(sRev)==0)
2408 *pResult = i;
2409 return;
2414 #endif
2415 *pResult = -1;
2418 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2420 ClearText();
2422 this->m_arShownList.SafeRemoveAll();
2424 this->m_logEntries.ClearAll();
2425 if (this->m_logEntries.ParserFromLog(path, -1, info, range))
2426 return -1;
2428 //this->m_logEntries.ParserFromLog();
2429 SetItemCountEx((int)m_logEntries.size());
2431 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2433 if(m_IsOldFirst)
2435 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2436 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2439 else
2441 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2442 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2446 ReloadHashMap();
2448 if(path)
2449 m_Path=*path;
2450 return 0;
2454 int CGitLogListBase::BeginFetchLog()
2456 ClearText();
2458 this->m_arShownList.SafeRemoveAll();
2460 this->m_logEntries.ClearAll();
2462 this->m_LogCache.ClearAllParent();
2464 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2466 CTGitPath *path;
2467 if(this->m_Path.IsEmpty())
2468 path=NULL;
2469 else
2470 path=&this->m_Path;
2472 int mask;
2473 mask = CGit::LOG_INFO_ONLY_HASH;
2474 if (m_bIncludeBoundaryCommits)
2475 mask |= CGit::LOG_INFO_BOUNDARY;
2476 // if(this->m_bAllBranch)
2477 mask |= m_ShowMask ;
2479 if(m_bShowWC)
2481 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2482 ResetWcRev();
2483 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2486 if (m_sRange.IsEmpty())
2487 m_sRange = _T("HEAD");
2489 CFilterData data;
2490 data.m_From = m_From;
2491 data.m_To =m_To;
2493 #if 0 /* use tortoiegit filter */
2494 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2495 data.m_Author = this->m_sFilterText;
2497 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2498 data.m_MessageFilter = this->m_sFilterText;
2500 data.m_IsRegex = m_bFilterWithRegex;
2501 #endif
2503 // follow does not work for directories
2504 if (!path || path->IsDirectory())
2505 mask &= ~CGit::LOG_INFO_FOLLOW;
2506 // follow does not work with all branches 8at least in TGit)
2507 if (mask & CGit::LOG_INFO_FOLLOW)
2508 mask &= ~CGit::LOG_INFO_ALL_BRANCH;
2510 CString cmd = g_Git.GetLogCmd(m_sRange, path, -1, mask, true, &data);
2512 //this->m_logEntries.ParserFromLog();
2513 if(IsInWorkingThread())
2515 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2517 else
2519 SetItemCountEx((int)m_logEntries.size());
2524 [] { git_init(); } ();
2526 catch (char* msg)
2528 CString err(msg);
2529 MessageBox(_T("Could not initialize libgit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2530 return -1;
2533 if (!g_Git.CanParseRev(m_sRange))
2535 if (!(mask & CGit::LOG_INFO_ALL_BRANCH))
2536 return 0;
2538 // if show all branches, pick any ref as dummy entry ref
2539 STRING_VECTOR list;
2540 if (g_Git.GetRefList(list))
2541 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2542 if (list.size() == 0)
2543 return 0;
2545 cmd = g_Git.GetLogCmd(list[0], path, -1, mask, true, &data);
2548 g_Git.m_critGitDllSec.Lock();
2549 try {
2550 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
2552 g_Git.m_critGitDllSec.Unlock();
2553 return -1;
2556 catch (char* msg)
2558 g_Git.m_critGitDllSec.Unlock();
2559 CString err(msg);
2560 MessageBox(_T("Could not open log.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2561 return -1;
2563 g_Git.m_critGitDllSec.Unlock();
2565 return 0;
2568 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2570 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2572 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2574 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2576 DiffSelectedRevWithPrevious();
2577 return TRUE;
2580 #if 0
2581 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2583 DiffSelectedFile();
2584 return TRUE;
2586 #endif
2588 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2590 // select all entries
2591 for (int i=0; i<GetItemCount(); ++i)
2593 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2595 return TRUE;
2598 #if 0
2599 if (m_hAccel && !bSkipAccelerator)
2601 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2602 if (ret)
2603 return TRUE;
2606 #endif
2607 //m_tooltips.RelayEvent(pMsg);
2608 return __super::PreTranslateMessage(pMsg);
2611 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2613 // a double click on an entry in the revision list has happened
2614 *pResult = 0;
2616 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2617 DiffSelectedRevWithPrevious();
2620 int CGitLogListBase::FetchLogAsync(void * data)
2622 ReloadHashMap();
2623 m_ProcData=data;
2624 m_bExitThread=FALSE;
2625 InterlockedExchange(&m_bThreadRunning, TRUE);
2626 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2627 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2628 if (m_LoadingThread ==NULL)
2630 InterlockedExchange(&m_bThreadRunning, FALSE);
2631 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2632 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2633 return -1;
2635 return 0;
2638 //this is the thread function which calls the subversion function
2639 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2641 return ((CGitLogListBase*)pVoid)->LogThread();
2644 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2646 //CTime time;
2647 oldest=CTime::GetCurrentTime();
2648 latest=CTime(1971,1,2,0,0,0);
2649 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2651 if(m_logEntries[i].IsEmpty())
2652 continue;
2654 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2655 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2657 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2658 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2662 if(latest<oldest)
2663 latest=oldest;
2666 UINT CGitLogListBase::LogThread()
2668 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2670 InterlockedExchange(&m_bThreadRunning, TRUE);
2671 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2673 ULONGLONG t1,t2;
2675 if(BeginFetchLog())
2677 InterlockedExchange(&m_bThreadRunning, FALSE);
2678 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2680 return 1;
2683 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2684 bool bRegex = false;
2685 if (m_bFilterWithRegex)
2686 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2688 TRACE(_T("\n===Begin===\n"));
2689 //Update work copy item;
2691 if (!m_logEntries.empty())
2693 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2695 m_arShownList.SafeAdd(pRev);
2699 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2701 // store commit number of the last selected commit/line before the refresh or -1
2702 int lastSelectedHashNItem = -1;
2703 int ret = 0;
2705 bool shouldWalk = true;
2706 if (!g_Git.CanParseRev(m_sRange))
2708 // walk revisions if show all branches and there exists any ref
2709 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH))
2710 shouldWalk = false;
2711 else
2713 STRING_VECTOR list;
2714 if (g_Git.GetRefList(list))
2715 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2716 if (list.size() == 0)
2717 shouldWalk = false;
2721 if (shouldWalk)
2723 g_Git.m_critGitDllSec.Lock();
2724 int total = 0;
2727 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2728 total = git_get_log_estimate_commit_count(m_DllGitLog);
2730 catch (char* msg)
2732 CString err(msg);
2733 MessageBox(_T("Could not get first commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2734 ret = -1;
2736 g_Git.m_critGitDllSec.Unlock();
2738 GIT_COMMIT commit;
2739 t2=t1=GetTickCount();
2740 int oldprecentage = 0;
2741 size_t oldsize = m_logEntries.size();
2742 std::map<CGitHash, std::set<CGitHash>> commitChildren;
2743 while (ret== 0 && !m_bExitThread)
2745 g_Git.m_critGitDllSec.Lock();
2748 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2750 catch (char* msg)
2752 g_Git.m_critGitDllSec.Unlock();
2753 CString err(msg);
2754 MessageBox(_T("Could not get next commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2755 break;
2757 g_Git.m_critGitDllSec.Unlock();
2759 if(ret)
2760 break;
2762 if (commit.m_ignore == 1)
2764 git_free_commit(&commit);
2765 continue;
2768 //printf("%s\r\n",commit.GetSubject());
2769 if(m_bExitThread)
2770 break;
2772 CGitHash hash = (char*)commit.m_hash ;
2774 GitRev *pRev = m_LogCache.GetCacheData(hash);
2775 pRev->m_GitCommit = commit;
2776 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2778 char *note=NULL;
2779 g_Git.m_critGitDllSec.Lock();
2782 git_get_notes(commit.m_hash, &note);
2784 catch (char* msg)
2786 g_Git.m_critGitDllSec.Unlock();
2787 CString err(msg);
2788 MessageBox(_T("Could not get commit notes.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2789 break;
2791 g_Git.m_critGitDllSec.Unlock();
2793 if(note)
2795 pRev->m_Notes.Empty();
2796 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2799 if(!pRev->m_IsDiffFiles)
2801 pRev->m_CallDiffAsync = DiffAsync;
2804 pRev->ParserParentFromCommit(&commit);
2805 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS) // See also ShouldShowFilter()
2807 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
2809 const CGitHash &parentHash = pRev->m_ParentHash[i];
2810 auto it = commitChildren.find(parentHash);
2811 if (it == commitChildren.end())
2813 it = commitChildren.insert(make_pair(parentHash, std::set<CGitHash>())).first;
2815 it->second.insert(pRev->m_CommitHash);
2819 #ifdef DEBUG
2820 pRev->DbgPrint();
2821 TRACE(_T("\n"));
2822 #endif
2824 bool visible = true;
2825 if(!m_sFilterText.IsEmpty())
2827 if(!IsMatchFilter(bRegex,pRev,pat))
2828 visible = false;
2830 if (visible && !ShouldShowFilter(pRev, commitChildren))
2831 visible = false;
2832 this->m_critSec.Lock();
2833 m_logEntries.append(hash, visible);
2834 if (visible)
2835 m_arShownList.SafeAdd(pRev);
2836 this->m_critSec.Unlock();
2838 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
2839 lastSelectedHashNItem = (int)m_arShownList.GetCount() - 1;
2841 if (!visible)
2842 continue;
2844 t2=GetTickCount();
2846 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2848 //update UI
2849 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
2850 if(percent > 99)
2851 percent =99;
2852 if(percent < GITLOG_START)
2853 percent = GITLOG_START +1;
2855 oldsize = m_logEntries.size();
2856 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2858 //if( percent > oldprecentage )
2860 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2861 oldprecentage = percent;
2864 if (lastSelectedHashNItem >= 0)
2865 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2867 t1 = t2;
2870 g_Git.m_critGitDllSec.Lock();
2871 git_close_log(m_DllGitLog);
2872 g_Git.m_critGitDllSec.Unlock();
2876 if (m_bExitThread)
2878 InterlockedExchange(&m_bThreadRunning, FALSE);
2879 return 0;
2882 //Update UI;
2883 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2885 if (lastSelectedHashNItem >= 0)
2886 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2888 if (this->m_hWnd)
2889 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2891 InterlockedExchange(&m_bThreadRunning, FALSE);
2893 return 0;
2896 void CGitLogListBase::FetchRemoteList()
2898 STRING_VECTOR remoteList;
2899 if (!g_Git.GetRemoteList(remoteList))
2900 m_SingleRemote = remoteList.size() == 1 ? remoteList[0] : _T("");
2901 else
2902 m_SingleRemote = _T("");
2905 void CGitLogListBase::FetchTrackingBranchList()
2907 m_TrackingMap.clear();
2908 for (MAP_HASH_NAME::iterator it = m_HashMap.begin(); it != m_HashMap.end(); ++it)
2910 for (size_t j = 0; j < it->second.size(); ++j)
2912 CString branchName;
2913 if (CGit::GetShortName(it->second[j], branchName, _T("refs/heads/")))
2915 CString configName;
2916 configName.Format(_T("branch.%s.remote"), branchName);
2917 CString pullRemote = g_Git.GetConfigValue(configName);
2919 configName.Format(_T("branch.%s.merge"), branchName);
2920 CString pullBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
2922 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2924 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
2931 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2933 SafeTerminateThread();
2935 this->SetItemCountEx(0);
2936 this->Clear();
2938 ResetWcRev();
2940 // HACK to hide graph column
2941 if (m_ShowMask & CGit::LOG_INFO_FOLLOW)
2942 SetColumnWidth(0, 0);
2943 else
2944 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
2946 //Update branch and Tag info
2947 ReloadHashMap();
2948 //Assume Thread have exited
2949 //if(!m_bThreadRunning)
2951 m_logEntries.clear();
2953 if(IsCleanFilter)
2955 m_sFilterText.Empty();
2956 m_From=-1;
2957 m_To=-1;
2960 InterlockedExchange(&m_bExitThread,FALSE);
2962 InterlockedExchange(&m_bThreadRunning, TRUE);
2963 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2964 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
2966 InterlockedExchange(&m_bThreadRunning, FALSE);
2967 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2968 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2972 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, std::tr1::wregex& pat, bool bMatchCase /* = false */)
2976 std::tr1::regex_constants::syntax_option_type type = std::tr1::regex_constants::ECMAScript;
2977 if (!bMatchCase)
2978 type |= std::tr1::regex_constants::icase;
2979 pat = std::tr1::wregex(regexp_str, type);
2980 return true;
2982 catch (std::exception) {}
2983 return false;
2985 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, std::tr1::wregex &pat)
2988 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
2989 CString sRev;
2991 if ((bRegex)&&(m_bFilterWithRegex))
2993 if (m_SelectedFilters & LOGFILTER_BUGID)
2995 if(this->m_bShowBugtraqColumn)
2997 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2999 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
3000 if (std::regex_search(std::wstring(sBugIds), pat, flags))
3002 return TRUE;
3007 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3009 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
3010 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
3012 return TRUE;
3016 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3018 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
3019 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetBody()), pat, flags))
3021 return TRUE;
3025 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3027 if (std::regex_search(std::wstring(pRev->GetAuthorName()), pat, flags))
3029 return TRUE;
3032 if (std::regex_search(std::wstring(pRev->GetCommitterName()), pat, flags))
3034 return TRUE;
3038 if (m_SelectedFilters & LOGFILTER_EMAILS)
3040 if (std::regex_search(std::wstring(pRev->GetAuthorEmail()), pat, flags))
3042 return TRUE;
3045 if (std::regex_search(std::wstring(pRev->GetCommitterEmail()), pat, flags))
3047 return TRUE;
3051 if (m_SelectedFilters & LOGFILTER_REVS)
3053 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3054 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags))
3056 return TRUE;
3060 if (m_SelectedFilters & LOGFILTER_REFNAME)
3062 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3063 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3065 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags))
3067 return TRUE;
3072 if (m_SelectedFilters & LOGFILTER_PATHS)
3074 CTGitPathList *pathList=NULL;
3075 if( pRev->m_IsDiffFiles)
3076 pathList = &pRev->GetFiles(this);
3077 else
3079 if(!pRev->m_IsSimpleListReady)
3080 pRev->SafeGetSimpleList(&g_Git);
3083 if(pathList)
3084 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
3086 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
3088 return true;
3090 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
3092 return true;
3096 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3098 if (std::regex_search(std::wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
3100 return true;
3105 else
3107 CString find = m_sFilterText;
3108 find.MakeLower();
3110 if (m_SelectedFilters & LOGFILTER_BUGID)
3112 if(this->m_bShowBugtraqColumn)
3114 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
3116 sBugIds.MakeLower();
3117 if ((sBugIds.Find(find) >= 0))
3119 return TRUE;
3124 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3126 CString msg = pRev->GetSubject();
3128 msg = msg.MakeLower();
3129 if ((msg.Find(find) >= 0))
3131 return TRUE;
3135 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3137 CString msg = pRev->GetBody();
3139 msg = msg.MakeLower();
3140 if ((msg.Find(find) >= 0))
3142 return TRUE;
3146 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3148 CString msg = pRev->GetAuthorName();
3149 msg = msg.MakeLower();
3150 if ((msg.Find(find) >= 0))
3152 return TRUE;
3156 if (m_SelectedFilters & LOGFILTER_EMAILS)
3158 CString msg = pRev->GetAuthorEmail();
3159 msg = msg.MakeLower();
3160 if ((msg.Find(find) >= 0))
3162 return TRUE;
3166 if (m_SelectedFilters & LOGFILTER_REVS)
3168 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3169 if ((sRev.Find(find) >= 0))
3171 return TRUE;
3175 if (m_SelectedFilters & LOGFILTER_REFNAME)
3177 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3178 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3180 if (it->Find(find) >= 0)
3182 return TRUE;
3187 if (m_SelectedFilters & LOGFILTER_PATHS)
3189 CTGitPathList *pathList=NULL;
3190 if( pRev->m_IsDiffFiles)
3191 pathList = &pRev->GetFiles(this);
3192 else
3194 if(!pRev->m_IsSimpleListReady)
3195 pRev->SafeGetSimpleList(&g_Git);
3197 if(pathList)
3198 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
3200 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
3201 CString path = cpath->GetGitOldPathString();
3202 path.MakeLower();
3203 if ((path.Find(find)>=0))
3205 return true;
3207 path = cpath->GetGitPathString();
3208 path.MakeLower();
3209 if ((path.Find(find)>=0))
3211 return true;
3215 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3217 CString path = pRev->m_SimpleFileList[i];
3218 path.MakeLower();
3219 if ((path.Find(find)>=0))
3221 return true;
3225 } // else (from if (bRegex))
3226 return FALSE;
3229 static bool CStringStartsWith(const CString &str, const CString &prefix)
3231 return str.Left(prefix.GetLength()) == prefix;
3233 bool CGitLogListBase::ShouldShowFilter(GitRev *pRev, const std::map<CGitHash, std::set<CGitHash>> &commitChildren)
3235 if (m_ShowFilter & FILTERSHOW_ANYCOMMIT)
3236 return true;
3238 if (m_ShowFilter & FILTERSHOW_REFS)
3240 // Keep all refs.
3241 const STRING_VECTOR &refList = m_HashMap[pRev->m_CommitHash];
3242 for (size_t i = 0; i < refList.size(); ++i)
3244 const CString &str = refList[i];
3245 if (CStringStartsWith(str, _T("refs/heads/")))
3247 if (m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES)
3248 return true;
3250 else if (CStringStartsWith(str, _T("refs/remotes/")))
3252 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
3253 return true;
3255 else if (CStringStartsWith(str, _T("refs/tags/")))
3257 if (m_ShowRefMask & LOGLIST_SHOWTAGS)
3258 return true;
3260 else if (CStringStartsWith(str, _T("refs/stash")))
3262 if (m_ShowRefMask & LOGLIST_SHOWSTASH)
3263 return true;
3265 else if (CStringStartsWith(str, _T("refs/bisect/")))
3267 if (m_ShowRefMask & LOGLIST_SHOWBISECT)
3268 return true;
3271 // Keep the head too.
3272 if (pRev->m_CommitHash == m_HeadHash)
3273 return true;
3276 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS)
3278 if (pRev->ParentsCount() > 1)
3279 return true;
3280 auto childrenIt = commitChildren.find(pRev->m_CommitHash);
3281 if (childrenIt != commitChildren.end())
3283 const std::set<CGitHash> &children = childrenIt->second;
3284 if (children.size() > 1)
3285 return true;
3288 return false;
3291 void CGitLogListBase::ShowGraphColumn(bool bShow)
3293 // HACK to hide graph column
3294 if (bShow)
3295 SetColumnWidth(0, 0);
3296 else
3297 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3300 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
3303 pShownlist->SafeRemoveAll();
3305 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
3306 bool bRegex = false;
3307 if (m_bFilterWithRegex)
3308 bRegex = ValidateRegexp(m_sFilterText, pat, false);
3310 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
3311 CString sRev;
3312 for (DWORD i=0; i<m_logEntries.size(); ++i)
3314 if ((bRegex)&&(m_bFilterWithRegex))
3316 #if 0
3317 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3319 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
3320 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
3322 pShownlist->SafeAdd(m_logEntries[i]);
3323 continue;
3326 #endif
3327 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3329 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
3330 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
3332 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3333 continue;
3336 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3338 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
3339 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
3341 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3342 continue;
3345 if (m_SelectedFilters & LOGFILTER_PATHS)
3347 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3349 bool bGoing = true;
3350 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3352 CTGitPath cpath = pathList[cpPathIndex];
3353 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
3355 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3356 bGoing = false;
3357 continue;
3359 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
3361 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3362 bGoing = false;
3363 continue;
3365 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
3367 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3368 bGoing = false;
3369 continue;
3373 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3375 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
3377 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3378 continue;
3381 if (m_SelectedFilters & LOGFILTER_EMAILS)
3383 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorEmail()), pat, flags) && IsEntryInDateRange(i))
3385 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3386 continue;
3389 if (m_SelectedFilters & LOGFILTER_REVS)
3391 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3392 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
3394 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3395 continue;
3398 if (m_SelectedFilters & LOGFILTER_REFNAME)
3400 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3401 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3403 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags) && IsEntryInDateRange(i))
3405 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3406 continue;
3410 } // if (bRegex)
3411 else
3413 CString find = m_sFilterText;
3414 find.MakeLower();
3415 #if 0
3416 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3418 CString sBugIDs = m_logEntries[i]->sBugIDs;
3420 sBugIDs = sBugIDs.MakeLower();
3421 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3423 pShownlist->SafeAdd(m_logEntries[i]);
3424 continue;
3427 #endif
3428 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3430 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
3432 msg = msg.MakeLower();
3433 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3435 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3436 continue;
3439 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3441 CString msg = m_logEntries.GetGitRevAt(i).GetBody();
3443 msg = msg.MakeLower();
3444 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3446 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3447 continue;
3450 if (m_SelectedFilters & LOGFILTER_PATHS)
3452 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3454 bool bGoing = true;
3455 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3457 CTGitPath cpath = pathList[cpPathIndex];
3458 CString path = cpath.GetGitOldPathString();
3459 path.MakeLower();
3460 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3462 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3463 bGoing = false;
3464 continue;
3466 path = cpath.GetGitPathString();
3467 path.MakeLower();
3468 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3470 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3471 bGoing = false;
3472 continue;
3474 path = cpath.GetActionName();
3475 path.MakeLower();
3476 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3478 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3479 bGoing = false;
3480 continue;
3484 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3486 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
3487 msg = msg.MakeLower();
3488 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3490 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3491 continue;
3494 if (m_SelectedFilters & LOGFILTER_EMAILS)
3496 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorEmail();
3497 msg = msg.MakeLower();
3498 if ((msg.Find(find) >= 0) && (IsEntryInDateRange(i)))
3500 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3501 continue;
3504 if (m_SelectedFilters & LOGFILTER_REVS)
3506 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3507 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3509 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3510 continue;
3513 if (m_SelectedFilters & LOGFILTER_REFNAME)
3515 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3516 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3518 if (it->Find(find) >= 0 && IsEntryInDateRange(i))
3520 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3521 continue;
3525 } // else (from if (bRegex))
3526 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
3530 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
3533 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
3535 if(m_From == -1)
3536 if(m_To == -1)
3537 return true;
3538 else
3539 return time <= m_To;
3540 else
3541 if(m_To == -1)
3542 return time >= m_From;
3543 else
3544 return ((time >= m_From)&&(time <= m_To));
3546 return TRUE; /* git dll will filter time range */
3548 // return TRUE;
3550 void CGitLogListBase::StartFilter()
3552 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3553 RecalculateShownList(&m_arShownList);
3554 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3557 DeleteAllItems();
3558 SetItemCountEx(ShownCountWithStopped());
3559 RedrawItems(0, ShownCountWithStopped());
3560 Invalidate();
3563 void CGitLogListBase::RemoveFilter()
3566 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3568 m_arShownList.SafeRemoveAll();
3570 // reset the time filter too
3571 #if 0
3572 m_timFrom = (__time64_t(m_tFrom));
3573 m_timTo = (__time64_t(m_tTo));
3574 m_DateFrom.SetTime(&m_timFrom);
3575 m_DateTo.SetTime(&m_timTo);
3576 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
3577 m_DateTo.SetRange(&m_timFrom, &m_timTo);
3578 #endif
3580 for (DWORD i=0; i<m_logEntries.size(); ++i)
3582 if(this->m_IsOldFirst)
3584 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
3586 else
3588 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
3591 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
3592 DeleteAllItems();
3593 SetItemCountEx(ShownCountWithStopped());
3594 RedrawItems(0, ShownCountWithStopped());
3596 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3599 void CGitLogListBase::Clear()
3601 m_arShownList.SafeRemoveAll();
3602 DeleteAllItems();
3604 m_logEntries.ClearAll();
3608 void CGitLogListBase::OnDestroy()
3610 // save the column widths to the registry
3611 SaveColumnWidths();
3613 SafeTerminateThread();
3614 SafeTerminateAsyncDiffThread();
3616 int retry = 0;
3617 while(m_LogCache.SaveCache())
3619 if(retry > 5)
3620 break;
3621 Sleep(1000);
3623 ++retry;
3625 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
3626 // MB_YESNO) == IDNO)
3627 // break;
3630 CHintListCtrl::OnDestroy();
3633 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3635 CRect rect;
3636 int i=(int)wParam;
3637 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3638 this->InvalidateRect(rect);
3640 return 0;
3644 * Save column widths to the registry
3646 void CGitLogListBase::SaveColumnWidths()
3648 int maxcol = m_ColumnManager.GetColumnCount();
3650 // HACK that graph column is always shown
3651 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3653 for (int col = 0; col < maxcol; ++col)
3654 if (m_ColumnManager.IsVisible (col))
3655 m_ColumnManager.ColumnResized (col);
3657 m_ColumnManager.WriteSettings();
3660 int CGitLogListBase::GetHeadIndex()
3662 if(m_HeadHash.IsEmpty())
3663 return -1;
3665 for (int i = 0; i < m_arShownList.GetCount(); ++i)
3667 GitRev *pRev = (GitRev*)m_arShownList.SafeGetAt(i);
3668 if(pRev)
3670 if(pRev->m_CommitHash.ToString() == m_HeadHash )
3671 return i;
3674 return -1;
3676 void CGitLogListBase::OnFind()
3678 if (!m_pFindDialog)
3680 m_pFindDialog = new CFindDlg(this);
3681 m_pFindDialog->Create(this);
3684 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
3686 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
3688 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
3690 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
3691 Default();
3693 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3695 if (GetSelectedCount() != 0)
3696 return 0;
3698 CGitHash theSelectedHash = m_lastSelectedHash;
3699 SetItemState((int)itemToSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3700 m_lastSelectedHash = theSelectedHash;
3702 int countPerPage = GetCountPerPage();
3703 EnsureVisible(max(0, (int)itemToSelect-countPerPage/2), FALSE);
3704 EnsureVisible(min(GetItemCount(), (int)itemToSelect+countPerPage/2), FALSE);
3705 EnsureVisible((int)itemToSelect, FALSE);
3706 return 0;
3708 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3711 ASSERT(m_pFindDialog != NULL);
3712 bool bFound = false;
3713 int i=0;
3715 if (m_pFindDialog->IsTerminating())
3717 // invalidate the handle identifying the dialog box.
3718 m_pFindDialog = NULL;
3719 return 0;
3722 INT_PTR cnt = m_arShownList.GetCount();
3724 if(m_pFindDialog->IsRef())
3726 CString str;
3727 str=m_pFindDialog->GetFindString();
3729 CGitHash hash;
3731 if(!str.IsEmpty())
3733 if (g_Git.GetHash(hash, str + _T("^{}"))) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3734 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ref \"") + str + _T("^{}\".")), _T("TortoiseGit"), MB_ICONERROR);
3737 if(!hash.IsEmpty())
3739 for (i = 0; i < cnt; ++i)
3741 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3742 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3744 bFound = true;
3745 break;
3749 if (!bFound)
3751 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3752 return 0;
3756 if (m_pFindDialog->FindNext() && !bFound)
3758 //read data from dialog
3759 CString FindText = m_pFindDialog->GetFindString();
3760 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3762 std::tr1::wregex pat;
3763 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3765 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_not_null;
3767 for (i = m_nSearchIndex + 1; ; ++i)
3769 if (i >= cnt)
3771 i = 0;
3772 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3774 if (m_nSearchIndex >= 0)
3776 if (i == m_nSearchIndex)
3778 ::MessageBeep(0xFFFFFFFF);
3779 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 3, 100);
3780 break;
3784 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3786 CString str;
3787 str+=pLogEntry->m_CommitHash.ToString();
3788 str+=_T("\n");
3790 for (size_t j = 0; j < this->m_HashMap[pLogEntry->m_CommitHash].size(); ++j)
3792 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3793 str+=_T("\n");
3796 str+=pLogEntry->GetAuthorEmail();
3797 str+=_T("\n");
3798 str+=pLogEntry->GetAuthorName();
3799 str+=_T("\n");
3800 str+=pLogEntry->GetBody();
3801 str+=_T("\n");
3802 str+=pLogEntry->GetCommitterEmail();
3803 str+=_T("\n");
3804 str+=pLogEntry->GetCommitterName();
3805 str+=_T("\n");
3806 str+=pLogEntry->GetSubject();
3807 str+=_T("\n");
3810 /*Because changed files list is loaded on demand when gui show,
3811 files will empty when files have not fetched.
3813 we can add it back by using one-way diff(with outnumber changed and rename detect.
3814 here just need changed filename list. one-way is much quicker.
3816 if(pLogEntry->m_IsFull)
3818 for (int i = 0; i < pLogEntry->GetFiles(this).GetCount(); ++i)
3820 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3821 str+=_T("\n");
3822 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3823 str+=_T("\n");
3826 else
3828 if(!pLogEntry->m_IsSimpleListReady)
3829 pLogEntry->SafeGetSimpleList(&g_Git);
3831 for (size_t i = 0; i < pLogEntry->m_SimpleFileList.size(); ++i)
3833 str+=pLogEntry->m_SimpleFileList[i];
3834 str+=_T("\n");
3840 if (bRegex)
3842 if (std::regex_search(std::wstring(str), pat, flags))
3844 bFound = true;
3845 break;
3848 else
3850 if (bMatchCase)
3852 if (str.Find(FindText) >= 0)
3854 bFound = true;
3855 break;
3859 else
3861 CString msg = str;
3862 msg = msg.MakeLower();
3863 CString find = FindText.MakeLower();
3864 if (msg.Find(find) >= 0)
3866 bFound = TRUE;
3867 break;
3871 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; ++i)
3873 } // if(m_pFindDialog->FindNext())
3874 //UpdateLogInfoLabel();
3876 if (bFound)
3878 m_nSearchIndex = i;
3879 EnsureVisible(i, FALSE);
3880 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3881 SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3882 SetSelectionMark(i);
3883 //FillLogMessageCtrl();
3884 UpdateData(FALSE);
3887 return 0;
3890 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3892 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3894 *pResult = FALSE;
3897 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3899 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3901 Invalidate(FALSE);