Add option 'Show complete log'
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
blob01d9339f02ac7091f5d716ff1a719295ce99aad5
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);
136 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAMEPREVIOUS);
138 m_ColumnRegKey=_T("log");
140 m_bTagsBranchesOnRightSide = !!CRegDWORD(_T("Software\\TortoiseGit\\DrawTagsBranchesOnRightSide"), FALSE);
141 m_bSymbolizeRefNames = !!CRegDWORD(_T("Software\\TortoiseGit\\SymbolizeRefNames"), FALSE);
142 m_bIncludeBoundaryCommits = !!CRegDWORD(_T("Software\\TortoiseGit\\LogIncludeBoundaryCommits"), FALSE);
144 m_LineWidth = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogLineWidth"), 2));
145 m_NodeSize = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogNodeSize"), 10));
147 m_AsyncThreadExit = FALSE;
148 m_AsyncDiffEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
149 m_AsynDiffListLock.Init();
151 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
152 if (m_DiffingThread ==NULL)
154 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
155 return;
160 int CGitLogListBase::AsyncDiffThread()
162 m_AsyncThreadExited = false;
163 while(!m_AsyncThreadExit)
165 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
167 GitRev *pRev = NULL;
168 while(!m_AsyncThreadExit && !m_AsynDiffList.empty())
170 m_AsynDiffListLock.Lock();
171 pRev = m_AsynDiffList.back();
172 m_AsynDiffList.pop_back();
173 m_AsynDiffListLock.Unlock();
175 if( pRev->m_CommitHash.IsEmpty() )
177 if(pRev->m_IsDiffFiles)
178 continue;
180 pRev->GetFiles(this).Clear();
181 pRev->m_ParentHash.clear();
182 pRev->m_ParentHash.push_back(m_HeadHash);
183 if(g_Git.IsInitRepos())
185 if (g_Git.GetInitAddList(pRev->GetFiles(this)))
186 CMessageBox::Show(NULL, _T("Run ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
189 else
191 g_Git.GetCommitDiffList(pRev->m_CommitHash.ToString(),this->m_HeadHash.ToString(), pRev->GetFiles(this));
193 int dummyAction = 0;
194 int *action = &dummyAction;
195 SafeGetAction(pRev, &action);
196 *action = 0;
198 for (int j = 0; j < pRev->GetFiles(this).GetCount(); ++j)
199 *action |= pRev->GetFiles(this)[j].m_Action;
201 pRev->GetUnRevFiles().FillUnRev(CTGitPath::LOGACTIONS_UNVER);
203 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
204 InterlockedExchange(&pRev->m_IsFull, TRUE);
206 pRev->GetBody().Format(IDS_FILESCHANGES, pRev->GetFiles(this).GetCount());
207 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
208 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
211 m_critSec_AsyncDiff.Lock();
212 int ret = pRev->CheckAndDiff();
213 m_critSec_AsyncDiff.Unlock();
214 if (!ret)
215 { // fetch change file list
216 for (int i = GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex() + GetCountPerPage(); ++i)
218 if(i < m_arShownList.GetCount())
220 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(i);
221 if(data->m_CommitHash == pRev->m_CommitHash)
223 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
224 break;
229 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
231 POSITION pos = GetFirstSelectedItemPosition();
232 int nItem = GetNextSelectedItem(pos);
234 if(nItem>=0)
236 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(nItem);
237 if(data)
238 if(data->m_CommitHash == pRev->m_CommitHash)
240 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
247 m_AsyncThreadExited = true;
248 return 0;
250 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
252 if (exclusivelyShow)
254 m_ContextMenuMask &= hideMask;
256 else
258 m_ContextMenuMask &= ~hideMask;
262 CGitLogListBase::~CGitLogListBase()
264 InterlockedExchange(&m_bNoDispUpdates, TRUE);
265 this->m_arShownList.SafeRemoveAll();
267 DestroyIcon(m_hModifiedIcon);
268 DestroyIcon(m_hReplacedIcon);
269 DestroyIcon(m_hAddedIcon);
270 DestroyIcon(m_hDeletedIcon);
271 m_logEntries.ClearAll();
273 if (m_boldFont)
274 DeleteObject(m_boldFont);
276 if ( m_pStoreSelection )
278 delete m_pStoreSelection;
279 m_pStoreSelection = NULL;
282 SafeTerminateThread();
283 SafeTerminateAsyncDiffThread();
285 if(m_AsyncDiffEvent)
286 CloseHandle(m_AsyncDiffEvent);
290 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)
291 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
292 ON_REGISTERED_MESSAGE(m_ScrollToMessage, OnScrollToMessage)
293 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
294 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
295 ON_WM_CONTEXTMENU()
296 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
297 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
298 ON_WM_CREATE()
299 ON_WM_DESTROY()
300 ON_MESSAGE(MSG_LOADED,OnLoad)
301 ON_WM_MEASUREITEM()
302 ON_WM_MEASUREITEM_REFLECT()
303 ON_NOTIFY(HDN_BEGINTRACKA, 0, OnHdnBegintrack)
304 ON_NOTIFY(HDN_BEGINTRACKW, 0, OnHdnBegintrack)
305 ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
306 ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
307 ON_NOTIFY(HDN_ENDTRACK, 0, OnColumnResized)
308 ON_NOTIFY(HDN_ENDDRAG, 0, OnColumnMoved)
309 END_MESSAGE_MAP()
311 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
313 //if (m_nRowHeight>0)
315 lpMeasureItemStruct->itemHeight = 50;
319 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
321 PreSubclassWindow();
322 return CHintListCtrl::OnCreate(lpCreateStruct);
325 void CGitLogListBase::PreSubclassWindow()
327 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
328 // load the icons for the action columns
329 // m_Theme.Open(m_hWnd, L"ListView");
330 SetWindowTheme(m_hWnd, L"Explorer", NULL);
331 CHintListCtrl::PreSubclassWindow();
334 void CGitLogListBase::InsertGitColumn()
336 CString temp;
338 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
339 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
340 if (DWORD(regFullRowSelect))
341 exStyle |= LVS_EX_FULLROWSELECT;
342 SetExtendedStyle(exStyle);
344 // only load properties if we have a repository
345 if (GitAdminDir().HasAdminDir(g_Git.m_CurrentDir) || GitAdminDir().IsBareRepo(g_Git.m_CurrentDir))
346 UpdateProjectProperties();
348 static UINT normal[] =
350 IDS_LOG_GRAPH,
351 IDS_LOG_REBASE,
352 IDS_LOG_ID,
353 IDS_LOG_HASH,
354 IDS_LOG_ACTIONS,
355 IDS_LOG_MESSAGE,
356 IDS_LOG_AUTHOR,
357 IDS_LOG_DATE,
358 IDS_LOG_EMAIL,
359 IDS_LOG_COMMIT_NAME,
360 IDS_LOG_COMMIT_EMAIL,
361 IDS_LOG_COMMIT_DATE,
362 IDS_LOG_BUGIDS,
365 static int with[] =
367 ICONITEMBORDER+16*4,
368 ICONITEMBORDER+16*4,
369 ICONITEMBORDER+16*4,
370 ICONITEMBORDER+16*4,
371 ICONITEMBORDER+16*4,
372 LOGLIST_MESSAGE_MIN,
373 ICONITEMBORDER+16*4,
374 ICONITEMBORDER+16*4,
375 ICONITEMBORDER+16*4,
376 ICONITEMBORDER+16*4,
377 ICONITEMBORDER+16*4,
378 ICONITEMBORDER+16*4,
379 ICONITEMBORDER+16*4,
381 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
383 DWORD hideColumns = 0;
384 if(this->m_IsRebaseReplaceGraph)
386 hideColumns |= GIT_LOG_GRAPH;
387 m_dwDefaultColumns |= GIT_LOG_REBASE;
389 else
391 hideColumns |= GIT_LOG_REBASE;
394 if(this->m_IsIDReplaceAction)
396 hideColumns |= GIT_LOG_ACTIONS;
397 m_dwDefaultColumns |= GIT_LOG_ID;
398 m_dwDefaultColumns |= GIT_LOG_HASH;
400 else
402 hideColumns |= GIT_LOG_ID;
404 if(this->m_bShowBugtraqColumn)
406 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
408 else
410 hideColumns |= GIT_LOGLIST_BUG;
412 SetRedraw(false);
414 m_ColumnManager.SetNames(normal, _countof(normal));
415 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey+_T("loglist"), _countof(normal), with);
417 SetRedraw(true);
421 * Resizes all columns in a list control to values in registry.
423 void CGitLogListBase::ResizeAllListCtrlCols()
425 // column max and min widths to allow
426 static const int nMinimumWidth = 10;
427 static const int nMaximumWidth = 1000;
428 CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
429 if (pHdrCtrl)
431 int numcols = pHdrCtrl->GetItemCount();
432 for (int col = 0; col < numcols; ++col)
434 // get width for this col last time from registry
435 CString regentry;
436 regentry.Format( _T("Software\\TortoiseGit\\%s\\ColWidth%d"),m_ColumnRegKey, col);
437 CRegDWORD regwidth(regentry, 0);
438 int cx = regwidth;
439 if ( cx == 0 )
441 // no saved value, setup sensible defaults
442 if (col == this->LOGLIST_MESSAGE)
444 cx = LOGLIST_MESSAGE_MIN;
446 else
448 cx = ICONITEMBORDER+16*4;
451 if (cx < nMinimumWidth)
453 cx = nMinimumWidth;
455 else if (cx > nMaximumWidth)
457 cx = nMaximumWidth;
460 SetColumnWidth(col, cx);
467 void CGitLogListBase::FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect)
469 LVITEM rItem;
470 SecureZeroMemory(&rItem, sizeof(LVITEM));
471 rItem.mask = LVIF_STATE;
472 rItem.iItem = (int)Index;
473 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
474 GetItem(&rItem);
476 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(Index);
477 HBRUSH brush = NULL;
479 if (!(rItem.state & LVIS_SELECTED))
481 int action = SafeGetAction(pLogEntry);
482 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
483 brush = ::CreateSolidBrush(RGB(156,156,156));
484 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
485 brush = ::CreateSolidBrush(RGB(200,200,128));
487 else if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
489 if (rItem.state & LVIS_SELECTED)
491 if (::GetFocus() == m_hWnd)
492 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
493 else
494 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
497 if (brush != NULL)
499 ::FillRect(hdc, &rect, brush);
500 ::DeleteObject(brush);
504 void DrawTrackingRoundRect(HDC hdc, CRect rect, HBRUSH brush, COLORREF darkColor)
506 POINT point = { 4, 4 };
507 CRect rt2 = rect;
508 rt2.DeflateRect(1, 1);
509 rt2.OffsetRect(2, 2);
511 HPEN nullPen = ::CreatePen(PS_NULL, 0, 0);
512 HPEN oldpen = (HPEN)::SelectObject(hdc, nullPen);
513 HBRUSH darkBrush = (HBRUSH)::CreateSolidBrush(darkColor);
514 HBRUSH oldbrush = (HBRUSH)::SelectObject(hdc, darkBrush);
515 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
517 ::SelectObject(hdc, brush);
518 rt2.OffsetRect(-2, -2);
519 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
520 ::SelectObject(hdc, oldbrush);
521 ::SelectObject(hdc, oldpen);
522 ::DeleteObject(nullPen);
523 ::DeleteObject(darkBrush);
526 void DrawLightning(HDC hdc, CRect rect, COLORREF color, int bold)
528 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
529 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
530 ::MoveToEx(hdc, rect.left + 7, rect.top, NULL);
531 ::LineTo(hdc, rect.left + 1, (rect.top + rect.bottom) / 2);
532 ::LineTo(hdc, rect.left + 6, (rect.top + rect.bottom) / 2);
533 ::LineTo(hdc, rect.left, rect.bottom);
534 ::SelectObject(hdc, oldpen);
535 ::DeleteObject(pen);
538 void DrawUpTriangle(HDC hdc, CRect rect, COLORREF color, int bold)
540 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
541 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
542 ::MoveToEx(hdc, (rect.left + rect.right) / 2, rect.top, NULL);
543 ::LineTo(hdc, rect.left, rect.bottom);
544 ::LineTo(hdc, rect.right, rect.bottom);
545 ::LineTo(hdc, (rect.left + rect.right) / 2, rect.top);
546 ::SelectObject(hdc, oldpen);
547 ::DeleteObject(pen);
550 void CGitLogListBase::DrawTagBranchMessage(HDC hdc, CRect &rect, INT_PTR index, std::vector<REFLABEL> &refList)
552 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
553 CRect rt=rect;
554 LVITEM rItem;
555 SecureZeroMemory(&rItem, sizeof(LVITEM));
556 rItem.mask = LVIF_STATE;
557 rItem.iItem = (int)index;
558 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
559 GetItem(&rItem);
561 CDC W_Dc;
562 W_Dc.Attach(hdc);
564 HTHEME hTheme = NULL;
565 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
566 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
568 if (!m_bTagsBranchesOnRightSide)
569 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
571 SIZE oneSpaceSize;
572 GetTextExtentPoint32(hdc, L" ", 1, &oneSpaceSize);
573 rt.left += oneSpaceSize.cx;
575 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
577 int txtState = LISS_NORMAL;
578 if (rItem.state & LVIS_SELECTED)
579 txtState = LISS_SELECTED;
581 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, data->GetSubject(), -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS, 0, &rt);
583 else
585 if (rItem.state & LVIS_SELECTED)
587 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
588 ::DrawText(hdc,data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
589 ::SetTextColor(hdc, clrOld);
591 else
593 ::DrawText(hdc, data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
597 if (m_bTagsBranchesOnRightSide)
599 SIZE size;
600 GetTextExtentPoint32(hdc, data->GetSubject(), data->GetSubject().GetLength(), &size);
602 rt.left += oneSpaceSize.cx + size.cx;
604 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
607 if (hTheme)
608 CloseThemeData(hTheme);
610 W_Dc.Detach();
613 void CGitLogListBase::DrawTagBranch(HDC hdc, CDC &W_Dc, HTHEME hTheme, CRect &rect, CRect &rt, LVITEM &rItem, GitRev* data, std::vector<REFLABEL> &refList)
615 for (unsigned int i = 0; i < refList.size(); ++i)
617 CString shortname = !refList[i].simplifiedName.IsEmpty() ? refList[i].simplifiedName : refList[i].name;
618 HBRUSH brush = 0;
619 COLORREF colRef = refList[i].color;
620 bool singleRemote = refList[i].singleRemote;
621 bool hasTracking = refList[i].hasTracking;
622 bool sameName = refList[i].sameName;
623 bool annotatedTag = refList[i].annotatedTag;
625 //When row selected, ajust label color
626 if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
628 if (rItem.state & LVIS_SELECTED)
629 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
632 brush = ::CreateSolidBrush(colRef);
634 if (!shortname.IsEmpty() && (rt.left < rect.right))
636 SIZE size;
637 memset(&size,0,sizeof(SIZE));
638 GetTextExtentPoint32(hdc, shortname, shortname.GetLength(), &size);
640 rt.SetRect(rt.left, rt.top, rt.left + size.cx, rt.bottom);
641 rt.right += 8;
643 int textpos = DT_CENTER;
645 if (rt.right > rect.right)
647 rt.right = rect.right;
648 textpos = 0;
651 CRect textRect = rt;
653 if (singleRemote)
655 rt.right += 8;
656 textRect.OffsetRect(8, 0);
659 if (sameName)
660 rt.right += 8;
662 if (hasTracking)
664 DrawTrackingRoundRect(hdc, rt, brush, m_Colors.Darken(colRef, 100));
666 else
668 //Fill interior of ref label
669 ::FillRect(hdc, &rt, brush);
672 //Draw edge of label
673 CRect rectEdge = rt;
675 if (!hasTracking)
677 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 100), m_Colors.Darken(colRef, 100));
678 rectEdge.DeflateRect(1, 1);
679 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 50), m_Colors.Darken(colRef, 50));
682 if (annotatedTag)
684 rt.right += 8;
685 POINT trianglept[3] = { { rt.right - 8, rt.top }, { rt.right, (rt.top + rt.bottom) / 2 }, { rt.right - 8, rt.bottom } };
686 HRGN hrgn = ::CreatePolygonRgn(trianglept, 3, ALTERNATE);
687 ::FillRgn(hdc, hrgn, brush);
688 ::DeleteObject(hrgn);
689 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[0].y, NULL);
690 HPEN pen;
691 HPEN oldpen = (HPEN)SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Lighten(colRef, 50)));
692 ::LineTo(hdc, trianglept[1].x - 1, trianglept[1].y - 1);
693 ::DeleteObject(pen);
694 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Darken(colRef, 50)));
695 ::LineTo(hdc, trianglept[2].x - 1, trianglept[2].y - 1);
696 ::DeleteObject(pen);
697 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, colRef));
698 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[2].y - 3, NULL);
699 ::LineTo(hdc, trianglept[0].x - 1, trianglept[0].y);
700 ::DeleteObject(pen);
701 SelectObject(hdc, oldpen);
704 //Draw text inside label
705 bool customColor = (colRef & 0xff) * 30 + ((colRef >> 8) & 0xff) * 59 + ((colRef >> 16) & 0xff) * 11 <= 12800; // check if dark background
706 if (!customColor && IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
708 int txtState = LISS_NORMAL;
709 if (rItem.state & LVIS_SELECTED)
710 txtState = LISS_SELECTED;
712 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, 0, &textRect);
714 else
716 W_Dc.SetBkMode(TRANSPARENT);
717 if (customColor || (rItem.state & LVIS_SELECTED))
719 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
720 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
721 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
722 ::SetTextColor(hdc,clrOld);
724 else
726 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
730 if (singleRemote)
732 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
733 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
734 CRect newRect;
735 newRect.SetRect(rt.left + 4, rt.top + 4, rt.left + 8, rt.bottom - 4);
736 DrawLightning(hdc, newRect, color, bold);
739 if (sameName)
741 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
742 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
743 CRect newRect;
744 newRect.SetRect(rt.right - 12, rt.top + 4, rt.right - 4, rt.bottom - 4);
745 DrawUpTriangle(hdc, newRect, color, bold);
748 rt.left = rt.right + 1;
750 if (brush)
751 ::DeleteObject(brush);
753 rt.right = rect.right;
756 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
758 // Returns ((256 - amount)*col1 + amount*col2) / 256;
759 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
760 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
761 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
764 Gdiplus::Color GetGdiColor(COLORREF col)
766 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
768 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
769 const COLORREF& col,const COLORREF& activeColor, int top
772 int h = laneHeight / 2;
773 int m = (x1 + x2) / 2;
774 int r = (x2 - x1) * m_NodeSize / 30;
775 int d = 2 * r;
777 #define P_CENTER m , h+top
778 #define P_0 x2, h+top
779 #define P_90 m , 0+top-1
780 #define P_180 x1, h+top
781 #define P_270 m , 2 * h+top +1
782 #define R_CENTER m - r, h - r+top, d, d
785 #define DELTA_UR_B 2*(x1 - m), 2*h +top
786 #define DELTA_UR_E 0*16, 90*16 +top // -,
788 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
789 #define DELTA_DR_E 270*16, 90*16 +top // -'
791 #define DELTA_UL_B 2*(x2 - m), 2*h +top
792 #define DELTA_UL_E 90*16, 90*16 +top // ,-
794 #define DELTA_DL_B 2*(x2 - m),2*-h +top
795 #define DELTA_DL_E 180*16, 90*16 // '-
797 #define CENTER_UR x1, 2*h, 225
798 #define CENTER_DR x1, 0 , 135
799 #define CENTER_UL x2, 2*h, 315
800 #define CENTER_DL x2, 0 , 45
803 Gdiplus::Graphics graphics( hdc );
805 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
807 // arc
808 switch (type) {
809 case Lanes::JOIN:
810 case Lanes::JOIN_R:
811 case Lanes::HEAD:
812 case Lanes::HEAD_R:
814 Gdiplus::LinearGradientBrush gradient(
815 Gdiplus::Point(x1-2, h+top-2),
816 Gdiplus::Point(P_270),
817 GetGdiColor(activeColor),GetGdiColor(col));
820 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
821 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
823 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
824 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
825 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
827 break;
829 case Lanes::JOIN_L:
832 Gdiplus::LinearGradientBrush gradient(
833 Gdiplus::Point(P_270),
834 Gdiplus::Point(x2+1, h+top-1),
835 GetGdiColor(col),GetGdiColor(activeColor));
838 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
839 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
841 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
842 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
843 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
846 break;
848 case Lanes::TAIL:
849 case Lanes::TAIL_R:
852 Gdiplus::LinearGradientBrush gradient(
853 Gdiplus::Point(x1-2, h+top-2),
854 Gdiplus::Point(P_90),
855 GetGdiColor(activeColor),GetGdiColor(col));
857 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
859 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
861 #if 0
862 QConicalGradient gradient(CENTER_DR);
863 gradient.setColorAt(0.375, activeCol);
864 gradient.setColorAt(0.625, col);
865 myPen.setBrush(gradient);
866 p->setPen(myPen);
867 p->drawArc(P_CENTER, DELTA_DR);
868 #endif
869 break;
871 default:
872 break;
876 //static QPen myPen(Qt::black, 2); // fast path here
877 CPen pen;
878 pen.CreatePen(PS_SOLID,2,col);
879 //myPen.setColor(col);
880 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
882 Gdiplus::Pen myPen(GetGdiColor(col), (Gdiplus::REAL)m_LineWidth);
884 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
886 //p->setPen(myPen);
888 // vertical line
889 switch (type) {
890 case Lanes::ACTIVE:
891 case Lanes::NOT_ACTIVE:
892 case Lanes::MERGE_FORK:
893 case Lanes::MERGE_FORK_R:
894 case Lanes::MERGE_FORK_L:
895 case Lanes::JOIN:
896 case Lanes::JOIN_R:
897 case Lanes::JOIN_L:
898 case Lanes::CROSS:
899 //DrawLine(hdc,P_90,P_270);
900 graphics.DrawLine(&myPen,P_90,P_270);
901 //p->drawLine(P_90, P_270);
902 break;
903 case Lanes::HEAD_L:
904 case Lanes::BRANCH:
905 //DrawLine(hdc,P_CENTER,P_270);
906 graphics.DrawLine(&myPen,P_CENTER,P_270);
907 //p->drawLine(P_CENTER, P_270);
908 break;
909 case Lanes::TAIL_L:
910 case Lanes::INITIAL:
911 case Lanes::BOUNDARY:
912 case Lanes::BOUNDARY_C:
913 case Lanes::BOUNDARY_R:
914 case Lanes::BOUNDARY_L:
915 //DrawLine(hdc,P_90, P_CENTER);
916 graphics.DrawLine(&myPen,P_90,P_CENTER);
917 //p->drawLine(P_90, P_CENTER);
918 break;
919 default:
920 break;
923 myPen.SetColor(GetGdiColor(activeColor));
925 // horizontal line
926 switch (type) {
927 case Lanes::MERGE_FORK:
928 case Lanes::JOIN:
929 case Lanes::HEAD:
930 case Lanes::TAIL:
931 case Lanes::CROSS:
932 case Lanes::CROSS_EMPTY:
933 case Lanes::BOUNDARY_C:
934 //DrawLine(hdc,P_180,P_0);
935 graphics.DrawLine(&myPen,P_180,P_0);
936 //p->drawLine(P_180, P_0);
937 break;
938 case Lanes::MERGE_FORK_R:
939 case Lanes::BOUNDARY_R:
940 //DrawLine(hdc,P_180,P_CENTER);
941 graphics.DrawLine(&myPen,P_180,P_CENTER);
942 //p->drawLine(P_180, P_CENTER);
943 break;
944 case Lanes::MERGE_FORK_L:
945 case Lanes::HEAD_L:
946 case Lanes::TAIL_L:
947 case Lanes::BOUNDARY_L:
948 //DrawLine(hdc,P_CENTER,P_0);
949 graphics.DrawLine(&myPen,P_CENTER,P_0);
950 //p->drawLine(P_CENTER, P_0);
951 break;
952 default:
953 break;
956 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
958 CBrush brush;
959 brush.CreateSolidBrush(col);
960 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
962 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
963 // center symbol, e.g. rect or ellipse
964 switch (type) {
965 case Lanes::ACTIVE:
966 case Lanes::INITIAL:
967 case Lanes::BRANCH:
969 //p->setPen(Qt::NoPen);
970 //p->setBrush(col);
971 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
972 graphics.FillEllipse(&myBrush, R_CENTER);
973 //p->drawEllipse(R_CENTER);
974 break;
975 case Lanes::MERGE_FORK:
976 case Lanes::MERGE_FORK_R:
977 case Lanes::MERGE_FORK_L:
978 //p->setPen(Qt::NoPen);
979 //p->setBrush(col);
980 //p->drawRect(R_CENTER);
981 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
982 graphics.FillRectangle(&myBrush, R_CENTER);
983 break;
984 case Lanes::UNAPPLIED:
985 // Red minus sign
986 //p->setPen(Qt::NoPen);
987 //p->setBrush(Qt::red);
988 //p->drawRect(m - r, h - 1, d, 2);
989 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
990 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
991 break;
992 case Lanes::APPLIED:
993 // Green plus sign
994 //p->setPen(Qt::NoPen);
995 //p->setBrush(DARK_GREEN);
996 //p->drawRect(m - r, h - 1, d, 2);
997 //p->drawRect(m - 1, h - r, 2, d);
998 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
999 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
1000 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
1001 break;
1002 case Lanes::BOUNDARY:
1003 //p->setBrush(back);
1004 //p->drawEllipse(R_CENTER);
1005 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
1006 graphics.DrawEllipse(&myPen, R_CENTER);
1007 break;
1008 case Lanes::BOUNDARY_C:
1009 case Lanes::BOUNDARY_R:
1010 case Lanes::BOUNDARY_L:
1011 //p->setBrush(back);
1012 //p->drawRect(R_CENTER);
1013 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
1014 graphics.FillRectangle(&myBrush,R_CENTER);
1015 break;
1016 default:
1017 break;
1020 ::SelectObject(hdc,oldpen);
1021 ::SelectObject(hdc,oldbrush);
1022 #undef P_CENTER
1023 #undef P_0
1024 #undef P_90
1025 #undef P_180
1026 #undef P_270
1027 #undef R_CENTER
1030 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
1032 // TODO: unfinished
1033 // return;
1034 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
1035 if(data->m_CommitHash.IsEmpty())
1036 return;
1038 CRect rt=rect;
1039 LVITEM rItem;
1040 SecureZeroMemory(&rItem, sizeof(LVITEM));
1041 rItem.mask = LVIF_STATE;
1042 rItem.iItem = (int)index;
1043 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
1044 GetItem(&rItem);
1046 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1048 if (data->m_Lanes.empty())
1049 m_logEntries.setLane(data->m_CommitHash);
1051 std::vector<int>& lanes=data->m_Lanes;
1052 size_t laneNum = lanes.size();
1053 UINT activeLane = 0;
1054 for (UINT i = 0; i < laneNum; ++i)
1055 if (Lanes::isMerge(lanes[i])) {
1056 activeLane = i;
1057 break;
1060 int x2 = 0;
1061 int maxWidth = rect.Width();
1062 int lw = 3 * rect.Height() / 4; //laneWidth()
1064 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1065 //if (opt.state & QStyle::State_Selected)
1066 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1068 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; ++i)
1071 int x1 = x2;
1072 x2 += lw;
1074 int ln = lanes[i];
1075 if (ln == Lanes::EMPTY)
1076 continue;
1078 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1079 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1082 #if 0
1083 for (UINT i = 0; i < laneNum && x2 < maxWidth; ++i) {
1085 int x1 = x2;
1086 x2 += lw;
1088 int ln = lanes[i];
1089 if (ln == Lanes::EMPTY)
1090 continue;
1092 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1093 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1095 if (ln == Lanes::CROSS)
1097 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1098 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1100 else
1101 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1103 #endif
1107 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1110 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1111 // Take the default processing unless we set this to something else below.
1112 *pResult = CDRF_DODEFAULT;
1114 if (m_bNoDispUpdates)
1115 return;
1117 switch (pLVCD->nmcd.dwDrawStage)
1119 case CDDS_PREPAINT:
1121 *pResult = CDRF_NOTIFYITEMDRAW;
1122 return;
1124 break;
1125 case CDDS_ITEMPREPAINT:
1127 // This is the prepaint stage for an item. Here's where we set the
1128 // item's text color.
1130 // Tell Windows to send draw notifications for each subitem.
1131 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1133 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1135 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1137 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1138 if (data)
1140 int action = SafeGetAction(data);
1141 if (action & (CTGitPath::LOGACTIONS_REBASE_DONE | CTGitPath::LOGACTIONS_REBASE_SKIP))
1142 crText = RGB(128,128,128);
1144 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
1145 pLVCD->clrTextBk = RGB(156,156,156);
1146 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
1147 pLVCD->clrTextBk = RGB(200,200,128);
1148 else
1149 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1151 if (action & CTGitPath::LOGACTIONS_REBASE_CURRENT)
1153 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1154 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1157 if (data->m_CommitHash == m_HeadHash && m_bNoHightlightHead == FALSE)
1159 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1160 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1163 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1164 // crText = GetSysColor(COLOR_GRAYTEXT);
1166 if (data->m_CommitHash.IsEmpty())
1168 //crText = GetSysColor(RGB(200,200,0));
1169 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1170 // We changed the font, so we're returning CDRF_NEWFONT. This
1171 // tells the control to recalculate the extent of the text.
1172 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1176 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
1178 if (m_bStrictStopped)
1179 crText = GetSysColor(COLOR_GRAYTEXT);
1181 // Store the color back in the NMLVCUSTOMDRAW struct.
1182 pLVCD->clrText = crText;
1183 return;
1185 break;
1186 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
1188 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
1190 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
1193 if (pLVCD->iSubItem == LOGLIST_GRAPH && m_sFilterText.IsEmpty() && (m_ShowFilter & FILTERSHOW_MERGEPOINTS))
1195 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )
1197 CRect rect;
1198 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1200 //TRACE(_T("A Graphic left %d right %d\r\n"),rect.left,rect.right);
1201 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1203 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1204 if( !data ->m_CommitHash.IsEmpty())
1205 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1207 *pResult = CDRF_SKIPDEFAULT;
1208 return;
1212 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
1214 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1216 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1218 if (!m_HashMap[data->m_CommitHash].empty() && !(SafeGetAction(data) & CTGitPath::LOGACTIONS_REBASE_DONE))
1220 CRect rect;
1221 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1223 // BEGIN: extended redraw, HACK for issue #1618 and #2014
1224 // not in FillBackGround method, because this only affected the message subitem
1225 if (0 != pLVCD->iStateId) // don't know why, but this helps against loosing the focus rect
1226 return;
1228 int index = (int)pLVCD->nmcd.dwItemSpec;
1229 int state = GetItemState(index, LVIS_SELECTED);
1230 int txtState = LISS_NORMAL;
1231 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater() && GetHotItem() == (int)index)
1233 if (state & LVIS_SELECTED)
1234 txtState = LISS_HOTSELECTED;
1235 else
1236 txtState = LISS_HOT;
1238 else if (state & LVIS_SELECTED)
1240 if (::GetFocus() == m_hWnd)
1241 txtState = LISS_SELECTED;
1242 else
1243 txtState = LISS_SELECTEDNOTFOCUS;
1246 HTHEME hTheme = nullptr;
1247 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
1248 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
1250 if (hTheme && IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTDETAIL, txtState))
1251 DrawThemeParentBackground(m_hWnd, pLVCD->nmcd.hdc, &rect);
1252 else
1254 HBRUSH brush = ::CreateSolidBrush(pLVCD->clrTextBk);
1255 ::FillRect(pLVCD->nmcd.hdc, rect, brush);
1256 ::DeleteObject(brush);
1258 if (hTheme)
1260 CRect rt;
1261 // get rect of whole line
1262 GetItemRect(index, rt, LVIR_BOUNDS);
1263 CRect rect2 = rect;
1264 if (txtState == LISS_NORMAL) // avoid drawing of grey borders
1265 rect2.DeflateRect(1, 1, 1, 1);
1267 // calculate background for rect of whole line, but limit redrawing to SubItem rect
1268 DrawThemeBackground(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, rt, rect2);
1270 CloseThemeData(hTheme);
1272 // END: extended redraw
1274 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1276 std::vector<REFLABEL> refsToShow;
1277 STRING_VECTOR remoteTrackingList;
1278 STRING_VECTOR refList = m_HashMap[data->m_CommitHash];
1279 for (unsigned int i = 0; i < refList.size(); ++i)
1281 CString str = refList[i];
1283 REFLABEL refLabel;
1284 refLabel.color = RGB(255, 255, 255);
1285 refLabel.singleRemote = false;
1286 refLabel.hasTracking = false;
1287 refLabel.sameName = false;
1288 refLabel.annotatedTag = false;
1289 if (CGit::GetShortName(str, refLabel.name, _T("refs/heads/")))
1291 if (!(m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES))
1292 continue;
1293 if (refLabel.name == m_CurrentBranch )
1294 refLabel.color = m_Colors.GetColor(CColors::CurrentBranch);
1295 else
1296 refLabel.color = m_Colors.GetColor(CColors::LocalBranch);
1298 std::pair<CString, CString> trackingEntry = m_TrackingMap[refLabel.name];
1299 CString pullRemote = trackingEntry.first;
1300 CString pullBranch = trackingEntry.second;
1301 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
1303 CString defaultUpstream;
1304 defaultUpstream.Format(_T("refs/remotes/%s/%s"), pullRemote, pullBranch);
1305 refLabel.hasTracking = true;
1306 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
1308 bool found = false;
1309 for (size_t j = i + 1; j < refList.size(); ++j)
1311 if (refList[j] == defaultUpstream)
1313 found = true;
1314 break;
1318 if (found)
1320 bool sameName = pullBranch == refLabel.name;
1321 refsToShow.push_back(refLabel);
1322 CGit::GetShortName(defaultUpstream, refLabel.name, _T("refs/remotes/"));
1323 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1324 if (m_bSymbolizeRefNames)
1326 if (!m_SingleRemote.IsEmpty() && m_SingleRemote == pullRemote)
1328 refLabel.simplifiedName = _T("/") + (sameName ? CString() : pullBranch);
1329 refLabel.singleRemote = true;
1331 else if (sameName)
1332 refLabel.simplifiedName = pullRemote + _T("/");
1333 refLabel.sameName = sameName;
1335 refsToShow.push_back(refLabel);
1336 remoteTrackingList.push_back(defaultUpstream);
1337 continue;
1342 else if (CGit::GetShortName(str, refLabel.name, _T("refs/remotes/")))
1344 if (!(m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES))
1345 continue;
1347 bool found = false;
1348 for (size_t j = 0; j < remoteTrackingList.size(); ++j)
1350 if (remoteTrackingList[j] == str)
1352 found = true;
1353 break;
1356 if (found)
1357 continue;
1359 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1360 if (m_bSymbolizeRefNames)
1362 if (!m_SingleRemote.IsEmpty() && refLabel.name.Left(m_SingleRemote.GetLength() + 1) == m_SingleRemote + _T("/"))
1364 refLabel.simplifiedName = _T("/") + refLabel.name.Mid(m_SingleRemote.GetLength() + 1);
1365 refLabel.singleRemote = true;
1369 else if (CGit::GetShortName(str, refLabel.name, _T("refs/tags/")))
1371 if (!(m_ShowRefMask & LOGLIST_SHOWTAGS))
1372 continue;
1373 refLabel.color = m_Colors.GetColor(CColors::Tag);
1374 refLabel.annotatedTag = str.Right(3) == _T("^{}");
1376 else if (CGit::GetShortName(str, refLabel.name, _T("refs/stash")))
1378 if (!(m_ShowRefMask & LOGLIST_SHOWSTASH))
1379 continue;
1380 refLabel.color = m_Colors.GetColor(CColors::Stash);
1381 refLabel.name = _T("stash");
1383 else if (CGit::GetShortName(str, refLabel.name, _T("refs/bisect/")))
1385 if (!(m_ShowRefMask & LOGLIST_SHOWBISECT))
1386 continue;
1387 if (refLabel.name.Find(_T("good")) == 0)
1389 refLabel.color = m_Colors.GetColor(CColors::BisectGood);
1390 refLabel.name = _T("good");
1392 if (refLabel.name.Find(_T("bad")) == 0)
1394 refLabel.color = m_Colors.GetColor(CColors::BisectBad);
1395 refLabel.name = _T("bad");
1398 else
1399 continue;
1401 refsToShow.push_back(refLabel);
1404 if (refsToShow.empty())
1406 *pResult = CDRF_DODEFAULT;
1407 return;
1410 DrawTagBranchMessage(pLVCD->nmcd.hdc, rect, pLVCD->nmcd.dwItemSpec, refsToShow);
1412 *pResult = CDRF_SKIPDEFAULT;
1413 return;
1420 if (pLVCD->iSubItem == LOGLIST_ACTION)
1422 if(this->m_IsIDReplaceAction)
1424 *pResult = CDRF_DODEFAULT;
1425 return;
1427 *pResult = CDRF_DODEFAULT;
1429 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
1430 return;
1432 int nIcons = 0;
1433 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1434 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1436 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec));
1437 CRect rect;
1438 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1439 //TRACE(_T("Action left %d right %d\r\n"),rect.left,rect.right);
1440 // Get the selected state of the
1441 // item being drawn.
1443 // Fill the background if necessary
1444 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec, rect);
1446 // Draw the icon(s) into the compatible DC
1447 int action = SafeGetAction(pLogEntry);
1449 if (!pLogEntry->m_IsDiffFiles)
1450 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1452 if (action & CTGitPath::LOGACTIONS_MODIFIED)
1453 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1454 ++nIcons;
1456 if (action & (CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_COPY))
1457 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1458 ++nIcons;
1460 if (action & CTGitPath::LOGACTIONS_DELETED)
1461 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1462 ++nIcons;
1464 if (action & CTGitPath::LOGACTIONS_REPLACED)
1465 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1466 ++nIcons;
1467 *pResult = CDRF_SKIPDEFAULT;
1468 return;
1471 break;
1473 *pResult = CDRF_DODEFAULT;
1476 // CGitLogListBase message handlers
1478 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1480 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1482 // Create a pointer to the item
1483 LV_ITEM* pItem = &(pDispInfo)->item;
1485 // Do the list need text information?
1486 if (!(pItem->mask & LVIF_TEXT))
1487 return;
1489 // By default, clear text buffer.
1490 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
1492 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
1494 *pResult = 0;
1495 if (m_bNoDispUpdates || bOutOfRange)
1496 return;
1498 // Which item number?
1499 int itemid = pItem->iItem;
1500 GitRev * pLogEntry = NULL;
1501 if (itemid < m_arShownList.GetCount())
1502 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(pItem->iItem));
1504 CString temp;
1505 if(m_IsOldFirst)
1507 temp.Format(_T("%d"),pItem->iItem+1);
1510 else
1512 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);
1515 // Which column?
1516 switch (pItem->iSubItem)
1518 case this->LOGLIST_GRAPH: //Graphic
1519 break;
1520 case this->LOGLIST_REBASE:
1522 if (this->m_IsRebaseReplaceGraph && pLogEntry)
1524 CTGitPath path;
1525 path.m_Action = SafeGetAction(pLogEntry) & CTGitPath::LOGACTIONS_REBASE_MODE_MASK;
1526 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);
1529 break;
1530 case this->LOGLIST_ACTION: //action -- no text in the column
1531 break;
1532 case this->LOGLIST_HASH:
1533 if(pLogEntry)
1534 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax);
1535 break;
1536 case this->LOGLIST_ID:
1537 if(this->m_IsIDReplaceAction)
1538 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);
1539 break;
1540 case this->LOGLIST_MESSAGE: //Message
1541 if (pLogEntry)
1542 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetSubject(), pItem->cchTextMax);
1543 break;
1544 case this->LOGLIST_AUTHOR: //Author
1545 if (pLogEntry)
1546 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorName(), pItem->cchTextMax);
1547 break;
1548 case this->LOGLIST_DATE: //Date
1549 if ( pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()) )
1550 lstrcpyn(pItem->pszText,
1551 CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
1552 pItem->cchTextMax);
1553 break;
1555 case this->LOGLIST_EMAIL:
1556 if (pLogEntry)
1557 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorEmail(), pItem->cchTextMax);
1558 break;
1560 case this->LOGLIST_COMMIT_NAME: //Commit
1561 if (pLogEntry)
1562 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterName(), pItem->cchTextMax);
1563 break;
1565 case this->LOGLIST_COMMIT_EMAIL: //Commit Email
1566 if (pLogEntry)
1567 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterEmail(), pItem->cchTextMax);
1568 break;
1570 case this->LOGLIST_COMMIT_DATE: //Commit Date
1571 if (pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()))
1572 lstrcpyn(pItem->pszText,
1573 CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes),
1574 pItem->cchTextMax);
1575 break;
1576 case this->LOGLIST_BUG: //Bug ID
1577 if(pLogEntry)
1578 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubject() + _T("\r\n\r\n") + pLogEntry->GetBody()), pItem->cchTextMax);
1579 break;
1581 default:
1582 ASSERT(false);
1586 bool CGitLogListBase::IsOnStash(int index)
1588 GitRev *rev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index));
1589 if (IsStash(rev))
1590 return true;
1591 if (index > 0)
1593 GitRev *preRev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index - 1));
1594 if (IsStash(preRev))
1595 return preRev->m_ParentHash.size() == 2 && preRev->m_ParentHash[1] == rev->m_CommitHash;
1597 return false;
1600 bool CGitLogListBase::IsStash(const GitRev * pSelLogEntry)
1602 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1604 if (m_HashMap[pSelLogEntry->m_CommitHash][i] == _T("refs/stash"))
1605 return true;
1607 return false;
1610 void CGitLogListBase::GetParentHashes(GitRev *pRev, GIT_REV_LIST &parentHash)
1612 if (pRev->m_ParentHash.empty())
1616 pRev->GetParentFromHash(pRev->m_CommitHash);
1618 catch (const char* msg)
1620 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1623 parentHash = pRev->m_ParentHash;
1626 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1629 if (pWnd == GetHeaderCtrl())
1631 return m_ColumnManager.OnContextMenuHeader(pWnd,point,!!IsGroupViewEnabled());
1634 int selIndex = GetSelectionMark();
1635 if (selIndex < 0)
1636 return; // nothing selected, nothing to do with a context menu
1638 // if the user selected the info text telling about not all revisions shown due to
1639 // the "stop on copy/rename" option, we also don't show the context menu
1640 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
1641 return;
1643 // if the context menu is invoked through the keyboard, we have to use
1644 // a calculated position on where to anchor the menu on
1645 if ((point.x == -1) && (point.y == -1))
1647 CRect rect;
1648 GetItemRect(selIndex, &rect, LVIR_LABEL);
1649 ClientToScreen(&rect);
1650 point = rect.CenterPoint();
1652 m_nSearchIndex = selIndex;
1653 m_bCancelled = FALSE;
1655 // calculate some information the context menu commands can use
1656 // CString pathURL = GetURLFromPath(m_path);
1658 POSITION pos = GetFirstSelectedItemPosition();
1659 int indexNext = GetNextSelectedItem(pos);
1660 if (indexNext < 0)
1661 return;
1663 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(indexNext));
1664 #if 0
1665 GitRev revSelected = pSelLogEntry->Rev;
1666 GitRev revPrevious = git_revnum_t(revSelected)-1;
1667 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
1669 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
1671 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->SafeGetAt(i);
1672 if (changedpath->lCopyFromRev)
1673 revPrevious = changedpath->lCopyFromRev;
1676 GitRev revSelected2;
1677 if (pos)
1679 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1680 revSelected2 = pLogEntry->Rev;
1682 bool bAllFromTheSameAuthor = true;
1683 CString firstAuthor;
1684 CLogDataVector selEntries;
1685 GitRev revLowest, revHighest;
1686 GitRevRangeArray revisionRanges;
1688 POSITION pos = GetFirstSelectedItemPosition();
1689 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1690 revisionRanges.AddRevision(pLogEntry->Rev);
1691 selEntries.push_back(pLogEntry);
1692 firstAuthor = pLogEntry->sAuthor;
1693 revLowest = pLogEntry->Rev;
1694 revHighest = pLogEntry->Rev;
1695 while (pos)
1697 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1698 revisionRanges.AddRevision(pLogEntry->Rev);
1699 selEntries.push_back(pLogEntry);
1700 if (firstAuthor.Compare(pLogEntry->sAuthor))
1701 bAllFromTheSameAuthor = false;
1702 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
1703 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
1707 #endif
1709 int FirstSelect=-1, LastSelect=-1;
1710 pos = GetFirstSelectedItemPosition();
1711 FirstSelect = GetNextSelectedItem(pos);
1712 while(pos)
1714 LastSelect = GetNextSelectedItem(pos);
1716 //entry is selected, now show the popup menu
1717 CIconMenu popup;
1718 CIconMenu subbranchmenu, submenu, gnudiffmenu, diffmenu, blamemenu, revertmenu;
1720 if (popup.CreatePopupMenu())
1722 bool isHeadCommit = (pSelLogEntry->m_CommitHash == m_HeadHash);
1723 CString currentBranch = _T("refs/heads/") + g_Git.GetCurrentBranch();
1724 bool isMergeActive = CTGitPath(g_Git.m_CurrentDir).IsMergeActive();
1725 bool isStash = IsOnStash(indexNext);
1726 GIT_REV_LIST parentHash;
1727 GetParentHashes(pSelLogEntry, parentHash);
1729 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_PICK))
1730 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1732 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SQUASH))
1733 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1735 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_EDIT))
1736 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1738 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SKIP))
1739 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1741 if(m_ContextMenuMask&(GetContextMenuBit(ID_REBASE_SKIP)|GetContextMenuBit(ID_REBASE_EDIT)|
1742 GetContextMenuBit(ID_REBASE_SQUASH)|GetContextMenuBit(ID_REBASE_PICK)))
1743 popup.AppendMenu(MF_SEPARATOR, NULL);
1745 if (GetSelectedCount() == 1)
1748 bool requiresSeparator = false;
1749 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1751 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE) && m_hasWC) // compare revision with WC
1753 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1754 requiresSeparator = true;
1757 else
1759 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1761 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1762 requiresSeparator = true;
1764 if (isMergeActive && (m_ContextMenuMask & GetContextMenuBit(ID_MERGE_ABORT)))
1766 popup.AppendMenuIcon(ID_MERGE_ABORT, IDS_MENUMERGEABORT, IDI_MERGEABORT);
1767 requiresSeparator = true;
1771 if (m_ContextMenuMask & GetContextMenuBit(ID_BLAMEPREVIOUS))
1773 if (parentHash.size() == 1)
1775 popup.AppendMenuIcon(ID_BLAMEPREVIOUS, IDS_LOG_POPUP_BLAMEPREVIOUS, IDI_BLAME);
1776 requiresSeparator = true;
1778 else if (parentHash.size() > 1)
1780 blamemenu.CreatePopupMenu();
1781 popup.AppendMenuIcon(ID_BLAMEPREVIOUS, IDS_LOG_POPUP_BLAMEPREVIOUS, IDI_BLAME, blamemenu.m_hMenu);
1782 for (size_t i = 0; i < parentHash.size(); ++i)
1784 CString str;
1785 str.Format(IDS_PARENT, i + 1);
1786 blamemenu.AppendMenuIcon(ID_BLAMEPREVIOUS +((i + 1) << 16), str);
1788 requiresSeparator = true;
1792 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1) && m_hasWC) // compare with WC, unified
1794 if (parentHash.size() == 1)
1796 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1797 requiresSeparator = true;
1799 else if (parentHash.size() > 1)
1801 gnudiffmenu.CreatePopupMenu();
1802 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1804 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1805 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1807 for (size_t i = 0; i < parentHash.size(); ++i)
1809 CString str;
1810 str.Format(IDS_PARENT, i + 1);
1811 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1813 requiresSeparator = true;
1817 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1819 if (parentHash.size() == 1)
1821 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1822 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1823 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1824 requiresSeparator = true;
1826 else if (parentHash.size() > 1)
1828 diffmenu.CreatePopupMenu();
1829 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1830 for (size_t i = 0; i < parentHash.size(); ++i)
1832 CString str;
1833 str.Format(IDS_PARENT, i + 1);
1834 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1835 if (i == 0 && CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1837 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1838 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1841 requiresSeparator = true;
1845 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1847 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1848 requiresSeparator = true;
1851 if (requiresSeparator)
1852 popup.AppendMenu(MF_SEPARATOR, NULL);
1854 if (pSelLogEntry->m_CommitHash.IsEmpty() && !isMergeActive)
1856 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1857 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_COMMIT);
1860 if (CTGitPath(g_Git.m_CurrentDir).HasStashDir() && (pSelLogEntry->m_CommitHash.IsEmpty() || isStash))
1862 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1863 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_RELOCATE);
1865 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1866 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1868 popup.AppendMenu(MF_SEPARATOR, NULL);
1871 if (pSelLogEntry->m_CommitHash.IsEmpty())
1873 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
1874 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_PULL);
1876 popup.AppendMenu(MF_SEPARATOR, NULL);
1880 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1881 // {
1882 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1883 // }
1884 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1885 // {
1886 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1887 // }
1888 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1889 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1890 // {
1891 // popup.AppendMenu(MF_SEPARATOR, NULL);
1892 // }
1894 CString str,format;
1895 //if (m_hasWC)
1896 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1898 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1900 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1901 GetSelectedCount() == 1)
1902 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1904 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
1905 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
1907 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1908 str.Format(format,g_Git.GetCurrentBranch());
1910 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC && !isMergeActive && !isStash)
1911 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1913 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1914 str.Format(format,g_Git.GetCurrentBranch());
1916 if (m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC && !isStash)
1917 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1920 // Add Switch Branch express Menu
1921 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1922 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC && !isStash)
1925 std::vector<CString *> branchs;
1926 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1928 CString ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1929 if(ref.Find(_T("refs/heads/")) == 0 && ref != currentBranch)
1931 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1935 CString str;
1936 str.LoadString(IDS_SWITCH_BRANCH);
1938 if(branchs.size() == 1)
1940 str+=_T(" ");
1941 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1942 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1944 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1947 else if(branchs.size() > 1)
1949 subbranchmenu.CreatePopupMenu();
1950 for (size_t i = 0 ; i < branchs.size(); ++i)
1952 if (*branchs[i] != currentBranch)
1954 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1955 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1959 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1963 if (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC && !isStash)
1964 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1966 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH) && !isStash)
1967 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1969 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG) && !isStash)
1970 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1972 format.LoadString(IDS_REBASE_THIS_FORMAT);
1973 str.Format(format,g_Git.GetCurrentBranch());
1975 if (pSelLogEntry->m_CommitHash != m_HeadHash && m_hasWC && !isMergeActive && !isStash)
1976 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1977 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1979 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1980 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1982 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive && !isStash)
1984 if (parentHash.size() == 1)
1986 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1988 else if (parentHash.size() > 1)
1990 revertmenu.CreatePopupMenu();
1991 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
1993 for (size_t i = 0; i < parentHash.size(); ++i)
1995 CString str;
1996 str.Format(IDS_PARENT, i + 1);
1997 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str);
2002 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE) && !isStash)
2003 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
2005 popup.AppendMenu(MF_SEPARATOR, NULL);
2010 if(!pSelLogEntry->m_Ref.IsEmpty())
2012 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
2013 if (GetSelectedCount() == 1 && pSelLogEntry->m_Ref.Find(_T("refs/stash")) == 0)
2014 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
2015 popup.AppendMenu(MF_SEPARATOR, NULL);
2018 if (GetSelectedCount() >= 2)
2020 bool bAddSeparator = false;
2021 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
2023 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
2024 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
2027 if (GetSelectedCount() == 2)
2029 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
2030 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
2032 if (!pSelLogEntry->m_CommitHash.IsEmpty())
2034 CString firstSelHash = pSelLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2035 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2036 CString lastSelHash = pLastEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2037 CString menu;
2038 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("..") + firstSelHash);
2039 popup.AppendMenuIcon(ID_LOG_VIEWRANGE, menu, IDI_LOG);
2040 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("...") + firstSelHash);
2041 popup.AppendMenuIcon(ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE, menu, IDI_LOG);
2044 bAddSeparator = true;
2047 if (m_hasWC)
2049 bAddSeparator = true;
2052 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive)
2053 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
2055 if (bAddSeparator)
2056 popup.AppendMenu(MF_SEPARATOR, NULL);
2059 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
2061 bool bAddSeparator = false;
2062 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
2064 if (m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC && !isMergeActive)
2066 CString head;
2067 int headindex;
2068 headindex = this->GetHeadIndex();
2069 if(headindex>=0 && LastSelect >= headindex)
2071 head.Format(_T("HEAD~%d"),LastSelect-headindex);
2072 CGitHash hash;
2073 if (g_Git.GetHash(hash, head))
2074 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + head + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
2075 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2076 if(pLastEntry->m_CommitHash == hash) {
2077 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
2078 bAddSeparator = true;
2083 if (m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC && !isMergeActive) {
2084 if (GetSelectedCount() >= 2)
2085 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_EXPORT);
2086 else
2087 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
2088 bAddSeparator = true;
2091 if (GetSelectedCount() <= 2 || (IsSelectionContinuous() && GetSelectedCount() > 0 && !isStash))
2092 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
2093 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
2094 bAddSeparator = true;
2097 if (bAddSeparator)
2098 popup.AppendMenu(MF_SEPARATOR, NULL);
2101 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())
2103 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART);
2104 popup.AppendMenu(MF_SEPARATOR, NULL);
2107 if (GetSelectedCount() == 1)
2109 bool bAddSeparator = false;
2110 if (m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && !isStash && !m_HashMap[pSelLogEntry->m_CommitHash].empty())
2112 // show the push-option only if the log entry has an associated local branch
2113 bool isLocal = false;
2114 for (size_t i = 0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2116 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
2117 isLocal = true;
2119 if (isLocal)
2121 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
2122 bAddSeparator = true;
2126 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2128 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2130 std::vector<CString *> branchs;
2131 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2133 if(m_HashMap[pSelLogEntry->m_CommitHash][i] != currentBranch)
2134 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
2136 CString str;
2137 if (branchs.size() == 1)
2139 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2140 str+=_T(" ");
2141 str += *branchs[0];
2142 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2143 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2144 bAddSeparator = true;
2146 else if (branchs.size() > 1)
2148 str.LoadString(IDS_DELETE_BRANCHTAG);
2149 submenu.CreatePopupMenu();
2150 for (size_t i = 0; i < branchs.size(); ++i)
2152 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2153 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2156 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2157 bAddSeparator = true;
2160 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2161 if (bAddSeparator)
2162 popup.AppendMenu(MF_SEPARATOR, NULL);
2163 } // GetSelectedCount() == 1
2165 if (GetSelectedCount() != 0)
2167 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
2168 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
2169 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
2170 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
2171 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARDMESSAGES))
2172 popup.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_COPYTOCLIPBOARDMESSAGES, IDI_COPYCLIP);
2175 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2176 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2178 if (GetSelectedCount() == 1 && m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES) && !pSelLogEntry->m_CommitHash.IsEmpty())
2179 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2181 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2182 // DialogEnableWindow(IDOK, FALSE);
2183 // SetPromptApp(&theApp);
2185 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2187 // EnableOKButton();
2188 } // if (popup.CreatePopupMenu())
2192 bool CGitLogListBase::IsSelectionContinuous()
2194 if ( GetSelectedCount()==1 )
2196 // if only one revision is selected, the selection is of course
2197 // continuous
2198 return true;
2201 POSITION pos = GetFirstSelectedItemPosition();
2202 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
2203 if (bContinuous)
2205 int itemindex = GetNextSelectedItem(pos);
2206 while (pos)
2208 int nextindex = GetNextSelectedItem(pos);
2209 if (nextindex - itemindex > 1)
2211 bContinuous = false;
2212 break;
2214 itemindex = nextindex;
2217 return bContinuous;
2220 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2223 CString sClipdata;
2224 POSITION pos = GetFirstSelectedItemPosition();
2225 if (pos != NULL)
2227 CString sRev;
2228 sRev.LoadString(IDS_LOG_REVISION);
2229 CString sAuthor;
2230 sAuthor.LoadString(IDS_LOG_AUTHOR);
2231 CString sDate;
2232 sDate.LoadString(IDS_LOG_DATE);
2233 CString sMessage;
2234 sMessage.LoadString(IDS_LOG_MESSAGE);
2235 bool first = true;
2236 while (pos)
2238 CString sLogCopyText;
2239 CString sPaths;
2240 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
2242 if (toCopy == ID_COPY_ALL)
2244 //pLogEntry->GetFiles(this)
2245 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2247 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2248 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2250 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(": ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2251 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED|CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2253 CString rename;
2254 rename.Format(from, ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2255 sPaths += _T(" ") + rename;
2257 sPaths += _T("\r\n");
2259 sPaths.Trim();
2260 CString body = pLogEntry->GetBody();
2261 body.Replace(_T("\n"), _T("\r\n"));
2262 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"),
2263 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
2264 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2265 (LPCTSTR)sDate,
2266 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2267 (LPCTSTR)sMessage, (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim(),
2268 (LPCTSTR)sPaths);
2269 sClipdata += sLogCopyText;
2271 else if (toCopy == ID_COPY_MESSAGE)
2273 CString body = pLogEntry->GetBody();
2274 body.Replace(_T("\n"), _T("\r\n"));
2275 sClipdata += _T("* ") + (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim() + _T("\r\n\r\n");
2277 else if (toCopy == ID_COPY_SUBJECT)
2279 sClipdata += _T("* ") + pLogEntry->GetSubject().Trim() + _T("\r\n\r\n");
2281 else
2283 if (!first)
2284 sClipdata += _T("\r\n");
2285 sClipdata += pLogEntry->m_CommitHash;
2288 first = false;
2290 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2295 void CGitLogListBase::DiffSelectedRevWithPrevious()
2297 if (m_bThreadRunning)
2298 return;
2300 int FirstSelect=-1, LastSelect=-1;
2301 POSITION pos = GetFirstSelectedItemPosition();
2302 FirstSelect = GetNextSelectedItem(pos);
2303 while(pos)
2305 LastSelect = GetNextSelectedItem(pos);
2308 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
2310 #if 0
2311 UpdateLogInfoLabel();
2312 int selIndex = m_LogList.GetSelectionMark();
2313 if (selIndex < 0)
2314 return;
2315 int selCount = m_LogList.GetSelectedCount();
2316 if (selCount != 1)
2317 return;
2319 // Find selected entry in the log list
2320 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2321 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2322 long rev1 = pLogEntry->Rev;
2323 long rev2 = rev1-1;
2324 CTGitPath path = m_path;
2326 // See how many files under the relative root were changed in selected revision
2327 int nChanged = 0;
2328 LogChangedPath * changed = NULL;
2329 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
2331 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
2332 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2334 ++nChanged;
2335 changed = cpath;
2339 if (m_path.IsDirectory() && nChanged == 1)
2341 // We're looking at the log for a directory and only one file under dir was changed in the revision
2342 // Do diff on that file instead of whole directory
2343 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
2346 m_bCancelled = FALSE;
2347 DialogEnableWindow(IDOK, FALSE);
2348 SetPromptApp(&theApp);
2349 theApp.DoWaitCursor(1);
2351 if (PromptShown())
2353 GitDiff diff(this, m_hWnd, true);
2354 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2355 diff.SetHEADPeg(m_LogRevision);
2356 diff.ShowCompare(path, rev2, path, rev1);
2358 else
2360 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2363 theApp.DoWaitCursor(-1);
2364 EnableOKButton();
2365 #endif
2368 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2370 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2371 *pResult = -1;
2373 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2374 return;
2375 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
2376 return;
2377 if (pFindInfo->lvfi.psz == 0)
2378 return;
2379 #if 0
2380 CString sCmp = pFindInfo->lvfi.psz;
2381 CString sRev;
2382 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2384 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2385 sRev.Format(_T("%ld"), pLogEntry->Rev);
2386 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2388 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2390 *pResult = i;
2391 return;
2394 else
2396 if (sCmp.Compare(sRev)==0)
2398 *pResult = i;
2399 return;
2403 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2405 for (int i=0; i<pFindInfo->iStart; ++i)
2407 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2408 sRev.Format(_T("%ld"), pLogEntry->Rev);
2409 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2411 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2413 *pResult = i;
2414 return;
2417 else
2419 if (sCmp.Compare(sRev)==0)
2421 *pResult = i;
2422 return;
2427 #endif
2428 *pResult = -1;
2431 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2433 ClearText();
2435 this->m_arShownList.SafeRemoveAll();
2437 this->m_logEntries.ClearAll();
2438 if (this->m_logEntries.ParserFromLog(path, -1, info, range))
2439 return -1;
2441 //this->m_logEntries.ParserFromLog();
2442 SetItemCountEx((int)m_logEntries.size());
2444 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2446 if(m_IsOldFirst)
2448 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2449 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2452 else
2454 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2455 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2459 ReloadHashMap();
2461 if(path)
2462 m_Path=*path;
2463 return 0;
2467 int CGitLogListBase::FillGitLog(std::set<CGitHash>& hashes)
2469 ClearText();
2471 m_arShownList.SafeRemoveAll();
2473 m_logEntries.ClearAll();
2474 if (m_logEntries.Fill(hashes))
2475 return -1;
2477 SetItemCountEx((int)m_logEntries.size());
2479 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2481 if (m_IsOldFirst)
2483 m_logEntries.GetGitRevAt(m_logEntries.size() - i - 1).m_IsFull = TRUE;
2484 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size() - i - 1));
2486 else
2488 m_logEntries.GetGitRevAt(i).m_IsFull = TRUE;
2489 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2493 ReloadHashMap();
2495 return 0;
2498 int CGitLogListBase::BeginFetchLog()
2500 ClearText();
2502 this->m_arShownList.SafeRemoveAll();
2504 this->m_logEntries.ClearAll();
2506 this->m_LogCache.ClearAllParent();
2508 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2510 CTGitPath *path;
2511 if(this->m_Path.IsEmpty())
2512 path=NULL;
2513 else
2514 path=&this->m_Path;
2516 int mask;
2517 mask = CGit::LOG_INFO_ONLY_HASH;
2518 if (m_bIncludeBoundaryCommits)
2519 mask |= CGit::LOG_INFO_BOUNDARY;
2520 // if(this->m_bAllBranch)
2521 mask |= m_ShowMask ;
2523 if(m_bShowWC)
2525 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2526 ResetWcRev();
2527 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2530 if (m_sRange.IsEmpty())
2531 m_sRange = _T("HEAD");
2533 CFilterData data;
2534 data.m_From = m_From;
2535 data.m_To =m_To;
2537 #if 0 /* use tortoiegit filter */
2538 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2539 data.m_Author = this->m_sFilterText;
2541 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2542 data.m_MessageFilter = this->m_sFilterText;
2544 data.m_IsRegex = m_bFilterWithRegex;
2545 #endif
2547 // follow does not work for directories
2548 if (!path || path->IsDirectory())
2549 mask &= ~CGit::LOG_INFO_FOLLOW;
2550 // follow does not work with all branches 8at least in TGit)
2551 if (mask & CGit::LOG_INFO_FOLLOW)
2552 mask &= ~CGit::LOG_INFO_ALL_BRANCH;
2554 CString cmd = g_Git.GetLogCmd(m_sRange, path, -1, mask, true, &data);
2556 //this->m_logEntries.ParserFromLog();
2557 if(IsInWorkingThread())
2559 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2561 else
2563 SetItemCountEx((int)m_logEntries.size());
2568 [] { git_init(); } ();
2570 catch (char* msg)
2572 CString err(msg);
2573 MessageBox(_T("Could not initialize libgit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2574 return -1;
2577 if (!g_Git.CanParseRev(m_sRange))
2579 if (!(mask & CGit::LOG_INFO_ALL_BRANCH))
2580 return 0;
2582 // if show all branches, pick any ref as dummy entry ref
2583 STRING_VECTOR list;
2584 if (g_Git.GetRefList(list))
2585 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2586 if (list.size() == 0)
2587 return 0;
2589 cmd = g_Git.GetLogCmd(list[0], path, -1, mask, true, &data);
2592 g_Git.m_critGitDllSec.Lock();
2593 try {
2594 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
2596 g_Git.m_critGitDllSec.Unlock();
2597 return -1;
2600 catch (char* msg)
2602 g_Git.m_critGitDllSec.Unlock();
2603 CString err(msg);
2604 MessageBox(_T("Could not open log.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2605 return -1;
2607 g_Git.m_critGitDllSec.Unlock();
2609 return 0;
2612 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2614 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2616 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2618 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2620 DiffSelectedRevWithPrevious();
2621 return TRUE;
2624 #if 0
2625 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2627 DiffSelectedFile();
2628 return TRUE;
2630 #endif
2632 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2634 // select all entries
2635 for (int i=0; i<GetItemCount(); ++i)
2637 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2639 return TRUE;
2642 #if 0
2643 if (m_hAccel && !bSkipAccelerator)
2645 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2646 if (ret)
2647 return TRUE;
2650 #endif
2651 //m_tooltips.RelayEvent(pMsg);
2652 return __super::PreTranslateMessage(pMsg);
2655 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2657 // a double click on an entry in the revision list has happened
2658 *pResult = 0;
2660 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2661 DiffSelectedRevWithPrevious();
2664 int CGitLogListBase::FetchLogAsync(void * data)
2666 ReloadHashMap();
2667 m_ProcData=data;
2668 m_bExitThread=FALSE;
2669 InterlockedExchange(&m_bThreadRunning, TRUE);
2670 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2671 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2672 if (m_LoadingThread ==NULL)
2674 InterlockedExchange(&m_bThreadRunning, FALSE);
2675 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2676 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2677 return -1;
2679 return 0;
2682 //this is the thread function which calls the subversion function
2683 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2685 return ((CGitLogListBase*)pVoid)->LogThread();
2688 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2690 //CTime time;
2691 oldest=CTime::GetCurrentTime();
2692 latest=CTime(1971,1,2,0,0,0);
2693 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2695 if(m_logEntries[i].IsEmpty())
2696 continue;
2698 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2699 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2701 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2702 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2706 if(latest<oldest)
2707 latest=oldest;
2710 UINT CGitLogListBase::LogThread()
2712 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2714 InterlockedExchange(&m_bThreadRunning, TRUE);
2715 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2717 ULONGLONG t1,t2;
2719 if(BeginFetchLog())
2721 InterlockedExchange(&m_bThreadRunning, FALSE);
2722 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2724 return 1;
2727 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2728 bool bRegex = false;
2729 if (m_bFilterWithRegex)
2730 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2732 TRACE(_T("\n===Begin===\n"));
2733 //Update work copy item;
2735 if (!m_logEntries.empty())
2737 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2739 m_arShownList.SafeAdd(pRev);
2743 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2745 // store commit number of the last selected commit/line before the refresh or -1
2746 int lastSelectedHashNItem = -1;
2747 int ret = 0;
2749 bool shouldWalk = true;
2750 if (!g_Git.CanParseRev(m_sRange))
2752 // walk revisions if show all branches and there exists any ref
2753 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH))
2754 shouldWalk = false;
2755 else
2757 STRING_VECTOR list;
2758 if (g_Git.GetRefList(list))
2759 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2760 if (list.size() == 0)
2761 shouldWalk = false;
2765 if (shouldWalk)
2767 g_Git.m_critGitDllSec.Lock();
2768 int total = 0;
2771 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2772 total = git_get_log_estimate_commit_count(m_DllGitLog);
2774 catch (char* msg)
2776 CString err(msg);
2777 MessageBox(_T("Could not get first commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2778 ret = -1;
2780 g_Git.m_critGitDllSec.Unlock();
2782 GIT_COMMIT commit;
2783 t2=t1=GetTickCount();
2784 int oldprecentage = 0;
2785 size_t oldsize = m_logEntries.size();
2786 std::map<CGitHash, std::set<CGitHash>> commitChildren;
2787 while (ret== 0 && !m_bExitThread)
2789 g_Git.m_critGitDllSec.Lock();
2792 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2794 catch (char* msg)
2796 g_Git.m_critGitDllSec.Unlock();
2797 CString err(msg);
2798 MessageBox(_T("Could not get next commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2799 break;
2801 g_Git.m_critGitDllSec.Unlock();
2803 if(ret)
2804 break;
2806 if (commit.m_ignore == 1)
2808 git_free_commit(&commit);
2809 continue;
2812 //printf("%s\r\n",commit.GetSubject());
2813 if(m_bExitThread)
2814 break;
2816 CGitHash hash = (char*)commit.m_hash ;
2818 GitRev *pRev = m_LogCache.GetCacheData(hash);
2819 pRev->m_GitCommit = commit;
2820 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2822 char *note=NULL;
2823 g_Git.m_critGitDllSec.Lock();
2826 git_get_notes(commit.m_hash, &note);
2828 catch (char* msg)
2830 g_Git.m_critGitDllSec.Unlock();
2831 CString err(msg);
2832 MessageBox(_T("Could not get commit notes.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2833 break;
2835 g_Git.m_critGitDllSec.Unlock();
2837 if(note)
2839 pRev->m_Notes.Empty();
2840 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2843 if(!pRev->m_IsDiffFiles)
2845 pRev->m_CallDiffAsync = DiffAsync;
2848 pRev->ParserParentFromCommit(&commit);
2849 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS) // See also ShouldShowFilter()
2851 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
2853 const CGitHash &parentHash = pRev->m_ParentHash[i];
2854 auto it = commitChildren.find(parentHash);
2855 if (it == commitChildren.end())
2857 it = commitChildren.insert(make_pair(parentHash, std::set<CGitHash>())).first;
2859 it->second.insert(pRev->m_CommitHash);
2863 #ifdef DEBUG
2864 pRev->DbgPrint();
2865 TRACE(_T("\n"));
2866 #endif
2868 bool visible = true;
2869 if(!m_sFilterText.IsEmpty())
2871 if(!IsMatchFilter(bRegex,pRev,pat))
2872 visible = false;
2874 if (visible && !ShouldShowFilter(pRev, commitChildren))
2875 visible = false;
2876 this->m_critSec.Lock();
2877 m_logEntries.append(hash, visible);
2878 if (visible)
2879 m_arShownList.SafeAdd(pRev);
2880 this->m_critSec.Unlock();
2882 if (!visible)
2883 continue;
2885 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
2886 lastSelectedHashNItem = (int)m_arShownList.GetCount() - 1;
2888 t2=GetTickCount();
2890 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2892 //update UI
2893 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
2894 if(percent > 99)
2895 percent =99;
2896 if(percent < GITLOG_START)
2897 percent = GITLOG_START +1;
2899 oldsize = m_logEntries.size();
2900 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2902 //if( percent > oldprecentage )
2904 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2905 oldprecentage = percent;
2908 if (lastSelectedHashNItem >= 0)
2909 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2911 t1 = t2;
2914 g_Git.m_critGitDllSec.Lock();
2915 git_close_log(m_DllGitLog);
2916 g_Git.m_critGitDllSec.Unlock();
2920 if (m_bExitThread)
2922 InterlockedExchange(&m_bThreadRunning, FALSE);
2923 return 0;
2926 //Update UI;
2927 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2929 if (lastSelectedHashNItem >= 0)
2930 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2932 if (this->m_hWnd)
2933 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2935 InterlockedExchange(&m_bThreadRunning, FALSE);
2937 return 0;
2940 void CGitLogListBase::FetchRemoteList()
2942 STRING_VECTOR remoteList;
2943 if (!g_Git.GetRemoteList(remoteList))
2944 m_SingleRemote = remoteList.size() == 1 ? remoteList[0] : _T("");
2945 else
2946 m_SingleRemote = _T("");
2949 void CGitLogListBase::FetchTrackingBranchList()
2951 m_TrackingMap.clear();
2952 for (MAP_HASH_NAME::iterator it = m_HashMap.begin(); it != m_HashMap.end(); ++it)
2954 for (size_t j = 0; j < it->second.size(); ++j)
2956 CString branchName;
2957 if (CGit::GetShortName(it->second[j], branchName, _T("refs/heads/")))
2959 CString configName;
2960 configName.Format(_T("branch.%s.remote"), branchName);
2961 CString pullRemote = g_Git.GetConfigValue(configName);
2963 configName.Format(_T("branch.%s.merge"), branchName);
2964 CString pullBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
2966 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2968 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
2975 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2977 SafeTerminateThread();
2979 this->SetItemCountEx(0);
2980 this->Clear();
2982 ResetWcRev();
2984 // HACK to hide graph column
2985 if (m_ShowMask & CGit::LOG_INFO_FOLLOW)
2986 SetColumnWidth(0, 0);
2987 else
2988 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
2990 //Update branch and Tag info
2991 ReloadHashMap();
2992 //Assume Thread have exited
2993 //if(!m_bThreadRunning)
2995 m_logEntries.clear();
2997 if(IsCleanFilter)
2999 m_sFilterText.Empty();
3000 m_From=-1;
3001 m_To=-1;
3004 InterlockedExchange(&m_bExitThread,FALSE);
3006 InterlockedExchange(&m_bThreadRunning, TRUE);
3007 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3008 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
3010 InterlockedExchange(&m_bThreadRunning, FALSE);
3011 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3012 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
3016 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, std::tr1::wregex& pat, bool bMatchCase /* = false */)
3020 std::tr1::regex_constants::syntax_option_type type = std::tr1::regex_constants::ECMAScript;
3021 if (!bMatchCase)
3022 type |= std::tr1::regex_constants::icase;
3023 pat = std::tr1::wregex(regexp_str, type);
3024 return true;
3026 catch (std::exception) {}
3027 return false;
3029 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, std::tr1::wregex &pat)
3032 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
3033 CString sRev;
3035 if ((bRegex)&&(m_bFilterWithRegex))
3037 if (m_SelectedFilters & LOGFILTER_BUGID)
3039 if(this->m_bShowBugtraqColumn)
3041 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
3043 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
3044 if (std::regex_search(std::wstring(sBugIds), pat, flags))
3046 return TRUE;
3051 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3053 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
3054 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
3056 return TRUE;
3060 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3062 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
3063 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetBody()), pat, flags))
3065 return TRUE;
3069 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3071 if (std::regex_search(std::wstring(pRev->GetAuthorName()), pat, flags))
3073 return TRUE;
3076 if (std::regex_search(std::wstring(pRev->GetCommitterName()), pat, flags))
3078 return TRUE;
3082 if (m_SelectedFilters & LOGFILTER_EMAILS)
3084 if (std::regex_search(std::wstring(pRev->GetAuthorEmail()), pat, flags))
3086 return TRUE;
3089 if (std::regex_search(std::wstring(pRev->GetCommitterEmail()), pat, flags))
3091 return TRUE;
3095 if (m_SelectedFilters & LOGFILTER_REVS)
3097 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3098 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags))
3100 return TRUE;
3104 if (m_SelectedFilters & LOGFILTER_REFNAME)
3106 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3107 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3109 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags))
3111 return TRUE;
3116 if (m_SelectedFilters & LOGFILTER_PATHS)
3118 CTGitPathList *pathList=NULL;
3119 if( pRev->m_IsDiffFiles)
3120 pathList = &pRev->GetFiles(this);
3121 else
3123 if(!pRev->m_IsSimpleListReady)
3124 pRev->SafeGetSimpleList(&g_Git);
3127 if(pathList)
3128 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
3130 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
3132 return true;
3134 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
3136 return true;
3140 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3142 if (std::regex_search(std::wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
3144 return true;
3149 else
3151 CString find = m_sFilterText;
3152 find.MakeLower();
3154 if (m_SelectedFilters & LOGFILTER_BUGID)
3156 if(this->m_bShowBugtraqColumn)
3158 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
3160 sBugIds.MakeLower();
3161 if ((sBugIds.Find(find) >= 0))
3163 return TRUE;
3168 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3170 CString msg = pRev->GetSubject();
3172 msg = msg.MakeLower();
3173 if ((msg.Find(find) >= 0))
3175 return TRUE;
3179 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3181 CString msg = pRev->GetBody();
3183 msg = msg.MakeLower();
3184 if ((msg.Find(find) >= 0))
3186 return TRUE;
3190 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3192 CString msg = pRev->GetAuthorName();
3193 msg = msg.MakeLower();
3194 if ((msg.Find(find) >= 0))
3196 return TRUE;
3200 if (m_SelectedFilters & LOGFILTER_EMAILS)
3202 CString msg = pRev->GetAuthorEmail();
3203 msg = msg.MakeLower();
3204 if ((msg.Find(find) >= 0))
3206 return TRUE;
3210 if (m_SelectedFilters & LOGFILTER_REVS)
3212 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3213 if ((sRev.Find(find) >= 0))
3215 return TRUE;
3219 if (m_SelectedFilters & LOGFILTER_REFNAME)
3221 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3222 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3224 if (it->Find(find) >= 0)
3226 return TRUE;
3231 if (m_SelectedFilters & LOGFILTER_PATHS)
3233 CTGitPathList *pathList=NULL;
3234 if( pRev->m_IsDiffFiles)
3235 pathList = &pRev->GetFiles(this);
3236 else
3238 if(!pRev->m_IsSimpleListReady)
3239 pRev->SafeGetSimpleList(&g_Git);
3241 if(pathList)
3242 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
3244 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
3245 CString path = cpath->GetGitOldPathString();
3246 path.MakeLower();
3247 if ((path.Find(find)>=0))
3249 return true;
3251 path = cpath->GetGitPathString();
3252 path.MakeLower();
3253 if ((path.Find(find)>=0))
3255 return true;
3259 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3261 CString path = pRev->m_SimpleFileList[i];
3262 path.MakeLower();
3263 if ((path.Find(find)>=0))
3265 return true;
3269 } // else (from if (bRegex))
3270 return FALSE;
3273 static bool CStringStartsWith(const CString &str, const CString &prefix)
3275 return str.Left(prefix.GetLength()) == prefix;
3277 bool CGitLogListBase::ShouldShowFilter(GitRev *pRev, const std::map<CGitHash, std::set<CGitHash>> &commitChildren)
3279 if (m_ShowFilter & FILTERSHOW_ANYCOMMIT)
3280 return true;
3282 if (m_ShowFilter & FILTERSHOW_REFS)
3284 // Keep all refs.
3285 const STRING_VECTOR &refList = m_HashMap[pRev->m_CommitHash];
3286 for (size_t i = 0; i < refList.size(); ++i)
3288 const CString &str = refList[i];
3289 if (CStringStartsWith(str, _T("refs/heads/")))
3291 if (m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES)
3292 return true;
3294 else if (CStringStartsWith(str, _T("refs/remotes/")))
3296 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
3297 return true;
3299 else if (CStringStartsWith(str, _T("refs/tags/")))
3301 if (m_ShowRefMask & LOGLIST_SHOWTAGS)
3302 return true;
3304 else if (CStringStartsWith(str, _T("refs/stash")))
3306 if (m_ShowRefMask & LOGLIST_SHOWSTASH)
3307 return true;
3309 else if (CStringStartsWith(str, _T("refs/bisect/")))
3311 if (m_ShowRefMask & LOGLIST_SHOWBISECT)
3312 return true;
3315 // Keep the head too.
3316 if (pRev->m_CommitHash == m_HeadHash)
3317 return true;
3320 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS)
3322 if (pRev->ParentsCount() > 1)
3323 return true;
3324 auto childrenIt = commitChildren.find(pRev->m_CommitHash);
3325 if (childrenIt != commitChildren.end())
3327 const std::set<CGitHash> &children = childrenIt->second;
3328 if (children.size() > 1)
3329 return true;
3332 return false;
3335 void CGitLogListBase::ShowGraphColumn(bool bShow)
3337 // HACK to hide graph column
3338 if (bShow)
3339 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3340 else
3341 SetColumnWidth(0, 0);
3344 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
3347 pShownlist->SafeRemoveAll();
3349 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
3350 bool bRegex = false;
3351 if (m_bFilterWithRegex)
3352 bRegex = ValidateRegexp(m_sFilterText, pat, false);
3354 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
3355 CString sRev;
3356 for (DWORD i=0; i<m_logEntries.size(); ++i)
3358 if ((bRegex)&&(m_bFilterWithRegex))
3360 #if 0
3361 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3363 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
3364 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
3366 pShownlist->SafeAdd(m_logEntries[i]);
3367 continue;
3370 #endif
3371 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3373 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
3374 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
3376 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3377 continue;
3380 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3382 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
3383 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
3385 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3386 continue;
3389 if (m_SelectedFilters & LOGFILTER_PATHS)
3391 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3393 bool bGoing = true;
3394 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3396 CTGitPath cpath = pathList[cpPathIndex];
3397 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
3399 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3400 bGoing = false;
3401 continue;
3403 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
3405 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3406 bGoing = false;
3407 continue;
3409 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
3411 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3412 bGoing = false;
3413 continue;
3417 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3419 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
3421 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3422 continue;
3425 if (m_SelectedFilters & LOGFILTER_EMAILS)
3427 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorEmail()), pat, flags) && IsEntryInDateRange(i))
3429 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3430 continue;
3433 if (m_SelectedFilters & LOGFILTER_REVS)
3435 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3436 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
3438 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3439 continue;
3442 if (m_SelectedFilters & LOGFILTER_REFNAME)
3444 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3445 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3447 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags) && IsEntryInDateRange(i))
3449 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3450 continue;
3454 } // if (bRegex)
3455 else
3457 CString find = m_sFilterText;
3458 find.MakeLower();
3459 #if 0
3460 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3462 CString sBugIDs = m_logEntries[i]->sBugIDs;
3464 sBugIDs = sBugIDs.MakeLower();
3465 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3467 pShownlist->SafeAdd(m_logEntries[i]);
3468 continue;
3471 #endif
3472 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3474 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
3476 msg = msg.MakeLower();
3477 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3479 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3480 continue;
3483 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3485 CString msg = m_logEntries.GetGitRevAt(i).GetBody();
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_PATHS)
3496 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3498 bool bGoing = true;
3499 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3501 CTGitPath cpath = pathList[cpPathIndex];
3502 CString path = cpath.GetGitOldPathString();
3503 path.MakeLower();
3504 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3506 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3507 bGoing = false;
3508 continue;
3510 path = cpath.GetGitPathString();
3511 path.MakeLower();
3512 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3514 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3515 bGoing = false;
3516 continue;
3518 path = cpath.GetActionName();
3519 path.MakeLower();
3520 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3522 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3523 bGoing = false;
3524 continue;
3528 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3530 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
3531 msg = msg.MakeLower();
3532 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3534 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3535 continue;
3538 if (m_SelectedFilters & LOGFILTER_EMAILS)
3540 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorEmail();
3541 msg = msg.MakeLower();
3542 if ((msg.Find(find) >= 0) && (IsEntryInDateRange(i)))
3544 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3545 continue;
3548 if (m_SelectedFilters & LOGFILTER_REVS)
3550 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3551 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3553 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3554 continue;
3557 if (m_SelectedFilters & LOGFILTER_REFNAME)
3559 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3560 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3562 if (it->Find(find) >= 0 && IsEntryInDateRange(i))
3564 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3565 continue;
3569 } // else (from if (bRegex))
3570 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
3574 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
3577 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
3579 if(m_From == -1)
3580 if(m_To == -1)
3581 return true;
3582 else
3583 return time <= m_To;
3584 else
3585 if(m_To == -1)
3586 return time >= m_From;
3587 else
3588 return ((time >= m_From)&&(time <= m_To));
3590 return TRUE; /* git dll will filter time range */
3592 // return TRUE;
3594 void CGitLogListBase::StartFilter()
3596 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3597 RecalculateShownList(&m_arShownList);
3598 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3601 DeleteAllItems();
3602 SetItemCountEx(ShownCountWithStopped());
3603 RedrawItems(0, ShownCountWithStopped());
3604 Invalidate();
3607 void CGitLogListBase::RemoveFilter()
3610 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3612 m_arShownList.SafeRemoveAll();
3614 // reset the time filter too
3615 #if 0
3616 m_timFrom = (__time64_t(m_tFrom));
3617 m_timTo = (__time64_t(m_tTo));
3618 m_DateFrom.SetTime(&m_timFrom);
3619 m_DateTo.SetTime(&m_timTo);
3620 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
3621 m_DateTo.SetRange(&m_timFrom, &m_timTo);
3622 #endif
3624 for (DWORD i=0; i<m_logEntries.size(); ++i)
3626 if(this->m_IsOldFirst)
3628 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
3630 else
3632 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
3635 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
3636 DeleteAllItems();
3637 SetItemCountEx(ShownCountWithStopped());
3638 RedrawItems(0, ShownCountWithStopped());
3640 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3643 void CGitLogListBase::Clear()
3645 m_arShownList.SafeRemoveAll();
3646 DeleteAllItems();
3648 m_logEntries.ClearAll();
3652 void CGitLogListBase::OnDestroy()
3654 // save the column widths to the registry
3655 SaveColumnWidths();
3657 SafeTerminateThread();
3658 SafeTerminateAsyncDiffThread();
3660 int retry = 0;
3661 while(m_LogCache.SaveCache())
3663 if(retry > 5)
3664 break;
3665 Sleep(1000);
3667 ++retry;
3669 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
3670 // MB_YESNO) == IDNO)
3671 // break;
3674 CHintListCtrl::OnDestroy();
3677 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3679 CRect rect;
3680 int i=(int)wParam;
3681 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3682 this->InvalidateRect(rect);
3684 return 0;
3688 * Save column widths to the registry
3690 void CGitLogListBase::SaveColumnWidths()
3692 int maxcol = m_ColumnManager.GetColumnCount();
3694 // HACK that graph column is always shown
3695 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3697 for (int col = 0; col < maxcol; ++col)
3698 if (m_ColumnManager.IsVisible (col))
3699 m_ColumnManager.ColumnResized (col);
3701 m_ColumnManager.WriteSettings();
3704 int CGitLogListBase::GetHeadIndex()
3706 if(m_HeadHash.IsEmpty())
3707 return -1;
3709 for (int i = 0; i < m_arShownList.GetCount(); ++i)
3711 GitRev *pRev = (GitRev*)m_arShownList.SafeGetAt(i);
3712 if(pRev)
3714 if(pRev->m_CommitHash.ToString() == m_HeadHash )
3715 return i;
3718 return -1;
3720 void CGitLogListBase::OnFind()
3722 if (!m_pFindDialog)
3724 m_pFindDialog = new CFindDlg(this);
3725 m_pFindDialog->Create(this);
3728 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
3730 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
3732 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
3734 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
3735 Default();
3737 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3739 if (GetSelectedCount() != 0)
3740 return 0;
3742 CGitHash theSelectedHash = m_lastSelectedHash;
3743 SetItemState((int)itemToSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3744 m_lastSelectedHash = theSelectedHash;
3746 int countPerPage = GetCountPerPage();
3747 EnsureVisible(max(0, (int)itemToSelect-countPerPage/2), FALSE);
3748 EnsureVisible(min(GetItemCount(), (int)itemToSelect+countPerPage/2), FALSE);
3749 EnsureVisible((int)itemToSelect, FALSE);
3750 return 0;
3752 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3755 ASSERT(m_pFindDialog != NULL);
3756 bool bFound = false;
3757 int i=0;
3759 if (m_pFindDialog->IsTerminating())
3761 // invalidate the handle identifying the dialog box.
3762 m_pFindDialog = NULL;
3763 return 0;
3766 INT_PTR cnt = m_arShownList.GetCount();
3768 if(m_pFindDialog->IsRef())
3770 CString str;
3771 str=m_pFindDialog->GetFindString();
3773 CGitHash hash;
3775 if(!str.IsEmpty())
3777 if (g_Git.GetHash(hash, str + _T("^{}"))) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3778 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ref \"") + str + _T("^{}\".")), _T("TortoiseGit"), MB_ICONERROR);
3781 if(!hash.IsEmpty())
3783 for (i = 0; i < cnt; ++i)
3785 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3786 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3788 bFound = true;
3789 break;
3793 if (!bFound)
3795 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3796 return 0;
3800 if (m_pFindDialog->FindNext() && !bFound)
3802 //read data from dialog
3803 CString FindText = m_pFindDialog->GetFindString();
3804 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3806 std::tr1::wregex pat;
3807 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3809 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_not_null;
3811 for (i = m_nSearchIndex + 1; ; ++i)
3813 if (i >= cnt)
3815 i = 0;
3816 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3818 if (m_nSearchIndex >= 0)
3820 if (i == m_nSearchIndex)
3822 ::MessageBeep(0xFFFFFFFF);
3823 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 3, 100);
3824 break;
3828 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3830 CString str;
3831 str+=pLogEntry->m_CommitHash.ToString();
3832 str+=_T("\n");
3834 for (size_t j = 0; j < this->m_HashMap[pLogEntry->m_CommitHash].size(); ++j)
3836 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3837 str+=_T("\n");
3840 str+=pLogEntry->GetAuthorEmail();
3841 str+=_T("\n");
3842 str+=pLogEntry->GetAuthorName();
3843 str+=_T("\n");
3844 str+=pLogEntry->GetBody();
3845 str+=_T("\n");
3846 str+=pLogEntry->GetCommitterEmail();
3847 str+=_T("\n");
3848 str+=pLogEntry->GetCommitterName();
3849 str+=_T("\n");
3850 str+=pLogEntry->GetSubject();
3851 str+=_T("\n");
3854 /*Because changed files list is loaded on demand when gui show,
3855 files will empty when files have not fetched.
3857 we can add it back by using one-way diff(with outnumber changed and rename detect.
3858 here just need changed filename list. one-way is much quicker.
3860 if(pLogEntry->m_IsFull)
3862 for (int i = 0; i < pLogEntry->GetFiles(this).GetCount(); ++i)
3864 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3865 str+=_T("\n");
3866 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3867 str+=_T("\n");
3870 else
3872 if(!pLogEntry->m_IsSimpleListReady)
3873 pLogEntry->SafeGetSimpleList(&g_Git);
3875 for (size_t i = 0; i < pLogEntry->m_SimpleFileList.size(); ++i)
3877 str+=pLogEntry->m_SimpleFileList[i];
3878 str+=_T("\n");
3884 if (bRegex)
3886 if (std::regex_search(std::wstring(str), pat, flags))
3888 bFound = true;
3889 break;
3892 else
3894 if (bMatchCase)
3896 if (str.Find(FindText) >= 0)
3898 bFound = true;
3899 break;
3903 else
3905 CString msg = str;
3906 msg = msg.MakeLower();
3907 CString find = FindText.MakeLower();
3908 if (msg.Find(find) >= 0)
3910 bFound = TRUE;
3911 break;
3915 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; ++i)
3917 } // if(m_pFindDialog->FindNext())
3918 //UpdateLogInfoLabel();
3920 if (bFound)
3922 m_nSearchIndex = i;
3923 EnsureVisible(i, FALSE);
3924 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3925 SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3926 SetSelectionMark(i);
3927 //FillLogMessageCtrl();
3928 UpdateData(FALSE);
3931 return 0;
3934 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3936 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3938 *pResult = FALSE;
3941 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3943 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3945 Invalidate(FALSE);