Don't show branches after rebase actions are done
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
blob565736b097057ff916a49af0b4511b213147fea0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
5 // Copyright (C) 2011-2013 - Sven Strickroth <email@cs-ware.de>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 // GitLogList.cpp : implementation file
23 #include "stdafx.h"
24 #include "resource.h"
25 #include "GitLogListBase.h"
26 #include "GitRev.h"
27 #include "IconMenu.h"
28 #include "cursor.h"
29 #include "InputDlg.h"
30 #include "GitProgressDlg.h"
31 #include "ProgressDlg.h"
32 #include "LogDlg.h"
33 #include "MessageBox.h"
34 #include "registry.h"
35 #include "LoglistUtils.h"
36 #include "PathUtils.h"
37 #include "StringUtils.h"
38 #include "UnicodeUtils.h"
39 #include "TempFile.h"
40 #include "IconMenu.h"
41 #include "GitStatus.h"
42 #include "..\\TortoiseShell\\Resource.h"
43 #include "FindDlg.h"
44 #include "SysInfo.h"
46 const UINT CGitLogListBase::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
47 const UINT CGitLogListBase::m_ScrollToMessage = RegisterWindowMessage(_T("TORTOISEGIT_LOG_SCROLLTO"));
48 const UINT CGitLogListBase::m_RebaseActionMessage = RegisterWindowMessage(_T("TORTOISEGIT_LOG_REBASEACTION"));
50 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintListCtrl)
52 CGitLogListBase::CGitLogListBase():CHintListCtrl()
53 ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
54 ,m_nSearchIndex(0)
55 ,m_bNoDispUpdates(FALSE)
56 , m_bThreadRunning(FALSE)
57 , m_bStrictStopped(false)
58 , m_pStoreSelection(NULL)
59 , m_SelectedFilters(LOGFILTER_ALL)
60 , m_ShowFilter(FILTERSHOW_ALL)
61 , m_bShowWC(false)
62 , m_logEntries(&m_LogCache)
63 , m_pFindDialog(NULL)
64 , m_ColumnManager(this)
65 , m_dwDefaultColumns(0)
66 , m_arShownList(&m_critSec)
67 , m_hasWC(true)
68 , m_bNoHightlightHead(FALSE)
69 , m_ShowRefMask(LOGLIST_SHOWALLREFS)
71 // use the default GUI font, create a copy of it and
72 // change the copy to BOLD (leave the rest of the font
73 // the same)
74 HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
75 LOGFONT lf = {0};
76 GetObject(hFont, sizeof(LOGFONT), &lf);
77 lf.lfWeight = FW_BOLD;
78 m_boldFont = CreateFontIndirect(&lf);
80 m_bShowBugtraqColumn=false;
82 m_IsIDReplaceAction=FALSE;
84 this->m_critSec.Init();
85 m_critSec_AsyncDiff.Init();
86 m_wcRev.m_CommitHash.Empty();
87 m_wcRev.GetSubject() = CString(MAKEINTRESOURCE(IDS_LOG_WORKINGDIRCHANGES));
88 m_wcRev.m_ParentHash.clear();
89 m_wcRev.m_Mark=_T('-');
90 m_wcRev.m_IsUpdateing=FALSE;
91 m_wcRev.m_IsFull = TRUE;
93 m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
94 m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
95 m_hAddedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
96 m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
97 m_hFetchIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONFETCHING), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
99 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
101 m_From = -1;
102 m_To = -1;
104 m_ShowMask = 0;
105 m_LoadingThread = NULL;
107 InterlockedExchange(&m_bExitThread,FALSE);
108 m_IsOldFirst = FALSE;
109 m_IsRebaseReplaceGraph = FALSE;
111 for (int i = 0; i < Lanes::COLORS_NUM; ++i)
113 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));
115 // get short/long datetime setting from registry
116 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
117 if ( RegUseShortDateFormat )
119 m_DateFormat = DATE_SHORTDATE;
121 else
123 m_DateFormat = DATE_LONGDATE;
125 // get relative time display setting from registry
126 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
127 m_bRelativeTimes = (regRelativeTimes != 0);
128 m_ContextMenuMask = 0xFFFFFFFFFFFFFFFF;
130 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_PICK);
131 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SQUASH);
132 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_EDIT);
133 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SKIP);
134 m_ContextMenuMask &= ~GetContextMenuBit(ID_LOG);
135 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAME);
137 m_ColumnRegKey=_T("log");
139 m_bTagsBranchesOnRightSide = !!CRegDWORD(_T("Software\\TortoiseGit\\DrawTagsBranchesOnRightSide"), FALSE);
140 m_bSymbolizeRefNames = !!CRegDWORD(_T("Software\\TortoiseGit\\SymbolizeRefNames"), FALSE);
141 m_bIncludeBoundaryCommits = !!CRegDWORD(_T("Software\\TortoiseGit\\LogIncludeBoundaryCommits"), FALSE);
143 m_LineWidth = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogLineWidth"), 2));
144 m_NodeSize = max(1, CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\Graph\\LogNodeSize"), 10));
146 m_AsyncThreadExit = FALSE;
147 m_AsyncDiffEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
148 m_AsynDiffListLock.Init();
150 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
151 if (m_DiffingThread ==NULL)
153 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
154 return;
159 int CGitLogListBase::AsyncDiffThread()
161 m_AsyncThreadExited = false;
162 while(!m_AsyncThreadExit)
164 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
166 GitRev *pRev = NULL;
167 while(!m_AsyncThreadExit && !m_AsynDiffList.empty())
169 m_AsynDiffListLock.Lock();
170 pRev = m_AsynDiffList.back();
171 m_AsynDiffList.pop_back();
172 m_AsynDiffListLock.Unlock();
174 if( pRev->m_CommitHash.IsEmpty() )
176 if(pRev->m_IsDiffFiles)
177 continue;
179 pRev->GetFiles(this).Clear();
180 pRev->m_ParentHash.clear();
181 pRev->m_ParentHash.push_back(m_HeadHash);
182 if(g_Git.IsInitRepos())
184 if (g_Git.GetInitAddList(pRev->GetFiles(this)))
185 CMessageBox::Show(NULL, _T("Run ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
188 else
190 g_Git.GetCommitDiffList(pRev->m_CommitHash.ToString(),this->m_HeadHash.ToString(), pRev->GetFiles(this));
192 int dummyAction = 0;
193 int *action = &dummyAction;
194 SafeGetAction(pRev, &action);
195 *action = 0;
197 for (int j = 0; j < pRev->GetFiles(this).GetCount(); ++j)
198 *action |= pRev->GetFiles(this)[j].m_Action;
200 pRev->GetUnRevFiles().FillUnRev(CTGitPath::LOGACTIONS_UNVER);
202 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
203 InterlockedExchange(&pRev->m_IsFull, TRUE);
205 pRev->GetBody().Format(IDS_FILESCHANGES, pRev->GetFiles(this).GetCount());
206 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
207 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
210 m_critSec_AsyncDiff.Lock();
211 int ret = pRev->CheckAndDiff();
212 m_critSec_AsyncDiff.Unlock();
213 if (!ret)
214 { // fetch change file list
215 for (int i = GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex() + GetCountPerPage(); ++i)
217 if(i < m_arShownList.GetCount())
219 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(i);
220 if(data->m_CommitHash == pRev->m_CommitHash)
222 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
223 break;
228 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
230 POSITION pos = GetFirstSelectedItemPosition();
231 int nItem = GetNextSelectedItem(pos);
233 if(nItem>=0)
235 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(nItem);
236 if(data)
237 if(data->m_CommitHash == pRev->m_CommitHash)
239 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
246 m_AsyncThreadExited = true;
247 return 0;
249 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
251 if (exclusivelyShow)
253 m_ContextMenuMask &= hideMask;
255 else
257 m_ContextMenuMask &= ~hideMask;
261 CGitLogListBase::~CGitLogListBase()
263 InterlockedExchange(&m_bNoDispUpdates, TRUE);
264 this->m_arShownList.SafeRemoveAll();
266 DestroyIcon(m_hModifiedIcon);
267 DestroyIcon(m_hReplacedIcon);
268 DestroyIcon(m_hAddedIcon);
269 DestroyIcon(m_hDeletedIcon);
270 m_logEntries.ClearAll();
272 if (m_boldFont)
273 DeleteObject(m_boldFont);
275 if ( m_pStoreSelection )
277 delete m_pStoreSelection;
278 m_pStoreSelection = NULL;
281 SafeTerminateThread();
282 SafeTerminateAsyncDiffThread();
284 if(m_AsyncDiffEvent)
285 CloseHandle(m_AsyncDiffEvent);
289 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)
290 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
291 ON_REGISTERED_MESSAGE(m_ScrollToMessage, OnScrollToMessage)
292 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
293 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
294 ON_WM_CONTEXTMENU()
295 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
296 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
297 ON_WM_CREATE()
298 ON_WM_DESTROY()
299 ON_MESSAGE(MSG_LOADED,OnLoad)
300 ON_WM_MEASUREITEM()
301 ON_WM_MEASUREITEM_REFLECT()
302 ON_NOTIFY(HDN_BEGINTRACKA, 0, OnHdnBegintrack)
303 ON_NOTIFY(HDN_BEGINTRACKW, 0, OnHdnBegintrack)
304 ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
305 ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
306 ON_NOTIFY(HDN_ENDTRACK, 0, OnColumnResized)
307 ON_NOTIFY(HDN_ENDDRAG, 0, OnColumnMoved)
308 END_MESSAGE_MAP()
310 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
312 //if (m_nRowHeight>0)
314 lpMeasureItemStruct->itemHeight = 50;
318 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
320 PreSubclassWindow();
321 return CHintListCtrl::OnCreate(lpCreateStruct);
324 void CGitLogListBase::PreSubclassWindow()
326 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
327 // load the icons for the action columns
328 // m_Theme.Open(m_hWnd, L"ListView");
329 SetWindowTheme(m_hWnd, L"Explorer", NULL);
330 CHintListCtrl::PreSubclassWindow();
333 void CGitLogListBase::InsertGitColumn()
335 CString temp;
337 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
338 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
339 if (DWORD(regFullRowSelect))
340 exStyle |= LVS_EX_FULLROWSELECT;
341 SetExtendedStyle(exStyle);
343 // only load properties if we have a repository
344 if (GitAdminDir().HasAdminDir(g_Git.m_CurrentDir) || GitAdminDir().IsBareRepo(g_Git.m_CurrentDir))
345 UpdateProjectProperties();
347 static UINT normal[] =
349 IDS_LOG_GRAPH,
350 IDS_LOG_REBASE,
351 IDS_LOG_ID,
352 IDS_LOG_HASH,
353 IDS_LOG_ACTIONS,
354 IDS_LOG_MESSAGE,
355 IDS_LOG_AUTHOR,
356 IDS_LOG_DATE,
357 IDS_LOG_EMAIL,
358 IDS_LOG_COMMIT_NAME,
359 IDS_LOG_COMMIT_EMAIL,
360 IDS_LOG_COMMIT_DATE,
361 IDS_LOG_BUGIDS,
364 static int with[] =
366 ICONITEMBORDER+16*4,
367 ICONITEMBORDER+16*4,
368 ICONITEMBORDER+16*4,
369 ICONITEMBORDER+16*4,
370 ICONITEMBORDER+16*4,
371 LOGLIST_MESSAGE_MIN,
372 ICONITEMBORDER+16*4,
373 ICONITEMBORDER+16*4,
374 ICONITEMBORDER+16*4,
375 ICONITEMBORDER+16*4,
376 ICONITEMBORDER+16*4,
377 ICONITEMBORDER+16*4,
378 ICONITEMBORDER+16*4,
380 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
382 DWORD hideColumns = 0;
383 if(this->m_IsRebaseReplaceGraph)
385 hideColumns |= GIT_LOG_GRAPH;
386 m_dwDefaultColumns |= GIT_LOG_REBASE;
388 else
390 hideColumns |= GIT_LOG_REBASE;
393 if(this->m_IsIDReplaceAction)
395 hideColumns |= GIT_LOG_ACTIONS;
396 m_dwDefaultColumns |= GIT_LOG_ID;
397 m_dwDefaultColumns |= GIT_LOG_HASH;
399 else
401 hideColumns |= GIT_LOG_ID;
403 if(this->m_bShowBugtraqColumn)
405 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
407 else
409 hideColumns |= GIT_LOGLIST_BUG;
411 SetRedraw(false);
413 m_ColumnManager.SetNames(normal, _countof(normal));
414 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey+_T("loglist"), _countof(normal), with);
416 SetRedraw(true);
420 * Resizes all columns in a list control to values in registry.
422 void CGitLogListBase::ResizeAllListCtrlCols()
424 // column max and min widths to allow
425 static const int nMinimumWidth = 10;
426 static const int nMaximumWidth = 1000;
427 CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
428 if (pHdrCtrl)
430 int numcols = pHdrCtrl->GetItemCount();
431 for (int col = 0; col < numcols; ++col)
433 // get width for this col last time from registry
434 CString regentry;
435 regentry.Format( _T("Software\\TortoiseGit\\%s\\ColWidth%d"),m_ColumnRegKey, col);
436 CRegDWORD regwidth(regentry, 0);
437 int cx = regwidth;
438 if ( cx == 0 )
440 // no saved value, setup sensible defaults
441 if (col == this->LOGLIST_MESSAGE)
443 cx = LOGLIST_MESSAGE_MIN;
445 else
447 cx = ICONITEMBORDER+16*4;
450 if (cx < nMinimumWidth)
452 cx = nMinimumWidth;
454 else if (cx > nMaximumWidth)
456 cx = nMaximumWidth;
459 SetColumnWidth(col, cx);
466 void CGitLogListBase::FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect)
468 LVITEM rItem;
469 SecureZeroMemory(&rItem, sizeof(LVITEM));
470 rItem.mask = LVIF_STATE;
471 rItem.iItem = (int)Index;
472 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
473 GetItem(&rItem);
475 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(Index);
476 HBRUSH brush = NULL;
478 if (!(rItem.state & LVIS_SELECTED))
480 int action = SafeGetAction(pLogEntry);
481 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
482 brush = ::CreateSolidBrush(RGB(156,156,156));
483 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
484 brush = ::CreateSolidBrush(RGB(200,200,128));
486 else if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
488 if (rItem.state & LVIS_SELECTED)
490 if (::GetFocus() == m_hWnd)
491 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
492 else
493 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
496 if (brush != NULL)
498 ::FillRect(hdc, &rect, brush);
499 ::DeleteObject(brush);
503 void DrawTrackingRoundRect(HDC hdc, CRect rect, HBRUSH brush, COLORREF darkColor)
505 POINT point = { 4, 4 };
506 CRect rt2 = rect;
507 rt2.DeflateRect(1, 1);
508 rt2.OffsetRect(2, 2);
510 HPEN nullPen = ::CreatePen(PS_NULL, 0, 0);
511 HPEN oldpen = (HPEN)::SelectObject(hdc, nullPen);
512 HBRUSH darkBrush = (HBRUSH)::CreateSolidBrush(darkColor);
513 HBRUSH oldbrush = (HBRUSH)::SelectObject(hdc, darkBrush);
514 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
516 ::SelectObject(hdc, brush);
517 rt2.OffsetRect(-2, -2);
518 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
519 ::SelectObject(hdc, oldbrush);
520 ::SelectObject(hdc, oldpen);
521 ::DeleteObject(nullPen);
522 ::DeleteObject(darkBrush);
525 void DrawLightning(HDC hdc, CRect rect, COLORREF color, int bold)
527 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
528 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
529 ::MoveToEx(hdc, rect.left + 7, rect.top, NULL);
530 ::LineTo(hdc, rect.left + 1, (rect.top + rect.bottom) / 2);
531 ::LineTo(hdc, rect.left + 6, (rect.top + rect.bottom) / 2);
532 ::LineTo(hdc, rect.left, rect.bottom);
533 ::SelectObject(hdc, oldpen);
534 ::DeleteObject(pen);
537 void DrawUpTriangle(HDC hdc, CRect rect, COLORREF color, int bold)
539 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
540 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
541 ::MoveToEx(hdc, (rect.left + rect.right) / 2, rect.top, NULL);
542 ::LineTo(hdc, rect.left, rect.bottom);
543 ::LineTo(hdc, rect.right, rect.bottom);
544 ::LineTo(hdc, (rect.left + rect.right) / 2, rect.top);
545 ::SelectObject(hdc, oldpen);
546 ::DeleteObject(pen);
549 void CGitLogListBase::DrawTagBranchMessage(HDC hdc, CRect &rect, INT_PTR index, std::vector<REFLABEL> &refList)
551 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
552 CRect rt=rect;
553 LVITEM rItem;
554 SecureZeroMemory(&rItem, sizeof(LVITEM));
555 rItem.mask = LVIF_STATE;
556 rItem.iItem = (int)index;
557 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
558 GetItem(&rItem);
560 CDC W_Dc;
561 W_Dc.Attach(hdc);
563 HTHEME hTheme = NULL;
564 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
565 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
567 if (!m_bTagsBranchesOnRightSide)
568 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
570 SIZE oneSpaceSize;
571 GetTextExtentPoint32(hdc, L" ", 1, &oneSpaceSize);
572 rt.left += oneSpaceSize.cx;
574 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
576 int txtState = LISS_NORMAL;
577 if (rItem.state & LVIS_SELECTED)
578 txtState = LISS_SELECTED;
580 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, data->GetSubject(), -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS, 0, &rt);
582 else
584 if (rItem.state & LVIS_SELECTED)
586 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
587 ::DrawText(hdc,data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
588 ::SetTextColor(hdc, clrOld);
590 else
592 ::DrawText(hdc, data->GetSubject(), data->GetSubject().GetLength(), &rt, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
596 if (m_bTagsBranchesOnRightSide)
598 SIZE size;
599 GetTextExtentPoint32(hdc, data->GetSubject(), data->GetSubject().GetLength(), &size);
601 rt.left += oneSpaceSize.cx + size.cx;
603 DrawTagBranch(hdc, W_Dc, hTheme, rect, rt, rItem, data, refList);
606 if (hTheme)
607 CloseThemeData(hTheme);
609 W_Dc.Detach();
612 void CGitLogListBase::DrawTagBranch(HDC hdc, CDC &W_Dc, HTHEME hTheme, CRect &rect, CRect &rt, LVITEM &rItem, GitRev* data, std::vector<REFLABEL> &refList)
614 for (unsigned int i = 0; i < refList.size(); ++i)
616 CString shortname = !refList[i].simplifiedName.IsEmpty() ? refList[i].simplifiedName : refList[i].name;
617 HBRUSH brush = 0;
618 COLORREF colRef = refList[i].color;
619 bool singleRemote = refList[i].singleRemote;
620 bool hasTracking = refList[i].hasTracking;
621 bool sameName = refList[i].sameName;
622 bool annotatedTag = refList[i].annotatedTag;
624 //When row selected, ajust label color
625 if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
627 if (rItem.state & LVIS_SELECTED)
628 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
631 brush = ::CreateSolidBrush(colRef);
633 if (!shortname.IsEmpty() && (rt.left < rect.right))
635 SIZE size;
636 memset(&size,0,sizeof(SIZE));
637 GetTextExtentPoint32(hdc, shortname, shortname.GetLength(), &size);
639 rt.SetRect(rt.left, rt.top, rt.left + size.cx, rt.bottom);
640 rt.right += 8;
642 int textpos = DT_CENTER;
644 if (rt.right > rect.right)
646 rt.right = rect.right;
647 textpos = 0;
650 CRect textRect = rt;
652 if (singleRemote)
654 rt.right += 8;
655 textRect.OffsetRect(8, 0);
658 if (sameName)
659 rt.right += 8;
661 if (hasTracking)
663 DrawTrackingRoundRect(hdc, rt, brush, m_Colors.Darken(colRef, 100));
665 else
667 //Fill interior of ref label
668 ::FillRect(hdc, &rt, brush);
671 //Draw edge of label
672 CRect rectEdge = rt;
674 if (!hasTracking)
676 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 100), m_Colors.Darken(colRef, 100));
677 rectEdge.DeflateRect(1, 1);
678 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 50), m_Colors.Darken(colRef, 50));
681 if (annotatedTag)
683 rt.right += 8;
684 POINT trianglept[3] = { { rt.right - 8, rt.top }, { rt.right, (rt.top + rt.bottom) / 2 }, { rt.right - 8, rt.bottom } };
685 HRGN hrgn = ::CreatePolygonRgn(trianglept, 3, ALTERNATE);
686 ::FillRgn(hdc, hrgn, brush);
687 ::DeleteObject(hrgn);
688 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[0].y, NULL);
689 HPEN pen;
690 HPEN oldpen = (HPEN)SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Lighten(colRef, 50)));
691 ::LineTo(hdc, trianglept[1].x - 1, trianglept[1].y - 1);
692 ::DeleteObject(pen);
693 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Darken(colRef, 50)));
694 ::LineTo(hdc, trianglept[2].x - 1, trianglept[2].y - 1);
695 ::DeleteObject(pen);
696 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, colRef));
697 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[2].y - 3, NULL);
698 ::LineTo(hdc, trianglept[0].x - 1, trianglept[0].y);
699 ::DeleteObject(pen);
700 SelectObject(hdc, oldpen);
703 //Draw text inside label
704 bool customColor = (colRef & 0xff) * 30 + ((colRef >> 8) & 0xff) * 59 + ((colRef >> 16) & 0xff) * 11 <= 12800; // check if dark background
705 if (!customColor && IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
707 int txtState = LISS_NORMAL;
708 if (rItem.state & LVIS_SELECTED)
709 txtState = LISS_SELECTED;
711 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, 0, &textRect);
713 else
715 W_Dc.SetBkMode(TRANSPARENT);
716 if (customColor || (rItem.state & LVIS_SELECTED))
718 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
719 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
720 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
721 ::SetTextColor(hdc,clrOld);
723 else
725 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
729 if (singleRemote)
731 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
732 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
733 CRect newRect;
734 newRect.SetRect(rt.left + 4, rt.top + 4, rt.left + 8, rt.bottom - 4);
735 DrawLightning(hdc, newRect, color, bold);
738 if (sameName)
740 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
741 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
742 CRect newRect;
743 newRect.SetRect(rt.right - 12, rt.top + 4, rt.right - 4, rt.bottom - 4);
744 DrawUpTriangle(hdc, newRect, color, bold);
747 rt.left = rt.right + 1;
749 if (brush)
750 ::DeleteObject(brush);
752 rt.right = rect.right;
755 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
757 // Returns ((256 - amount)*col1 + amount*col2) / 256;
758 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
759 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
760 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
763 Gdiplus::Color GetGdiColor(COLORREF col)
765 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
767 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
768 const COLORREF& col,const COLORREF& activeColor, int top
771 int h = laneHeight / 2;
772 int m = (x1 + x2) / 2;
773 int r = (x2 - x1) * m_NodeSize / 30;
774 int d = 2 * r;
776 #define P_CENTER m , h+top
777 #define P_0 x2, h+top
778 #define P_90 m , 0+top-1
779 #define P_180 x1, h+top
780 #define P_270 m , 2 * h+top +1
781 #define R_CENTER m - r, h - r+top, d, d
784 #define DELTA_UR_B 2*(x1 - m), 2*h +top
785 #define DELTA_UR_E 0*16, 90*16 +top // -,
787 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
788 #define DELTA_DR_E 270*16, 90*16 +top // -'
790 #define DELTA_UL_B 2*(x2 - m), 2*h +top
791 #define DELTA_UL_E 90*16, 90*16 +top // ,-
793 #define DELTA_DL_B 2*(x2 - m),2*-h +top
794 #define DELTA_DL_E 180*16, 90*16 // '-
796 #define CENTER_UR x1, 2*h, 225
797 #define CENTER_DR x1, 0 , 135
798 #define CENTER_UL x2, 2*h, 315
799 #define CENTER_DL x2, 0 , 45
802 Gdiplus::Graphics graphics( hdc );
804 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
806 // arc
807 switch (type) {
808 case Lanes::JOIN:
809 case Lanes::JOIN_R:
810 case Lanes::HEAD:
811 case Lanes::HEAD_R:
813 Gdiplus::LinearGradientBrush gradient(
814 Gdiplus::Point(x1-2, h+top-2),
815 Gdiplus::Point(P_270),
816 GetGdiColor(activeColor),GetGdiColor(col));
819 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
820 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
822 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
823 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
824 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
826 break;
828 case Lanes::JOIN_L:
831 Gdiplus::LinearGradientBrush gradient(
832 Gdiplus::Point(P_270),
833 Gdiplus::Point(x2+1, h+top-1),
834 GetGdiColor(col),GetGdiColor(activeColor));
837 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
838 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
840 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
841 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
842 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
845 break;
847 case Lanes::TAIL:
848 case Lanes::TAIL_R:
851 Gdiplus::LinearGradientBrush gradient(
852 Gdiplus::Point(x1-2, h+top-2),
853 Gdiplus::Point(P_90),
854 GetGdiColor(activeColor),GetGdiColor(col));
856 Gdiplus::Pen mypen(&gradient, (Gdiplus::REAL)m_LineWidth);
858 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
860 #if 0
861 QConicalGradient gradient(CENTER_DR);
862 gradient.setColorAt(0.375, activeCol);
863 gradient.setColorAt(0.625, col);
864 myPen.setBrush(gradient);
865 p->setPen(myPen);
866 p->drawArc(P_CENTER, DELTA_DR);
867 #endif
868 break;
870 default:
871 break;
875 //static QPen myPen(Qt::black, 2); // fast path here
876 CPen pen;
877 pen.CreatePen(PS_SOLID,2,col);
878 //myPen.setColor(col);
879 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
881 Gdiplus::Pen myPen(GetGdiColor(col), (Gdiplus::REAL)m_LineWidth);
883 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
885 //p->setPen(myPen);
887 // vertical line
888 switch (type) {
889 case Lanes::ACTIVE:
890 case Lanes::NOT_ACTIVE:
891 case Lanes::MERGE_FORK:
892 case Lanes::MERGE_FORK_R:
893 case Lanes::MERGE_FORK_L:
894 case Lanes::JOIN:
895 case Lanes::JOIN_R:
896 case Lanes::JOIN_L:
897 case Lanes::CROSS:
898 //DrawLine(hdc,P_90,P_270);
899 graphics.DrawLine(&myPen,P_90,P_270);
900 //p->drawLine(P_90, P_270);
901 break;
902 case Lanes::HEAD_L:
903 case Lanes::BRANCH:
904 //DrawLine(hdc,P_CENTER,P_270);
905 graphics.DrawLine(&myPen,P_CENTER,P_270);
906 //p->drawLine(P_CENTER, P_270);
907 break;
908 case Lanes::TAIL_L:
909 case Lanes::INITIAL:
910 case Lanes::BOUNDARY:
911 case Lanes::BOUNDARY_C:
912 case Lanes::BOUNDARY_R:
913 case Lanes::BOUNDARY_L:
914 //DrawLine(hdc,P_90, P_CENTER);
915 graphics.DrawLine(&myPen,P_90,P_CENTER);
916 //p->drawLine(P_90, P_CENTER);
917 break;
918 default:
919 break;
922 myPen.SetColor(GetGdiColor(activeColor));
924 // horizontal line
925 switch (type) {
926 case Lanes::MERGE_FORK:
927 case Lanes::JOIN:
928 case Lanes::HEAD:
929 case Lanes::TAIL:
930 case Lanes::CROSS:
931 case Lanes::CROSS_EMPTY:
932 case Lanes::BOUNDARY_C:
933 //DrawLine(hdc,P_180,P_0);
934 graphics.DrawLine(&myPen,P_180,P_0);
935 //p->drawLine(P_180, P_0);
936 break;
937 case Lanes::MERGE_FORK_R:
938 case Lanes::BOUNDARY_R:
939 //DrawLine(hdc,P_180,P_CENTER);
940 graphics.DrawLine(&myPen,P_180,P_CENTER);
941 //p->drawLine(P_180, P_CENTER);
942 break;
943 case Lanes::MERGE_FORK_L:
944 case Lanes::HEAD_L:
945 case Lanes::TAIL_L:
946 case Lanes::BOUNDARY_L:
947 //DrawLine(hdc,P_CENTER,P_0);
948 graphics.DrawLine(&myPen,P_CENTER,P_0);
949 //p->drawLine(P_CENTER, P_0);
950 break;
951 default:
952 break;
955 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
957 CBrush brush;
958 brush.CreateSolidBrush(col);
959 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
961 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
962 // center symbol, e.g. rect or ellipse
963 switch (type) {
964 case Lanes::ACTIVE:
965 case Lanes::INITIAL:
966 case Lanes::BRANCH:
968 //p->setPen(Qt::NoPen);
969 //p->setBrush(col);
970 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
971 graphics.FillEllipse(&myBrush, R_CENTER);
972 //p->drawEllipse(R_CENTER);
973 break;
974 case Lanes::MERGE_FORK:
975 case Lanes::MERGE_FORK_R:
976 case Lanes::MERGE_FORK_L:
977 //p->setPen(Qt::NoPen);
978 //p->setBrush(col);
979 //p->drawRect(R_CENTER);
980 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
981 graphics.FillRectangle(&myBrush, R_CENTER);
982 break;
983 case Lanes::UNAPPLIED:
984 // Red minus sign
985 //p->setPen(Qt::NoPen);
986 //p->setBrush(Qt::red);
987 //p->drawRect(m - r, h - 1, d, 2);
988 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
989 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
990 break;
991 case Lanes::APPLIED:
992 // Green plus sign
993 //p->setPen(Qt::NoPen);
994 //p->setBrush(DARK_GREEN);
995 //p->drawRect(m - r, h - 1, d, 2);
996 //p->drawRect(m - 1, h - r, 2, d);
997 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
998 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
999 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
1000 break;
1001 case Lanes::BOUNDARY:
1002 //p->setBrush(back);
1003 //p->drawEllipse(R_CENTER);
1004 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
1005 graphics.DrawEllipse(&myPen, R_CENTER);
1006 break;
1007 case Lanes::BOUNDARY_C:
1008 case Lanes::BOUNDARY_R:
1009 case Lanes::BOUNDARY_L:
1010 //p->setBrush(back);
1011 //p->drawRect(R_CENTER);
1012 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
1013 graphics.FillRectangle(&myBrush,R_CENTER);
1014 break;
1015 default:
1016 break;
1019 ::SelectObject(hdc,oldpen);
1020 ::SelectObject(hdc,oldbrush);
1021 #undef P_CENTER
1022 #undef P_0
1023 #undef P_90
1024 #undef P_180
1025 #undef P_270
1026 #undef R_CENTER
1029 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
1031 // TODO: unfinished
1032 // return;
1033 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
1034 if(data->m_CommitHash.IsEmpty())
1035 return;
1037 CRect rt=rect;
1038 LVITEM rItem;
1039 SecureZeroMemory(&rItem, sizeof(LVITEM));
1040 rItem.mask = LVIF_STATE;
1041 rItem.iItem = (int)index;
1042 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
1043 GetItem(&rItem);
1045 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1047 if (data->m_Lanes.empty())
1048 m_logEntries.setLane(data->m_CommitHash);
1050 std::vector<int>& lanes=data->m_Lanes;
1051 size_t laneNum = lanes.size();
1052 UINT activeLane = 0;
1053 for (UINT i = 0; i < laneNum; ++i)
1054 if (Lanes::isMerge(lanes[i])) {
1055 activeLane = i;
1056 break;
1059 int x2 = 0;
1060 int maxWidth = rect.Width();
1061 int lw = 3 * rect.Height() / 4; //laneWidth()
1063 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1064 //if (opt.state & QStyle::State_Selected)
1065 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1067 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; ++i)
1070 int x1 = x2;
1071 x2 += lw;
1073 int ln = lanes[i];
1074 if (ln == Lanes::EMPTY)
1075 continue;
1077 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1078 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1081 #if 0
1082 for (UINT i = 0; i < laneNum && x2 < maxWidth; ++i) {
1084 int x1 = x2;
1085 x2 += lw;
1087 int ln = lanes[i];
1088 if (ln == Lanes::EMPTY)
1089 continue;
1091 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1092 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1094 if (ln == Lanes::CROSS)
1096 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1097 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1099 else
1100 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1102 #endif
1106 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1109 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1110 // Take the default processing unless we set this to something else below.
1111 *pResult = CDRF_DODEFAULT;
1113 if (m_bNoDispUpdates)
1114 return;
1116 switch (pLVCD->nmcd.dwDrawStage)
1118 case CDDS_PREPAINT:
1120 *pResult = CDRF_NOTIFYITEMDRAW;
1121 return;
1123 break;
1124 case CDDS_ITEMPREPAINT:
1126 // This is the prepaint stage for an item. Here's where we set the
1127 // item's text color.
1129 // Tell Windows to send draw notifications for each subitem.
1130 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1132 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1134 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1136 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1137 if (data)
1139 int action = SafeGetAction(data);
1140 if (action & (CTGitPath::LOGACTIONS_REBASE_DONE | CTGitPath::LOGACTIONS_REBASE_SKIP))
1141 crText = RGB(128,128,128);
1143 if (action & CTGitPath::LOGACTIONS_REBASE_SQUASH)
1144 pLVCD->clrTextBk = RGB(156,156,156);
1145 else if (action & CTGitPath::LOGACTIONS_REBASE_EDIT)
1146 pLVCD->clrTextBk = RGB(200,200,128);
1147 else
1148 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1150 if (action & CTGitPath::LOGACTIONS_REBASE_CURRENT)
1152 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1153 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1156 if (data->m_CommitHash == m_HeadHash && m_bNoHightlightHead == FALSE)
1158 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1159 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1162 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1163 // crText = GetSysColor(COLOR_GRAYTEXT);
1165 if (data->m_CommitHash.IsEmpty())
1167 //crText = GetSysColor(RGB(200,200,0));
1168 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1169 // We changed the font, so we're returning CDRF_NEWFONT. This
1170 // tells the control to recalculate the extent of the text.
1171 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1175 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
1177 if (m_bStrictStopped)
1178 crText = GetSysColor(COLOR_GRAYTEXT);
1180 // Store the color back in the NMLVCUSTOMDRAW struct.
1181 pLVCD->clrText = crText;
1182 return;
1184 break;
1185 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
1187 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
1189 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
1192 if (pLVCD->iSubItem == LOGLIST_GRAPH && m_sFilterText.IsEmpty() && (m_ShowFilter & FILTERSHOW_MERGEPOINTS))
1194 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )
1196 CRect rect;
1197 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1199 //TRACE(_T("A Graphic left %d right %d\r\n"),rect.left,rect.right);
1200 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1202 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1203 if( !data ->m_CommitHash.IsEmpty())
1204 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1206 *pResult = CDRF_SKIPDEFAULT;
1207 return;
1211 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
1213 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1215 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1217 if (!m_HashMap[data->m_CommitHash].empty() && !(SafeGetAction(data) & CTGitPath::LOGACTIONS_REBASE_DONE))
1219 CRect rect;
1220 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1222 // BEGIN: extended redraw, HACK for issue #1618 and #2014
1223 // not in FillBackGround method, because this only affected the message subitem
1224 if (0 != pLVCD->iStateId) // don't know why, but this helps against loosing the focus rect
1225 return;
1227 int index = (int)pLVCD->nmcd.dwItemSpec;
1228 int state = GetItemState(index, LVIS_SELECTED);
1229 int txtState = LISS_NORMAL;
1230 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater() && GetHotItem() == (int)index)
1232 if (state & LVIS_SELECTED)
1233 txtState = LISS_HOTSELECTED;
1234 else
1235 txtState = LISS_HOT;
1237 else if (state & LVIS_SELECTED)
1239 if (::GetFocus() == m_hWnd)
1240 txtState = LISS_SELECTED;
1241 else
1242 txtState = LISS_SELECTEDNOTFOCUS;
1245 HTHEME hTheme = nullptr;
1246 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
1247 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
1249 if (hTheme && IsThemeBackgroundPartiallyTransparent(hTheme, LVP_LISTDETAIL, txtState))
1250 DrawThemeParentBackground(m_hWnd, pLVCD->nmcd.hdc, &rect);
1251 else
1253 HBRUSH brush = ::CreateSolidBrush(pLVCD->clrTextBk);
1254 ::FillRect(pLVCD->nmcd.hdc, rect, brush);
1255 ::DeleteObject(brush);
1257 if (hTheme)
1259 CRect rt;
1260 // get rect of whole line
1261 GetItemRect(index, rt, LVIR_BOUNDS);
1262 CRect rect2 = rect;
1263 if (txtState == LISS_NORMAL) // avoid drawing of grey borders
1264 rect2.DeflateRect(1, 1, 1, 1);
1266 // calculate background for rect of whole line, but limit redrawing to SubItem rect
1267 DrawThemeBackground(hTheme, pLVCD->nmcd.hdc, LVP_LISTITEM, txtState, rt, rect2);
1269 CloseThemeData(hTheme);
1271 // END: extended redraw
1273 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1275 std::vector<REFLABEL> refsToShow;
1276 STRING_VECTOR remoteTrackingList;
1277 STRING_VECTOR refList = m_HashMap[data->m_CommitHash];
1278 for (unsigned int i = 0; i < refList.size(); ++i)
1280 CString str = refList[i];
1282 REFLABEL refLabel;
1283 refLabel.color = RGB(255, 255, 255);
1284 refLabel.singleRemote = false;
1285 refLabel.hasTracking = false;
1286 refLabel.sameName = false;
1287 refLabel.annotatedTag = false;
1288 if (CGit::GetShortName(str, refLabel.name, _T("refs/heads/")))
1290 if (!(m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES))
1291 continue;
1292 if (refLabel.name == m_CurrentBranch )
1293 refLabel.color = m_Colors.GetColor(CColors::CurrentBranch);
1294 else
1295 refLabel.color = m_Colors.GetColor(CColors::LocalBranch);
1297 std::pair<CString, CString> trackingEntry = m_TrackingMap[refLabel.name];
1298 CString pullRemote = trackingEntry.first;
1299 CString pullBranch = trackingEntry.second;
1300 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
1302 CString defaultUpstream;
1303 defaultUpstream.Format(_T("refs/remotes/%s/%s"), pullRemote, pullBranch);
1304 refLabel.hasTracking = true;
1305 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
1307 bool found = false;
1308 for (size_t j = i + 1; j < refList.size(); ++j)
1310 if (refList[j] == defaultUpstream)
1312 found = true;
1313 break;
1317 if (found)
1319 bool sameName = pullBranch == refLabel.name;
1320 refsToShow.push_back(refLabel);
1321 CGit::GetShortName(defaultUpstream, refLabel.name, _T("refs/remotes/"));
1322 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1323 if (m_bSymbolizeRefNames)
1325 if (!m_SingleRemote.IsEmpty() && m_SingleRemote == pullRemote)
1327 refLabel.simplifiedName = _T("/") + (sameName ? CString() : pullBranch);
1328 refLabel.singleRemote = true;
1330 else if (sameName)
1331 refLabel.simplifiedName = pullRemote + _T("/");
1332 refLabel.sameName = sameName;
1334 refsToShow.push_back(refLabel);
1335 remoteTrackingList.push_back(defaultUpstream);
1336 continue;
1341 else if (CGit::GetShortName(str, refLabel.name, _T("refs/remotes/")))
1343 if (!(m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES))
1344 continue;
1346 bool found = false;
1347 for (size_t j = 0; j < remoteTrackingList.size(); ++j)
1349 if (remoteTrackingList[j] == str)
1351 found = true;
1352 break;
1355 if (found)
1356 continue;
1358 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1359 if (m_bSymbolizeRefNames)
1361 if (!m_SingleRemote.IsEmpty() && refLabel.name.Left(m_SingleRemote.GetLength() + 1) == m_SingleRemote + _T("/"))
1363 refLabel.simplifiedName = _T("/") + refLabel.name.Mid(m_SingleRemote.GetLength() + 1);
1364 refLabel.singleRemote = true;
1368 else if (CGit::GetShortName(str, refLabel.name, _T("refs/tags/")))
1370 if (!(m_ShowRefMask & LOGLIST_SHOWTAGS))
1371 continue;
1372 refLabel.color = m_Colors.GetColor(CColors::Tag);
1373 refLabel.annotatedTag = str.Right(3) == _T("^{}");
1375 else if (CGit::GetShortName(str, refLabel.name, _T("refs/stash")))
1377 if (!(m_ShowRefMask & LOGLIST_SHOWSTASH))
1378 continue;
1379 refLabel.color = m_Colors.GetColor(CColors::Stash);
1380 refLabel.name = _T("stash");
1382 else if (CGit::GetShortName(str, refLabel.name, _T("refs/bisect/")))
1384 if (!(m_ShowRefMask & LOGLIST_SHOWBISECT))
1385 continue;
1386 if (refLabel.name.Find(_T("good")) == 0)
1388 refLabel.color = m_Colors.GetColor(CColors::BisectGood);
1389 refLabel.name = _T("good");
1391 if (refLabel.name.Find(_T("bad")) == 0)
1393 refLabel.color = m_Colors.GetColor(CColors::BisectBad);
1394 refLabel.name = _T("bad");
1397 else
1398 continue;
1400 refsToShow.push_back(refLabel);
1403 if (refsToShow.empty())
1405 *pResult = CDRF_DODEFAULT;
1406 return;
1409 DrawTagBranchMessage(pLVCD->nmcd.hdc, rect, pLVCD->nmcd.dwItemSpec, refsToShow);
1411 *pResult = CDRF_SKIPDEFAULT;
1412 return;
1419 if (pLVCD->iSubItem == LOGLIST_ACTION)
1421 if(this->m_IsIDReplaceAction)
1423 *pResult = CDRF_DODEFAULT;
1424 return;
1426 *pResult = CDRF_DODEFAULT;
1428 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
1429 return;
1431 int nIcons = 0;
1432 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1433 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1435 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec));
1436 CRect rect;
1437 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1438 //TRACE(_T("Action left %d right %d\r\n"),rect.left,rect.right);
1439 // Get the selected state of the
1440 // item being drawn.
1442 // Fill the background if necessary
1443 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec, rect);
1445 // Draw the icon(s) into the compatible DC
1446 int action = SafeGetAction(pLogEntry);
1448 if (!pLogEntry->m_IsDiffFiles)
1449 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1451 if (action & CTGitPath::LOGACTIONS_MODIFIED)
1452 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1453 ++nIcons;
1455 if (action & (CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_COPY))
1456 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1457 ++nIcons;
1459 if (action & CTGitPath::LOGACTIONS_DELETED)
1460 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1461 ++nIcons;
1463 if (action & CTGitPath::LOGACTIONS_REPLACED)
1464 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1465 ++nIcons;
1466 *pResult = CDRF_SKIPDEFAULT;
1467 return;
1470 break;
1472 *pResult = CDRF_DODEFAULT;
1475 // CGitLogListBase message handlers
1477 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1479 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1481 // Create a pointer to the item
1482 LV_ITEM* pItem = &(pDispInfo)->item;
1484 // Do the list need text information?
1485 if (!(pItem->mask & LVIF_TEXT))
1486 return;
1488 // By default, clear text buffer.
1489 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
1491 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
1493 *pResult = 0;
1494 if (m_bNoDispUpdates || bOutOfRange)
1495 return;
1497 // Which item number?
1498 int itemid = pItem->iItem;
1499 GitRev * pLogEntry = NULL;
1500 if (itemid < m_arShownList.GetCount())
1501 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(pItem->iItem));
1503 CString temp;
1504 if(m_IsOldFirst)
1506 temp.Format(_T("%d"),pItem->iItem+1);
1509 else
1511 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);
1514 // Which column?
1515 switch (pItem->iSubItem)
1517 case this->LOGLIST_GRAPH: //Graphic
1518 break;
1519 case this->LOGLIST_REBASE:
1521 if (this->m_IsRebaseReplaceGraph && pLogEntry)
1523 CTGitPath path;
1524 path.m_Action = SafeGetAction(pLogEntry) & CTGitPath::LOGACTIONS_REBASE_MODE_MASK;
1525 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);
1528 break;
1529 case this->LOGLIST_ACTION: //action -- no text in the column
1530 break;
1531 case this->LOGLIST_HASH:
1532 if(pLogEntry)
1533 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax);
1534 break;
1535 case this->LOGLIST_ID:
1536 if(this->m_IsIDReplaceAction)
1537 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);
1538 break;
1539 case this->LOGLIST_MESSAGE: //Message
1540 if (pLogEntry)
1541 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetSubject(), pItem->cchTextMax);
1542 break;
1543 case this->LOGLIST_AUTHOR: //Author
1544 if (pLogEntry)
1545 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorName(), pItem->cchTextMax);
1546 break;
1547 case this->LOGLIST_DATE: //Date
1548 if ( pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()) )
1549 lstrcpyn(pItem->pszText,
1550 CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
1551 pItem->cchTextMax);
1552 break;
1554 case this->LOGLIST_EMAIL:
1555 if (pLogEntry)
1556 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorEmail(), pItem->cchTextMax);
1557 break;
1559 case this->LOGLIST_COMMIT_NAME: //Commit
1560 if (pLogEntry)
1561 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterName(), pItem->cchTextMax);
1562 break;
1564 case this->LOGLIST_COMMIT_EMAIL: //Commit Email
1565 if (pLogEntry)
1566 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterEmail(), pItem->cchTextMax);
1567 break;
1569 case this->LOGLIST_COMMIT_DATE: //Commit Date
1570 if (pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()))
1571 lstrcpyn(pItem->pszText,
1572 CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes),
1573 pItem->cchTextMax);
1574 break;
1575 case this->LOGLIST_BUG: //Bug ID
1576 if(pLogEntry)
1577 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubject() + _T("\r\n\r\n") + pLogEntry->GetBody()), pItem->cchTextMax);
1578 break;
1580 default:
1581 ASSERT(false);
1585 bool CGitLogListBase::IsOnStash(int index)
1587 GitRev *rev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index));
1588 if (IsStash(rev))
1589 return true;
1590 if (index > 0)
1592 GitRev *preRev = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(index - 1));
1593 if (IsStash(preRev))
1594 return preRev->m_ParentHash.size() == 2 && preRev->m_ParentHash[1] == rev->m_CommitHash;
1596 return false;
1599 bool CGitLogListBase::IsStash(const GitRev * pSelLogEntry)
1601 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1603 if (m_HashMap[pSelLogEntry->m_CommitHash][i] == _T("refs/stash"))
1604 return true;
1606 return false;
1609 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1612 if (pWnd == GetHeaderCtrl())
1614 return m_ColumnManager.OnContextMenuHeader(pWnd,point,!!IsGroupViewEnabled());
1617 int selIndex = GetSelectionMark();
1618 if (selIndex < 0)
1619 return; // nothing selected, nothing to do with a context menu
1621 // if the user selected the info text telling about not all revisions shown due to
1622 // the "stop on copy/rename" option, we also don't show the context menu
1623 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
1624 return;
1626 // if the context menu is invoked through the keyboard, we have to use
1627 // a calculated position on where to anchor the menu on
1628 if ((point.x == -1) && (point.y == -1))
1630 CRect rect;
1631 GetItemRect(selIndex, &rect, LVIR_LABEL);
1632 ClientToScreen(&rect);
1633 point = rect.CenterPoint();
1635 m_nSearchIndex = selIndex;
1636 m_bCancelled = FALSE;
1638 // calculate some information the context menu commands can use
1639 // CString pathURL = GetURLFromPath(m_path);
1641 POSITION pos = GetFirstSelectedItemPosition();
1642 int indexNext = GetNextSelectedItem(pos);
1643 if (indexNext < 0)
1644 return;
1646 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(indexNext));
1647 #if 0
1648 GitRev revSelected = pSelLogEntry->Rev;
1649 GitRev revPrevious = git_revnum_t(revSelected)-1;
1650 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
1652 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
1654 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->SafeGetAt(i);
1655 if (changedpath->lCopyFromRev)
1656 revPrevious = changedpath->lCopyFromRev;
1659 GitRev revSelected2;
1660 if (pos)
1662 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1663 revSelected2 = pLogEntry->Rev;
1665 bool bAllFromTheSameAuthor = true;
1666 CString firstAuthor;
1667 CLogDataVector selEntries;
1668 GitRev revLowest, revHighest;
1669 GitRevRangeArray revisionRanges;
1671 POSITION pos = GetFirstSelectedItemPosition();
1672 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1673 revisionRanges.AddRevision(pLogEntry->Rev);
1674 selEntries.push_back(pLogEntry);
1675 firstAuthor = pLogEntry->sAuthor;
1676 revLowest = pLogEntry->Rev;
1677 revHighest = pLogEntry->Rev;
1678 while (pos)
1680 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1681 revisionRanges.AddRevision(pLogEntry->Rev);
1682 selEntries.push_back(pLogEntry);
1683 if (firstAuthor.Compare(pLogEntry->sAuthor))
1684 bAllFromTheSameAuthor = false;
1685 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
1686 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
1690 #endif
1692 int FirstSelect=-1, LastSelect=-1;
1693 pos = GetFirstSelectedItemPosition();
1694 FirstSelect = GetNextSelectedItem(pos);
1695 while(pos)
1697 LastSelect = GetNextSelectedItem(pos);
1699 //entry is selected, now show the popup menu
1700 CIconMenu popup;
1701 CIconMenu subbranchmenu, submenu, gnudiffmenu, diffmenu, revertmenu;
1703 if (popup.CreatePopupMenu())
1705 bool isHeadCommit = (pSelLogEntry->m_CommitHash == m_HeadHash);
1706 CString currentBranch = _T("refs/heads/") + g_Git.GetCurrentBranch();
1707 bool isMergeActive = CTGitPath(g_Git.m_CurrentDir).IsMergeActive();
1708 bool isStash = IsOnStash(indexNext);
1710 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_PICK))
1711 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1713 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SQUASH))
1714 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1716 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_EDIT))
1717 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1719 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SKIP))
1720 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1722 if(m_ContextMenuMask&(GetContextMenuBit(ID_REBASE_SKIP)|GetContextMenuBit(ID_REBASE_EDIT)|
1723 GetContextMenuBit(ID_REBASE_SQUASH)|GetContextMenuBit(ID_REBASE_PICK)))
1724 popup.AppendMenu(MF_SEPARATOR, NULL);
1726 if (GetSelectedCount() == 1)
1730 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1732 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE) && m_hasWC) // compare revision with WC
1733 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1735 else
1737 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1738 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1739 if (isMergeActive && (m_ContextMenuMask & GetContextMenuBit(ID_MERGE_ABORT)))
1740 popup.AppendMenuIcon(ID_MERGE_ABORT, IDS_MENUMERGEABORT, IDI_MERGEABORT);
1742 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1) && m_hasWC) // compare with WC, unified
1744 GitRev *pRev=pSelLogEntry;
1745 if (pSelLogEntry->m_ParentHash.empty())
1749 pRev->GetParentFromHash(pRev->m_CommitHash);
1751 catch (const char* msg)
1753 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1756 if(pRev->m_ParentHash.size()<=1)
1758 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1761 else
1763 gnudiffmenu.CreatePopupMenu();
1764 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1766 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1767 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1769 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1771 CString str;
1772 str.Format(IDS_PARENT, i + 1);
1773 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1778 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1781 GitRev *pRev=pSelLogEntry;
1782 if (pSelLogEntry->m_ParentHash.empty())
1786 pRev->GetParentFromHash(pRev->m_CommitHash);
1788 catch (const char* msg)
1790 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1793 if(pRev->m_ParentHash.size()<=1)
1795 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1796 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1797 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1799 else
1801 diffmenu.CreatePopupMenu();
1802 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1803 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1805 CString str;
1806 str.Format(IDS_PARENT, i + 1);
1807 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1808 if (i == 0 && CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1810 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1811 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1817 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1818 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1820 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
1821 popup.AppendMenu(MF_SEPARATOR, NULL);
1823 if (pSelLogEntry->m_CommitHash.IsEmpty() && !isMergeActive)
1825 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1826 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_COMMIT);
1829 if (CTGitPath(g_Git.m_CurrentDir).HasStashDir() && (pSelLogEntry->m_CommitHash.IsEmpty() || isStash))
1831 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1832 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_RELOCATE);
1834 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1835 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1837 popup.AppendMenu(MF_SEPARATOR, NULL);
1840 if (pSelLogEntry->m_CommitHash.IsEmpty())
1842 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
1843 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_PULL);
1845 popup.AppendMenu(MF_SEPARATOR, NULL);
1849 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1850 // {
1851 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1852 // }
1853 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1854 // {
1855 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1856 // }
1857 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1858 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1859 // {
1860 // popup.AppendMenu(MF_SEPARATOR, NULL);
1861 // }
1863 CString str,format;
1864 //if (m_hasWC)
1865 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1867 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1869 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1870 GetSelectedCount() == 1)
1871 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1873 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
1874 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
1876 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1877 str.Format(format,g_Git.GetCurrentBranch());
1879 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC && !isMergeActive && !isStash)
1880 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1882 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1883 str.Format(format,g_Git.GetCurrentBranch());
1885 if (m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC && !isStash)
1886 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1889 // Add Switch Branch express Menu
1890 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1891 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC && !isStash)
1894 std::vector<CString *> branchs;
1895 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1897 CString ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1898 if(ref.Find(_T("refs/heads/")) == 0 && ref != currentBranch)
1900 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1904 CString str;
1905 str.LoadString(IDS_SWITCH_BRANCH);
1907 if(branchs.size() == 1)
1909 str+=_T(" ");
1910 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1911 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1913 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1916 else if(branchs.size() > 1)
1918 subbranchmenu.CreatePopupMenu();
1919 for (size_t i = 0 ; i < branchs.size(); ++i)
1921 if (*branchs[i] != currentBranch)
1923 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1924 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1928 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1932 if (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC && !isStash)
1933 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1935 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH) && !isStash)
1936 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1938 if (m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG) && !isStash)
1939 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1941 format.LoadString(IDS_REBASE_THIS_FORMAT);
1942 str.Format(format,g_Git.GetCurrentBranch());
1944 if (pSelLogEntry->m_CommitHash != m_HeadHash && m_hasWC && !isMergeActive && !isStash)
1945 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1946 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1948 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1949 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1951 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive && !isStash)
1953 GitRev *pRev = pSelLogEntry;
1954 if (pSelLogEntry->m_ParentHash.empty())
1958 pRev->GetParentFromHash(pRev->m_CommitHash);
1960 catch (const char* msg)
1962 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1965 if (pRev->m_ParentHash.size() == 1)
1967 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1969 else
1971 revertmenu.CreatePopupMenu();
1972 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
1974 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1976 CString str;
1977 str.Format(IDS_PARENT, i + 1);
1978 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str);
1983 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE) && !isStash)
1984 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
1986 popup.AppendMenu(MF_SEPARATOR, NULL);
1991 if(!pSelLogEntry->m_Ref.IsEmpty())
1993 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
1994 if (GetSelectedCount() == 1 && pSelLogEntry->m_Ref.Find(_T("refs/stash")) == 0)
1995 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
1996 popup.AppendMenu(MF_SEPARATOR, NULL);
1999 if (GetSelectedCount() >= 2)
2001 bool bAddSeparator = false;
2002 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
2004 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
2005 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
2008 if (GetSelectedCount() == 2)
2010 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
2011 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
2013 if (!pSelLogEntry->m_CommitHash.IsEmpty())
2015 CString firstSelHash = pSelLogEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2016 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2017 CString lastSelHash = pLastEntry->m_CommitHash.ToString().Left(g_Git.GetShortHASHLength());
2018 CString menu;
2019 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("..") + firstSelHash);
2020 popup.AppendMenuIcon(ID_LOG_VIEWRANGE, menu, IDI_LOG);
2021 menu.Format(IDS_SHOWLOG_OF, lastSelHash + _T("...") + firstSelHash);
2022 popup.AppendMenuIcon(ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE, menu, IDI_LOG);
2025 bAddSeparator = true;
2028 if (m_hasWC)
2030 bAddSeparator = true;
2033 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC && !isMergeActive)
2034 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
2036 if (bAddSeparator)
2037 popup.AppendMenu(MF_SEPARATOR, NULL);
2040 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
2042 bool bAddSeparator = false;
2043 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
2045 if (m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC && !isMergeActive)
2047 CString head;
2048 int headindex;
2049 headindex = this->GetHeadIndex();
2050 if(headindex>=0 && LastSelect >= headindex)
2052 head.Format(_T("HEAD~%d"),LastSelect-headindex);
2053 CGitHash hash;
2054 if (g_Git.GetHash(hash, head))
2055 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + head + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
2056 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
2057 if(pLastEntry->m_CommitHash == hash) {
2058 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
2059 bAddSeparator = true;
2064 if (m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC && !isMergeActive) {
2065 if (GetSelectedCount() >= 2)
2066 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_EXPORT);
2067 else
2068 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
2069 bAddSeparator = true;
2072 if (GetSelectedCount() <= 2 || (IsSelectionContinuous() && GetSelectedCount() > 0 && !isStash))
2073 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
2074 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
2075 bAddSeparator = true;
2078 if (bAddSeparator)
2079 popup.AppendMenu(MF_SEPARATOR, NULL);
2082 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())
2084 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART);
2085 popup.AppendMenu(MF_SEPARATOR, NULL);
2088 if (GetSelectedCount() == 1)
2090 bool bAddSeparator = false;
2091 if (m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && !isStash && !m_HashMap[pSelLogEntry->m_CommitHash].empty())
2093 // show the push-option only if the log entry has an associated local branch
2094 bool isLocal = false;
2095 for (size_t i = 0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2097 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
2098 isLocal = true;
2100 if (isLocal)
2102 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
2103 bAddSeparator = true;
2107 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2109 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2111 std::vector<CString *> branchs;
2112 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2114 if(m_HashMap[pSelLogEntry->m_CommitHash][i] != currentBranch)
2115 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
2117 CString str;
2118 if (branchs.size() == 1)
2120 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2121 str+=_T(" ");
2122 str += *branchs[0];
2123 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2124 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2125 bAddSeparator = true;
2127 else if (branchs.size() > 1)
2129 str.LoadString(IDS_DELETE_BRANCHTAG);
2130 submenu.CreatePopupMenu();
2131 for (size_t i = 0; i < branchs.size(); ++i)
2133 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2134 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2137 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2138 bAddSeparator = true;
2141 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2142 if (bAddSeparator)
2143 popup.AppendMenu(MF_SEPARATOR, NULL);
2144 } // GetSelectedCount() == 1
2146 if (GetSelectedCount() != 0)
2148 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
2149 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
2150 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
2151 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
2152 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARDMESSAGES))
2153 popup.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_COPYTOCLIPBOARDMESSAGES, IDI_COPYCLIP);
2156 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2157 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2159 if (GetSelectedCount() == 1 && m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES) && !pSelLogEntry->m_CommitHash.IsEmpty())
2160 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2162 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2163 // DialogEnableWindow(IDOK, FALSE);
2164 // SetPromptApp(&theApp);
2166 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2168 // EnableOKButton();
2169 } // if (popup.CreatePopupMenu())
2173 bool CGitLogListBase::IsSelectionContinuous()
2175 if ( GetSelectedCount()==1 )
2177 // if only one revision is selected, the selection is of course
2178 // continuous
2179 return true;
2182 POSITION pos = GetFirstSelectedItemPosition();
2183 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
2184 if (bContinuous)
2186 int itemindex = GetNextSelectedItem(pos);
2187 while (pos)
2189 int nextindex = GetNextSelectedItem(pos);
2190 if (nextindex - itemindex > 1)
2192 bContinuous = false;
2193 break;
2195 itemindex = nextindex;
2198 return bContinuous;
2201 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2204 CString sClipdata;
2205 POSITION pos = GetFirstSelectedItemPosition();
2206 if (pos != NULL)
2208 CString sRev;
2209 sRev.LoadString(IDS_LOG_REVISION);
2210 CString sAuthor;
2211 sAuthor.LoadString(IDS_LOG_AUTHOR);
2212 CString sDate;
2213 sDate.LoadString(IDS_LOG_DATE);
2214 CString sMessage;
2215 sMessage.LoadString(IDS_LOG_MESSAGE);
2216 bool first = true;
2217 while (pos)
2219 CString sLogCopyText;
2220 CString sPaths;
2221 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
2223 if (toCopy == ID_COPY_ALL)
2225 //pLogEntry->GetFiles(this)
2226 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2228 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2229 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2231 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(": ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2232 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED|CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2234 CString rename;
2235 rename.Format(from, ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2236 sPaths += _T(" ") + rename;
2238 sPaths += _T("\r\n");
2240 sPaths.Trim();
2241 CString body = pLogEntry->GetBody();
2242 body.Replace(_T("\n"), _T("\r\n"));
2243 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"),
2244 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
2245 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2246 (LPCTSTR)sDate,
2247 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2248 (LPCTSTR)sMessage, (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim(),
2249 (LPCTSTR)sPaths);
2250 sClipdata += sLogCopyText;
2252 else if (toCopy == ID_COPY_MESSAGE)
2254 CString body = pLogEntry->GetBody();
2255 body.Replace(_T("\n"), _T("\r\n"));
2256 sClipdata += _T("* ") + (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim() + _T("\r\n\r\n");
2258 else if (toCopy == ID_COPY_SUBJECT)
2260 sClipdata += _T("* ") + pLogEntry->GetSubject().Trim() + _T("\r\n\r\n");
2262 else
2264 if (!first)
2265 sClipdata += _T("\r\n");
2266 sClipdata += pLogEntry->m_CommitHash;
2269 first = false;
2271 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2276 void CGitLogListBase::DiffSelectedRevWithPrevious()
2278 if (m_bThreadRunning)
2279 return;
2281 int FirstSelect=-1, LastSelect=-1;
2282 POSITION pos = GetFirstSelectedItemPosition();
2283 FirstSelect = GetNextSelectedItem(pos);
2284 while(pos)
2286 LastSelect = GetNextSelectedItem(pos);
2289 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
2291 #if 0
2292 UpdateLogInfoLabel();
2293 int selIndex = m_LogList.GetSelectionMark();
2294 if (selIndex < 0)
2295 return;
2296 int selCount = m_LogList.GetSelectedCount();
2297 if (selCount != 1)
2298 return;
2300 // Find selected entry in the log list
2301 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2302 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2303 long rev1 = pLogEntry->Rev;
2304 long rev2 = rev1-1;
2305 CTGitPath path = m_path;
2307 // See how many files under the relative root were changed in selected revision
2308 int nChanged = 0;
2309 LogChangedPath * changed = NULL;
2310 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
2312 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
2313 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2315 ++nChanged;
2316 changed = cpath;
2320 if (m_path.IsDirectory() && nChanged == 1)
2322 // We're looking at the log for a directory and only one file under dir was changed in the revision
2323 // Do diff on that file instead of whole directory
2324 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
2327 m_bCancelled = FALSE;
2328 DialogEnableWindow(IDOK, FALSE);
2329 SetPromptApp(&theApp);
2330 theApp.DoWaitCursor(1);
2332 if (PromptShown())
2334 GitDiff diff(this, m_hWnd, true);
2335 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2336 diff.SetHEADPeg(m_LogRevision);
2337 diff.ShowCompare(path, rev2, path, rev1);
2339 else
2341 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2344 theApp.DoWaitCursor(-1);
2345 EnableOKButton();
2346 #endif
2349 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2351 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2352 *pResult = -1;
2354 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2355 return;
2356 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
2357 return;
2358 if (pFindInfo->lvfi.psz == 0)
2359 return;
2360 #if 0
2361 CString sCmp = pFindInfo->lvfi.psz;
2362 CString sRev;
2363 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2365 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2366 sRev.Format(_T("%ld"), pLogEntry->Rev);
2367 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2369 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2371 *pResult = i;
2372 return;
2375 else
2377 if (sCmp.Compare(sRev)==0)
2379 *pResult = i;
2380 return;
2384 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2386 for (int i=0; i<pFindInfo->iStart; ++i)
2388 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2389 sRev.Format(_T("%ld"), pLogEntry->Rev);
2390 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2392 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2394 *pResult = i;
2395 return;
2398 else
2400 if (sCmp.Compare(sRev)==0)
2402 *pResult = i;
2403 return;
2408 #endif
2409 *pResult = -1;
2412 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2414 ClearText();
2416 this->m_arShownList.SafeRemoveAll();
2418 this->m_logEntries.ClearAll();
2419 if (this->m_logEntries.ParserFromLog(path, -1, info, range))
2420 return -1;
2422 //this->m_logEntries.ParserFromLog();
2423 SetItemCountEx((int)m_logEntries.size());
2425 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2427 if(m_IsOldFirst)
2429 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2430 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2433 else
2435 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2436 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2440 ReloadHashMap();
2442 if(path)
2443 m_Path=*path;
2444 return 0;
2448 int CGitLogListBase::BeginFetchLog()
2450 ClearText();
2452 this->m_arShownList.SafeRemoveAll();
2454 this->m_logEntries.ClearAll();
2456 this->m_LogCache.ClearAllParent();
2458 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2460 CTGitPath *path;
2461 if(this->m_Path.IsEmpty())
2462 path=NULL;
2463 else
2464 path=&this->m_Path;
2466 int mask;
2467 mask = CGit::LOG_INFO_ONLY_HASH;
2468 if (m_bIncludeBoundaryCommits)
2469 mask |= CGit::LOG_INFO_BOUNDARY;
2470 // if(this->m_bAllBranch)
2471 mask |= m_ShowMask ;
2473 if(m_bShowWC)
2475 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2476 ResetWcRev();
2477 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2480 if (m_sRange.IsEmpty())
2481 m_sRange = _T("HEAD");
2483 CFilterData data;
2484 data.m_From = m_From;
2485 data.m_To =m_To;
2487 #if 0 /* use tortoiegit filter */
2488 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2489 data.m_Author = this->m_sFilterText;
2491 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2492 data.m_MessageFilter = this->m_sFilterText;
2494 data.m_IsRegex = m_bFilterWithRegex;
2495 #endif
2497 // follow does not work for directories
2498 if (!path || path->IsDirectory())
2499 mask &= ~CGit::LOG_INFO_FOLLOW;
2500 // follow does not work with all branches 8at least in TGit)
2501 if (mask & CGit::LOG_INFO_FOLLOW)
2502 mask &= ~CGit::LOG_INFO_ALL_BRANCH;
2504 CString cmd = g_Git.GetLogCmd(m_sRange, path, -1, mask, true, &data);
2506 //this->m_logEntries.ParserFromLog();
2507 if(IsInWorkingThread())
2509 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2511 else
2513 SetItemCountEx((int)m_logEntries.size());
2518 [] { git_init(); } ();
2520 catch (char* msg)
2522 CString err(msg);
2523 MessageBox(_T("Could not initialize libgit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2524 return -1;
2527 if (!g_Git.CanParseRev(m_sRange))
2529 if (!(mask & CGit::LOG_INFO_ALL_BRANCH))
2530 return 0;
2532 // if show all branches, pick any ref as dummy entry ref
2533 STRING_VECTOR list;
2534 if (g_Git.GetRefList(list))
2535 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2536 if (list.size() == 0)
2537 return 0;
2539 cmd = g_Git.GetLogCmd(list[0], path, -1, mask, true, &data);
2542 g_Git.m_critGitDllSec.Lock();
2543 try {
2544 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
2546 g_Git.m_critGitDllSec.Unlock();
2547 return -1;
2550 catch (char* msg)
2552 g_Git.m_critGitDllSec.Unlock();
2553 CString err(msg);
2554 MessageBox(_T("Could not open log.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2555 return -1;
2557 g_Git.m_critGitDllSec.Unlock();
2559 return 0;
2562 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2564 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2566 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2568 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2570 DiffSelectedRevWithPrevious();
2571 return TRUE;
2574 #if 0
2575 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2577 DiffSelectedFile();
2578 return TRUE;
2580 #endif
2582 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2584 // select all entries
2585 for (int i=0; i<GetItemCount(); ++i)
2587 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2589 return TRUE;
2592 #if 0
2593 if (m_hAccel && !bSkipAccelerator)
2595 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2596 if (ret)
2597 return TRUE;
2600 #endif
2601 //m_tooltips.RelayEvent(pMsg);
2602 return __super::PreTranslateMessage(pMsg);
2605 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2607 // a double click on an entry in the revision list has happened
2608 *pResult = 0;
2610 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2611 DiffSelectedRevWithPrevious();
2614 int CGitLogListBase::FetchLogAsync(void * data)
2616 ReloadHashMap();
2617 m_ProcData=data;
2618 m_bExitThread=FALSE;
2619 InterlockedExchange(&m_bThreadRunning, TRUE);
2620 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2621 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2622 if (m_LoadingThread ==NULL)
2624 InterlockedExchange(&m_bThreadRunning, FALSE);
2625 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2626 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2627 return -1;
2629 return 0;
2632 //this is the thread function which calls the subversion function
2633 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2635 return ((CGitLogListBase*)pVoid)->LogThread();
2638 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2640 //CTime time;
2641 oldest=CTime::GetCurrentTime();
2642 latest=CTime(1971,1,2,0,0,0);
2643 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2645 if(m_logEntries[i].IsEmpty())
2646 continue;
2648 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2649 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2651 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2652 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2656 if(latest<oldest)
2657 latest=oldest;
2660 UINT CGitLogListBase::LogThread()
2662 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2664 InterlockedExchange(&m_bThreadRunning, TRUE);
2665 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2667 ULONGLONG t1,t2;
2669 if(BeginFetchLog())
2671 InterlockedExchange(&m_bThreadRunning, FALSE);
2672 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2674 return 1;
2677 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2678 bool bRegex = false;
2679 if (m_bFilterWithRegex)
2680 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2682 TRACE(_T("\n===Begin===\n"));
2683 //Update work copy item;
2685 if (!m_logEntries.empty())
2687 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2689 m_arShownList.SafeAdd(pRev);
2693 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2695 // store commit number of the last selected commit/line before the refresh or -1
2696 int lastSelectedHashNItem = -1;
2697 int ret = 0;
2699 bool shouldWalk = true;
2700 if (!g_Git.CanParseRev(m_sRange))
2702 // walk revisions if show all branches and there exists any ref
2703 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH))
2704 shouldWalk = false;
2705 else
2707 STRING_VECTOR list;
2708 if (g_Git.GetRefList(list))
2709 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2710 if (list.size() == 0)
2711 shouldWalk = false;
2715 if (shouldWalk)
2717 g_Git.m_critGitDllSec.Lock();
2718 int total = 0;
2721 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2722 total = git_get_log_estimate_commit_count(m_DllGitLog);
2724 catch (char* msg)
2726 CString err(msg);
2727 MessageBox(_T("Could not get first commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2728 ret = -1;
2730 g_Git.m_critGitDllSec.Unlock();
2732 GIT_COMMIT commit;
2733 t2=t1=GetTickCount();
2734 int oldprecentage = 0;
2735 size_t oldsize = m_logEntries.size();
2736 std::map<CGitHash, std::set<CGitHash>> commitChildren;
2737 while (ret== 0 && !m_bExitThread)
2739 g_Git.m_critGitDllSec.Lock();
2742 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2744 catch (char* msg)
2746 g_Git.m_critGitDllSec.Unlock();
2747 CString err(msg);
2748 MessageBox(_T("Could not get next commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2749 break;
2751 g_Git.m_critGitDllSec.Unlock();
2753 if(ret)
2754 break;
2756 if (commit.m_ignore == 1)
2758 git_free_commit(&commit);
2759 continue;
2762 //printf("%s\r\n",commit.GetSubject());
2763 if(m_bExitThread)
2764 break;
2766 CGitHash hash = (char*)commit.m_hash ;
2768 GitRev *pRev = m_LogCache.GetCacheData(hash);
2769 pRev->m_GitCommit = commit;
2770 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2772 char *note=NULL;
2773 g_Git.m_critGitDllSec.Lock();
2776 git_get_notes(commit.m_hash, &note);
2778 catch (char* msg)
2780 g_Git.m_critGitDllSec.Unlock();
2781 CString err(msg);
2782 MessageBox(_T("Could not get commit notes.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2783 break;
2785 g_Git.m_critGitDllSec.Unlock();
2787 if(note)
2789 pRev->m_Notes.Empty();
2790 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2793 if(!pRev->m_IsDiffFiles)
2795 pRev->m_CallDiffAsync = DiffAsync;
2798 pRev->ParserParentFromCommit(&commit);
2799 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS) // See also ShouldShowFilter()
2801 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
2803 const CGitHash &parentHash = pRev->m_ParentHash[i];
2804 auto it = commitChildren.find(parentHash);
2805 if (it == commitChildren.end())
2807 it = commitChildren.insert(make_pair(parentHash, std::set<CGitHash>())).first;
2809 it->second.insert(pRev->m_CommitHash);
2813 #ifdef DEBUG
2814 pRev->DbgPrint();
2815 TRACE(_T("\n"));
2816 #endif
2818 bool visible = true;
2819 if(!m_sFilterText.IsEmpty())
2821 if(!IsMatchFilter(bRegex,pRev,pat))
2822 visible = false;
2824 if (visible && !ShouldShowFilter(pRev, commitChildren))
2825 visible = false;
2826 this->m_critSec.Lock();
2827 m_logEntries.append(hash, visible);
2828 if (visible)
2829 m_arShownList.SafeAdd(pRev);
2830 this->m_critSec.Unlock();
2832 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
2833 lastSelectedHashNItem = (int)m_arShownList.GetCount() - 1;
2835 if (!visible)
2836 continue;
2838 t2=GetTickCount();
2840 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2842 //update UI
2843 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
2844 if(percent > 99)
2845 percent =99;
2846 if(percent < GITLOG_START)
2847 percent = GITLOG_START +1;
2849 oldsize = m_logEntries.size();
2850 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2852 //if( percent > oldprecentage )
2854 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2855 oldprecentage = percent;
2858 if (lastSelectedHashNItem >= 0)
2859 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2861 t1 = t2;
2864 g_Git.m_critGitDllSec.Lock();
2865 git_close_log(m_DllGitLog);
2866 g_Git.m_critGitDllSec.Unlock();
2870 if (m_bExitThread)
2872 InterlockedExchange(&m_bThreadRunning, FALSE);
2873 return 0;
2876 //Update UI;
2877 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2879 if (lastSelectedHashNItem >= 0)
2880 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2882 if (this->m_hWnd)
2883 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2885 InterlockedExchange(&m_bThreadRunning, FALSE);
2887 return 0;
2890 void CGitLogListBase::FetchRemoteList()
2892 STRING_VECTOR remoteList;
2893 if (!g_Git.GetRemoteList(remoteList))
2894 m_SingleRemote = remoteList.size() == 1 ? remoteList[0] : _T("");
2895 else
2896 m_SingleRemote = _T("");
2899 void CGitLogListBase::FetchTrackingBranchList()
2901 m_TrackingMap.clear();
2902 for (MAP_HASH_NAME::iterator it = m_HashMap.begin(); it != m_HashMap.end(); ++it)
2904 for (size_t j = 0; j < it->second.size(); ++j)
2906 CString branchName;
2907 if (CGit::GetShortName(it->second[j], branchName, _T("refs/heads/")))
2909 CString configName;
2910 configName.Format(_T("branch.%s.remote"), branchName);
2911 CString pullRemote = g_Git.GetConfigValue(configName);
2913 configName.Format(_T("branch.%s.merge"), branchName);
2914 CString pullBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
2916 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2918 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
2925 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2927 SafeTerminateThread();
2929 this->SetItemCountEx(0);
2930 this->Clear();
2932 ResetWcRev();
2934 // HACK to hide graph column
2935 if (m_ShowMask & CGit::LOG_INFO_FOLLOW)
2936 SetColumnWidth(0, 0);
2937 else
2938 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
2940 //Update branch and Tag info
2941 ReloadHashMap();
2942 //Assume Thread have exited
2943 //if(!m_bThreadRunning)
2945 m_logEntries.clear();
2947 if(IsCleanFilter)
2949 m_sFilterText.Empty();
2950 m_From=-1;
2951 m_To=-1;
2954 InterlockedExchange(&m_bExitThread,FALSE);
2956 InterlockedExchange(&m_bThreadRunning, TRUE);
2957 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2958 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
2960 InterlockedExchange(&m_bThreadRunning, FALSE);
2961 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2962 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2966 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, std::tr1::wregex& pat, bool bMatchCase /* = false */)
2970 std::tr1::regex_constants::syntax_option_type type = std::tr1::regex_constants::ECMAScript;
2971 if (!bMatchCase)
2972 type |= std::tr1::regex_constants::icase;
2973 pat = std::tr1::wregex(regexp_str, type);
2974 return true;
2976 catch (std::exception) {}
2977 return false;
2979 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, std::tr1::wregex &pat)
2982 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
2983 CString sRev;
2985 if ((bRegex)&&(m_bFilterWithRegex))
2987 if (m_SelectedFilters & LOGFILTER_BUGID)
2989 if(this->m_bShowBugtraqColumn)
2991 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2993 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
2994 if (std::regex_search(std::wstring(sBugIds), pat, flags))
2996 return TRUE;
3001 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3003 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
3004 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
3006 return TRUE;
3010 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3012 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
3013 if (std::regex_search(std::wstring((LPCTSTR)pRev->GetBody()), pat, flags))
3015 return TRUE;
3019 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3021 if (std::regex_search(std::wstring(pRev->GetAuthorName()), pat, flags))
3023 return TRUE;
3026 if (std::regex_search(std::wstring(pRev->GetCommitterName()), pat, flags))
3028 return TRUE;
3032 if (m_SelectedFilters & LOGFILTER_EMAILS)
3034 if (std::regex_search(std::wstring(pRev->GetAuthorEmail()), pat, flags))
3036 return TRUE;
3039 if (std::regex_search(std::wstring(pRev->GetCommitterEmail()), pat, flags))
3041 return TRUE;
3045 if (m_SelectedFilters & LOGFILTER_REVS)
3047 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3048 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags))
3050 return TRUE;
3054 if (m_SelectedFilters & LOGFILTER_REFNAME)
3056 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3057 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3059 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags))
3061 return TRUE;
3066 if (m_SelectedFilters & LOGFILTER_PATHS)
3068 CTGitPathList *pathList=NULL;
3069 if( pRev->m_IsDiffFiles)
3070 pathList = &pRev->GetFiles(this);
3071 else
3073 if(!pRev->m_IsSimpleListReady)
3074 pRev->SafeGetSimpleList(&g_Git);
3077 if(pathList)
3078 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
3080 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
3082 return true;
3084 if (std::regex_search(std::wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
3086 return true;
3090 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3092 if (std::regex_search(std::wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
3094 return true;
3099 else
3101 CString find = m_sFilterText;
3102 find.MakeLower();
3104 if (m_SelectedFilters & LOGFILTER_BUGID)
3106 if(this->m_bShowBugtraqColumn)
3108 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
3110 sBugIds.MakeLower();
3111 if ((sBugIds.Find(find) >= 0))
3113 return TRUE;
3118 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3120 CString msg = pRev->GetSubject();
3122 msg = msg.MakeLower();
3123 if ((msg.Find(find) >= 0))
3125 return TRUE;
3129 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3131 CString msg = pRev->GetBody();
3133 msg = msg.MakeLower();
3134 if ((msg.Find(find) >= 0))
3136 return TRUE;
3140 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3142 CString msg = pRev->GetAuthorName();
3143 msg = msg.MakeLower();
3144 if ((msg.Find(find) >= 0))
3146 return TRUE;
3150 if (m_SelectedFilters & LOGFILTER_EMAILS)
3152 CString msg = pRev->GetAuthorEmail();
3153 msg = msg.MakeLower();
3154 if ((msg.Find(find) >= 0))
3156 return TRUE;
3160 if (m_SelectedFilters & LOGFILTER_REVS)
3162 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3163 if ((sRev.Find(find) >= 0))
3165 return TRUE;
3169 if (m_SelectedFilters & LOGFILTER_REFNAME)
3171 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3172 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3174 if (it->Find(find) >= 0)
3176 return TRUE;
3181 if (m_SelectedFilters & LOGFILTER_PATHS)
3183 CTGitPathList *pathList=NULL;
3184 if( pRev->m_IsDiffFiles)
3185 pathList = &pRev->GetFiles(this);
3186 else
3188 if(!pRev->m_IsSimpleListReady)
3189 pRev->SafeGetSimpleList(&g_Git);
3191 if(pathList)
3192 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
3194 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
3195 CString path = cpath->GetGitOldPathString();
3196 path.MakeLower();
3197 if ((path.Find(find)>=0))
3199 return true;
3201 path = cpath->GetGitPathString();
3202 path.MakeLower();
3203 if ((path.Find(find)>=0))
3205 return true;
3209 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3211 CString path = pRev->m_SimpleFileList[i];
3212 path.MakeLower();
3213 if ((path.Find(find)>=0))
3215 return true;
3219 } // else (from if (bRegex))
3220 return FALSE;
3223 static bool CStringStartsWith(const CString &str, const CString &prefix)
3225 return str.Left(prefix.GetLength()) == prefix;
3227 bool CGitLogListBase::ShouldShowFilter(GitRev *pRev, const std::map<CGitHash, std::set<CGitHash>> &commitChildren)
3229 if (m_ShowFilter & FILTERSHOW_ANYCOMMIT)
3230 return true;
3232 if (m_ShowFilter & FILTERSHOW_REFS)
3234 // Keep all refs.
3235 const STRING_VECTOR &refList = m_HashMap[pRev->m_CommitHash];
3236 for (size_t i = 0; i < refList.size(); ++i)
3238 const CString &str = refList[i];
3239 if (CStringStartsWith(str, _T("refs/heads/")))
3241 if (m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES)
3242 return true;
3244 else if (CStringStartsWith(str, _T("refs/remotes/")))
3246 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
3247 return true;
3249 else if (CStringStartsWith(str, _T("refs/tags/")))
3251 if (m_ShowRefMask & LOGLIST_SHOWTAGS)
3252 return true;
3254 else if (CStringStartsWith(str, _T("refs/stash")))
3256 if (m_ShowRefMask & LOGLIST_SHOWSTASH)
3257 return true;
3259 else if (CStringStartsWith(str, _T("refs/bisect/")))
3261 if (m_ShowRefMask & LOGLIST_SHOWBISECT)
3262 return true;
3265 // Keep the head too.
3266 if (pRev->m_CommitHash == m_HeadHash)
3267 return true;
3270 if (m_ShowFilter & FILTERSHOW_MERGEPOINTS)
3272 if (pRev->ParentsCount() > 1)
3273 return true;
3274 auto childrenIt = commitChildren.find(pRev->m_CommitHash);
3275 if (childrenIt != commitChildren.end())
3277 const std::set<CGitHash> &children = childrenIt->second;
3278 if (children.size() > 1)
3279 return true;
3282 return false;
3285 void CGitLogListBase::ShowGraphColumn(bool bShow)
3287 // HACK to hide graph column
3288 if (bShow)
3289 SetColumnWidth(0, 0);
3290 else
3291 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3294 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
3297 pShownlist->SafeRemoveAll();
3299 std::tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
3300 bool bRegex = false;
3301 if (m_bFilterWithRegex)
3302 bRegex = ValidateRegexp(m_sFilterText, pat, false);
3304 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_any;
3305 CString sRev;
3306 for (DWORD i=0; i<m_logEntries.size(); ++i)
3308 if ((bRegex)&&(m_bFilterWithRegex))
3310 #if 0
3311 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3313 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
3314 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
3316 pShownlist->SafeAdd(m_logEntries[i]);
3317 continue;
3320 #endif
3321 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3323 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
3324 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
3326 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3327 continue;
3330 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3332 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
3333 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
3335 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3336 continue;
3339 if (m_SelectedFilters & LOGFILTER_PATHS)
3341 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3343 bool bGoing = true;
3344 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3346 CTGitPath cpath = pathList[cpPathIndex];
3347 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
3349 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3350 bGoing = false;
3351 continue;
3353 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
3355 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3356 bGoing = false;
3357 continue;
3359 if (std::regex_search(std::wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
3361 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3362 bGoing = false;
3363 continue;
3367 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3369 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
3371 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3372 continue;
3375 if (m_SelectedFilters & LOGFILTER_EMAILS)
3377 if (std::regex_search(std::wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorEmail()), pat, flags) && IsEntryInDateRange(i))
3379 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3380 continue;
3383 if (m_SelectedFilters & LOGFILTER_REVS)
3385 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3386 if (std::regex_search(std::wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
3388 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3389 continue;
3392 if (m_SelectedFilters & LOGFILTER_REFNAME)
3394 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3395 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3397 if (std::regex_search(std::wstring((LPCTSTR)*it), pat, flags) && IsEntryInDateRange(i))
3399 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3400 continue;
3404 } // if (bRegex)
3405 else
3407 CString find = m_sFilterText;
3408 find.MakeLower();
3409 #if 0
3410 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3412 CString sBugIDs = m_logEntries[i]->sBugIDs;
3414 sBugIDs = sBugIDs.MakeLower();
3415 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3417 pShownlist->SafeAdd(m_logEntries[i]);
3418 continue;
3421 #endif
3422 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3424 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
3426 msg = msg.MakeLower();
3427 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3429 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3430 continue;
3433 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3435 CString msg = m_logEntries.GetGitRevAt(i).GetBody();
3437 msg = msg.MakeLower();
3438 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3440 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3441 continue;
3444 if (m_SelectedFilters & LOGFILTER_PATHS)
3446 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3448 bool bGoing = true;
3449 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3451 CTGitPath cpath = pathList[cpPathIndex];
3452 CString path = cpath.GetGitOldPathString();
3453 path.MakeLower();
3454 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3456 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3457 bGoing = false;
3458 continue;
3460 path = cpath.GetGitPathString();
3461 path.MakeLower();
3462 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3464 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3465 bGoing = false;
3466 continue;
3468 path = cpath.GetActionName();
3469 path.MakeLower();
3470 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3472 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3473 bGoing = false;
3474 continue;
3478 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3480 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
3481 msg = msg.MakeLower();
3482 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3484 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3485 continue;
3488 if (m_SelectedFilters & LOGFILTER_EMAILS)
3490 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorEmail();
3491 msg = msg.MakeLower();
3492 if ((msg.Find(find) >= 0) && (IsEntryInDateRange(i)))
3494 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3495 continue;
3498 if (m_SelectedFilters & LOGFILTER_REVS)
3500 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3501 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3503 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3504 continue;
3507 if (m_SelectedFilters & LOGFILTER_REFNAME)
3509 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3510 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3512 if (it->Find(find) >= 0 && IsEntryInDateRange(i))
3514 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3515 continue;
3519 } // else (from if (bRegex))
3520 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
3524 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
3527 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
3529 if(m_From == -1)
3530 if(m_To == -1)
3531 return true;
3532 else
3533 return time <= m_To;
3534 else
3535 if(m_To == -1)
3536 return time >= m_From;
3537 else
3538 return ((time >= m_From)&&(time <= m_To));
3540 return TRUE; /* git dll will filter time range */
3542 // return TRUE;
3544 void CGitLogListBase::StartFilter()
3546 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3547 RecalculateShownList(&m_arShownList);
3548 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3551 DeleteAllItems();
3552 SetItemCountEx(ShownCountWithStopped());
3553 RedrawItems(0, ShownCountWithStopped());
3554 Invalidate();
3557 void CGitLogListBase::RemoveFilter()
3560 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3562 m_arShownList.SafeRemoveAll();
3564 // reset the time filter too
3565 #if 0
3566 m_timFrom = (__time64_t(m_tFrom));
3567 m_timTo = (__time64_t(m_tTo));
3568 m_DateFrom.SetTime(&m_timFrom);
3569 m_DateTo.SetTime(&m_timTo);
3570 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
3571 m_DateTo.SetRange(&m_timFrom, &m_timTo);
3572 #endif
3574 for (DWORD i=0; i<m_logEntries.size(); ++i)
3576 if(this->m_IsOldFirst)
3578 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
3580 else
3582 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
3585 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
3586 DeleteAllItems();
3587 SetItemCountEx(ShownCountWithStopped());
3588 RedrawItems(0, ShownCountWithStopped());
3590 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3593 void CGitLogListBase::Clear()
3595 m_arShownList.SafeRemoveAll();
3596 DeleteAllItems();
3598 m_logEntries.ClearAll();
3602 void CGitLogListBase::OnDestroy()
3604 // save the column widths to the registry
3605 SaveColumnWidths();
3607 SafeTerminateThread();
3608 SafeTerminateAsyncDiffThread();
3610 int retry = 0;
3611 while(m_LogCache.SaveCache())
3613 if(retry > 5)
3614 break;
3615 Sleep(1000);
3617 ++retry;
3619 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
3620 // MB_YESNO) == IDNO)
3621 // break;
3624 CHintListCtrl::OnDestroy();
3627 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3629 CRect rect;
3630 int i=(int)wParam;
3631 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3632 this->InvalidateRect(rect);
3634 return 0;
3638 * Save column widths to the registry
3640 void CGitLogListBase::SaveColumnWidths()
3642 int maxcol = m_ColumnManager.GetColumnCount();
3644 // HACK that graph column is always shown
3645 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3647 for (int col = 0; col < maxcol; ++col)
3648 if (m_ColumnManager.IsVisible (col))
3649 m_ColumnManager.ColumnResized (col);
3651 m_ColumnManager.WriteSettings();
3654 int CGitLogListBase::GetHeadIndex()
3656 if(m_HeadHash.IsEmpty())
3657 return -1;
3659 for (int i = 0; i < m_arShownList.GetCount(); ++i)
3661 GitRev *pRev = (GitRev*)m_arShownList.SafeGetAt(i);
3662 if(pRev)
3664 if(pRev->m_CommitHash.ToString() == m_HeadHash )
3665 return i;
3668 return -1;
3670 void CGitLogListBase::OnFind()
3672 if (!m_pFindDialog)
3674 m_pFindDialog = new CFindDlg(this);
3675 m_pFindDialog->Create(this);
3678 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
3680 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
3682 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
3684 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
3685 Default();
3687 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3689 if (GetSelectedCount() != 0)
3690 return 0;
3692 CGitHash theSelectedHash = m_lastSelectedHash;
3693 SetItemState((int)itemToSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3694 m_lastSelectedHash = theSelectedHash;
3696 int countPerPage = GetCountPerPage();
3697 EnsureVisible(max(0, (int)itemToSelect-countPerPage/2), FALSE);
3698 EnsureVisible(min(GetItemCount(), (int)itemToSelect+countPerPage/2), FALSE);
3699 EnsureVisible((int)itemToSelect, FALSE);
3700 return 0;
3702 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3705 ASSERT(m_pFindDialog != NULL);
3706 bool bFound = false;
3707 int i=0;
3709 if (m_pFindDialog->IsTerminating())
3711 // invalidate the handle identifying the dialog box.
3712 m_pFindDialog = NULL;
3713 return 0;
3716 INT_PTR cnt = m_arShownList.GetCount();
3718 if(m_pFindDialog->IsRef())
3720 CString str;
3721 str=m_pFindDialog->GetFindString();
3723 CGitHash hash;
3725 if(!str.IsEmpty())
3727 if (g_Git.GetHash(hash, str + _T("^{}"))) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3728 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ref \"") + str + _T("^{}\".")), _T("TortoiseGit"), MB_ICONERROR);
3731 if(!hash.IsEmpty())
3733 for (i = 0; i < cnt; ++i)
3735 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3736 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3738 bFound = true;
3739 break;
3743 if (!bFound)
3745 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3746 return 0;
3750 if (m_pFindDialog->FindNext() && !bFound)
3752 //read data from dialog
3753 CString FindText = m_pFindDialog->GetFindString();
3754 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3756 std::tr1::wregex pat;
3757 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3759 std::tr1::regex_constants::match_flag_type flags = std::tr1::regex_constants::match_not_null;
3761 for (i = m_nSearchIndex + 1; ; ++i)
3763 if (i >= cnt)
3765 i = 0;
3766 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
3768 if (m_nSearchIndex >= 0)
3770 if (i == m_nSearchIndex)
3772 ::MessageBeep(0xFFFFFFFF);
3773 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 3, 100);
3774 break;
3778 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3780 CString str;
3781 str+=pLogEntry->m_CommitHash.ToString();
3782 str+=_T("\n");
3784 for (size_t j = 0; j < this->m_HashMap[pLogEntry->m_CommitHash].size(); ++j)
3786 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3787 str+=_T("\n");
3790 str+=pLogEntry->GetAuthorEmail();
3791 str+=_T("\n");
3792 str+=pLogEntry->GetAuthorName();
3793 str+=_T("\n");
3794 str+=pLogEntry->GetBody();
3795 str+=_T("\n");
3796 str+=pLogEntry->GetCommitterEmail();
3797 str+=_T("\n");
3798 str+=pLogEntry->GetCommitterName();
3799 str+=_T("\n");
3800 str+=pLogEntry->GetSubject();
3801 str+=_T("\n");
3804 /*Because changed files list is loaded on demand when gui show,
3805 files will empty when files have not fetched.
3807 we can add it back by using one-way diff(with outnumber changed and rename detect.
3808 here just need changed filename list. one-way is much quicker.
3810 if(pLogEntry->m_IsFull)
3812 for (int i = 0; i < pLogEntry->GetFiles(this).GetCount(); ++i)
3814 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3815 str+=_T("\n");
3816 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3817 str+=_T("\n");
3820 else
3822 if(!pLogEntry->m_IsSimpleListReady)
3823 pLogEntry->SafeGetSimpleList(&g_Git);
3825 for (size_t i = 0; i < pLogEntry->m_SimpleFileList.size(); ++i)
3827 str+=pLogEntry->m_SimpleFileList[i];
3828 str+=_T("\n");
3834 if (bRegex)
3836 if (std::regex_search(std::wstring(str), pat, flags))
3838 bFound = true;
3839 break;
3842 else
3844 if (bMatchCase)
3846 if (str.Find(FindText) >= 0)
3848 bFound = true;
3849 break;
3853 else
3855 CString msg = str;
3856 msg = msg.MakeLower();
3857 CString find = FindText.MakeLower();
3858 if (msg.Find(find) >= 0)
3860 bFound = TRUE;
3861 break;
3865 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; ++i)
3867 } // if(m_pFindDialog->FindNext())
3868 //UpdateLogInfoLabel();
3870 if (bFound)
3872 m_nSearchIndex = i;
3873 EnsureVisible(i, FALSE);
3874 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3875 SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
3876 SetSelectionMark(i);
3877 //FillLogMessageCtrl();
3878 UpdateData(FALSE);
3881 return 0;
3884 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3886 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3888 *pResult = FALSE;
3891 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3893 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3895 Invalidate(FALSE);