Dropped unused variables
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.cpp
blob5264d77e98e2e7b5083a3349dcb7ae6520e2420d
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())
1665 pRev->GetParentFromHash(pRev->m_CommitHash);
1667 catch (const char* msg)
1669 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1672 if(pRev->m_ParentHash.size()<=1)
1674 popup.AppendMenuIcon(ID_GNUDIFF1, IDS_LOG_POPUP_GNUDIFF_CH, IDI_DIFF);
1677 else
1679 gnudiffmenu.CreatePopupMenu();
1680 popup.AppendMenuIcon(ID_GNUDIFF1,IDS_LOG_POPUP_GNUDIFF_PARENT, IDI_DIFF, gnudiffmenu.m_hMenu);
1682 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFF << 16)), CString(MAKEINTRESOURCE(IDS_ALLPARENTS)));
1683 gnudiffmenu.AppendMenuIcon((UINT_PTR)(ID_GNUDIFF1 + (0xFFFE << 16)), CString(MAKEINTRESOURCE(IDS_ONLYMERGEDFILES)));
1685 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1687 CString str;
1688 str.Format(IDS_PARENT, i + 1);
1689 gnudiffmenu.AppendMenuIcon(ID_GNUDIFF1+((i+1)<<16),str);
1694 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPAREWITHPREVIOUS))
1697 GitRev *pRev=pSelLogEntry;
1698 if (pSelLogEntry->m_ParentHash.empty())
1702 pRev->GetParentFromHash(pRev->m_CommitHash);
1704 catch (const char* msg)
1706 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1709 if(pRev->m_ParentHash.size()<=1)
1711 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
1712 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1713 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1715 else
1717 diffmenu.CreatePopupMenu();
1718 popup.AppendMenuIcon(ID_COMPAREWITHPREVIOUS, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF, diffmenu.m_hMenu);
1719 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1721 CString str;
1722 str.Format(IDS_PARENT, i + 1);
1723 diffmenu.AppendMenuIcon(ID_COMPAREWITHPREVIOUS +((i+1)<<16),str);
1724 if (i == 0 && CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
1726 popup.SetDefaultItem(ID_COMPAREWITHPREVIOUS, FALSE);
1727 diffmenu.SetDefaultItem((UINT)(ID_COMPAREWITHPREVIOUS + ((i + 1) << 16)), FALSE);
1733 if(m_ContextMenuMask&GetContextMenuBit(ID_BLAME))
1734 popup.AppendMenuIcon(ID_BLAME, IDS_LOG_POPUP_BLAME, IDI_BLAME);
1736 //popup.AppendMenuIcon(ID_BLAMEWITHPREVIOUS, IDS_LOG_POPUP_BLAMEWITHPREVIOUS, IDI_BLAME);
1737 popup.AppendMenu(MF_SEPARATOR, NULL);
1739 if (pSelLogEntry->m_CommitHash.IsEmpty())
1741 if(m_ContextMenuMask&GetContextMenuBit(ID_STASH_SAVE))
1742 popup.AppendMenuIcon(ID_STASH_SAVE, IDS_MENUSTASHSAVE, IDI_COMMIT);
1745 bool isStash = false;
1746 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1748 if (m_HashMap[pSelLogEntry->m_CommitHash][i] == _T("refs/stash"))
1750 isStash = true;
1751 break;
1755 if (CTGitPath(g_Git.m_CurrentDir).HasStashDir() && (pSelLogEntry->m_CommitHash.IsEmpty() || isStash))
1757 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_POP))
1758 popup.AppendMenuIcon(ID_STASH_POP, IDS_MENUSTASHPOP, IDI_RELOCATE);
1760 if (m_ContextMenuMask&GetContextMenuBit(ID_STASH_LIST))
1761 popup.AppendMenuIcon(ID_STASH_LIST, IDS_MENUSTASHLIST, IDI_LOG);
1763 popup.AppendMenu(MF_SEPARATOR, NULL);
1766 if (pSelLogEntry->m_CommitHash.IsEmpty())
1768 if(m_ContextMenuMask&GetContextMenuBit(ID_FETCH))
1769 popup.AppendMenuIcon(ID_FETCH, IDS_MENUFETCH, IDI_PULL);
1771 popup.AppendMenu(MF_SEPARATOR, NULL);
1775 // if (!m_ProjectProperties.sWebViewerRev.IsEmpty())
1776 // {
1777 // popup.AppendMenuIcon(ID_VIEWREV, IDS_LOG_POPUP_VIEWREV);
1778 // }
1779 // if (!m_ProjectProperties.sWebViewerPathRev.IsEmpty())
1780 // {
1781 // popup.AppendMenuIcon(ID_VIEWPATHREV, IDS_LOG_POPUP_VIEWPATHREV);
1782 // }
1783 // if ((!m_ProjectProperties.sWebViewerPathRev.IsEmpty())||
1784 // (!m_ProjectProperties.sWebViewerRev.IsEmpty()))
1785 // {
1786 // popup.AppendMenu(MF_SEPARATOR, NULL);
1787 // }
1789 CString str,format;
1790 //if (m_hasWC)
1791 // popup.AppendMenuIcon(ID_REVERTTOREV, IDS_LOG_POPUP_REVERTTOREV, IDI_REVERT);
1793 if(!pSelLogEntry->m_CommitHash.IsEmpty())
1795 if((m_ContextMenuMask&GetContextMenuBit(ID_LOG)) &&
1796 GetSelectedCount() == 1)
1797 popup.AppendMenuIcon(ID_LOG, IDS_LOG_POPUP_LOG, IDI_LOG);
1799 if (m_ContextMenuMask&GetContextMenuBit(ID_REPOBROWSE))
1800 popup.AppendMenuIcon(ID_REPOBROWSE, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
1802 format.LoadString(IDS_LOG_POPUP_MERGEREV);
1803 str.Format(format,g_Git.GetCurrentBranch());
1805 if (m_ContextMenuMask&GetContextMenuBit(ID_MERGEREV) && !isHeadCommit && m_hasWC)
1806 popup.AppendMenuIcon(ID_MERGEREV, str, IDI_MERGE);
1808 format.LoadString(IDS_RESET_TO_THIS_FORMAT);
1809 str.Format(format,g_Git.GetCurrentBranch());
1811 if(m_ContextMenuMask&GetContextMenuBit(ID_RESET) && m_hasWC)
1812 popup.AppendMenuIcon(ID_RESET,str,IDI_REVERT);
1815 // Add Switch Branch express Menu
1816 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end()
1817 && (m_ContextMenuMask&GetContextMenuBit(ID_SWITCHBRANCH) && m_hasWC)
1820 std::vector<CString *> branchs;
1821 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
1823 CString ref = m_HashMap[pSelLogEntry->m_CommitHash][i];
1824 if(ref.Find(_T("refs/heads/")) == 0 && ref != currentBranch)
1826 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
1830 CString str;
1831 str.LoadString(IDS_SWITCH_BRANCH);
1833 if(branchs.size() == 1)
1835 str+=_T(" ");
1836 str+= _T('"') + branchs[0]->Mid(11) + _T('"');
1837 popup.AppendMenuIcon(ID_SWITCHBRANCH,str,IDI_SWITCH);
1839 popup.SetMenuItemData(ID_SWITCHBRANCH,(ULONG_PTR)branchs[0]);
1842 else if(branchs.size() > 1)
1844 subbranchmenu.CreatePopupMenu();
1845 for (size_t i = 0 ; i < branchs.size(); ++i)
1847 if (*branchs[i] != currentBranch)
1849 subbranchmenu.AppendMenuIcon(ID_SWITCHBRANCH+(i<<16), branchs[i]->Mid(11));
1850 subbranchmenu.SetMenuItemData(ID_SWITCHBRANCH+(i<<16), (ULONG_PTR) branchs[i]);
1854 popup.AppendMenuIcon(ID_SWITCHBRANCH, str, IDI_SWITCH, subbranchmenu.m_hMenu);
1858 if(m_ContextMenuMask&GetContextMenuBit(ID_SWITCHTOREV) && !isHeadCommit && m_hasWC)
1859 popup.AppendMenuIcon(ID_SWITCHTOREV, IDS_SWITCH_TO_THIS , IDI_SWITCH);
1861 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_BRANCH))
1862 popup.AppendMenuIcon(ID_CREATE_BRANCH, IDS_CREATE_BRANCH_AT_THIS , IDI_COPY);
1864 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_TAG))
1865 popup.AppendMenuIcon(ID_CREATE_TAG,IDS_CREATE_TAG_AT_THIS , IDI_TAG);
1867 format.LoadString(IDS_REBASE_THIS_FORMAT);
1868 str.Format(format,g_Git.GetCurrentBranch());
1870 if(pSelLogEntry->m_CommitHash != m_HeadHash && m_hasWC)
1871 if(m_ContextMenuMask&GetContextMenuBit(ID_REBASE_TO_VERSION))
1872 popup.AppendMenuIcon(ID_REBASE_TO_VERSION, str , IDI_REBASE);
1874 if(m_ContextMenuMask&GetContextMenuBit(ID_EXPORT))
1875 popup.AppendMenuIcon(ID_EXPORT,IDS_EXPORT_TO_THIS, IDI_EXPORT);
1877 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC)
1879 GitRev *pRev = pSelLogEntry;
1880 if (pSelLogEntry->m_ParentHash.empty())
1884 pRev->GetParentFromHash(pRev->m_CommitHash);
1886 catch (const char* msg)
1888 MessageBox(_T("Could not get parent.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
1891 if (pRev->m_ParentHash.size() == 1)
1893 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT);
1895 else
1897 revertmenu.CreatePopupMenu();
1898 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREV, IDI_REVERT, revertmenu.m_hMenu);
1900 for (size_t i = 0; i < pRev->m_ParentHash.size(); ++i)
1902 CString str;
1903 str.Format(IDS_PARENT, i + 1);
1904 revertmenu.AppendMenuIcon(ID_REVERTREV + ((i + 1) << 16), str);
1909 if (m_ContextMenuMask&GetContextMenuBit(ID_EDITNOTE))
1910 popup.AppendMenuIcon(ID_EDITNOTE, IDS_EDIT_NOTES, IDI_EDIT);
1912 popup.AppendMenu(MF_SEPARATOR, NULL);
1917 if(!pSelLogEntry->m_Ref.IsEmpty())
1919 popup.AppendMenuIcon(ID_REFLOG_DEL, IDS_REFLOG_DEL, IDI_DELETE);
1920 if (GetSelectedCount() == 1 && pSelLogEntry->m_Ref.Find(_T("refs/stash")) == 0)
1921 popup.AppendMenuIcon(ID_REFLOG_STASH_APPLY, IDS_MENUSTASHAPPLY, IDI_RELOCATE);
1922 popup.AppendMenu(MF_SEPARATOR, NULL);
1925 if (GetSelectedCount() >= 2)
1927 bool bAddSeparator = false;
1928 if (IsSelectionContinuous() || (GetSelectedCount() == 2))
1930 if(m_ContextMenuMask&GetContextMenuBit(ID_COMPARETWO)) // compare two revisions
1931 popup.AppendMenuIcon(ID_COMPARETWO, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
1934 if (GetSelectedCount() == 2)
1936 if(m_ContextMenuMask&GetContextMenuBit(ID_GNUDIFF2) && m_hasWC) // compare two revisions, unified
1937 popup.AppendMenuIcon(ID_GNUDIFF2, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
1938 bAddSeparator = true;
1941 if (m_hasWC)
1943 bAddSeparator = true;
1946 if (m_ContextMenuMask&GetContextMenuBit(ID_REVERTREV) && m_hasWC)
1947 popup.AppendMenuIcon(ID_REVERTREV, IDS_LOG_POPUP_REVERTREVS, IDI_REVERT);
1949 if (bAddSeparator)
1950 popup.AppendMenu(MF_SEPARATOR, NULL);
1953 if ( GetSelectedCount() >0 && (!pSelLogEntry->m_CommitHash.IsEmpty()))
1955 bool bAddSeparator = false;
1956 if ( IsSelectionContinuous() && GetSelectedCount() >= 2 )
1958 if(m_ContextMenuMask&GetContextMenuBit(ID_COMBINE_COMMIT) && m_hasWC)
1960 CString head;
1961 int headindex;
1962 headindex = this->GetHeadIndex();
1963 if(headindex>=0 && LastSelect >= headindex)
1965 head.Format(_T("HEAD~%d"),LastSelect-headindex);
1966 CGitHash hash;
1967 if (g_Git.GetHash(hash, head))
1968 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ") + head + _T(".")), _T("TortoiseGit"), MB_ICONERROR);
1969 GitRev* pLastEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(LastSelect));
1970 if(pLastEntry->m_CommitHash == hash) {
1971 popup.AppendMenuIcon(ID_COMBINE_COMMIT,IDS_COMBINE_TO_ONE,IDI_COMBINE);
1972 bAddSeparator = true;
1977 if(m_ContextMenuMask&GetContextMenuBit(ID_CHERRY_PICK) && !isHeadCommit && m_hasWC) {
1978 if (GetSelectedCount() >= 2)
1979 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSIONS, IDI_EXPORT);
1980 else
1981 popup.AppendMenuIcon(ID_CHERRY_PICK, IDS_CHERRY_PICK_VERSION, IDI_EXPORT);
1982 bAddSeparator = true;
1985 if(GetSelectedCount()<=2 || (IsSelectionContinuous() && GetSelectedCount() > 0))
1986 if(m_ContextMenuMask&GetContextMenuBit(ID_CREATE_PATCH)) {
1987 popup.AppendMenuIcon(ID_CREATE_PATCH, IDS_CREATE_PATCH, IDI_PATCH);
1988 bAddSeparator = true;
1991 if (bAddSeparator)
1992 popup.AppendMenu(MF_SEPARATOR, NULL);
1995 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())
1997 popup.AppendMenuIcon(ID_BISECTSTART, IDS_MENUBISECTSTART);
1998 popup.AppendMenu(MF_SEPARATOR, NULL);
2001 if (GetSelectedCount() == 1)
2003 bool bAddSeparator = false;
2004 if(m_ContextMenuMask&GetContextMenuBit(ID_PUSH) && !m_HashMap[pSelLogEntry->m_CommitHash].empty())
2006 // show the push-option only if the log entry has an associated local branch
2007 bool isLocal = false;
2008 for (size_t i = 0; isLocal == false && i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2010 if (m_HashMap[pSelLogEntry->m_CommitHash][i].Find(_T("refs/heads/")) == 0)
2011 isLocal = true;
2013 if (isLocal)
2015 popup.AppendMenuIcon(ID_PUSH, IDS_LOG_PUSH, IDI_PUSH);
2016 bAddSeparator = true;
2020 if(m_ContextMenuMask &GetContextMenuBit(ID_DELETE))
2022 if( this->m_HashMap.find(pSelLogEntry->m_CommitHash) != m_HashMap.end() )
2024 std::vector<CString *> branchs;
2025 for (size_t i = 0; i < m_HashMap[pSelLogEntry->m_CommitHash].size(); ++i)
2027 if(m_HashMap[pSelLogEntry->m_CommitHash][i] != currentBranch)
2028 branchs.push_back(&m_HashMap[pSelLogEntry->m_CommitHash][i]);
2030 CString str;
2031 if (branchs.size() == 1)
2033 str.LoadString(IDS_DELETE_BRANCHTAG_SHORT);
2034 str+=_T(" ");
2035 str += *branchs[0];
2036 popup.AppendMenuIcon(ID_DELETE, str, IDI_DELETE);
2037 popup.SetMenuItemData(ID_DELETE, (ULONG_PTR)branchs[0]);
2038 bAddSeparator = true;
2040 else if (branchs.size() > 1)
2042 str.LoadString(IDS_DELETE_BRANCHTAG);
2043 submenu.CreatePopupMenu();
2044 for (size_t i = 0; i < branchs.size(); ++i)
2046 submenu.AppendMenuIcon(ID_DELETE + (i << 16), *branchs[i]);
2047 submenu.SetMenuItemData(ID_DELETE + (i << 16), (ULONG_PTR)branchs[i]);
2050 popup.AppendMenuIcon(ID_DELETE,str, IDI_DELETE, submenu.m_hMenu);
2051 bAddSeparator = true;
2054 } // m_ContextMenuMask &GetContextMenuBit(ID_DELETE)
2055 if (bAddSeparator)
2056 popup.AppendMenu(MF_SEPARATOR, NULL);
2057 } // GetSelectedCount() == 1
2059 if (GetSelectedCount() != 0)
2061 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYHASH))
2062 popup.AppendMenuIcon(ID_COPYHASH, IDS_COPY_COMMIT_HASH, IDI_COPYCLIP);
2063 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARD))
2064 popup.AppendMenuIcon(ID_COPYCLIPBOARD, IDS_LOG_POPUP_COPYTOCLIPBOARD, IDI_COPYCLIP);
2065 if(m_ContextMenuMask&GetContextMenuBit(ID_COPYCLIPBOARDMESSAGES))
2066 popup.AppendMenuIcon(ID_COPYCLIPBOARDMESSAGES, IDS_LOG_POPUP_COPYTOCLIPBOARDMESSAGES, IDI_COPYCLIP);
2069 if(m_ContextMenuMask&GetContextMenuBit(ID_FINDENTRY))
2070 popup.AppendMenuIcon(ID_FINDENTRY, IDS_LOG_POPUP_FIND, IDI_FILTEREDIT);
2072 if (GetSelectedCount() == 1 && m_ContextMenuMask & GetContextMenuBit(ID_SHOWBRANCHES) && !pSelLogEntry->m_CommitHash.IsEmpty())
2073 popup.AppendMenuIcon(ID_SHOWBRANCHES, IDS_LOG_POPUP_SHOWBRANCHES, IDI_SHOWBRANCHES);
2075 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
2076 // DialogEnableWindow(IDOK, FALSE);
2077 // SetPromptApp(&theApp);
2079 this->ContextMenuAction(cmd, FirstSelect, LastSelect, &popup);
2081 // EnableOKButton();
2082 } // if (popup.CreatePopupMenu())
2086 bool CGitLogListBase::IsSelectionContinuous()
2088 if ( GetSelectedCount()==1 )
2090 // if only one revision is selected, the selection is of course
2091 // continuous
2092 return true;
2095 POSITION pos = GetFirstSelectedItemPosition();
2096 bool bContinuous = (m_arShownList.GetCount() == (INT_PTR)m_logEntries.size());
2097 if (bContinuous)
2099 int itemindex = GetNextSelectedItem(pos);
2100 while (pos)
2102 int nextindex = GetNextSelectedItem(pos);
2103 if (nextindex - itemindex > 1)
2105 bContinuous = false;
2106 break;
2108 itemindex = nextindex;
2111 return bContinuous;
2114 void CGitLogListBase::CopySelectionToClipBoard(int toCopy)
2117 CString sClipdata;
2118 POSITION pos = GetFirstSelectedItemPosition();
2119 if (pos != NULL)
2121 CString sRev;
2122 sRev.LoadString(IDS_LOG_REVISION);
2123 CString sAuthor;
2124 sAuthor.LoadString(IDS_LOG_AUTHOR);
2125 CString sDate;
2126 sDate.LoadString(IDS_LOG_DATE);
2127 CString sMessage;
2128 sMessage.LoadString(IDS_LOG_MESSAGE);
2129 bool first = true;
2130 while (pos)
2132 CString sLogCopyText;
2133 CString sPaths;
2134 GitRev * pLogEntry = reinterpret_cast<GitRev *>(m_arShownList.SafeGetAt(GetNextSelectedItem(pos)));
2136 if (toCopy == ID_COPY_ALL)
2138 //pLogEntry->GetFiles(this)
2139 //LogChangedPathArray * cpatharray = pLogEntry->pArChangedPaths;
2141 CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
2142 for (int cpPathIndex = 0; cpPathIndex<pLogEntry->GetFiles(this).GetCount(); ++cpPathIndex)
2144 sPaths += ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetActionName() + _T(": ") + pLogEntry->GetFiles(this)[cpPathIndex].GetGitPathString();
2145 if (((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).m_Action & (CTGitPath::LOGACTIONS_REPLACED|CTGitPath::LOGACTIONS_COPY) && !((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString().IsEmpty())
2147 CString rename;
2148 rename.Format(from, ((CTGitPath&)pLogEntry->GetFiles(this)[cpPathIndex]).GetGitOldPathString());
2149 sPaths += _T(" ") + rename;
2151 sPaths += _T("\r\n");
2153 sPaths.Trim();
2154 CString body = pLogEntry->GetBody();
2155 body.Replace(_T("\n"), _T("\r\n"));
2156 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"),
2157 (LPCTSTR)sRev, pLogEntry->m_CommitHash.ToString(),
2158 (LPCTSTR)sAuthor, (LPCTSTR)pLogEntry->GetAuthorName(), (LPCTSTR)pLogEntry->GetAuthorEmail(),
2159 (LPCTSTR)sDate,
2160 (LPCTSTR)CLoglistUtils::FormatDateAndTime(pLogEntry->GetAuthorDate(), m_DateFormat, true, m_bRelativeTimes),
2161 (LPCTSTR)sMessage, (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim(),
2162 (LPCTSTR)sPaths);
2163 sClipdata += sLogCopyText;
2165 else if (toCopy == ID_COPY_MESSAGE)
2167 CString body = pLogEntry->GetBody();
2168 body.Replace(_T("\n"), _T("\r\n"));
2169 sClipdata += _T("* ") + (pLogEntry->GetSubject().Trim() + _T("\r\n\r\n") + body.Trim()).Trim() + _T("\r\n\r\n");
2171 else if (toCopy == ID_COPY_SUBJECT)
2173 sClipdata += _T("* ") + pLogEntry->GetSubject().Trim() + _T("\r\n\r\n");
2175 else
2177 if (!first)
2178 sClipdata += _T("\r\n");
2179 sClipdata += pLogEntry->m_CommitHash;
2182 first = false;
2184 CStringUtils::WriteAsciiStringToClipboard(sClipdata, GetSafeHwnd());
2189 void CGitLogListBase::DiffSelectedRevWithPrevious()
2191 if (m_bThreadRunning)
2192 return;
2194 int FirstSelect=-1, LastSelect=-1;
2195 POSITION pos = GetFirstSelectedItemPosition();
2196 FirstSelect = GetNextSelectedItem(pos);
2197 while(pos)
2199 LastSelect = GetNextSelectedItem(pos);
2202 ContextMenuAction(ID_COMPAREWITHPREVIOUS,FirstSelect,LastSelect, NULL);
2204 #if 0
2205 UpdateLogInfoLabel();
2206 int selIndex = m_LogList.GetSelectionMark();
2207 if (selIndex < 0)
2208 return;
2209 int selCount = m_LogList.GetSelectedCount();
2210 if (selCount != 1)
2211 return;
2213 // Find selected entry in the log list
2214 POSITION pos = m_LogList.GetFirstSelectedItemPosition();
2215 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(m_LogList.GetNextSelectedItem(pos)));
2216 long rev1 = pLogEntry->Rev;
2217 long rev2 = rev1-1;
2218 CTGitPath path = m_path;
2220 // See how many files under the relative root were changed in selected revision
2221 int nChanged = 0;
2222 LogChangedPath * changed = NULL;
2223 for (INT_PTR c = 0; c < pLogEntry->pArChangedPaths->GetCount(); ++c)
2225 LogChangedPath * cpath = pLogEntry->pArChangedPaths->SafeGetAt(c);
2226 if (cpath && cpath -> sPath.Left(m_sRelativeRoot.GetLength()).Compare(m_sRelativeRoot)==0)
2228 ++nChanged;
2229 changed = cpath;
2233 if (m_path.IsDirectory() && nChanged == 1)
2235 // We're looking at the log for a directory and only one file under dir was changed in the revision
2236 // Do diff on that file instead of whole directory
2237 path.AppendPathString(changed->sPath.Mid(m_sRelativeRoot.GetLength()));
2240 m_bCancelled = FALSE;
2241 DialogEnableWindow(IDOK, FALSE);
2242 SetPromptApp(&theApp);
2243 theApp.DoWaitCursor(1);
2245 if (PromptShown())
2247 GitDiff diff(this, m_hWnd, true);
2248 diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2249 diff.SetHEADPeg(m_LogRevision);
2250 diff.ShowCompare(path, rev2, path, rev1);
2252 else
2254 CAppUtils::StartShowCompare(m_hWnd, path, rev2, path, rev1, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
2257 theApp.DoWaitCursor(-1);
2258 EnableOKButton();
2259 #endif
2262 void CGitLogListBase::OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2264 LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
2265 *pResult = -1;
2267 if (pFindInfo->lvfi.flags & LVFI_PARAM)
2268 return;
2269 if ((pFindInfo->iStart < 0)||(pFindInfo->iStart >= m_arShownList.GetCount()))
2270 return;
2271 if (pFindInfo->lvfi.psz == 0)
2272 return;
2273 #if 0
2274 CString sCmp = pFindInfo->lvfi.psz;
2275 CString sRev;
2276 for (int i=pFindInfo->iStart; i<m_arShownList.GetCount(); ++i)
2278 GitRev * pLogEntry = reinterpret_cast<GitRev*>(m_arShownList.SafeGetAt(i));
2279 sRev.Format(_T("%ld"), pLogEntry->Rev);
2280 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2282 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2284 *pResult = i;
2285 return;
2288 else
2290 if (sCmp.Compare(sRev)==0)
2292 *pResult = i;
2293 return;
2297 if (pFindInfo->lvfi.flags & LVFI_WRAP)
2299 for (int i=0; i<pFindInfo->iStart; ++i)
2301 PLOGENTRYDATA pLogEntry = reinterpret_cast<PLOGENTRYDATA>(m_arShownList.SafeGetAt(i));
2302 sRev.Format(_T("%ld"), pLogEntry->Rev);
2303 if (pFindInfo->lvfi.flags & LVFI_PARTIAL)
2305 if (sCmp.Compare(sRev.Left(sCmp.GetLength()))==0)
2307 *pResult = i;
2308 return;
2311 else
2313 if (sCmp.Compare(sRev)==0)
2315 *pResult = i;
2316 return;
2321 #endif
2322 *pResult = -1;
2325 int CGitLogListBase::FillGitLog(CTGitPath *path, CString *range, int info)
2327 ClearText();
2329 this->m_arShownList.SafeRemoveAll();
2331 this->m_logEntries.ClearAll();
2332 if (this->m_logEntries.ParserFromLog(path, -1, info, range))
2333 return -1;
2335 //this->m_logEntries.ParserFromLog();
2336 SetItemCountEx((int)m_logEntries.size());
2338 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2340 if(m_IsOldFirst)
2342 m_logEntries.GetGitRevAt(m_logEntries.size()-i-1).m_IsFull=TRUE;
2343 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
2346 else
2348 m_logEntries.GetGitRevAt(i).m_IsFull=TRUE;
2349 this->m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
2353 ReloadHashMap();
2355 if(path)
2356 m_Path=*path;
2357 return 0;
2361 int CGitLogListBase::BeginFetchLog()
2363 ClearText();
2365 this->m_arShownList.SafeRemoveAll();
2367 this->m_logEntries.ClearAll();
2369 this->m_LogCache.ClearAllParent();
2371 m_LogCache.FetchCacheIndex(g_Git.m_CurrentDir);
2373 CTGitPath *path;
2374 if(this->m_Path.IsEmpty())
2375 path=NULL;
2376 else
2377 path=&this->m_Path;
2379 int mask;
2380 mask = CGit::LOG_INFO_ONLY_HASH;
2381 if (m_bIncludeBoundaryCommits)
2382 mask |= CGit::LOG_INFO_BOUNDARY;
2383 // if(this->m_bAllBranch)
2384 mask |= m_ShowMask ;
2386 if(m_bShowWC)
2388 this->m_logEntries.insert(m_logEntries.begin(),this->m_wcRev.m_CommitHash);
2389 ResetWcRev();
2390 this->m_LogCache.m_HashMap[m_wcRev.m_CommitHash]=m_wcRev;
2393 if (m_sRange.IsEmpty())
2394 m_sRange = _T("HEAD");
2396 CFilterData data;
2397 data.m_From = m_From;
2398 data.m_To =m_To;
2400 #if 0 /* use tortoiegit filter */
2401 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_AUTHORS)
2402 data.m_Author = this->m_sFilterText;
2404 if(this->m_nSelectedFilter == LOGFILTER_ALL || m_nSelectedFilter == LOGFILTER_MESSAGES)
2405 data.m_MessageFilter = this->m_sFilterText;
2407 data.m_IsRegex = m_bFilterWithRegex;
2408 #endif
2410 // follow does not work for directories
2411 if (!path || path->IsDirectory())
2412 mask &= ~CGit::LOG_INFO_FOLLOW;
2413 // follow does not work with all branches 8at least in TGit)
2414 if (mask & CGit::LOG_INFO_FOLLOW)
2415 mask &= ~CGit::LOG_INFO_ALL_BRANCH;
2417 CString cmd = g_Git.GetLogCmd(m_sRange, path, -1, mask, true, &data);
2419 //this->m_logEntries.ParserFromLog();
2420 if(IsInWorkingThread())
2422 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL);
2424 else
2426 SetItemCountEx((int)m_logEntries.size());
2431 [] { git_init(); } ();
2433 catch (char* msg)
2435 CString err(msg);
2436 MessageBox(_T("Could not initialize libgit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2437 return -1;
2440 if (!g_Git.CanParseRev(m_sRange))
2442 if (!(mask & CGit::LOG_INFO_ALL_BRANCH))
2443 return 0;
2445 // if show all branches, pick any ref as dummy entry ref
2446 STRING_VECTOR list;
2447 if (g_Git.GetRefList(list))
2448 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2449 if (list.size() == 0)
2450 return 0;
2452 cmd = g_Git.GetLogCmd(list[0], path, -1, mask, true, &data);
2455 g_Git.m_critGitDllSec.Lock();
2456 try {
2457 if (git_open_log(&m_DllGitLog, CUnicodeUtils::GetMulti(cmd, CP_UTF8).GetBuffer()))
2459 g_Git.m_critGitDllSec.Unlock();
2460 return -1;
2463 catch (char* msg)
2465 g_Git.m_critGitDllSec.Unlock();
2466 CString err(msg);
2467 MessageBox(_T("Could not open log.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2468 return -1;
2470 g_Git.m_critGitDllSec.Unlock();
2472 return 0;
2475 BOOL CGitLogListBase::PreTranslateMessage(MSG* pMsg)
2477 if (pMsg->message == WM_KEYDOWN && pMsg->wParam=='\r')
2479 //if (GetFocus()==GetDlgItem(IDC_LOGLIST))
2481 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2483 DiffSelectedRevWithPrevious();
2484 return TRUE;
2487 #if 0
2488 if (GetFocus()==GetDlgItem(IDC_LOGMSG))
2490 DiffSelectedFile();
2491 return TRUE;
2493 #endif
2495 else if (pMsg->message == WM_KEYDOWN && pMsg->wParam == 'A' && GetAsyncKeyState(VK_CONTROL)&0x8000)
2497 // select all entries
2498 for (int i=0; i<GetItemCount(); ++i)
2500 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2502 return TRUE;
2505 #if 0
2506 if (m_hAccel && !bSkipAccelerator)
2508 int ret = TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
2509 if (ret)
2510 return TRUE;
2513 #endif
2514 //m_tooltips.RelayEvent(pMsg);
2515 return __super::PreTranslateMessage(pMsg);
2518 void CGitLogListBase::OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult)
2520 // a double click on an entry in the revision list has happened
2521 *pResult = 0;
2523 if (CRegDWORD(_T("Software\\TortoiseGit\\DiffByDoubleClickInLog"), FALSE))
2524 DiffSelectedRevWithPrevious();
2527 int CGitLogListBase::FetchLogAsync(void * data)
2529 ReloadHashMap();
2530 m_ProcData=data;
2531 m_bExitThread=FALSE;
2532 InterlockedExchange(&m_bThreadRunning, TRUE);
2533 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2534 m_LoadingThread = AfxBeginThread(LogThreadEntry, this, THREAD_PRIORITY_LOWEST);
2535 if (m_LoadingThread ==NULL)
2537 InterlockedExchange(&m_bThreadRunning, FALSE);
2538 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2539 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2540 return -1;
2542 return 0;
2545 //this is the thread function which calls the subversion function
2546 UINT CGitLogListBase::LogThreadEntry(LPVOID pVoid)
2548 return ((CGitLogListBase*)pVoid)->LogThread();
2551 void CGitLogListBase::GetTimeRange(CTime &oldest, CTime &latest)
2553 //CTime time;
2554 oldest=CTime::GetCurrentTime();
2555 latest=CTime(1971,1,2,0,0,0);
2556 for (unsigned int i = 0; i < m_logEntries.size(); ++i)
2558 if(m_logEntries[i].IsEmpty())
2559 continue;
2561 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() < oldest.GetTime())
2562 oldest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2564 if(m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime() > latest.GetTime())
2565 latest = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
2569 if(latest<oldest)
2570 latest=oldest;
2573 UINT CGitLogListBase::LogThread()
2575 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_START,0);
2577 InterlockedExchange(&m_bThreadRunning, TRUE);
2578 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2580 ULONGLONG t1,t2;
2582 if(BeginFetchLog())
2584 InterlockedExchange(&m_bThreadRunning, FALSE);
2585 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2587 return 1;
2590 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
2591 bool bRegex = false;
2592 if (m_bFilterWithRegex)
2593 bRegex = ValidateRegexp(m_sFilterText, pat, false);
2595 TRACE(_T("\n===Begin===\n"));
2596 //Update work copy item;
2598 if (!m_logEntries.empty())
2600 GitRev *pRev = &m_logEntries.GetGitRevAt(0);
2602 m_arShownList.SafeAdd(pRev);
2606 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2608 // store commit number of the last selected commit/line before the refresh or -1
2609 int lastSelectedHashNItem = -1;
2610 int ret = 0;
2612 bool shouldWalk = true;
2613 if (!g_Git.CanParseRev(m_sRange))
2615 // walk revisions if show all branches and there exists any ref
2616 if (!(m_ShowMask & CGit::LOG_INFO_ALL_BRANCH))
2617 shouldWalk = false;
2618 else
2620 STRING_VECTOR list;
2621 if (g_Git.GetRefList(list))
2622 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
2623 if (list.size() == 0)
2624 shouldWalk = false;
2628 if (shouldWalk)
2630 g_Git.m_critGitDllSec.Lock();
2631 int total = 0;
2634 [&] {git_get_log_firstcommit(m_DllGitLog);}();
2635 total = git_get_log_estimate_commit_count(m_DllGitLog);
2637 catch (char* msg)
2639 CString err(msg);
2640 MessageBox(_T("Could not get first commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2641 ret = -1;
2643 g_Git.m_critGitDllSec.Unlock();
2645 GIT_COMMIT commit;
2646 t2=t1=GetTickCount();
2647 int oldprecentage = 0;
2648 size_t oldsize = m_logEntries.size();
2649 while (ret== 0 && !m_bExitThread)
2651 g_Git.m_critGitDllSec.Lock();
2654 [&] { ret = git_get_log_nextcommit(this->m_DllGitLog, &commit, m_ShowMask & CGit::LOG_INFO_FOLLOW); } ();
2656 catch (char* msg)
2658 g_Git.m_critGitDllSec.Unlock();
2659 CString err(msg);
2660 MessageBox(_T("Could not get next commit.\nlibgit reports:\n") + err, _T("TortoiseGit"), MB_ICONERROR);
2661 break;
2663 g_Git.m_critGitDllSec.Unlock();
2665 if(ret)
2666 break;
2668 if (commit.m_ignore == 1)
2670 git_free_commit(&commit);
2671 continue;
2674 //printf("%s\r\n",commit.GetSubject());
2675 if(m_bExitThread)
2676 break;
2678 CGitHash hash = (char*)commit.m_hash ;
2680 GitRev *pRev = m_LogCache.GetCacheData(hash);
2681 pRev->m_GitCommit = commit;
2682 InterlockedExchange(&pRev->m_IsCommitParsed, FALSE);
2684 char *note=NULL;
2685 g_Git.m_critGitDllSec.Lock();
2686 git_get_notes(commit.m_hash,&note);
2687 g_Git.m_critGitDllSec.Unlock();
2689 if(note)
2691 pRev->m_Notes.Empty();
2692 g_Git.StringAppend(&pRev->m_Notes,(BYTE*)note);
2695 if(!pRev->m_IsDiffFiles)
2697 pRev->m_CallDiffAsync = DiffAsync;
2700 pRev->ParserParentFromCommit(&commit);
2702 #ifdef DEBUG
2703 pRev->DbgPrint();
2704 TRACE(_T("\n"));
2705 #endif
2707 if(!m_sFilterText.IsEmpty())
2709 if(!IsMatchFilter(bRegex,pRev,pat))
2710 continue;
2712 this->m_critSec.Lock();
2713 m_logEntries.push_back(hash);
2714 m_arShownList.SafeAdd(pRev);
2715 this->m_critSec.Unlock();
2717 if (lastSelectedHashNItem == -1 && hash == m_lastSelectedHash)
2718 lastSelectedHashNItem = (int)m_arShownList.GetCount() - 1;
2720 t2=GetTickCount();
2722 if(t2-t1>500 || (m_logEntries.size()-oldsize >100))
2724 //update UI
2725 int percent = (int)m_logEntries.size() * 100 / total + GITLOG_START + 1;
2726 if(percent > 99)
2727 percent =99;
2728 if(percent < GITLOG_START)
2729 percent = GITLOG_START +1;
2731 oldsize = m_logEntries.size();
2732 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2734 //if( percent > oldprecentage )
2736 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) percent,0);
2737 oldprecentage = percent;
2739 t1 = t2;
2742 g_Git.m_critGitDllSec.Lock();
2743 git_close_log(m_DllGitLog);
2744 g_Git.m_critGitDllSec.Unlock();
2748 if (m_bExitThread)
2750 InterlockedExchange(&m_bThreadRunning, FALSE);
2751 return 0;
2754 //Update UI;
2755 PostMessage(LVM_SETITEMCOUNT, (WPARAM) this->m_logEntries.size(),(LPARAM) LVSICF_NOINVALIDATEALL|LVSICF_NOSCROLL);
2757 if (lastSelectedHashNItem >= 0)
2758 PostMessage(m_ScrollToMessage, lastSelectedHashNItem);
2760 if (this->m_hWnd)
2761 ::PostMessage(this->GetParent()->m_hWnd,MSG_LOAD_PERCENTAGE,(WPARAM) GITLOG_END,0);
2763 InterlockedExchange(&m_bThreadRunning, FALSE);
2765 return 0;
2768 void CGitLogListBase::FetchRemoteList()
2770 STRING_VECTOR remoteList;
2771 if (!g_Git.GetRemoteList(remoteList))
2772 m_SingleRemote = remoteList.size() == 1 ? remoteList[0] : _T("");
2773 else
2774 m_SingleRemote = _T("");
2777 void CGitLogListBase::FetchTrackingBranchList()
2779 m_TrackingMap.clear();
2780 for (MAP_HASH_NAME::iterator it = m_HashMap.begin(); it != m_HashMap.end(); ++it)
2782 for (int j = 0; j < it->second.size(); ++j)
2784 CString branchName;
2785 if (CGit::GetShortName(it->second[j], branchName, _T("refs/heads/")))
2787 CString configName;
2788 configName.Format(_T("branch.%s.remote"), branchName);
2789 CString pullRemote = g_Git.GetConfigValue(configName);
2791 configName.Format(_T("branch.%s.merge"), branchName);
2792 CString pullBranch = CGit::StripRefName(g_Git.GetConfigValue(configName));
2794 if (!pullRemote.IsEmpty() && !pullBranch.IsEmpty())
2796 m_TrackingMap[branchName] = std::make_pair(pullRemote, pullBranch);
2803 void CGitLogListBase::Refresh(BOOL IsCleanFilter)
2805 SafeTerminateThread();
2807 this->SetItemCountEx(0);
2808 this->Clear();
2810 ResetWcRev();
2812 // HACK to hide graph column
2813 if (m_ShowMask & CGit::LOG_INFO_FOLLOW)
2814 SetColumnWidth(0, 0);
2815 else
2816 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
2818 //Update branch and Tag info
2819 ReloadHashMap();
2820 //Assume Thread have exited
2821 //if(!m_bThreadRunning)
2823 m_logEntries.clear();
2825 if(IsCleanFilter)
2827 m_sFilterText.Empty();
2828 m_From=-1;
2829 m_To=-1;
2832 InterlockedExchange(&m_bExitThread,FALSE);
2834 InterlockedExchange(&m_bThreadRunning, TRUE);
2835 InterlockedExchange(&m_bNoDispUpdates, TRUE);
2836 if ( (m_LoadingThread=AfxBeginThread(LogThreadEntry, this)) ==NULL)
2838 InterlockedExchange(&m_bThreadRunning, FALSE);
2839 InterlockedExchange(&m_bNoDispUpdates, FALSE);
2840 CMessageBox::Show(NULL, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
2844 bool CGitLogListBase::ValidateRegexp(LPCTSTR regexp_str, tr1::wregex& pat, bool bMatchCase /* = false */)
2848 tr1::regex_constants::syntax_option_type type = tr1::regex_constants::ECMAScript;
2849 if (!bMatchCase)
2850 type |= tr1::regex_constants::icase;
2851 pat = tr1::wregex(regexp_str, type);
2852 return true;
2854 catch (exception) {}
2855 return false;
2857 BOOL CGitLogListBase::IsMatchFilter(bool bRegex, GitRev *pRev, tr1::wregex &pat)
2860 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
2861 CString sRev;
2863 if ((bRegex)&&(m_bFilterWithRegex))
2865 if (m_SelectedFilters & LOGFILTER_BUGID)
2867 if(this->m_bShowBugtraqColumn)
2869 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2871 ATLTRACE(_T("bugID = \"%s\"\n"), sBugIds);
2872 if (regex_search(wstring(sBugIds), pat, flags))
2874 return TRUE;
2879 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
2881 ATLTRACE(_T("messge = \"%s\"\n"), pRev->GetSubject());
2882 if (regex_search(wstring((LPCTSTR)pRev->GetSubject()), pat, flags))
2884 return TRUE;
2888 if (m_SelectedFilters & LOGFILTER_MESSAGES)
2890 ATLTRACE(_T("messge = \"%s\"\n"),pRev->GetBody());
2891 if (regex_search(wstring((LPCTSTR)pRev->GetBody()), pat, flags))
2893 return TRUE;
2897 if (m_SelectedFilters & LOGFILTER_AUTHORS)
2899 if (regex_search(wstring(pRev->GetAuthorName()), pat, flags))
2901 return TRUE;
2904 if (regex_search(wstring(pRev->GetCommitterName()), pat, flags))
2906 return TRUE;
2910 if (m_SelectedFilters & LOGFILTER_EMAILS)
2912 if (regex_search(wstring(pRev->GetAuthorEmail()), pat, flags))
2914 return TRUE;
2917 if (regex_search(wstring(pRev->GetCommitterEmail()), pat, flags))
2919 return TRUE;
2923 if (m_SelectedFilters & LOGFILTER_REVS)
2925 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
2926 if (regex_search(wstring((LPCTSTR)sRev), pat, flags))
2928 return TRUE;
2932 if (m_SelectedFilters & LOGFILTER_REFNAME)
2934 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
2935 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
2937 if (regex_search(wstring((LPCTSTR)*it), pat, flags))
2939 return TRUE;
2944 if (m_SelectedFilters & LOGFILTER_PATHS)
2946 CTGitPathList *pathList=NULL;
2947 if( pRev->m_IsDiffFiles)
2948 pathList = &pRev->GetFiles(this);
2949 else
2951 if(!pRev->m_IsSimpleListReady)
2952 pRev->SafeGetSimpleList(&g_Git);
2955 if(pathList)
2956 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount(); ++cpPathIndex)
2958 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitOldPathString()), pat, flags))
2960 return true;
2962 if (regex_search(wstring((LPCTSTR)pathList->m_paths.at(cpPathIndex).GetGitPathString()), pat, flags))
2964 return true;
2968 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
2970 if (regex_search(wstring((LPCTSTR)pRev->m_SimpleFileList[i]), pat, flags))
2972 return true;
2977 else
2979 CString find = m_sFilterText;
2980 find.MakeLower();
2982 if (m_SelectedFilters & LOGFILTER_BUGID)
2984 if(this->m_bShowBugtraqColumn)
2986 CString sBugIds = m_ProjectProperties.FindBugID(pRev->GetSubject() + _T("\r\n\r\n") + pRev->GetBody());
2988 sBugIds.MakeLower();
2989 if ((sBugIds.Find(find) >= 0))
2991 return TRUE;
2996 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
2998 CString msg = pRev->GetSubject();
3000 msg = msg.MakeLower();
3001 if ((msg.Find(find) >= 0))
3003 return TRUE;
3007 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3009 CString msg = pRev->GetBody();
3011 msg = msg.MakeLower();
3012 if ((msg.Find(find) >= 0))
3014 return TRUE;
3018 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3020 CString msg = pRev->GetAuthorName();
3021 msg = msg.MakeLower();
3022 if ((msg.Find(find) >= 0))
3024 return TRUE;
3028 if (m_SelectedFilters & LOGFILTER_EMAILS)
3030 CString msg = pRev->GetAuthorEmail();
3031 msg = msg.MakeLower();
3032 if ((msg.Find(find) >= 0))
3034 return TRUE;
3038 if (m_SelectedFilters & LOGFILTER_REVS)
3040 sRev.Format(_T("%s"), pRev->m_CommitHash.ToString());
3041 if ((sRev.Find(find) >= 0))
3043 return TRUE;
3047 if (m_SelectedFilters & LOGFILTER_REFNAME)
3049 STRING_VECTOR refs = m_HashMap[pRev->m_CommitHash];
3050 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3052 if (it->Find(find) >= 0)
3054 return TRUE;
3059 if (m_SelectedFilters & LOGFILTER_PATHS)
3061 CTGitPathList *pathList=NULL;
3062 if( pRev->m_IsDiffFiles)
3063 pathList = &pRev->GetFiles(this);
3064 else
3066 if(!pRev->m_IsSimpleListReady)
3067 pRev->SafeGetSimpleList(&g_Git);
3069 if(pathList)
3070 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList->GetCount() ; ++cpPathIndex)
3072 CTGitPath *cpath = &pathList->m_paths.at(cpPathIndex);
3073 CString path = cpath->GetGitOldPathString();
3074 path.MakeLower();
3075 if ((path.Find(find)>=0))
3077 return true;
3079 path = cpath->GetGitPathString();
3080 path.MakeLower();
3081 if ((path.Find(find)>=0))
3083 return true;
3087 for (size_t i = 0; i < pRev->m_SimpleFileList.size(); ++i)
3089 CString path = pRev->m_SimpleFileList[i];
3090 path.MakeLower();
3091 if ((path.Find(find)>=0))
3093 return true;
3097 } // else (from if (bRegex))
3098 return FALSE;
3102 void CGitLogListBase::RecalculateShownList(CThreadSafePtrArray * pShownlist)
3105 pShownlist->SafeRemoveAll();
3107 tr1::wregex pat;//(_T("Remove"), tr1::regex_constants::icase);
3108 bool bRegex = false;
3109 if (m_bFilterWithRegex)
3110 bRegex = ValidateRegexp(m_sFilterText, pat, false);
3112 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_any;
3113 CString sRev;
3114 for (DWORD i=0; i<m_logEntries.size(); ++i)
3116 if ((bRegex)&&(m_bFilterWithRegex))
3118 #if 0
3119 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3121 ATLTRACE(_T("bugID = \"%s\"\n"), (LPCTSTR)m_logEntries[i]->sBugIDs);
3122 if (regex_search(wstring((LPCTSTR)m_logEntries[i]->sBugIDs), pat, flags)&&IsEntryInDateRange(i))
3124 pShownlist->SafeAdd(m_logEntries[i]);
3125 continue;
3128 #endif
3129 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3131 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetSubject());
3132 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetSubject()), pat, flags)&&IsEntryInDateRange(i))
3134 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3135 continue;
3138 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3140 ATLTRACE(_T("messge = \"%s\"\n"),m_logEntries.GetGitRevAt(i).GetBody());
3141 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetBody()), pat, flags)&&IsEntryInDateRange(i))
3143 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3144 continue;
3147 if (m_SelectedFilters & LOGFILTER_PATHS)
3149 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3151 bool bGoing = true;
3152 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3154 CTGitPath cpath = pathList[cpPathIndex];
3155 if (regex_search(wstring((LPCTSTR)cpath.GetGitOldPathString()), pat, flags)&&IsEntryInDateRange(i))
3157 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3158 bGoing = false;
3159 continue;
3161 if (regex_search(wstring((LPCTSTR)cpath.GetGitPathString()), pat, flags)&&IsEntryInDateRange(i))
3163 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3164 bGoing = false;
3165 continue;
3167 if (regex_search(wstring((LPCTSTR)cpath.GetActionName()), pat, flags)&&IsEntryInDateRange(i))
3169 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3170 bGoing = false;
3171 continue;
3175 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3177 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorName()), pat, flags)&&IsEntryInDateRange(i))
3179 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3180 continue;
3183 if (m_SelectedFilters & LOGFILTER_EMAILS)
3185 if (regex_search(wstring((LPCTSTR)m_logEntries.GetGitRevAt(i).GetAuthorEmail()), pat, flags) && IsEntryInDateRange(i))
3187 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3188 continue;
3191 if (m_SelectedFilters & LOGFILTER_REVS)
3193 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3194 if (regex_search(wstring((LPCTSTR)sRev), pat, flags)&&IsEntryInDateRange(i))
3196 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3197 continue;
3200 if (m_SelectedFilters & LOGFILTER_REFNAME)
3202 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3203 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3205 if (regex_search(wstring((LPCTSTR)*it), pat, flags) && IsEntryInDateRange(i))
3207 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3208 continue;
3212 } // if (bRegex)
3213 else
3215 CString find = m_sFilterText;
3216 find.MakeLower();
3217 #if 0
3218 if ((m_nSelectedFilter == LOGFILTER_ALL)||(m_nSelectedFilter == LOGFILTER_BUGID))
3220 CString sBugIDs = m_logEntries[i]->sBugIDs;
3222 sBugIDs = sBugIDs.MakeLower();
3223 if ((sBugIDs.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3225 pShownlist->SafeAdd(m_logEntries[i]);
3226 continue;
3229 #endif
3230 if ((m_SelectedFilters & LOGFILTER_SUBJECT) || (m_SelectedFilters & LOGFILTER_MESSAGES))
3232 CString msg = m_logEntries.GetGitRevAt(i).GetSubject();
3234 msg = msg.MakeLower();
3235 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3237 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3238 continue;
3241 if (m_SelectedFilters & LOGFILTER_MESSAGES)
3243 CString msg = m_logEntries.GetGitRevAt(i).GetBody();
3245 msg = msg.MakeLower();
3246 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3248 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3249 continue;
3252 if (m_SelectedFilters & LOGFILTER_PATHS)
3254 CTGitPathList pathList = m_logEntries.GetGitRevAt(i).GetFiles(this);
3256 bool bGoing = true;
3257 for (INT_PTR cpPathIndex = 0; cpPathIndex < pathList.GetCount() && bGoing; ++cpPathIndex)
3259 CTGitPath cpath = pathList[cpPathIndex];
3260 CString path = cpath.GetGitOldPathString();
3261 path.MakeLower();
3262 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3264 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3265 bGoing = false;
3266 continue;
3268 path = cpath.GetGitPathString();
3269 path.MakeLower();
3270 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3272 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3273 bGoing = false;
3274 continue;
3276 path = cpath.GetActionName();
3277 path.MakeLower();
3278 if ((path.Find(find)>=0)&&(IsEntryInDateRange(i)))
3280 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3281 bGoing = false;
3282 continue;
3286 if (m_SelectedFilters & LOGFILTER_AUTHORS)
3288 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorName();
3289 msg = msg.MakeLower();
3290 if ((msg.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3292 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3293 continue;
3296 if (m_SelectedFilters & LOGFILTER_EMAILS)
3298 CString msg = m_logEntries.GetGitRevAt(i).GetAuthorEmail();
3299 msg = msg.MakeLower();
3300 if ((msg.Find(find) >= 0) && (IsEntryInDateRange(i)))
3302 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3303 continue;
3306 if (m_SelectedFilters & LOGFILTER_REVS)
3308 sRev.Format(_T("%s"), m_logEntries.GetGitRevAt(i).m_CommitHash.ToString());
3309 if ((sRev.Find(find) >= 0)&&(IsEntryInDateRange(i)))
3311 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3312 continue;
3315 if (m_SelectedFilters & LOGFILTER_REFNAME)
3317 STRING_VECTOR refs = m_HashMap[m_logEntries.GetGitRevAt(i).m_CommitHash];
3318 for (auto it = refs.cbegin(); it != refs.cend(); ++it)
3320 if (it->Find(find) >= 0 && IsEntryInDateRange(i))
3322 pShownlist->SafeAdd(&m_logEntries.GetGitRevAt(i));
3323 continue;
3327 } // else (from if (bRegex))
3328 } // for (DWORD i=0; i<m_logEntries.size(); ++i)
3332 BOOL CGitLogListBase::IsEntryInDateRange(int /*i*/)
3335 __time64_t time = m_logEntries.GetGitRevAt(i).GetAuthorDate().GetTime();
3337 if(m_From == -1)
3338 if(m_To == -1)
3339 return true;
3340 else
3341 return time <= m_To;
3342 else
3343 if(m_To == -1)
3344 return time >= m_From;
3345 else
3346 return ((time >= m_From)&&(time <= m_To));
3348 return TRUE; /* git dll will filter time range */
3350 // return TRUE;
3352 void CGitLogListBase::StartFilter()
3354 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3355 RecalculateShownList(&m_arShownList);
3356 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3359 DeleteAllItems();
3360 SetItemCountEx(ShownCountWithStopped());
3361 RedrawItems(0, ShownCountWithStopped());
3362 Invalidate();
3365 void CGitLogListBase::RemoveFilter()
3368 InterlockedExchange(&m_bNoDispUpdates, TRUE);
3370 m_arShownList.SafeRemoveAll();
3372 // reset the time filter too
3373 #if 0
3374 m_timFrom = (__time64_t(m_tFrom));
3375 m_timTo = (__time64_t(m_tTo));
3376 m_DateFrom.SetTime(&m_timFrom);
3377 m_DateTo.SetTime(&m_timTo);
3378 m_DateFrom.SetRange(&m_timFrom, &m_timTo);
3379 m_DateTo.SetRange(&m_timFrom, &m_timTo);
3380 #endif
3382 for (DWORD i=0; i<m_logEntries.size(); ++i)
3384 if(this->m_IsOldFirst)
3386 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(m_logEntries.size()-i-1));
3388 else
3390 m_arShownList.SafeAdd(&m_logEntries.GetGitRevAt(i));
3393 // InterlockedExchange(&m_bNoDispUpdates, FALSE);
3394 DeleteAllItems();
3395 SetItemCountEx(ShownCountWithStopped());
3396 RedrawItems(0, ShownCountWithStopped());
3398 InterlockedExchange(&m_bNoDispUpdates, FALSE);
3401 void CGitLogListBase::Clear()
3403 m_arShownList.SafeRemoveAll();
3404 DeleteAllItems();
3406 m_logEntries.ClearAll();
3410 void CGitLogListBase::OnDestroy()
3412 // save the column widths to the registry
3413 SaveColumnWidths();
3415 SafeTerminateThread();
3416 SafeTerminateAsyncDiffThread();
3418 int retry = 0;
3419 while(m_LogCache.SaveCache())
3421 if(retry > 5)
3422 break;
3423 Sleep(1000);
3425 ++retry;
3427 //if(CMessageBox::Show(NULL,_T("Cannot Save Log Cache to Disk. To retry click yes. To give up click no."),_T("TortoiseGit"),
3428 // MB_YESNO) == IDNO)
3429 // break;
3432 CHintListCtrl::OnDestroy();
3435 LRESULT CGitLogListBase::OnLoad(WPARAM wParam,LPARAM /*lParam*/)
3437 CRect rect;
3438 int i=(int)wParam;
3439 this->GetItemRect(i,&rect,LVIR_BOUNDS);
3440 this->InvalidateRect(rect);
3442 return 0;
3446 * Save column widths to the registry
3448 void CGitLogListBase::SaveColumnWidths()
3450 int maxcol = m_ColumnManager.GetColumnCount();
3452 // HACK that graph column is always shown
3453 SetColumnWidth(0, m_ColumnManager.GetWidth(0, false));
3455 for (int col = 0; col < maxcol; ++col)
3456 if (m_ColumnManager.IsVisible (col))
3457 m_ColumnManager.ColumnResized (col);
3459 m_ColumnManager.WriteSettings();
3462 int CGitLogListBase::GetHeadIndex()
3464 if(m_HeadHash.IsEmpty())
3465 return -1;
3467 for (int i = 0; i < m_arShownList.GetCount(); ++i)
3469 GitRev *pRev = (GitRev*)m_arShownList.SafeGetAt(i);
3470 if(pRev)
3472 if(pRev->m_CommitHash.ToString() == m_HeadHash )
3473 return i;
3476 return -1;
3478 void CGitLogListBase::OnFind()
3480 if (!m_pFindDialog)
3482 m_pFindDialog = new CFindDlg(this);
3483 m_pFindDialog->Create(this);
3486 void CGitLogListBase::OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult)
3488 m_ColumnManager.OnHdnBegintrack(pNMHDR, pResult);
3490 void CGitLogListBase::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
3492 if(!m_ColumnManager.OnHdnItemchanging(pNMHDR, pResult))
3493 Default();
3495 LRESULT CGitLogListBase::OnScrollToMessage(WPARAM itemToSelect, LPARAM /*lParam*/)
3497 if (GetSelectedCount() != 0)
3498 return 0;
3499 SetItemState((int)itemToSelect, LVIS_SELECTED, LVIS_SELECTED);
3500 EnsureVisible((int)itemToSelect, FALSE);
3501 return 0;
3503 LRESULT CGitLogListBase::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
3506 ASSERT(m_pFindDialog != NULL);
3507 bool bFound = false;
3508 int i=0;
3510 if (m_pFindDialog->IsTerminating())
3512 // invalidate the handle identifying the dialog box.
3513 m_pFindDialog = NULL;
3514 return 0;
3517 if(m_pFindDialog->IsRef())
3519 CString str;
3520 str=m_pFindDialog->GetFindString();
3522 CGitHash hash;
3524 if(!str.IsEmpty())
3526 if (g_Git.GetHash(hash, str + _T("^{}"))) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
3527 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of ref \"") + str + _T("^{}\".")), _T("TortoiseGit"), MB_ICONERROR);
3530 if(!hash.IsEmpty())
3532 for (i = 0; i<m_arShownList.GetCount(); ++i)
3534 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3535 if(pLogEntry && pLogEntry->m_CommitHash == hash)
3537 bFound = true;
3538 break;
3545 if(m_pFindDialog->FindNext())
3547 //read data from dialog
3548 CString FindText = m_pFindDialog->GetFindString();
3549 bool bMatchCase = (m_pFindDialog->MatchCase() == TRUE);
3551 tr1::wregex pat;
3552 bool bRegex = ValidateRegexp(FindText, pat, bMatchCase);
3554 tr1::regex_constants::match_flag_type flags = tr1::regex_constants::match_not_null;
3557 for (i = this->m_nSearchIndex; i < m_arShownList.GetCount() && !bFound; ++i)
3559 GitRev* pLogEntry = (GitRev*)m_arShownList.SafeGetAt(i);
3561 CString str;
3562 str+=pLogEntry->m_CommitHash.ToString();
3563 str+=_T("\n");
3565 for (size_t j = 0; j < this->m_HashMap[pLogEntry->m_CommitHash].size(); ++j)
3567 str+=m_HashMap[pLogEntry->m_CommitHash][j];
3568 str+=_T("\n");
3571 str+=pLogEntry->GetAuthorEmail();
3572 str+=_T("\n");
3573 str+=pLogEntry->GetAuthorName();
3574 str+=_T("\n");
3575 str+=pLogEntry->GetBody();
3576 str+=_T("\n");
3577 str+=pLogEntry->GetCommitterEmail();
3578 str+=_T("\n");
3579 str+=pLogEntry->GetCommitterName();
3580 str+=_T("\n");
3581 str+=pLogEntry->GetSubject();
3582 str+=_T("\n");
3585 /*Because changed files list is loaded on demand when gui show,
3586 files will empty when files have not fetched.
3588 we can add it back by using one-way diff(with outnumber changed and rename detect.
3589 here just need changed filename list. one-way is much quicker.
3591 if(pLogEntry->m_IsFull)
3593 for (int i = 0; i < pLogEntry->GetFiles(this).GetCount(); ++i)
3595 str+=pLogEntry->GetFiles(this)[i].GetWinPath();
3596 str+=_T("\n");
3597 str+=pLogEntry->GetFiles(this)[i].GetGitOldPathString();
3598 str+=_T("\n");
3601 else
3603 if(!pLogEntry->m_IsSimpleListReady)
3604 pLogEntry->SafeGetSimpleList(&g_Git);
3606 for (size_t i = 0; i < pLogEntry->m_SimpleFileList.size(); ++i)
3608 str+=pLogEntry->m_SimpleFileList[i];
3609 str+=_T("\n");
3615 if (bRegex)
3617 if (regex_search(wstring(str), pat, flags))
3619 bFound = true;
3620 break;
3623 else
3625 if (bMatchCase)
3627 if (str.Find(FindText) >= 0)
3629 bFound = true;
3630 break;
3634 else
3636 CString msg = str;
3637 msg = msg.MakeLower();
3638 CString find = FindText.MakeLower();
3639 if (msg.Find(find) >= 0)
3641 bFound = TRUE;
3642 break;
3646 } // for (i = this->m_nSearchIndex; i<m_arShownList.GetItemCount()&&!bFound; ++i)
3648 } // if(m_pFindDialog->FindNext())
3649 //UpdateLogInfoLabel();
3651 if (bFound)
3653 this->m_nSearchIndex = i;
3654 EnsureVisible(i, FALSE);
3655 SetItemState(GetSelectionMark(), 0, LVIS_SELECTED);
3656 SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
3657 SetSelectionMark(i);
3658 //FillLogMessageCtrl();
3659 UpdateData(FALSE);
3660 ++m_nSearchIndex;
3661 if (m_nSearchIndex >= m_arShownList.GetCount())
3662 m_nSearchIndex = (int)m_arShownList.GetCount()-1;
3665 return 0;
3668 void CGitLogListBase::OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult)
3670 m_ColumnManager.OnColumnResized(pNMHDR,pResult);
3672 *pResult = FALSE;
3675 void CGitLogListBase::OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult)
3677 m_ColumnManager.OnColumnMoved(pNMHDR, pResult);
3679 Invalidate(FALSE);