log list: indicate annotated tag in tag shape
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
bloba47bac1ea27679bfde08939bb922abe4f7ba1b21
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 "VssStyle.h"
28 #include "IconMenu.h"
29 // CGitLogListBase
30 #include "cursor.h"
31 #include "InputDlg.h"
32 #include "GITProgressDlg.h"
33 #include "ProgressDlg.h"
34 //#include "RepositoryBrowser.h"
35 //#include "CopyDlg.h"
36 //#include "StatGraphDlg.h"
37 #include "Logdlg.h"
38 #include "MessageBox.h"
39 #include "registry.h"
40 #include "LoglistUtils.h"
41 #include "PathUtils.h"
42 #include "StringUtils.h"
43 #include "UnicodeUtils.h"
44 #include "TempFile.h"
45 //#include "GitInfo.h"
46 //#include "GitDiff.h"
47 #include "IconMenu.h"
48 //#include "RevisionRangeDlg.h"
49 //#include "BrowseFolder.h"
50 //#include "BlameDlg.h"
51 //#include "Blame.h"
52 //#include "GitHelpers.h"
53 #include "GitStatus.h"
54 //#include "LogDlgHelper.h"
55 //#include "CachedLogInfo.h"
56 //#include "RepositoryInfo.h"
57 //#include "EditPropertiesDlg.h"
58 #include "FileDiffDlg.h"
59 #include "..\\TortoiseShell\\Resource.h"
60 #include "FindDlg.h"
61 #include "SysInfo.h"
63 const UINT CGitLogListBase::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
64 const UINT CGitLogListBase::m_ScrollToMessage = RegisterWindowMessage(_T("TORTOISEGIT_LOG_SCROLLTO"));
66 IMPLEMENT_DYNAMIC(CGitLogListBase, CHintListCtrl)
68 CGitLogListBase::CGitLogListBase():CHintListCtrl()
69 ,m_regMaxBugIDColWidth(_T("Software\\TortoiseGit\\MaxBugIDColWidth"), 200)
70 ,m_nSearchIndex(0)
71 ,m_bNoDispUpdates(FALSE)
72 , m_bThreadRunning(FALSE)
73 , m_bStrictStopped(false)
74 , m_pStoreSelection(NULL)
75 , m_SelectedFilters(LOGFILTER_ALL)
76 , m_bShowWC(false)
77 , m_logEntries(&m_LogCache)
78 , m_pFindDialog(NULL)
79 , m_ColumnManager(this)
80 , m_dwDefaultColumns(0)
81 , m_arShownList(&m_critSec)
82 , m_hasWC(true)
83 , m_bNoHightlightHead(FALSE)
84 , m_ShowRefMask(LOGLIST_SHOWALLREFS)
86 // use the default GUI font, create a copy of it and
87 // change the copy to BOLD (leave the rest of the font
88 // the same)
89 HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
90 LOGFONT lf = {0};
91 GetObject(hFont, sizeof(LOGFONT), &lf);
92 lf.lfWeight = FW_BOLD;
93 m_boldFont = CreateFontIndirect(&lf);
95 m_bShowBugtraqColumn=false;
97 m_IsIDReplaceAction=FALSE;
99 this->m_critSec.Init();
100 m_wcRev.m_CommitHash.Empty();
101 m_wcRev.GetSubject() = CString(MAKEINTRESOURCE(IDS_LOG_WORKINGDIRCHANGES));
102 m_wcRev.m_ParentHash.clear();
103 m_wcRev.m_Mark=_T('-');
104 m_wcRev.m_IsUpdateing=FALSE;
105 m_wcRev.m_IsFull = TRUE;
107 m_hModifiedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONMODIFIED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
108 m_hReplacedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONREPLACED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
109 m_hAddedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONADDED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
110 m_hDeletedIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONDELETED), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
111 m_hFetchIcon = (HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_ACTIONFETCHING), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
113 m_bFilterWithRegex = !!CRegDWORD(_T("Software\\TortoiseGit\\UseRegexFilter"), TRUE);
115 m_From = -1;
116 m_To = -1;
118 m_ShowMask = 0;
119 m_LoadingThread = NULL;
121 InterlockedExchange(&m_bExitThread,FALSE);
122 m_IsOldFirst = FALSE;
123 m_IsRebaseReplaceGraph = FALSE;
125 for (int i = 0; i < Lanes::COLORS_NUM; ++i)
127 m_LineColors[i] = m_Colors.GetColor((CColors::Colors)(CColors::BranchLine1+i));
129 // get short/long datetime setting from registry
130 DWORD RegUseShortDateFormat = CRegDWORD(_T("Software\\TortoiseGit\\LogDateFormat"), TRUE);
131 if ( RegUseShortDateFormat )
133 m_DateFormat = DATE_SHORTDATE;
135 else
137 m_DateFormat = DATE_LONGDATE;
139 // get relative time display setting from registry
140 DWORD regRelativeTimes = CRegDWORD(_T("Software\\TortoiseGit\\RelativeTimes"), FALSE);
141 m_bRelativeTimes = (regRelativeTimes != 0);
142 m_ContextMenuMask = 0xFFFFFFFFFFFFFFFF;
144 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_PICK);
145 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SQUASH);
146 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_EDIT);
147 m_ContextMenuMask &= ~GetContextMenuBit(ID_REBASE_SKIP);
148 m_ContextMenuMask &= ~GetContextMenuBit(ID_LOG);
149 m_ContextMenuMask &= ~GetContextMenuBit(ID_BLAME);
151 m_ColumnRegKey=_T("log");
153 m_bSymbolizeRefNames = !!CRegDWORD(_T("Software\\TortoiseGit\\SymbolizeRefNames"), FALSE);
154 m_bIncludeBoundaryCommits = !!CRegDWORD(_T("Software\\TortoiseGit\\LogIncludeBoundaryCommits"), FALSE);
156 m_AsyncThreadExit = FALSE;
157 m_AsyncDiffEvent = ::CreateEvent(NULL,FALSE,TRUE,NULL);
158 m_AsynDiffListLock.Init();
160 m_DiffingThread = AfxBeginThread(AsyncThread, this, THREAD_PRIORITY_BELOW_NORMAL);
161 if (m_DiffingThread ==NULL)
163 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
164 return;
169 int CGitLogListBase::AsyncDiffThread()
171 m_AsyncThreadExited = false;
172 while(!m_AsyncThreadExit)
174 ::WaitForSingleObject(m_AsyncDiffEvent, INFINITE);
176 GitRev *pRev = NULL;
177 while(!m_AsyncThreadExit && !m_AsynDiffList.empty())
179 m_AsynDiffListLock.Lock();
180 pRev = m_AsynDiffList.back();
181 m_AsynDiffList.pop_back();
182 m_AsynDiffListLock.Unlock();
184 if( pRev->m_CommitHash.IsEmpty() )
186 if(pRev->m_IsDiffFiles)
187 continue;
189 pRev->GetFiles(this).Clear();
190 pRev->m_ParentHash.clear();
191 pRev->m_ParentHash.push_back(m_HeadHash);
192 if(g_Git.IsInitRepos())
194 if (g_Git.GetInitAddList(pRev->GetFiles(this)))
195 CMessageBox::Show(NULL, _T("Run ls-files failed!"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
198 else
200 g_Git.GetCommitDiffList(pRev->m_CommitHash.ToString(),this->m_HeadHash.ToString(), pRev->GetFiles(this));
202 pRev->GetAction(this) = 0;
204 for (int j = 0; j < pRev->GetFiles(this).GetCount(); ++j)
205 pRev->GetAction(this) |= pRev->GetFiles(this)[j].m_Action;
207 pRev->GetUnRevFiles().FillUnRev(CTGitPath::LOGACTIONS_UNVER);
209 InterlockedExchange(&pRev->m_IsDiffFiles, TRUE);
210 InterlockedExchange(&pRev->m_IsFull, TRUE);
212 pRev->GetBody().Format(IDS_FILESCHANGES, pRev->GetFiles(this).GetCount());
213 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)0,0);
214 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
217 if(!pRev->CheckAndDiff())
218 { // fetch change file list
219 for (int i = GetTopIndex(); !m_AsyncThreadExit && i <= GetTopIndex() + GetCountPerPage(); ++i)
221 if(i < m_arShownList.GetCount())
223 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(i);
224 if(data->m_CommitHash == pRev->m_CommitHash)
226 ::PostMessage(m_hWnd,MSG_LOADED,(WPARAM)i,0);
227 break;
232 if(!m_AsyncThreadExit && GetSelectedCount() == 1)
234 POSITION pos = GetFirstSelectedItemPosition();
235 int nItem = GetNextSelectedItem(pos);
237 if(nItem>=0)
239 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(nItem);
240 if(data)
241 if(data->m_CommitHash == pRev->m_CommitHash)
243 this->GetParent()->PostMessage(WM_COMMAND, MSG_FETCHED_DIFF, 0);
250 m_AsyncThreadExited = true;
251 return 0;
253 void CGitLogListBase::hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow)
255 if (exclusivelyShow)
257 m_ContextMenuMask &= hideMask;
259 else
261 m_ContextMenuMask &= ~hideMask;
265 CGitLogListBase::~CGitLogListBase()
267 InterlockedExchange(&m_bNoDispUpdates, TRUE);
268 this->m_arShownList.SafeRemoveAll();
270 DestroyIcon(m_hModifiedIcon);
271 DestroyIcon(m_hReplacedIcon);
272 DestroyIcon(m_hAddedIcon);
273 DestroyIcon(m_hDeletedIcon);
274 m_logEntries.ClearAll();
276 if (m_boldFont)
277 DeleteObject(m_boldFont);
279 if ( m_pStoreSelection )
281 delete m_pStoreSelection;
282 m_pStoreSelection = NULL;
285 SafeTerminateThread();
286 SafeTerminateAsyncDiffThread();
288 if(m_AsyncDiffEvent)
289 CloseHandle(m_AsyncDiffEvent);
293 BEGIN_MESSAGE_MAP(CGitLogListBase, CHintListCtrl)
294 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
295 ON_REGISTERED_MESSAGE(m_ScrollToMessage, OnScrollToMessage)
296 ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdrawLoglist)
297 ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnLvnGetdispinfoLoglist)
298 ON_WM_CONTEXTMENU()
299 ON_NOTIFY_REFLECT(NM_DBLCLK, OnNMDblclkLoglist)
300 ON_NOTIFY_REFLECT(LVN_ODFINDITEM,OnLvnOdfinditemLoglist)
301 ON_WM_CREATE()
302 ON_WM_DESTROY()
303 ON_MESSAGE(MSG_LOADED,OnLoad)
304 ON_WM_MEASUREITEM()
305 ON_WM_MEASUREITEM_REFLECT()
306 ON_NOTIFY(HDN_BEGINTRACKA, 0, OnHdnBegintrack)
307 ON_NOTIFY(HDN_BEGINTRACKW, 0, OnHdnBegintrack)
308 ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
309 ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
310 ON_NOTIFY(HDN_ENDTRACK, 0, OnColumnResized)
311 ON_NOTIFY(HDN_ENDDRAG, 0, OnColumnMoved)
312 END_MESSAGE_MAP()
314 void CGitLogListBase::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
316 //if (m_nRowHeight>0)
318 lpMeasureItemStruct->itemHeight = 50;
322 int CGitLogListBase:: OnCreate(LPCREATESTRUCT lpCreateStruct)
324 PreSubclassWindow();
325 return CHintListCtrl::OnCreate(lpCreateStruct);
328 void CGitLogListBase::PreSubclassWindow()
330 SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES);
331 // load the icons for the action columns
332 // m_Theme.Open(m_hWnd, L"ListView");
333 SetWindowTheme(m_hWnd, L"Explorer", NULL);
334 CHintListCtrl::PreSubclassWindow();
337 void CGitLogListBase::InsertGitColumn()
339 CString temp;
341 CRegDWORD regFullRowSelect(_T("Software\\TortoiseGit\\FullRowSelect"), TRUE);
342 DWORD exStyle = LVS_EX_HEADERDRAGDROP | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_SUBITEMIMAGES;
343 if (DWORD(regFullRowSelect))
344 exStyle |= LVS_EX_FULLROWSELECT;
345 SetExtendedStyle(exStyle);
347 // only load properties if we have a repository
348 if (GitAdminDir().HasAdminDir(g_Git.m_CurrentDir) || GitAdminDir().IsBareRepo(g_Git.m_CurrentDir))
349 UpdateProjectProperties();
351 static UINT normal[] =
353 IDS_LOG_GRAPH,
354 IDS_LOG_REBASE,
355 IDS_LOG_ID,
356 IDS_LOG_HASH,
357 IDS_LOG_ACTIONS,
358 IDS_LOG_MESSAGE,
359 IDS_LOG_AUTHOR,
360 IDS_LOG_DATE,
361 IDS_LOG_EMAIL,
362 IDS_LOG_COMMIT_NAME,
363 IDS_LOG_COMMIT_EMAIL,
364 IDS_LOG_COMMIT_DATE,
365 IDS_LOG_BUGIDS,
368 static int with[] =
370 ICONITEMBORDER+16*4,
371 ICONITEMBORDER+16*4,
372 ICONITEMBORDER+16*4,
373 ICONITEMBORDER+16*4,
374 ICONITEMBORDER+16*4,
375 LOGLIST_MESSAGE_MIN,
376 ICONITEMBORDER+16*4,
377 ICONITEMBORDER+16*4,
378 ICONITEMBORDER+16*4,
379 ICONITEMBORDER+16*4,
380 ICONITEMBORDER+16*4,
381 ICONITEMBORDER+16*4,
382 ICONITEMBORDER+16*4,
384 m_dwDefaultColumns = GIT_LOG_GRAPH|GIT_LOG_ACTIONS|GIT_LOG_MESSAGE|GIT_LOG_AUTHOR|GIT_LOG_DATE;
386 DWORD hideColumns = 0;
387 if(this->m_IsRebaseReplaceGraph)
389 hideColumns |= GIT_LOG_GRAPH;
390 m_dwDefaultColumns |= GIT_LOG_REBASE;
392 else
394 hideColumns |= GIT_LOG_REBASE;
397 if(this->m_IsIDReplaceAction)
399 hideColumns |= GIT_LOG_ACTIONS;
400 m_dwDefaultColumns |= GIT_LOG_ID;
401 m_dwDefaultColumns |= GIT_LOG_HASH;
403 else
405 hideColumns |= GIT_LOG_ID;
407 if(this->m_bShowBugtraqColumn)
409 m_dwDefaultColumns |= GIT_LOGLIST_BUG;
411 else
413 hideColumns |= GIT_LOGLIST_BUG;
415 SetRedraw(false);
417 m_ColumnManager.SetNames(normal, _countof(normal));
418 m_ColumnManager.ReadSettings(m_dwDefaultColumns, hideColumns, m_ColumnRegKey+_T("loglist"), _countof(normal), with);
420 SetRedraw(true);
424 * Resizes all columns in a list control to values in registry.
426 void CGitLogListBase::ResizeAllListCtrlCols()
428 // column max and min widths to allow
429 static const int nMinimumWidth = 10;
430 static const int nMaximumWidth = 1000;
431 CHeaderCtrl* pHdrCtrl = (CHeaderCtrl*)(GetDlgItem(0));
432 if (pHdrCtrl)
434 int numcols = pHdrCtrl->GetItemCount();
435 for (int col = 0; col < numcols; ++col)
437 // get width for this col last time from registry
438 CString regentry;
439 regentry.Format( _T("Software\\TortoiseGit\\%s\\ColWidth%d"),m_ColumnRegKey, col);
440 CRegDWORD regwidth(regentry, 0);
441 int cx = regwidth;
442 if ( cx == 0 )
444 // no saved value, setup sensible defaults
445 if (col == this->LOGLIST_MESSAGE)
447 cx = LOGLIST_MESSAGE_MIN;
449 else
451 cx = ICONITEMBORDER+16*4;
454 if (cx < nMinimumWidth)
456 cx = nMinimumWidth;
458 else if (cx > nMaximumWidth)
460 cx = nMaximumWidth;
463 SetColumnWidth(col, cx);
470 void CGitLogListBase::FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect)
472 LVITEM rItem;
473 SecureZeroMemory(&rItem, sizeof(LVITEM));
474 rItem.mask = LVIF_STATE;
475 rItem.iItem = (int)Index;
476 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
477 GetItem(&rItem);
479 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(Index);
480 HBRUSH brush = NULL;
482 if (!(rItem.state & LVIS_SELECTED))
484 if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_SQUASH)
485 brush = ::CreateSolidBrush(RGB(156,156,156));
486 else if(pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_EDIT)
487 brush = ::CreateSolidBrush(RGB(200,200,128));
489 else if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
491 if (rItem.state & LVIS_SELECTED)
493 if (::GetFocus() == m_hWnd)
494 brush = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
495 else
496 brush = ::CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
500 if (brush != NULL)
502 ::FillRect(hdc, &rect, brush);
503 ::DeleteObject(brush);
507 void DrawTrackingRoundRect(HDC hdc, CRect rect, HBRUSH brush, COLORREF darkColor)
509 POINT point = { 4, 4 };
510 CRect rt2 = rect;
511 rt2.DeflateRect(1, 1);
512 rt2.OffsetRect(2, 2);
514 HPEN nullPen = ::CreatePen(PS_NULL, 0, 0);
515 HPEN oldpen = (HPEN)::SelectObject(hdc, nullPen);
516 HBRUSH darkBrush = (HBRUSH)::CreateSolidBrush(darkColor);
517 HBRUSH oldbrush = (HBRUSH)::SelectObject(hdc, darkBrush);
518 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
520 ::SelectObject(hdc, brush);
521 rt2.OffsetRect(-2, -2);
522 ::RoundRect(hdc, rt2.left, rt2.top, rt2.right, rt2.bottom, point.x, point.y);
523 ::SelectObject(hdc, oldbrush);
524 ::SelectObject(hdc, oldpen);
525 ::DeleteObject(nullPen);
526 ::DeleteObject(darkBrush);
529 void DrawLightning(HDC hdc, CRect rect, COLORREF color, int bold)
531 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
532 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
533 ::MoveToEx(hdc, rect.left + 7, rect.top, NULL);
534 ::LineTo(hdc, rect.left + 1, (rect.top + rect.bottom) / 2);
535 ::LineTo(hdc, rect.left + 6, (rect.top + rect.bottom) / 2);
536 ::LineTo(hdc, rect.left, rect.bottom);
537 ::SelectObject(hdc, oldpen);
538 ::DeleteObject(pen);
541 void DrawUpTriangle(HDC hdc, CRect rect, COLORREF color, int bold)
543 HPEN pen = ::CreatePen(PS_SOLID, bold, color);
544 HPEN oldpen = (HPEN)::SelectObject(hdc, pen);
545 ::MoveToEx(hdc, (rect.left + rect.right) / 2, rect.top, NULL);
546 ::LineTo(hdc, rect.left, rect.bottom);
547 ::LineTo(hdc, rect.right, rect.bottom);
548 ::LineTo(hdc, (rect.left + rect.right) / 2, rect.top);
549 ::SelectObject(hdc, oldpen);
550 ::DeleteObject(pen);
553 void CGitLogListBase::DrawTagBranch(HDC hdc, CRect &rect, INT_PTR index, std::vector<REFLABEL> refList)
555 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
556 CRect rt=rect;
557 LVITEM rItem;
558 SecureZeroMemory(&rItem, sizeof(LVITEM));
559 rItem.mask = LVIF_STATE;
560 rItem.iItem = (int)index;
561 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
562 GetItem(&rItem);
564 CDC W_Dc;
565 W_Dc.Attach(hdc);
567 HTHEME hTheme = NULL;
568 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
569 hTheme = OpenThemeData(m_hWnd, L"Explorer::ListView;ListView");
571 for (unsigned int i = 0; i < refList.size(); ++i)
573 CString shortname = !refList[i].simplifiedName.IsEmpty() ? refList[i].simplifiedName : refList[i].name;
574 HBRUSH brush = 0;
575 COLORREF colRef = refList[i].color;
576 bool singleRemote = refList[i].singleRemote;
577 bool hasTracking = refList[i].hasTracking;
578 bool sameName = refList[i].sameName;
579 bool annotatedTag = refList[i].annotatedTag;
581 //When row selected, ajust label color
582 if (!(IsAppThemed() && SysInfo::Instance().IsVistaOrLater()))
583 if (rItem.state & LVIS_SELECTED)
584 colRef = CColors::MixColors(colRef, ::GetSysColor(COLOR_HIGHLIGHT), 150);
586 brush = ::CreateSolidBrush(colRef);
588 if(!shortname.IsEmpty() && (rt.left<rect.right) )
590 SIZE size;
591 memset(&size,0,sizeof(SIZE));
592 GetTextExtentPoint32(hdc, shortname,shortname.GetLength(),&size);
594 rt.SetRect(rt.left,rt.top,rt.left+size.cx,rt.bottom);
595 rt.right+=8;
597 int textpos = DT_CENTER;
599 if(rt.right > rect.right)
601 rt.right = rect.right;
602 textpos =0;
605 CRect textRect = rt;
607 if (singleRemote)
609 rt.right += 8;
610 textRect.OffsetRect(8, 0);
613 if (sameName)
614 rt.right += 8;
616 if (hasTracking)
618 DrawTrackingRoundRect(hdc, rt, brush, m_Colors.Darken(colRef, 100));
620 else
622 //Fill interior of ref label
623 ::FillRect(hdc, &rt, brush);
626 //Draw edge of label
628 CRect rectEdge = rt;
630 if (!hasTracking)
632 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 100), m_Colors.Darken(colRef, 100));
633 rectEdge.DeflateRect(1, 1);
634 W_Dc.Draw3dRect(rectEdge, m_Colors.Lighten(colRef, 50), m_Colors.Darken(colRef, 50));
637 if (annotatedTag)
639 rt.right += 8;
640 POINT trianglept[3] = { { rt.right - 8, rt.top }, { rt.right, (rt.top + rt.bottom) / 2 }, { rt.right - 8, rt.bottom } };
641 HRGN hrgn = ::CreatePolygonRgn(trianglept, 3, ALTERNATE);
642 ::FillRgn(hdc, hrgn, brush);
643 ::DeleteObject(hrgn);
644 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[0].y, NULL);
645 HPEN pen;
646 HPEN oldpen = (HPEN)SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Lighten(colRef, 50)));
647 ::LineTo(hdc, trianglept[1].x - 1, trianglept[1].y - 1);
648 ::DeleteObject(pen);
649 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, m_Colors.Darken(colRef, 50)));
650 ::LineTo(hdc, trianglept[2].x - 1, trianglept[2].y - 1);
651 ::DeleteObject(pen);
652 SelectObject(hdc, pen = ::CreatePen(PS_SOLID, 2, colRef));
653 ::MoveToEx(hdc, trianglept[0].x - 1, trianglept[2].y - 3, NULL);
654 ::LineTo(hdc, trianglept[0].x - 1, trianglept[0].y);
655 ::DeleteObject(pen);
656 SelectObject(hdc, oldpen);
659 //Draw text inside label
660 bool customColor = (colRef & 0xff) * 30 + ((colRef >> 8) & 0xff) * 59 + ((colRef >> 16) & 0xff) * 11 <= 12800; // check if dark background
661 if (!customColor && IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
663 int txtState = LISS_NORMAL;
664 if (rItem.state & LVIS_SELECTED)
665 txtState = LISS_SELECTED;
667 DrawThemeText(hTheme, hdc, LVP_LISTITEM, txtState, shortname, -1, textpos | DT_SINGLELINE | DT_VCENTER, 0, &textRect);
669 else
671 W_Dc.SetBkMode(TRANSPARENT);
672 if (customColor || (rItem.state & LVIS_SELECTED))
674 COLORREF clrNew = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
675 COLORREF clrOld = ::SetTextColor(hdc,clrNew);
676 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
677 ::SetTextColor(hdc,clrOld);
679 else
681 ::DrawText(hdc, shortname, shortname.GetLength(), &textRect, textpos | DT_SINGLELINE | DT_VCENTER);
685 if (singleRemote)
687 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
688 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
689 CRect newRect;
690 newRect.SetRect(rt.left + 4, rt.top + 4, rt.left + 8, rt.bottom - 4);
691 DrawLightning(hdc, newRect, color, bold);
694 if (sameName)
696 COLORREF color = ::GetSysColor(customColor ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
697 int bold = data->m_CommitHash == m_HeadHash ? 2 : 1;
698 CRect newRect;
699 newRect.SetRect(rt.right - 12, rt.top + 4, rt.right - 4, rt.bottom - 4);
700 DrawUpTriangle(hdc, newRect, color, bold);
703 //::MoveToEx(hdc,rt.left,rt.top,NULL);
704 //::LineTo(hdc,rt.right,rt.top);
705 //::LineTo(hdc,rt.right,rt.bottom);
706 //::LineTo(hdc,rt.left,rt.bottom);
707 //::LineTo(hdc,rt.left,rt.top);
709 rt.left=rt.right+1;
711 if(brush)
712 ::DeleteObject(brush);
714 rt.right=rect.right;
716 if (IsAppThemed() && SysInfo::Instance().IsVistaOrLater())
718 int txtState = LISS_NORMAL;
719 if (rItem.state & LVIS_SELECTED)
720 txtState = LISS_SELECTED;
722 DrawThemeText(hTheme,hdc, LVP_LISTITEM, txtState, data->GetSubject(), -1, DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS, 0, &rt);
724 else
726 if (rItem.state & LVIS_SELECTED)
728 COLORREF clrOld = ::SetTextColor(hdc,::GetSysColor(COLOR_HIGHLIGHTTEXT));
729 ::DrawText(hdc,data->GetSubject(),data->GetSubject().GetLength(),&rt,DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
730 ::SetTextColor(hdc,clrOld);
732 else
734 ::DrawText(hdc,data->GetSubject(),data->GetSubject().GetLength(),&rt,DT_NOPREFIX | DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
738 if (hTheme)
739 CloseThemeData(hTheme);
741 W_Dc.Detach();
744 static COLORREF blend(const COLORREF& col1, const COLORREF& col2, int amount = 128) {
746 // Returns ((256 - amount)*col1 + amount*col2) / 256;
747 return RGB(((256 - amount)*GetRValue(col1) + amount*GetRValue(col2) ) / 256,
748 ((256 - amount)*GetGValue(col1) + amount*GetGValue(col2) ) / 256,
749 ((256 - amount)*GetBValue(col1) + amount*GetBValue(col2) ) / 256);
752 Gdiplus::Color GetGdiColor(COLORREF col)
754 return Gdiplus::Color(GetRValue(col),GetGValue(col),GetBValue(col));
756 void CGitLogListBase::paintGraphLane(HDC hdc, int laneHeight,int type, int x1, int x2,
757 const COLORREF& col,const COLORREF& activeColor, int top
760 int h = laneHeight / 2;
761 int m = (x1 + x2) / 2;
762 int r = (x2 - x1) / 3;
763 int d = 2 * r;
765 #define P_CENTER m , h+top
766 #define P_0 x2, h+top
767 #define P_90 m , 0+top-1
768 #define P_180 x1, h+top
769 #define P_270 m , 2 * h+top +1
770 #define R_CENTER m - r, h - r+top, d, d
773 #define DELTA_UR_B 2*(x1 - m), 2*h +top
774 #define DELTA_UR_E 0*16, 90*16 +top // -,
776 #define DELTA_DR_B 2*(x1 - m), 2*-h +top
777 #define DELTA_DR_E 270*16, 90*16 +top // -'
779 #define DELTA_UL_B 2*(x2 - m), 2*h +top
780 #define DELTA_UL_E 90*16, 90*16 +top // ,-
782 #define DELTA_DL_B 2*(x2 - m),2*-h +top
783 #define DELTA_DL_E 180*16, 90*16 // '-
785 #define CENTER_UR x1, 2*h, 225
786 #define CENTER_DR x1, 0 , 135
787 #define CENTER_UL x2, 2*h, 315
788 #define CENTER_DL x2, 0 , 45
791 Gdiplus::Graphics graphics( hdc );
793 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
795 // arc
796 switch (type) {
797 case Lanes::JOIN:
798 case Lanes::JOIN_R:
799 case Lanes::HEAD:
800 case Lanes::HEAD_R:
802 Gdiplus::LinearGradientBrush gradient(
803 Gdiplus::Point(x1-2, h+top-2),
804 Gdiplus::Point(P_270),
805 GetGdiColor(activeColor),GetGdiColor(col));
808 Gdiplus::Pen mypen(&gradient,2);
809 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
811 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
812 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top+h-1, x2-x1,laneHeight,270,90);
813 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
815 break;
817 case Lanes::JOIN_L:
820 Gdiplus::LinearGradientBrush gradient(
821 Gdiplus::Point(P_270),
822 Gdiplus::Point(x2+1, h+top-1),
823 GetGdiColor(col),GetGdiColor(activeColor));
826 Gdiplus::Pen mypen(&gradient,2);
827 //Gdiplus::Pen mypen(Gdiplus::Color(0,0,0),2);
829 //graphics.DrawRectangle(&mypen,x1-(x2-x1)/2,top+h, x2-x1,laneHeight);
830 graphics.DrawArc(&mypen,x1+(x2-x1)/2,top+h-1, x2-x1,laneHeight,180,90);
831 //graphics.DrawLine(&mypen,x1-1,h+top,P_270);
834 break;
836 case Lanes::TAIL:
837 case Lanes::TAIL_R:
840 Gdiplus::LinearGradientBrush gradient(
841 Gdiplus::Point(x1-2, h+top-2),
842 Gdiplus::Point(P_90),
843 GetGdiColor(activeColor),GetGdiColor(col));
845 Gdiplus::Pen mypen(&gradient,2);
847 graphics.DrawArc(&mypen,x1-(x2-x1)/2-1,top-h-1, x2-x1,laneHeight,0,90);
849 #if 0
850 QConicalGradient gradient(CENTER_DR);
851 gradient.setColorAt(0.375, activeCol);
852 gradient.setColorAt(0.625, col);
853 myPen.setBrush(gradient);
854 p->setPen(myPen);
855 p->drawArc(P_CENTER, DELTA_DR);
856 #endif
857 break;
859 default:
860 break;
864 //static QPen myPen(Qt::black, 2); // fast path here
865 CPen pen;
866 pen.CreatePen(PS_SOLID,2,col);
867 //myPen.setColor(col);
868 HPEN oldpen=(HPEN)::SelectObject(hdc,(HPEN)pen);
870 Gdiplus::Pen myPen(GetGdiColor(col),2);
872 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
874 //p->setPen(myPen);
876 // vertical line
877 switch (type) {
878 case Lanes::ACTIVE:
879 case Lanes::NOT_ACTIVE:
880 case Lanes::MERGE_FORK:
881 case Lanes::MERGE_FORK_R:
882 case Lanes::MERGE_FORK_L:
883 case Lanes::JOIN:
884 case Lanes::JOIN_R:
885 case Lanes::JOIN_L:
886 case Lanes::CROSS:
887 //DrawLine(hdc,P_90,P_270);
888 graphics.DrawLine(&myPen,P_90,P_270);
889 //p->drawLine(P_90, P_270);
890 break;
891 case Lanes::HEAD_L:
892 case Lanes::BRANCH:
893 //DrawLine(hdc,P_CENTER,P_270);
894 graphics.DrawLine(&myPen,P_CENTER,P_270);
895 //p->drawLine(P_CENTER, P_270);
896 break;
897 case Lanes::TAIL_L:
898 case Lanes::INITIAL:
899 case Lanes::BOUNDARY:
900 case Lanes::BOUNDARY_C:
901 case Lanes::BOUNDARY_R:
902 case Lanes::BOUNDARY_L:
903 //DrawLine(hdc,P_90, P_CENTER);
904 graphics.DrawLine(&myPen,P_90,P_CENTER);
905 //p->drawLine(P_90, P_CENTER);
906 break;
907 default:
908 break;
911 myPen.SetColor(GetGdiColor(activeColor));
913 // horizontal line
914 switch (type) {
915 case Lanes::MERGE_FORK:
916 case Lanes::JOIN:
917 case Lanes::HEAD:
918 case Lanes::TAIL:
919 case Lanes::CROSS:
920 case Lanes::CROSS_EMPTY:
921 case Lanes::BOUNDARY_C:
922 //DrawLine(hdc,P_180,P_0);
923 graphics.DrawLine(&myPen,P_180,P_0);
924 //p->drawLine(P_180, P_0);
925 break;
926 case Lanes::MERGE_FORK_R:
927 case Lanes::BOUNDARY_R:
928 //DrawLine(hdc,P_180,P_CENTER);
929 graphics.DrawLine(&myPen,P_180,P_CENTER);
930 //p->drawLine(P_180, P_CENTER);
931 break;
932 case Lanes::MERGE_FORK_L:
933 case Lanes::HEAD_L:
934 case Lanes::TAIL_L:
935 case Lanes::BOUNDARY_L:
936 //DrawLine(hdc,P_CENTER,P_0);
937 graphics.DrawLine(&myPen,P_CENTER,P_0);
938 //p->drawLine(P_CENTER, P_0);
939 break;
940 default:
941 break;
944 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
946 CBrush brush;
947 brush.CreateSolidBrush(col);
948 HBRUSH oldbrush=(HBRUSH)::SelectObject(hdc,(HBRUSH)brush);
950 Gdiplus::SolidBrush myBrush(GetGdiColor(col));
951 // center symbol, e.g. rect or ellipse
952 switch (type) {
953 case Lanes::ACTIVE:
954 case Lanes::INITIAL:
955 case Lanes::BRANCH:
957 //p->setPen(Qt::NoPen);
958 //p->setBrush(col);
959 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
960 graphics.FillEllipse(&myBrush, R_CENTER);
961 //p->drawEllipse(R_CENTER);
962 break;
963 case Lanes::MERGE_FORK:
964 case Lanes::MERGE_FORK_R:
965 case Lanes::MERGE_FORK_L:
966 //p->setPen(Qt::NoPen);
967 //p->setBrush(col);
968 //p->drawRect(R_CENTER);
969 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
970 graphics.FillRectangle(&myBrush, R_CENTER);
971 break;
972 case Lanes::UNAPPLIED:
973 // Red minus sign
974 //p->setPen(Qt::NoPen);
975 //p->setBrush(Qt::red);
976 //p->drawRect(m - r, h - 1, d, 2);
977 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
978 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
979 break;
980 case Lanes::APPLIED:
981 // Green plus sign
982 //p->setPen(Qt::NoPen);
983 //p->setBrush(DARK_GREEN);
984 //p->drawRect(m - r, h - 1, d, 2);
985 //p->drawRect(m - 1, h - r, 2, d);
986 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
987 graphics.FillRectangle(&myBrush,m-r,h-1,d,2);
988 graphics.FillRectangle(&myBrush,m-1,h-r,2,d);
989 break;
990 case Lanes::BOUNDARY:
991 //p->setBrush(back);
992 //p->drawEllipse(R_CENTER);
993 graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
994 graphics.DrawEllipse(&myPen, R_CENTER);
995 break;
996 case Lanes::BOUNDARY_C:
997 case Lanes::BOUNDARY_R:
998 case Lanes::BOUNDARY_L:
999 //p->setBrush(back);
1000 //p->drawRect(R_CENTER);
1001 graphics.SetSmoothingMode(Gdiplus::SmoothingModeNone);
1002 graphics.FillRectangle(&myBrush,R_CENTER);
1003 break;
1004 default:
1005 break;
1008 ::SelectObject(hdc,oldpen);
1009 ::SelectObject(hdc,oldbrush);
1010 #undef P_CENTER
1011 #undef P_0
1012 #undef P_90
1013 #undef P_180
1014 #undef P_270
1015 #undef R_CENTER
1018 void CGitLogListBase::DrawGraph(HDC hdc,CRect &rect,INT_PTR index)
1020 // TODO: unfinished
1021 // return;
1022 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(index);
1023 if(data->m_CommitHash.IsEmpty())
1024 return;
1026 CRect rt=rect;
1027 LVITEM rItem;
1028 SecureZeroMemory(&rItem, sizeof(LVITEM));
1029 rItem.mask = LVIF_STATE;
1030 rItem.iItem = (int)index;
1031 rItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
1032 GetItem(&rItem);
1034 // p->translate(QPoint(opt.rect.left(), opt.rect.top()));
1036 if (data->m_Lanes.empty())
1037 m_logEntries.setLane(data->m_CommitHash);
1039 std::vector<int>& lanes=data->m_Lanes;
1040 size_t laneNum = lanes.size();
1041 UINT activeLane = 0;
1042 for (UINT i = 0; i < laneNum; ++i)
1043 if (Lanes::isMerge(lanes[i])) {
1044 activeLane = i;
1045 break;
1048 int x1 = 0, x2 = 0;
1049 int maxWidth = rect.Width();
1050 int lw = 3 * rect.Height() / 4; //laneWidth()
1052 COLORREF activeColor = m_LineColors[activeLane % Lanes::COLORS_NUM];
1053 //if (opt.state & QStyle::State_Selected)
1054 // activeColor = blend(activeColor, opt.palette.highlightedText().color(), 208);
1056 for (unsigned int i = 0; i < laneNum && x2 < maxWidth; ++i)
1059 x1 = x2;
1060 x2 += lw;
1062 int ln = lanes[i];
1063 if (ln == Lanes::EMPTY)
1064 continue;
1066 COLORREF color = i == activeLane ? activeColor : m_LineColors[i % Lanes::COLORS_NUM];
1067 paintGraphLane(hdc, rect.Height(),ln, x1+rect.left, x2+rect.left, color,activeColor, rect.top);
1070 #if 0
1071 for (UINT i = 0; i < laneNum && x2 < maxWidth; ++i) {
1073 x1 = x2;
1074 x2 += lw;
1076 int ln = lanes[i];
1077 if (ln == Lanes::EMPTY)
1078 continue;
1080 UINT col = ( Lanes:: isHead(ln) ||Lanes:: isTail(ln) || Lanes::isJoin(ln)
1081 || ln ==Lanes:: CROSS_EMPTY) ? activeLane : i;
1083 if (ln == Lanes::CROSS)
1085 paintGraphLane(hdc, rect.Height(),Lanes::NOT_ACTIVE, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1086 paintGraphLane(hdc, rect.Height(),Lanes::CROSS, x1, x2, m_LineColors[activeLane % Lanes::COLORS_NUM],rect.top);
1088 else
1089 paintGraphLane(hdc, rect.Height(),ln, x1, x2, m_LineColors[col % Lanes::COLORS_NUM],rect.top);
1091 #endif
1095 void CGitLogListBase::OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1098 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
1099 // Take the default processing unless we set this to something else below.
1100 *pResult = CDRF_DODEFAULT;
1102 if (m_bNoDispUpdates)
1103 return;
1105 switch (pLVCD->nmcd.dwDrawStage)
1107 case CDDS_PREPAINT:
1109 *pResult = CDRF_NOTIFYITEMDRAW;
1110 return;
1112 break;
1113 case CDDS_ITEMPREPAINT:
1115 // This is the prepaint stage for an item. Here's where we set the
1116 // item's text color.
1118 // Tell Windows to send draw notifications for each subitem.
1119 *pResult = CDRF_NOTIFYSUBITEMDRAW;
1121 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
1123 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1125 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1126 if (data)
1128 if (data->GetAction(this)& (CTGitPath::LOGACTIONS_REBASE_DONE| CTGitPath::LOGACTIONS_REBASE_SKIP) )
1129 crText = RGB(128,128,128);
1131 if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_SQUASH)
1132 pLVCD->clrTextBk = RGB(156,156,156);
1133 else if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_EDIT)
1134 pLVCD->clrTextBk = RGB(200,200,128);
1135 else
1136 pLVCD->clrTextBk = ::GetSysColor(COLOR_WINDOW);
1138 if(data->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_CURRENT)
1140 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1141 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1144 if (data->m_CommitHash == m_HeadHash && m_bNoHightlightHead == FALSE)
1146 SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1147 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1150 // if ((data->childStackDepth)||(m_mergedRevs.find(data->Rev) != m_mergedRevs.end()))
1151 // crText = GetSysColor(COLOR_GRAYTEXT);
1153 if (data->m_CommitHash.IsEmpty())
1155 //crText = GetSysColor(RGB(200,200,0));
1156 //SelectObject(pLVCD->nmcd.hdc, m_boldFont);
1157 // We changed the font, so we're returning CDRF_NEWFONT. This
1158 // tells the control to recalculate the extent of the text.
1159 *pResult = CDRF_NOTIFYSUBITEMDRAW | CDRF_NEWFONT;
1163 if (m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec)
1165 if (m_bStrictStopped)
1166 crText = GetSysColor(COLOR_GRAYTEXT);
1168 // Store the color back in the NMLVCUSTOMDRAW struct.
1169 pLVCD->clrText = crText;
1170 return;
1172 break;
1173 case CDDS_ITEMPREPAINT|CDDS_ITEM|CDDS_SUBITEM:
1175 if ((m_bStrictStopped)&&(m_arShownList.GetCount() == (INT_PTR)pLVCD->nmcd.dwItemSpec))
1177 pLVCD->nmcd.uItemState &= ~(CDIS_SELECTED|CDIS_FOCUS);
1180 if (pLVCD->iSubItem == LOGLIST_GRAPH && m_sFilterText.IsEmpty())
1182 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec && (!this->m_IsRebaseReplaceGraph) )
1184 CRect rect;
1185 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_LABEL, rect);
1187 //TRACE(_T("A Graphic left %d right %d\r\n"),rect.left,rect.right);
1188 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1190 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1191 if( !data ->m_CommitHash.IsEmpty())
1192 DrawGraph(pLVCD->nmcd.hdc,rect,pLVCD->nmcd.dwItemSpec);
1194 *pResult = CDRF_SKIPDEFAULT;
1195 return;
1199 if (pLVCD->iSubItem == LOGLIST_MESSAGE)
1201 if (m_arShownList.GetCount() > (INT_PTR)pLVCD->nmcd.dwItemSpec)
1203 GitRev* data = (GitRev*)m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec);
1204 //if(!data->m_IsFull)
1206 //if(data->SafeFetchFullInfo(&g_Git))
1207 // this->Invalidate();
1208 //TRACE(_T("Update ... %d\r\n"),pLVCD->nmcd.dwItemSpec);
1211 if (!m_HashMap[data->m_CommitHash].empty())
1213 CRect rect;
1215 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1217 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec,rect);
1219 std::vector<REFLABEL> refsToShow;
1220 STRING_VECTOR remoteTrackingList;
1221 STRING_VECTOR refList = m_HashMap[data->m_CommitHash];
1222 for (unsigned int i = 0; i < refList.size(); ++i)
1224 CString str = refList[i];
1226 REFLABEL refLabel;
1227 refLabel.color = RGB(255, 255, 255);
1228 refLabel.singleRemote = false;
1229 refLabel.hasTracking = false;
1230 refLabel.sameName = false;
1231 refLabel.annotatedTag = false;
1232 if (CGit::GetShortName(str, refLabel.name, _T("refs/heads/")))
1234 if (!(m_ShowRefMask & LOGLIST_SHOWLOCALBRANCHES))
1235 continue;
1236 if (refLabel.name == m_CurrentBranch )
1237 refLabel.color = m_Colors.GetColor(CColors::CurrentBranch);
1238 else
1239 refLabel.color = m_Colors.GetColor(CColors::LocalBranch);
1241 std::pair<CString, CString> trackingEntry = m_TrackingMap[refLabel.name];
1242 CString pullRemote = trackingEntry.first;
1243 CString pullBranch = trackingEntry.second;
1244 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
1246 CString defaultUpstream;
1247 defaultUpstream.Format(_T("refs/remotes/%s/%s"), pullRemote, pullBranch);
1248 refLabel.hasTracking = true;
1249 if (m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES)
1251 bool found = false;
1252 for (int j = i + 1; j < refList.size(); ++j)
1254 if (refList[j] == defaultUpstream)
1256 found = true;
1257 break;
1261 if (found)
1263 bool sameName = pullBranch == refLabel.name;
1264 refsToShow.push_back(refLabel);
1265 CGit::GetShortName(defaultUpstream, refLabel.name, _T("refs/remotes/"));
1266 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1267 if (m_bSymbolizeRefNames)
1269 if (!m_SingleRemote.IsEmpty() && m_SingleRemote == pullRemote)
1271 refLabel.simplifiedName = _T("/") + (sameName ? CString() : pullBranch);
1272 refLabel.singleRemote = true;
1274 else if (sameName)
1275 refLabel.simplifiedName = pullRemote + _T("/");
1276 refLabel.sameName = sameName;
1278 refsToShow.push_back(refLabel);
1279 remoteTrackingList.push_back(defaultUpstream);
1280 continue;
1285 else if (CGit::GetShortName(str, refLabel.name, _T("refs/remotes/")))
1287 if (!(m_ShowRefMask & LOGLIST_SHOWREMOTEBRANCHES))
1288 continue;
1290 bool found = false;
1291 for (int j = 0; j < remoteTrackingList.size(); ++j)
1293 if (remoteTrackingList[j] == str)
1295 found = true;
1296 break;
1299 if (found)
1300 continue;
1302 refLabel.color = m_Colors.GetColor(CColors::RemoteBranch);
1303 if (m_bSymbolizeRefNames)
1305 if (!m_SingleRemote.IsEmpty() && refLabel.name.Left(m_SingleRemote.GetLength() + 1) == m_SingleRemote + _T("/"))
1307 refLabel.simplifiedName = _T("/") + refLabel.name.Mid(m_SingleRemote.GetLength() + 1);
1308 refLabel.singleRemote = true;
1312 else if (CGit::GetShortName(str, refLabel.name, _T("refs/tags/")))
1314 if (!(m_ShowRefMask & LOGLIST_SHOWTAGS))
1315 continue;
1316 refLabel.color = m_Colors.GetColor(CColors::Tag);
1317 refLabel.annotatedTag = str.Right(3) == _T("^{}");
1319 else if (CGit::GetShortName(str, refLabel.name, _T("refs/stash")))
1321 if (!(m_ShowRefMask & LOGLIST_SHOWSTASH))
1322 continue;
1323 refLabel.color = m_Colors.GetColor(CColors::Stash);
1324 refLabel.name = _T("stash");
1326 else if (CGit::GetShortName(str, refLabel.name, _T("refs/bisect/")))
1328 if (!(m_ShowRefMask & LOGLIST_SHOWBISECT))
1329 continue;
1330 if (refLabel.name.Find(_T("good")) == 0)
1332 refLabel.color = m_Colors.GetColor(CColors::BisectGood);
1333 refLabel.name = _T("good");
1335 if (refLabel.name.Find(_T("bad")) == 0)
1337 refLabel.color = m_Colors.GetColor(CColors::BisectBad);
1338 refLabel.name = _T("bad");
1341 else
1342 continue;
1344 refsToShow.push_back(refLabel);
1347 if (refsToShow.empty())
1349 *pResult = CDRF_DODEFAULT;
1350 return;
1353 DrawTagBranch(pLVCD->nmcd.hdc, rect, pLVCD->nmcd.dwItemSpec, refsToShow);
1355 *pResult = CDRF_SKIPDEFAULT;
1356 return;
1363 if (pLVCD->iSubItem == LOGLIST_ACTION)
1365 if(this->m_IsIDReplaceAction)
1367 *pResult = CDRF_DODEFAULT;
1368 return;
1370 *pResult = CDRF_DODEFAULT;
1372 if (m_arShownList.GetCount() <= (INT_PTR)pLVCD->nmcd.dwItemSpec)
1373 return;
1375 int nIcons = 0;
1376 int iconwidth = ::GetSystemMetrics(SM_CXSMICON);
1377 int iconheight = ::GetSystemMetrics(SM_CYSMICON);
1379 GitRev* pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(pLVCD->nmcd.dwItemSpec));
1380 CRect rect;
1381 GetSubItemRect((int)pLVCD->nmcd.dwItemSpec, pLVCD->iSubItem, LVIR_BOUNDS, rect);
1382 //TRACE(_T("Action left %d right %d\r\n"),rect.left,rect.right);
1383 // Get the selected state of the
1384 // item being drawn.
1386 // Fill the background if necessary
1387 FillBackGround(pLVCD->nmcd.hdc, pLVCD->nmcd.dwItemSpec, rect);
1389 // Draw the icon(s) into the compatible DC
1390 pLogEntry->GetAction(this);
1392 if (!pLogEntry->m_IsDiffFiles)
1393 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hFetchIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1395 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_MODIFIED)
1396 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left + ICONITEMBORDER, rect.top, m_hModifiedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1397 ++nIcons;
1399 if (pLogEntry->GetAction(this) & (CTGitPath::LOGACTIONS_ADDED|CTGitPath::LOGACTIONS_COPY) )
1400 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hAddedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1401 ++nIcons;
1403 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_DELETED)
1404 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hDeletedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1405 ++nIcons;
1407 if (pLogEntry->GetAction(this) & CTGitPath::LOGACTIONS_REPLACED)
1408 ::DrawIconEx(pLVCD->nmcd.hdc, rect.left+nIcons*iconwidth + ICONITEMBORDER, rect.top, m_hReplacedIcon, iconwidth, iconheight, 0, NULL, DI_NORMAL);
1409 ++nIcons;
1410 *pResult = CDRF_SKIPDEFAULT;
1411 return;
1414 break;
1416 *pResult = CDRF_DODEFAULT;
1419 // CGitLogListBase message handlers
1421 void CGitLogListBase::OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult)
1423 NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
1425 // Create a pointer to the item
1426 LV_ITEM* pItem = &(pDispInfo)->item;
1428 // Do the list need text information?
1429 if (!(pItem->mask & LVIF_TEXT))
1430 return;
1432 // By default, clear text buffer.
1433 lstrcpyn(pItem->pszText, _T(""), pItem->cchTextMax);
1435 bool bOutOfRange = pItem->iItem >= ShownCountWithStopped();
1437 *pResult = 0;
1438 if (m_bNoDispUpdates || bOutOfRange)
1439 return;
1441 // Which item number?
1442 int itemid = pItem->iItem;
1443 GitRev * pLogEntry = NULL;
1444 if (itemid < m_arShownList.GetCount())
1445 pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(pItem->iItem));
1447 CString temp;
1448 if(m_IsOldFirst)
1450 temp.Format(_T("%d"),pItem->iItem+1);
1453 else
1455 temp.Format(_T("%d"),m_arShownList.GetCount()-pItem->iItem);
1458 // Which column?
1459 switch (pItem->iSubItem)
1461 case this->LOGLIST_GRAPH: //Graphic
1462 break;
1463 case this->LOGLIST_REBASE:
1465 if(this->m_IsRebaseReplaceGraph)
1467 CTGitPath path;
1468 path.m_Action=pLogEntry->GetAction(this)&CTGitPath::LOGACTIONS_REBASE_MODE_MASK;
1469 lstrcpyn(pItem->pszText,path.GetActionName(), pItem->cchTextMax);
1472 break;
1473 case this->LOGLIST_ACTION: //action -- no text in the column
1474 break;
1475 case this->LOGLIST_HASH:
1476 if(pLogEntry)
1477 lstrcpyn(pItem->pszText, pLogEntry->m_CommitHash.ToString(), pItem->cchTextMax);
1478 break;
1479 case this->LOGLIST_ID:
1480 if(this->m_IsIDReplaceAction)
1481 lstrcpyn(pItem->pszText, temp, pItem->cchTextMax);
1482 break;
1483 case this->LOGLIST_MESSAGE: //Message
1484 if (pLogEntry)
1485 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetSubject(), pItem->cchTextMax);
1486 break;
1487 case this->LOGLIST_AUTHOR: //Author
1488 if (pLogEntry)
1489 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorName(), pItem->cchTextMax);
1490 break;
1491 case this->LOGLIST_DATE: //Date
1492 if ( pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()) )
1493 lstrcpyn(pItem->pszText,
1494 CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
1495 pItem->cchTextMax);
1496 break;
1498 case this->LOGLIST_EMAIL:
1499 if (pLogEntry)
1500 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetAuthorEmail(), pItem->cchTextMax);
1501 break;
1503 case this->LOGLIST_COMMIT_NAME: //Commit
1504 if (pLogEntry)
1505 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterName(), pItem->cchTextMax);
1506 break;
1508 case this->LOGLIST_COMMIT_EMAIL: //Commit Email
1509 if (pLogEntry)
1510 lstrcpyn(pItem->pszText, (LPCTSTR)pLogEntry->GetCommitterEmail(), pItem->cchTextMax);
1511 break;
1513 case this->LOGLIST_COMMIT_DATE: //Commit Date
1514 if (pLogEntry && (!pLogEntry->m_CommitHash.IsEmpty()))
1515 lstrcpyn(pItem->pszText,
1516 CLoglistUtils::FormatDateAndTime(pLogEntry->GetCommitterDate(), m_DateFormat, true, m_bRelativeTimes),
1517 pItem->cchTextMax);
1518 break;
1519 case this->LOGLIST_BUG: //Bug ID
1520 if(pLogEntry)
1521 lstrcpyn(pItem->pszText, (LPCTSTR)this->m_ProjectProperties.FindBugID(pLogEntry->GetSubject() + _T("\r\n\r\n") + pLogEntry->GetBody()), pItem->cchTextMax);
1522 break;
1524 default:
1525 ASSERT(false);
1529 void CGitLogListBase::OnContextMenu(CWnd* pWnd, CPoint point)
1532 if (pWnd == GetHeaderCtrl())
1534 return m_ColumnManager.OnContextMenuHeader(pWnd,point,!!IsGroupViewEnabled());
1537 int selIndex = GetSelectionMark();
1538 if (selIndex < 0)
1539 return; // nothing selected, nothing to do with a context menu
1541 // if the user selected the info text telling about not all revisions shown due to
1542 // the "stop on copy/rename" option, we also don't show the context menu
1543 if ((m_bStrictStopped)&&(selIndex == m_arShownList.GetCount()))
1544 return;
1546 // if the context menu is invoked through the keyboard, we have to use
1547 // a calculated position on where to anchor the menu on
1548 if ((point.x == -1) && (point.y == -1))
1550 CRect rect;
1551 GetItemRect(selIndex, &rect, LVIR_LABEL);
1552 ClientToScreen(&rect);
1553 point = rect.CenterPoint();
1555 m_nSearchIndex = selIndex;
1556 m_bCancelled = FALSE;
1558 // calculate some information the context menu commands can use
1559 // CString pathURL = GetURLFromPath(m_path);
1561 POSITION pos = GetFirstSelectedItemPosition();
1562 int indexNext = GetNextSelectedItem(pos);
1563 if (indexNext < 0)
1564 return;
1566 GitRev* pSelLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(indexNext));
1567 #if 0
1568 GitRev revSelected = pSelLogEntry->Rev;
1569 GitRev revPrevious = git_revnum_t(revSelected)-1;
1570 if ((pSelLogEntry->pArChangedPaths)&&(pSelLogEntry->pArChangedPaths->GetCount() <= 2))
1572 for (int i=0; i<pSelLogEntry->pArChangedPaths->GetCount(); ++i)
1574 LogChangedPath * changedpath = (LogChangedPath *)pSelLogEntry->pArChangedPaths->SafeGetAt(i);
1575 if (changedpath->lCopyFromRev)
1576 revPrevious = changedpath->lCopyFromRev;
1579 GitRev revSelected2;
1580 if (pos)
1582 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1583 revSelected2 = pLogEntry->Rev;
1585 bool bAllFromTheSameAuthor = true;
1586 CString firstAuthor;
1587 CLogDataVector selEntries;
1588 GitRev revLowest, revHighest;
1589 GitRevRangeArray revisionRanges;
1591 POSITION pos = GetFirstSelectedItemPosition();
1592 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1593 revisionRanges.AddRevision(pLogEntry->Rev);
1594 selEntries.push_back(pLogEntry);
1595 firstAuthor = pLogEntry->sAuthor;
1596 revLowest = pLogEntry->Rev;
1597 revHighest = pLogEntry->Rev;
1598 while (pos)
1600 pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
1601 revisionRanges.AddRevision(pLogEntry->Rev);
1602 selEntries.push_back(pLogEntry);
1603 if (firstAuthor.Compare(pLogEntry->sAuthor))
1604 bAllFromTheSameAuthor = false;
1605 revLowest = (git_revnum_t(pLogEntry->Rev) > git_revnum_t(revLowest) ? revLowest : pLogEntry->Rev);
1606 revHighest = (git_revnum_t(pLogEntry->Rev) < git_revnum_t(revHighest) ? revHighest : pLogEntry->Rev);
1610 #endif
1612 int FirstSelect=-1, LastSelect=-1;
1613 pos = GetFirstSelectedItemPosition();
1614 FirstSelect = GetNextSelectedItem(pos);
1615 while(pos)
1617 LastSelect = GetNextSelectedItem(pos);
1619 //entry is selected, now show the popup menu
1620 CIconMenu popup;
1621 CIconMenu subbranchmenu, submenu, gnudiffmenu, diffmenu, revertmenu;
1623 if (popup.CreatePopupMenu())
1625 bool isHeadCommit = (pSelLogEntry->m_CommitHash == m_HeadHash);
1626 CString currentBranch = _T("refs/heads/") + g_Git.GetCurrentBranch();
1628 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_PICK))
1629 popup.AppendMenuIcon(ID_REBASE_PICK, IDS_REBASE_PICK, IDI_PICK);
1631 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SQUASH))
1632 popup.AppendMenuIcon(ID_REBASE_SQUASH, IDS_REBASE_SQUASH, IDI_SQUASH);
1634 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_EDIT))
1635 popup.AppendMenuIcon(ID_REBASE_EDIT, IDS_REBASE_EDIT, IDI_EDIT);
1637 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_SKIP))
1638 popup.AppendMenuIcon(ID_REBASE_SKIP, IDS_REBASE_SKIP, IDI_SKIP);
1640 if(m_ContextMenuMask&(GetContextMenuBit(ID_REBASE_SKIP)|GetContextMenuBit(ID_REBASE_EDIT)|
1641 GetContextMenuBit(ID_REBASE_SQUASH)|GetContextMenuBit(ID_REBASE_PICK)))
1642 popup.AppendMenu(MF_SEPARATOR, NULL);
1644 if (GetSelectedCount() == 1)
1648 if( !pSelLogEntry->m_CommitHash.IsEmpty())
1650 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARE) && m_hasWC) // compare revision with WC
1651 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
1653 else
1655 if(m_ContextMenuMask&GetContextMenuBit(ID_COMMIT))
1656 popup.AppendMenuIcon(ID_COMMIT, IDS_LOG_POPUP_COMMIT, IDI_COMMIT);
1658 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF1) && m_hasWC) // compare with WC, unified
1660 GitRev *pRev=pSelLogEntry;
1661 if (pSelLogEntry->m_ParentHash.empty())
1663 pRev->GetParentFromHash(pRev->m_CommitHash);
1665 if(pRev->m_ParentHash.size()<=1)
1667 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1670 else
1672 gnudiffmenu.CreatePopupMenu();
1673 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1675 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1676 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1678 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1680 CString str;
1681 str.Format(IDS_PARENT, i + 1);
1682 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1687 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1690 GitRev *pRev=pSelLogEntry;
1691 if (pSelLogEntry->m_ParentHash.empty())
1693 pRev->GetParentFromHash(pRev->m_CommitHash);
1695 if(pRev->m_ParentHash.size()<=1)
1697 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1698 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1699 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1701 else
1703 diffmenu.CreatePopupMenu();
1704 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1705 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1707 CString str;
1708 str.Format(IDS_PARENT, i + 1);
1709 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1710 if (i == 0 && CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1712 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1713 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1719 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1720 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1722 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
1723 popup.AppendMenu(MF_SEPARATOR, NULL);
1725 if (pSelLogEntry->m_CommitHash.IsEmpty())
1727 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1728 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_COMMIT);
1731 bool isStash = false;
1732 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1734 if (m_HashMap[pSelLogEntry->m_CommitHash][i] == _T("refs/stash"))
1736 isStash = true;
1737 break;
1741 if (CTGitPath(g_Git.m_CurrentDir).HasStashDir() && (pSelLogEntry->m_CommitHash.IsEmpty() || isStash))
1743 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1744 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_RELOCATE);
1746 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1747 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1749 popup.AppendMenu(MF_SEPARATOR, NULL);
1752 if (pSelLogEntry->m_CommitHash.IsEmpty())
1754 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
1755 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_PULL);
1757 popup.AppendMenu(MF_SEPARATOR, NULL);
1761 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1762 // {
1763 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1764 // }
1765 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1766 // {
1767 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1768 // }
1769 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1770 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1771 // {
1772 // popup.AppendMenu(MF_SEPARATOR, NULL);
1773 // }
1775 CString str,format;
1776 //if (m_hasWC)
1777 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1779 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1781 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1782 GetSelectedCount() == 1)
1783 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1785 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
1786 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
1788 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1789 str.Format(format,g_Git.GetCurrentBranch());
1791 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC)
1792 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1794 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1795 str.Format(format,g_Git.GetCurrentBranch());
1797 if(m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC)
1798 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1801 // Add Switch Branch express Menu
1802 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1803 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC)
1806 std::vector<CString *> branchs;
1807 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1809 CString ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1810 if(ref.Find(_T("refs/heads/")) == 0 && ref != currentBranch)
1812 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1816 CString str;
1817 str.LoadString(IDS_SWITCH_BRANCH);
1819 if(branchs.size() == 1)
1821 str+=_T(" ");
1822 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1823 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1825 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1828 else if(branchs.size() > 1)
1830 subbranchmenu.CreatePopupMenu();
1831 for (size_t i = 0 ; i < branchs.size(); ++i)
1833 if (*branchs[i] != currentBranch)
1835 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1836 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1840 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1844 if(m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC)
1845 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1847 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH))
1848 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1850 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG))
1851 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1853 format.LoadString(IDS_REBASE_THIS_FORMAT);
1854 str.Format(format,g_Git.GetCurrentBranch());
1856 if(pSelLogEntry->m_CommitHash != m_HeadHash && m_hasWC)
1857 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1858 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1860 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1861 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1863 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC)
1865 GitRev *pRev = pSelLogEntry;
1866 if (pSelLogEntry->m_ParentHash.empty())
1868 pRev->GetParentFromHash(pRev->m_CommitHash);
1870 if (pRev->m_ParentHash.size() == 1)
1872 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1874 else
1876 revertmenu.CreatePopupMenu();
1877 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
1879 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1881 CString str;
1882 str.Format(IDS_PARENT, i + 1);
1883 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str);
1888 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE))
1889 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
1891 popup.AppendMenu(MF_SEPARATOR, NULL);
1896 if(!pSelLogEntry->m_Ref.IsEmpty())
1898 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
1899 if (GetSelectedCount() == 1 && pSelLogEntry->m_Ref.Find(_T("refs/stash")) == 0)
1900 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
1901 popup.AppendMenu(MF_SEPARATOR, NULL);
1904 if (GetSelectedCount() >= 2)
1906 bool bAddSeparator = false;
1907 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
1909 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
1910 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
1913 if (GetSelectedCount() == 2)
1915 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
1916 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1917 bAddSeparator = true;
1920 if (m_hasWC)
1922 bAddSeparator = true;
1925 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC)
1926 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1928 if (bAddSeparator)
1929 popup.AppendMenu(MF_SEPARATOR, NULL);
1932 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
1934 bool bAddSeparator = false;
1935 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
1937 if(m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC)
1939 CString head;
1940 int headindex;
1941 headindex = this->GetHeadIndex();
1942 if(headindex>=0 && LastSelect >= headindex)
1944 head.Format(_T("HEAD~%d"),LastSelect-headindex);
1945 CGitHash hash;
1946 if (g_Git.GetHash(hash, head))
1947 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + head + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1948 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
1949 if(pLastEntry->m_CommitHash == hash) {
1950 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
1951 bAddSeparator = true;
1956 if(m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC) {
1957 if (GetSelectedCount() >= 2)
1958 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_EXPORT);
1959 else
1960 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
1961 bAddSeparator = true;
1964 if(GetSelectedCount()<=2 || (IsSelectionContinuous() && GetSelectedCount() > 0))
1965 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
1966 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
1967 bAddSeparator = true;
1970 if (bAddSeparator)
1971 popup.AppendMenu(MF_SEPARATOR, NULL);
1974 if (m_hasWC && (m_ContextMenuMask & GetContextMenuBit(ID_BISECTSTART)) && GetSelectedCount() == 2 && !reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(FirstSelect))->m_CommitHash.IsEmpty() && !CTGitPath(g_Git.m_CurrentDir).IsBisectActive())
1976 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART);
1977 popup.AppendMenu(MF_SEPARATOR, NULL);
1980 if (GetSelectedCount() == 1)
1982 bool bAddSeparator = false;
1983 if(m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && !m_HashMap[pSelLogEntry->m_CommitHash].empty())
1985 // show the push-option only if the log entry has an associated local branch
1986 bool isLocal = false;
1987 for (size_t i = 0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1989 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
1990 isLocal = true;
1992 if (isLocal)
1994 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
1995 bAddSeparator = true;
1999 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2001 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2003 std::vector<CString *> branchs;
2004 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2006 if(m_HashMap[pSelLogEntry->m_CommitHash][i] != currentBranch)
2007 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
2009 CString str;
2010 if (branchs.size() == 1)
2012 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2013 str+=_T(" ");
2014 str += *branchs[0];
2015 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2016 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2017 bAddSeparator = true;
2019 else if (branchs.size() > 1)
2021 str.LoadString(IDS_DELETE_BRANCHTAG);
2022 submenu.CreatePopupMenu();
2023 for (size_t i = 0; i < branchs.size(); ++i)
2025 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2026 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2029 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2030 bAddSeparator = true;
2033 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2034 if (bAddSeparator)
2035 popup.AppendMenu(MF_SEPARATOR, NULL);
2036 } // GetSelectedCount() == 1
2038 if (GetSelectedCount() != 0)
2040 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
2041 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
2042 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
2043 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
2044 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARDMESSAGES))
2045 popup.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_COPYTOCLIPBOARDMESSAGES, IDI_COPYCLIP);
2048 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2049 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2051 if (GetSelectedCount() == 1 && m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES) && !pSelLogEntry->m_CommitHash.IsEmpty())
2052 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2054 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2055 // DialogEnableWindow(IDOK, FALSE);
2056 // SetPromptApp(&theApp);
2058 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2060 // EnableOKButton();
2061 } // if (popup.CreatePopupMenu())
2065 bool CGitLogListBase::IsSelectionContinuous()
2067 if ( GetSelectedCount()==1 )
2069 // if only one revision is selected, the selection is of course
2070 // continuous
2071 return true;
2074 POSITION pos = GetFirstSelectedItemPosition();
2075 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
2076 if (bContinuous)
2078 int itemindex = GetNextSelectedItem(pos);
2079 while (pos)
2081 int nextindex = GetNextSelectedItem(pos);
2082 if (nextindex - itemindex > 1)
2084 bContinuous = false;
2085 break;
2087 itemindex = nextindex;
2090 return bContinuous;
2093 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2096 CString sClipdata;
2097 POSITION pos = GetFirstSelectedItemPosition();
2098 if (pos != NULL)
2100 CString sRev;
2101 sRev.LoadString(IDS_LOG_REVISION);
2102 CString sAuthor;
2103 sAuthor.LoadString(IDS_LOG_AUTHOR);
2104 CString sDate;
2105 sDate.LoadString(IDS_LOG_DATE);
2106 CString sMessage;
2107 sMessage.LoadString(IDS_LOG_MESSAGE);
2108 bool first = true;
2109 while (pos)
2111 CString sLogCopyText;
2112 CString sPaths;
2113 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
2115 if (toCopy == ID_COPY_ALL)
2117 //pLogEntry->GetFiles(this)
2118 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2120 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2121 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2123 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(": ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2124 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED|CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2126 CString rename;
2127 rename.Format(from, ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2128 sPaths += _T(" ") + rename;
2130 sPaths += _T("\r\n");
2132 sPaths.Trim();
2133 CString body = pLogEntry->GetBody();
2134 body.Replace(_T("\n"), _T("\r\n"));
2135 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"),
2136 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
2137 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2138 (LPCTSTR)sDate,
2139 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2140 (LPCTSTR)sMessage, (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim(),
2141 (LPCTSTR)sPaths);
2142 sClipdata += sLogCopyText;
2144 else if (toCopy == ID_COPY_MESSAGE)
2146 CString body = pLogEntry->GetBody();
2147 body.Replace(_T("\n"), _T("\r\n"));
2148 sClipdata += _T("* ") + (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim() + _T("\r\n\r\n");
2150 else if (toCopy == ID_COPY_SUBJECT)
2152 sClipdata += _T("* ") + pLogEntry->GetSubject().Trim() + _T("\r\n\r\n");
2154 else
2156 if (!first)
2157 sClipdata += _T("\r\n");
2158 sClipdata += pLogEntry->m_CommitHash;
2161 first = false;
2163 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2168 void CGitLogListBase::DiffSelectedRevWithPrevious()
2170 if (m_bThreadRunning)
2171 return;
2173 int FirstSelect=-1, LastSelect=-1;
2174 POSITION pos = GetFirstSelectedItemPosition();
2175 FirstSelect = GetNextSelectedItem(pos);
2176 while(pos)
2178 LastSelect = GetNextSelectedItem(pos);
2181 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
2183 #if 0
2184 UpdateLogInfoLabel();
2185 int selIndex = m_LogList.GetSelectionMark();
2186 if (selIndex < 0)
2187 return;
2188 int selCount = m_LogList.GetSelectedCount();
2189 if (selCount != 1)
2190 return;
2192 // Find selected entry in the log list
2193 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2194 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2195 long rev1 = pLogEntry->Rev;
2196 long rev2 = rev1-1;
2197 CTGitPath path = m_path;
2199 // See how many files under the relative root were changed in selected revision
2200 int nChanged = 0;
2201 LogChangedPath * changed = NULL;
2202 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
2204 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
2205 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2207 ++nChanged;
2208 changed = cpath;
2212 if (m_path.IsDirectory() && nChanged == 1)
2214 // We're looking at the log for a directory and only one file under dir was changed in the revision
2215 // Do diff on that file instead of whole directory
2216 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
2219 m_bCancelled = FALSE;
2220 DialogEnableWindow(IDOK, FALSE);
2221 SetPromptApp(&theApp);
2222 theApp.DoWaitCursor(1);
2224 if (PromptShown())
2226 GitDiff diff(this, m_hWnd, true);
2227 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2228 diff.SetHEADPeg(m_LogRevision);
2229 diff.ShowCompare(path, rev2, path, rev1);
2231 else
2233 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2236 theApp.DoWaitCursor(-1);
2237 EnableOKButton();
2238 #endif
2241 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2243 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2244 *pResult = -1;
2246 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2247 return;
2248 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
2249 return;
2250 if (pFindInfo->lvfi.psz == 0)
2251 return;
2252 #if 0
2253 CString sCmp = pFindInfo->lvfi.psz;
2254 CString sRev;
2255 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2257 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2258 sRev.Format(_T("%ld"), pLogEntry->Rev);
2259 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2261 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2263 *pResult = i;
2264 return;
2267 else
2269 if (sCmp.Compare(sRev)==0)
2271 *pResult = i;
2272 return;
2276 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2278 for (int i=0; i<pFindInfo->iStart; ++i)
2280 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2281 sRev.Format(_T("%ld"), pLogEntry->Rev);
2282 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2284 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2286 *pResult = i;
2287 return;
2290 else
2292 if (sCmp.Compare(sRev)==0)
2294 *pResult = i;
2295 return;
2300 #endif
2301 *pResult = -1;
2304 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2306 ClearText();
2308 this->m_arShownList.SafeRemoveAll();
2310 this->m_logEntries.ClearAll();
2311 if (this->m_logEntries.ParserFromLog(path, -1, info, range))
2312 return -1;
2314 //this->m_logEntries.ParserFromLog();
2315 SetItemCountEx((int)m_logEntries.size());
2317 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2319 if(m_IsOldFirst)
2321 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2322 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2325 else
2327 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2328 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2332 ReloadHashMap();
2334 if(path)
2335 m_Path=*path;
2336 return 0;
2340 int CGitLogListBase::BeginFetchLog()
2342 ClearText();
2344 this->m_arShownList.SafeRemoveAll();
2346 this->m_logEntries.ClearAll();
2348 this->m_LogCache.ClearAllParent();
2350 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2352 CTGitPath *path;
2353 if(this->m_Path.IsEmpty())
2354 path=NULL;
2355 else
2356 path=&this->m_Path;
2358 int mask;
2359 mask = CGit::LOG_INFO_ONLY_HASH;
2360 if (m_bIncludeBoundaryCommits)
2361 mask |= CGit::LOG_INFO_BOUNDARY;
2362 // if(this->m_bAllBranch)
2363 mask |= m_ShowMask ;
2365 if(m_bShowWC)
2367 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2368 ResetWcRev();
2369 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2372 if (m_sRange.IsEmpty())
2373 m_sRange = _T("HEAD");
2375 CFilterData data;
2376 data.m_From = m_From;
2377 data.m_To =m_To;
2379 #if 0 /* use tortoiegit filter */
2380 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2381 data.m_Author = this->m_sFilterText;
2383 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2384 data.m_MessageFilter = this->m_sFilterText;
2386 data.m_IsRegex = m_bFilterWithRegex;
2387 #endif
2389 // follow does not work for directories
2390 if (!path || path->IsDirectory())
2391 mask &= ~CGit::LOG_INFO_FOLLOW;
2392 // follow does not work with all branches 8at least in TGit)
2393 if (mask & CGit::LOG_INFO_FOLLOW)
2394 mask &= ~CGit::LOG_INFO_ALL_BRANCH;
2396 CString cmd = g_Git.GetLogCmd(m_sRange, path, -1, mask, true, &data);
2398 //this->m_logEntries.ParserFromLog();
2399 if(IsInWorkingThread())
2401 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2403 else
2405 SetItemCountEx((int)m_logEntries.size());
2410 [] { git_init(); } ();
2412 catch (char* msg)
2414 CString err(msg);
2415 MessageBox(_T("Could not initialize libgit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2416 return -1;
2419 if (!g_Git.CanParseRev(m_sRange))
2421 if (!(mask & CGit::LOG_INFO_ALL_BRANCH))
2422 return 0;
2424 // if show all branches, pick any ref as dummy entry ref
2425 STRING_VECTOR list;
2426 if (g_Git.GetRefList(list))
2427 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2428 if (list.size() == 0)
2429 return 0;
2431 cmd = g_Git.GetLogCmd(list[0], path, -1, mask, true, &data);
2434 g_Git.m_critGitDllSec.Lock();
2435 try {
2436 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
2438 g_Git.m_critGitDllSec.Unlock();
2439 return -1;
2442 catch (char* msg)
2444 g_Git.m_critGitDllSec.Unlock();
2445 CString err(msg);
2446 MessageBox(_T("Could not open log.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2447 return -1;
2449 g_Git.m_critGitDllSec.Unlock();
2451 return 0;
2454 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2456 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2458 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2460 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2462 DiffSelectedRevWithPrevious();
2463 return TRUE;
2466 #if 0
2467 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2469 DiffSelectedFile();
2470 return TRUE;
2472 #endif
2474 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2476 // select all entries
2477 for (int i=0; i<GetItemCount(); ++i)
2479 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2481 return TRUE;
2484 #if 0
2485 if (m_hAccel && !bSkipAccelerator)
2487 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2488 if (ret)
2489 return TRUE;
2492 #endif
2493 //m_tooltips.RelayEvent(pMsg);
2494 return __super::PreTranslateMessage(pMsg);
2497 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2499 // a double click on an entry in the revision list has happened
2500 *pResult = 0;
2502 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2503 DiffSelectedRevWithPrevious();
2506 int CGitLogListBase::FetchLogAsync(void * data)
2508 ReloadHashMap();
2509 m_ProcData=data;
2510 m_bExitThread=FALSE;
2511 InterlockedExchange(&m_bThreadRunning, TRUE);
2512 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2513 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2514 if (m_LoadingThread ==NULL)
2516 InterlockedExchange(&m_bThreadRunning, FALSE);
2517 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2518 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2519 return -1;
2521 return 0;
2524 //this is the thread function which calls the subversion function
2525 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2527 return ((CGitLogListBase*)pVoid)->LogThread();
2530 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2532 //CTime time;
2533 oldest=CTime::GetCurrentTime();
2534 latest=CTime(1971,1,2,0,0,0);
2535 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2537 if(m_logEntries[i].IsEmpty())
2538 continue;
2540 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2541 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2543 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2544 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2548 if(latest<oldest)
2549 latest=oldest;
2552 UINT CGitLogListBase::LogThread()
2554 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2556 InterlockedExchange(&m_bThreadRunning, TRUE);
2557 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2559 ULONGLONG t1,t2;
2561 if(BeginFetchLog())
2563 InterlockedExchange(&m_bThreadRunning, FALSE);
2564 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2566 return 1;
2569 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2570 bool bRegex = false;
2571 if (m_bFilterWithRegex)
2572 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2574 TRACE(_T("\n===Begin===\n"));
2575 //Update work copy item;
2577 if (!m_logEntries.empty())
2579 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2581 m_arShownList.SafeAdd(pRev);
2585 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2587 // store commit number of the last selected commit/line before the refresh or -1
2588 int lastSelectedHashNItem = -1;
2589 int ret = 0;
2591 bool shouldWalk = true;
2592 if (!g_Git.CanParseRev(m_sRange))
2594 // walk revisions if show all branches and there exists any ref
2595 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH))
2596 shouldWalk = false;
2597 else
2599 STRING_VECTOR list;
2600 if (g_Git.GetRefList(list))
2601 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2602 if (list.size() == 0)
2603 shouldWalk = false;
2607 if (shouldWalk)
2609 g_Git.m_critGitDllSec.Lock();
2610 int total = 0;
2613 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2614 total = git_get_log_estimate_commit_count(m_DllGitLog);
2616 catch (char* msg)
2618 CString err(msg);
2619 MessageBox(_T("Could not get first commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2620 ret = -1;
2622 g_Git.m_critGitDllSec.Unlock();
2624 GIT_COMMIT commit;
2625 t2=t1=GetTickCount();
2626 int oldprecentage = 0;
2627 size_t oldsize = m_logEntries.size();
2628 while (ret== 0 && !m_bExitThread)
2630 g_Git.m_critGitDllSec.Lock();
2633 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2635 catch (char* msg)
2637 g_Git.m_critGitDllSec.Unlock();
2638 CString err(msg);
2639 MessageBox(_T("Could not get next commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2640 break;
2642 g_Git.m_critGitDllSec.Unlock();
2644 if(ret)
2645 break;
2647 if (commit.m_ignore == 1)
2649 git_free_commit(&commit);
2650 continue;
2653 //printf("%s\r\n",commit.GetSubject());
2654 if(m_bExitThread)
2655 break;
2657 CGitHash hash = (char*)commit.m_hash ;
2659 GitRev *pRev = m_LogCache.GetCacheData(hash);
2660 pRev->m_GitCommit = commit;
2661 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2663 char *note=NULL;
2664 g_Git.m_critGitDllSec.Lock();
2665 git_get_notes(commit.m_hash,&note);
2666 g_Git.m_critGitDllSec.Unlock();
2668 if(note)
2670 pRev->m_Notes.Empty();
2671 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2674 if(!pRev->m_IsDiffFiles)
2676 pRev->m_CallDiffAsync = DiffAsync;
2679 pRev->ParserParentFromCommit(&commit);
2681 #ifdef DEBUG
2682 pRev->DbgPrint();
2683 TRACE(_T("\n"));
2684 #endif
2686 if(!m_sFilterText.IsEmpty())
2688 if(!IsMatchFilter(bRegex,pRev,pat))
2689 continue;
2691 this->m_critSec.Lock();
2692 m_logEntries.push_back(hash);
2693 m_arShownList.SafeAdd(pRev);
2694 this->m_critSec.Unlock();
2696 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
2697 lastSelectedHashNItem = (int)m_arShownList.GetCount() - 1;
2699 t2=GetTickCount();
2701 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2703 //update UI
2704 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
2705 if(percent > 99)
2706 percent =99;
2707 if(percent < GITLOG_START)
2708 percent = GITLOG_START +1;
2710 oldsize = m_logEntries.size();
2711 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2713 //if( percent > oldprecentage )
2715 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2716 oldprecentage = percent;
2718 t1 = t2;
2721 g_Git.m_critGitDllSec.Lock();
2722 git_close_log(m_DllGitLog);
2723 g_Git.m_critGitDllSec.Unlock();
2727 if (m_bExitThread)
2729 InterlockedExchange(&m_bThreadRunning, FALSE);
2730 return 0;
2733 //Update UI;
2734 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2736 if (lastSelectedHashNItem >= 0)
2737 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2739 if (this->m_hWnd)
2740 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2742 InterlockedExchange(&m_bThreadRunning, FALSE);
2744 return 0;
2747 void CGitLogListBase::FetchRemoteList()
2749 STRING_VECTOR remoteList;
2750 if (!g_Git.GetRemoteList(remoteList))
2751 m_SingleRemote = remoteList.size() == 1 ? remoteList[0] : _T("");
2752 else
2753 m_SingleRemote = _T("");
2756 void CGitLogListBase::FetchTrackingBranchList()
2758 m_TrackingMap.clear();
2759 for (MAP_HASH_NAME::iterator it = m_HashMap.begin(); it != m_HashMap.end(); ++it)
2761 for (int j = 0; j < it->second.size(); ++j)
2763 CString branchName;
2764 if (CGit::GetShortName(it->second[j], branchName, _T("refs/heads/")))
2766 CString configName;
2767 configName.Format(_T("branch.%s.remote"), branchName);
2768 CString pullRemote = g_Git.GetConfigValue(configName);
2770 configName.Format(_T("branch.%s.merge"), branchName);
2771 CString pullBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
2773 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2775 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
2782 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2784 SafeTerminateThread();
2786 this->SetItemCountEx(0);
2787 this->Clear();
2789 ResetWcRev();
2791 // HACK to hide graph column
2792 if (m_ShowMask & CGit::LOG_INFO_FOLLOW)
2793 SetColumnWidth(0, 0);
2794 else
2795 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
2797 //Update branch and Tag info
2798 ReloadHashMap();
2799 //Assume Thread have exited
2800 //if(!m_bThreadRunning)
2802 m_logEntries.clear();
2804 if(IsCleanFilter)
2806 m_sFilterText.Empty();
2807 m_From=-1;
2808 m_To=-1;
2811 InterlockedExchange(&m_bExitThread,FALSE);
2813 InterlockedExchange(&m_bThreadRunning, TRUE);
2814 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2815 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
2817 InterlockedExchange(&m_bThreadRunning, FALSE);
2818 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2819 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2823 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, tr1::wregex& pat, bool bMatchCase /* = false */)
2827 tr1::regex_constants::syntax_option_type type = tr1::regex_constants::ECMAScript;
2828 if (!bMatchCase)
2829 type |= tr1::regex_constants::icase;
2830 pat = tr1::wregex(regexp_str, type);
2831 return true;
2833 catch (exception) {}
2834 return false;
2836 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, tr1::wregex &pat)
2839 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
2840 CString sRev;
2842 if ((bRegex)&&(m_bFilterWithRegex))
2844 if (m_SelectedFilters & LOGFILTER_BUGID)
2846 if(this->m_bShowBugtraqColumn)
2848 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2850 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
2851 if (regex_search(wstring(sBugIds), pat, flags))
2853 return TRUE;
2858 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
2860 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
2861 if (regex_search(wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
2863 return TRUE;
2867 if (m_SelectedFilters & LOGFILTER_MESSAGES)
2869 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
2870 if (regex_search(wstring((LPCTSTR)pRev->GetBody()), pat, flags))
2872 return TRUE;
2876 if (m_SelectedFilters & LOGFILTER_AUTHORS)
2878 if (regex_search(wstring(pRev->GetAuthorName()), pat, flags))
2880 return TRUE;
2883 if (regex_search(wstring(pRev->GetCommitterName()), pat, flags))
2885 return TRUE;
2889 if (m_SelectedFilters & LOGFILTER_EMAILS)
2891 if (regex_search(wstring(pRev->GetAuthorEmail()), pat, flags))
2893 return TRUE;
2896 if (regex_search(wstring(pRev->GetCommitterEmail()), pat, flags))
2898 return TRUE;
2902 if (m_SelectedFilters & LOGFILTER_REVS)
2904 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
2905 if (regex_search(wstring((LPCTSTR)sRev), pat, flags))
2907 return TRUE;
2911 if (m_SelectedFilters & LOGFILTER_REFNAME)
2913 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
2914 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
2916 if (regex_search(wstring((LPCTSTR)*it), pat, flags))
2918 return TRUE;
2923 if (m_SelectedFilters & LOGFILTER_PATHS)
2925 CTGitPathList *pathList=NULL;
2926 if( pRev->m_IsDiffFiles)
2927 pathList = &pRev->GetFiles(this);
2928 else
2930 if(!pRev->m_IsSimpleListReady)
2931 pRev->SafeGetSimpleList(&g_Git);
2934 if(pathList)
2935 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
2937 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
2939 return true;
2941 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
2943 return true;
2947 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
2949 if (regex_search(wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
2951 return true;
2956 else
2958 CString find = m_sFilterText;
2959 find.MakeLower();
2961 if (m_SelectedFilters & LOGFILTER_BUGID)
2963 if(this->m_bShowBugtraqColumn)
2965 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2967 sBugIds.MakeLower();
2968 if ((sBugIds.Find(find) >= 0))
2970 return TRUE;
2975 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
2977 CString msg = pRev->GetSubject();
2979 msg = msg.MakeLower();
2980 if ((msg.Find(find) >= 0))
2982 return TRUE;
2986 if (m_SelectedFilters & LOGFILTER_MESSAGES)
2988 CString msg = pRev->GetBody();
2990 msg = msg.MakeLower();
2991 if ((msg.Find(find) >= 0))
2993 return TRUE;
2997 if (m_SelectedFilters & LOGFILTER_AUTHORS)
2999 CString msg = pRev->GetAuthorName();
3000 msg = msg.MakeLower();
3001 if ((msg.Find(find) >= 0))
3003 return TRUE;
3007 if (m_SelectedFilters & LOGFILTER_EMAILS)
3009 CString msg = pRev->GetAuthorEmail();
3010 msg = msg.MakeLower();
3011 if ((msg.Find(find) >= 0))
3013 return TRUE;
3017 if (m_SelectedFilters & LOGFILTER_REVS)
3019 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3020 if ((sRev.Find(find) >= 0))
3022 return TRUE;
3026 if (m_SelectedFilters & LOGFILTER_REFNAME)
3028 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3029 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3031 if (it->Find(find) >= 0)
3033 return TRUE;
3038 if (m_SelectedFilters & LOGFILTER_PATHS)
3040 CTGitPathList *pathList=NULL;
3041 if( pRev->m_IsDiffFiles)
3042 pathList = &pRev->GetFiles(this);
3043 else
3045 if(!pRev->m_IsSimpleListReady)
3046 pRev->SafeGetSimpleList(&g_Git);
3048 if(pathList)
3049 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
3051 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
3052 CString path = cpath->GetGitOldPathString();
3053 path.MakeLower();
3054 if ((path.Find(find)>=0))
3056 return true;
3058 path = cpath->GetGitPathString();
3059 path.MakeLower();
3060 if ((path.Find(find)>=0))
3062 return true;
3066 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3068 CString path = pRev->m_SimpleFileList[i];
3069 path.MakeLower();
3070 if ((path.Find(find)>=0))
3072 return true;
3076 } // else (from if (bRegex))
3077 return FALSE;
3081 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
3084 pShownlist->SafeRemoveAll();
3086 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
3087 bool bRegex = false;
3088 if (m_bFilterWithRegex)
3089 bRegex = ValidateRegexp(m_sFilterText, pat, false);
3091 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
3092 CString sRev;
3093 for (DWORD i=0; i<m_logEntries.size(); ++i)
3095 if ((bRegex)&&(m_bFilterWithRegex))
3097 #if 0
3098 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3100 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
3101 if (regex_search(wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
3103 pShownlist->SafeAdd(m_logEntries[i]);
3104 continue;
3107 #endif
3108 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3110 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
3111 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
3113 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3114 continue;
3117 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3119 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
3120 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
3122 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3123 continue;
3126 if (m_SelectedFilters & LOGFILTER_PATHS)
3128 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3130 bool bGoing = true;
3131 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3133 CTGitPath cpath = pathList[cpPathIndex];
3134 if (regex_search(wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
3136 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3137 bGoing = false;
3138 continue;
3140 if (regex_search(wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
3142 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3143 bGoing = false;
3144 continue;
3146 if (regex_search(wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
3148 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3149 bGoing = false;
3150 continue;
3154 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3156 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
3158 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3159 continue;
3162 if (m_SelectedFilters & LOGFILTER_EMAILS)
3164 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorEmail()), pat, flags) && IsEntryInDateRange(i))
3166 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3167 continue;
3170 if (m_SelectedFilters & LOGFILTER_REVS)
3172 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3173 if (regex_search(wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
3175 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3176 continue;
3179 if (m_SelectedFilters & LOGFILTER_REFNAME)
3181 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3182 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3184 if (regex_search(wstring((LPCTSTR)*it), pat, flags) && IsEntryInDateRange(i))
3186 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3187 continue;
3191 } // if (bRegex)
3192 else
3194 CString find = m_sFilterText;
3195 find.MakeLower();
3196 #if 0
3197 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3199 CString sBugIDs = m_logEntries[i]->sBugIDs;
3201 sBugIDs = sBugIDs.MakeLower();
3202 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3204 pShownlist->SafeAdd(m_logEntries[i]);
3205 continue;
3208 #endif
3209 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3211 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
3213 msg = msg.MakeLower();
3214 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3216 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3217 continue;
3220 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3222 CString msg = m_logEntries.GetGitRevAt(i).GetBody();
3224 msg = msg.MakeLower();
3225 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3227 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3228 continue;
3231 if (m_SelectedFilters & LOGFILTER_PATHS)
3233 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3235 bool bGoing = true;
3236 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3238 CTGitPath cpath = pathList[cpPathIndex];
3239 CString path = cpath.GetGitOldPathString();
3240 path.MakeLower();
3241 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3243 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3244 bGoing = false;
3245 continue;
3247 path = cpath.GetGitPathString();
3248 path.MakeLower();
3249 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3251 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3252 bGoing = false;
3253 continue;
3255 path = cpath.GetActionName();
3256 path.MakeLower();
3257 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3259 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3260 bGoing = false;
3261 continue;
3265 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3267 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
3268 msg = msg.MakeLower();
3269 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3271 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3272 continue;
3275 if (m_SelectedFilters & LOGFILTER_EMAILS)
3277 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorEmail();
3278 msg = msg.MakeLower();
3279 if ((msg.Find(find) >= 0) && (IsEntryInDateRange(i)))
3281 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3282 continue;
3285 if (m_SelectedFilters & LOGFILTER_REVS)
3287 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3288 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3290 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3291 continue;
3294 if (m_SelectedFilters & LOGFILTER_REFNAME)
3296 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3297 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3299 if (it->Find(find) >= 0 && IsEntryInDateRange(i))
3301 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3302 continue;
3306 } // else (from if (bRegex))
3307 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
3311 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
3314 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
3316 if(m_From == -1)
3317 if(m_To == -1)
3318 return true;
3319 else
3320 return time <= m_To;
3321 else
3322 if(m_To == -1)
3323 return time >= m_From;
3324 else
3325 return ((time >= m_From)&&(time <= m_To));
3327 return TRUE; /* git dll will filter time range */
3329 // return TRUE;
3331 void CGitLogListBase::StartFilter()
3333 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3334 RecalculateShownList(&m_arShownList);
3335 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3338 DeleteAllItems();
3339 SetItemCountEx(ShownCountWithStopped());
3340 RedrawItems(0, ShownCountWithStopped());
3341 Invalidate();
3344 void CGitLogListBase::RemoveFilter()
3347 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3349 m_arShownList.SafeRemoveAll();
3351 // reset the time filter too
3352 #if 0
3353 m_timFrom = (__time64_t(m_tFrom));
3354 m_timTo = (__time64_t(m_tTo));
3355 m_DateFrom.SetTime(&m_timFrom);
3356 m_DateTo.SetTime(&m_timTo);
3357 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
3358 m_DateTo.SetRange(&m_timFrom, &m_timTo);
3359 #endif
3361 for (DWORD i=0; i<m_logEntries.size(); ++i)
3363 if(this->m_IsOldFirst)
3365 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
3367 else
3369 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
3372 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
3373 DeleteAllItems();
3374 SetItemCountEx(ShownCountWithStopped());
3375 RedrawItems(0, ShownCountWithStopped());
3377 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3380 void CGitLogListBase::Clear()
3382 m_arShownList.SafeRemoveAll();
3383 DeleteAllItems();
3385 m_logEntries.ClearAll();
3389 void CGitLogListBase::OnDestroy()
3391 // save the column widths to the registry
3392 SaveColumnWidths();
3394 SafeTerminateThread();
3395 SafeTerminateAsyncDiffThread();
3397 int retry = 0;
3398 while(m_LogCache.SaveCache())
3400 if(retry > 5)
3401 break;
3402 Sleep(1000);
3404 ++retry;
3406 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
3407 // MB_YESNO) == IDNO)
3408 // break;
3411 CHintListCtrl::OnDestroy();
3414 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3416 CRect rect;
3417 int i=(int)wParam;
3418 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3419 this->InvalidateRect(rect);
3421 return 0;
3425 * Save column widths to the registry
3427 void CGitLogListBase::SaveColumnWidths()
3429 int maxcol = m_ColumnManager.GetColumnCount();
3431 // HACK that graph column is always shown
3432 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3434 for (int col = 0; col < maxcol; ++col)
3435 if (m_ColumnManager.IsVisible (col))
3436 m_ColumnManager.ColumnResized (col);
3438 m_ColumnManager.WriteSettings();
3441 int CGitLogListBase::GetHeadIndex()
3443 if(m_HeadHash.IsEmpty())
3444 return -1;
3446 for (int i = 0; i < m_arShownList.GetCount(); ++i)
3448 GitRev *pRev = (GitRev*)m_arShownList.SafeGetAt(i);
3449 if(pRev)
3451 if(pRev->m_CommitHash.ToString() == m_HeadHash )
3452 return i;
3455 return -1;
3457 void CGitLogListBase::OnFind()
3459 if (!m_pFindDialog)
3461 m_pFindDialog = new CFindDlg(this);
3462 m_pFindDialog->Create(this);
3465 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
3467 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
3469 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
3471 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
3472 Default();
3474 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3476 if (GetSelectedCount() != 0)
3477 return 0;
3478 SetItemState((int)itemToSelect, LVIS_SELECTED, LVIS_SELECTED);
3479 EnsureVisible((int)itemToSelect, FALSE);
3480 return 0;
3482 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3485 ASSERT(m_pFindDialog != NULL);
3486 bool bFound = false;
3487 int i=0;
3489 if (m_pFindDialog->IsTerminating())
3491 // invalidate the handle identifying the dialog box.
3492 m_pFindDialog = NULL;
3493 return 0;
3496 if(m_pFindDialog->IsRef())
3498 CString str;
3499 str=m_pFindDialog->GetFindString();
3501 CGitHash hash;
3503 if(!str.IsEmpty())
3505 if (g_Git.GetHash(hash, str + _T("^{}"))) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3506 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ref \"") + str + _T("^{}\".")), _T("TortoiseGit"), MB_ICONERROR);
3509 if(!hash.IsEmpty())
3511 for (i = 0; i<m_arShownList.GetCount(); ++i)
3513 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3514 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3516 bFound = true;
3517 break;
3524 if(m_pFindDialog->FindNext())
3526 //read data from dialog
3527 CString FindText = m_pFindDialog->GetFindString();
3528 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3530 tr1::wregex pat;
3531 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3533 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_not_null;
3536 for (i = this->m_nSearchIndex; i < m_arShownList.GetCount() && !bFound; ++i)
3538 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3540 CString str;
3541 str+=pLogEntry->m_CommitHash.ToString();
3542 str+=_T("\n");
3544 for (size_t j = 0; j < this->m_HashMap[pLogEntry->m_CommitHash].size(); ++j)
3546 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3547 str+=_T("\n");
3550 str+=pLogEntry->GetAuthorEmail();
3551 str+=_T("\n");
3552 str+=pLogEntry->GetAuthorName();
3553 str+=_T("\n");
3554 str+=pLogEntry->GetBody();
3555 str+=_T("\n");
3556 str+=pLogEntry->GetCommitterEmail();
3557 str+=_T("\n");
3558 str+=pLogEntry->GetCommitterName();
3559 str+=_T("\n");
3560 str+=pLogEntry->GetSubject();
3561 str+=_T("\n");
3564 /*Because changed files list is loaded on demand when gui show,
3565 files will empty when files have not fetched.
3567 we can add it back by using one-way diff(with outnumber changed and rename detect.
3568 here just need changed filename list. one-way is much quicker.
3570 if(pLogEntry->m_IsFull)
3572 for (int i = 0; i < pLogEntry->GetFiles(this).GetCount(); ++i)
3574 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3575 str+=_T("\n");
3576 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3577 str+=_T("\n");
3580 else
3582 if(!pLogEntry->m_IsSimpleListReady)
3583 pLogEntry->SafeGetSimpleList(&g_Git);
3585 for (size_t i = 0; i < pLogEntry->m_SimpleFileList.size(); ++i)
3587 str+=pLogEntry->m_SimpleFileList[i];
3588 str+=_T("\n");
3594 if (bRegex)
3596 if (regex_search(wstring(str), pat, flags))
3598 bFound = true;
3599 break;
3602 else
3604 if (bMatchCase)
3606 if (str.Find(FindText) >= 0)
3608 bFound = true;
3609 break;
3613 else
3615 CString msg = str;
3616 msg = msg.MakeLower();
3617 CString find = FindText.MakeLower();
3618 if (msg.Find(find) >= 0)
3620 bFound = TRUE;
3621 break;
3625 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; ++i)
3627 } // if(m_pFindDialog->FindNext())
3628 //UpdateLogInfoLabel();
3630 if (bFound)
3632 this->m_nSearchIndex = i;
3633 EnsureVisible(i, FALSE);
3634 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3635 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3636 SetSelectionMark(i);
3637 //FillLogMessageCtrl();
3638 UpdateData(FALSE);
3639 ++m_nSearchIndex;
3640 if (m_nSearchIndex >= m_arShownList.GetCount())
3641 m_nSearchIndex = (int)m_arShownList.GetCount()-1;
3644 return 0;
3647 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3649 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3651 *pResult = FALSE;
3654 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3656 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3658 Invalidate(FALSE);