Fixed issue #2170: Please add interface to "git svn log -v"
[TortoiseGit.git] / src / TortoiseProc / GitLogListBase.h
blobc0c7b77354268dcea01a6b0784cdd6eaccf9f814
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // GitLogList.cpp : implementation file
21 #pragma once
23 #include "HintListCtrl.h"
24 #include "Git.h"
25 #include "ProjectProperties.h"
26 #include "TGitPath.h"
27 #include "registry.h"
28 #include "SplitterControl.h"
29 #include "Colors.h"
30 #include "MenuButton.h"
31 #include "LogDlgHelper.h"
32 #include "FilterEdit.h"
33 #include "GitRev.h"
34 #include "Tooltip.h"
35 #include "lanes.h"
36 #include "GitLogCache.h"
37 #include <regex>
38 #include "GitStatusListCtrl.h"
39 #include "FindDlg.h"
41 // CGitLogList
42 #define ICONITEMBORDER 5
44 #define GITLOG_START 0
45 #define GITLOG_START_ALL 1
46 #define GITLOG_END 100
48 #define LOGFILTER_ALL 0xFFFF
49 #define LOGFILTER_TOGGLE 0x8000
50 #define LOGFILTER_MESSAGES 0x0001
51 #define LOGFILTER_PATHS 0x0002
52 #define LOGFILTER_AUTHORS 0x0004
53 #define LOGFILTER_REVS 0x0008
54 #define LOGFILTER_REGEX 0x0010
55 #define LOGFILTER_BUGID 0x0020
56 #define LOGFILTER_SUBJECT 0x0040
57 #define LOGFILTER_REFNAME 0x0080
58 #define LOGFILTER_EMAILS 0x0100
60 #define LOGLIST_SHOWNOTHING 0x0000
61 #define LOGLIST_SHOWLOCALBRANCHES 0x0001
62 #define LOGLIST_SHOWREMOTEBRANCHES 0x0002
63 #define LOGLIST_SHOWTAGS 0x0004
64 #define LOGLIST_SHOWSTASH 0x0008
65 #define LOGLIST_SHOWBISECT 0x0010
66 #define LOGLIST_SHOWALLREFS 0xFFFF
68 //typedef void CALLBACK_PROCESS(void * data, int progress);
69 #define MSG_LOADED (WM_USER+110)
70 #define MSG_LOAD_PERCENTAGE (WM_USER+111)
71 #define MSG_REFLOG_CHANGED (WM_USER+112)
72 #define MSG_FETCHED_DIFF (WM_USER+113)
74 class CThreadSafePtrArray: public CPtrArray
76 CComCriticalSection *m_critSec;
77 public:
78 CThreadSafePtrArray(CComCriticalSection *section){ m_critSec = section ;}
79 void * SafeGetAt(INT_PTR i)
81 if(m_critSec)
82 m_critSec->Lock();
84 if( i<0 || i>=GetCount())
86 if(m_critSec)
87 m_critSec->Unlock();
89 return NULL;
92 if(m_critSec)
93 m_critSec->Unlock();
95 return GetAt(i);
97 INT_PTR SafeAdd(void *newElement)
99 INT_PTR ret;
100 if(m_critSec)
101 m_critSec->Lock();
102 ret = Add(newElement);
103 if(m_critSec)
104 m_critSec->Unlock();
105 return ret;
108 void SafeRemoveAll()
110 if(m_critSec)
111 m_critSec->Lock();
112 RemoveAll();
113 if(m_critSec)
114 m_critSec->Unlock();
119 class CGitLogListBase : public CHintListCtrl
121 DECLARE_DYNAMIC(CGitLogListBase)
123 public:
124 CGitLogListBase();
125 virtual ~CGitLogListBase();
126 ProjectProperties m_ProjectProperties;
128 CFilterData m_Filter;
130 void UpdateProjectProperties()
132 m_ProjectProperties.ReadProps();
134 if ((!m_ProjectProperties.sUrl.IsEmpty())||(!m_ProjectProperties.sCheckRe.IsEmpty()))
135 m_bShowBugtraqColumn = true;
136 else
137 m_bShowBugtraqColumn = false;
140 void ResetWcRev(bool refresh = false)
142 m_wcRev.Clear();
143 m_wcRev.GetSubject() = CString(MAKEINTRESOURCE(IDS_LOG_WORKINGDIRCHANGES));
144 m_wcRev.m_Mark = _T('-');
145 m_wcRev.GetBody() = CString(MAKEINTRESOURCE(IDS_LOG_FETCHINGSTATUS));
146 m_wcRev.m_CallDiffAsync = DiffAsync;
147 InterlockedExchange(&m_wcRev.m_IsDiffFiles, FALSE);
148 if (refresh && m_bShowWC)
149 m_arShownList[0] = &m_wcRev;
152 volatile LONG m_bNoDispUpdates;
153 BOOL m_IsIDReplaceAction;
154 BOOL m_IsOldFirst;
155 void hideFromContextMenu(unsigned __int64 hideMask, bool exclusivelyShow);
156 BOOL m_IsRebaseReplaceGraph;
157 BOOL m_bNoHightlightHead;
159 void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
161 BOOL m_bStrictStopped;
162 BOOL m_bShowBugtraqColumn;
163 BOOL m_bSearchIndex;
164 BOOL m_bCancelled;
165 unsigned __int64 m_ContextMenuMask;
167 bool m_hasWC;
168 bool m_bShowWC;
169 GitRev m_wcRev;
170 volatile LONG m_bThreadRunning;
171 CLogCache m_LogCache;
173 CString m_sRange;
174 // don't forget to bump BLAME_COLUMN_VERSION in GitStatusListCtrlHelpers.cpp if you change columns
175 enum
177 LOGLIST_GRAPH,
178 LOGLIST_REBASE,
179 LOGLIST_ID,
180 LOGLIST_HASH,
181 LOGLIST_ACTION,
182 LOGLIST_MESSAGE,
183 LOGLIST_AUTHOR,
184 LOGLIST_DATE,
185 LOGLIST_EMAIL,
186 LOGLIST_COMMIT_NAME,
187 LOGLIST_COMMIT_EMAIL,
188 LOGLIST_COMMIT_DATE,
189 LOGLIST_BUG,
190 LOGLIST_SVNREV,
191 LOGLIST_MESSAGE_MAX=300,
192 LOGLIST_MESSAGE_MIN=200,
194 GIT_LOG_GRAPH = 1<< LOGLIST_GRAPH,
195 GIT_LOG_REBASE = 1<< LOGLIST_REBASE,
196 GIT_LOG_ID = 1<< LOGLIST_ID,
197 GIT_LOG_HASH = 1<< LOGLIST_HASH,
198 GIT_LOG_ACTIONS = 1<< LOGLIST_ACTION,
199 GIT_LOG_MESSAGE = 1<< LOGLIST_MESSAGE,
200 GIT_LOG_AUTHOR = 1<< LOGLIST_AUTHOR,
201 GIT_LOG_DATE = 1<< LOGLIST_DATE,
202 GIT_LOG_EMAIL = 1<< LOGLIST_EMAIL,
203 GIT_LOG_COMMIT_NAME = 1<< LOGLIST_COMMIT_NAME,
204 GIT_LOG_COMMIT_EMAIL= 1<< LOGLIST_COMMIT_EMAIL,
205 GIT_LOG_COMMIT_DATE = 1<< LOGLIST_COMMIT_DATE,
206 GIT_LOGLIST_BUG = 1<< LOGLIST_BUG,
207 GIT_LOGLIST_SVNREV = 1<< LOGLIST_SVNREV,
210 enum
212 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
213 ID_COMPARE = 1, // compare revision with WC
214 ID_SAVEAS,
215 ID_COMPARETWO, // compare two revisions
216 ID_COPY,
217 ID_REVERTREV,
218 ID_MERGEREV,
219 ID_GNUDIFF1, // compare with WC, unified
220 ID_GNUDIFF2, // compare two revisions, unified
221 ID_FINDENTRY,
222 ID_OPEN,
223 ID_BLAME,
224 ID_REPOBROWSE,
225 ID_LOG,
226 ID_EDITNOTE,
227 ID_DIFF,
228 ID_OPENWITH,
229 ID_COPYCLIPBOARD,
230 ID_COPYHASH,
231 ID_REVERTTOREV,
232 ID_BLAMECOMPARE,
233 ID_BLAMEDIFF,
234 ID_VIEWREV,
235 ID_VIEWPATHREV,
236 ID_EXPORT,
237 ID_COMPAREWITHPREVIOUS,
238 ID_BLAMEPREVIOUS,
239 ID_CHERRY_PICK,
240 ID_CREATE_BRANCH,
241 ID_CREATE_TAG,
242 ID_SWITCHTOREV,
243 ID_SWITCHBRANCH,
244 ID_RESET,
245 ID_REBASE_PICK,
246 ID_REBASE_EDIT,
247 ID_REBASE_SQUASH,
248 ID_REBASE_SKIP,
249 ID_COMBINE_COMMIT,
250 ID_STASH_SAVE,
251 ID_STASH_LIST,
252 ID_STASH_POP,
253 ID_REFLOG_STASH_APPLY,
254 ID_REFLOG_DEL,
255 ID_REBASE_TO_VERSION,
256 ID_CREATE_PATCH,
257 ID_DELETE,
258 ID_COMMIT,
259 ID_PUSH,
260 ID_PULL,
261 ID_FETCH,
262 ID_SHOWBRANCHES,
263 ID_COPYCLIPBOARDMESSAGES,
264 ID_BISECTSTART,
265 ID_LOG_VIEWRANGE,
266 ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE,
267 ID_MERGE_ABORT,
269 enum
271 ID_COPY_ALL,
272 ID_COPY_MESSAGE,
273 ID_COPY_SUBJECT,
274 ID_COPY_HASH,
276 enum FilterShow
278 FILTERSHOW_REFS = 1,
279 FILTERSHOW_MERGEPOINTS = 2,
280 FILTERSHOW_ANYCOMMIT = 4,
281 FILTERSHOW_ALL = FILTERSHOW_ANYCOMMIT | FILTERSHOW_REFS | FILTERSHOW_MERGEPOINTS
283 enum : unsigned int
285 // For Rebase only
286 LOGACTIONS_REBASE_CURRENT = 0x08000000,
287 LOGACTIONS_REBASE_PICK = 0x04000000,
288 LOGACTIONS_REBASE_SQUASH = 0x02000000,
289 LOGACTIONS_REBASE_EDIT = 0x01000000,
290 LOGACTIONS_REBASE_DONE = 0x00800000,
291 LOGACTIONS_REBASE_SKIP = 0x00400000,
292 LOGACTIONS_REBASE_MASK = 0x0FC00000,
293 LOGACTIONS_REBASE_MODE_MASK = 0x07C00000,
295 inline unsigned __int64 GetContextMenuBit(int i){ return ((unsigned __int64 )0x1)<<i ;}
296 static CString GetRebaseActionName(int action);
297 void InsertGitColumn();
298 void ResizeAllListCtrlCols();
299 void CopySelectionToClipBoard(int toCopy = ID_COPY_ALL);
300 void DiffSelectedRevWithPrevious();
301 bool IsSelectionContinuous();
302 int BeginFetchLog();
303 int FillGitLog(CTGitPath *path, CString *range = NULL, int infomask = CGit::LOG_INFO_STAT| CGit::LOG_INFO_FILESTATE | CGit::LOG_INFO_SHOW_MERGEDFILE);
304 int FillGitLog(std::set<CGitHash>& hashes);
305 BOOL IsMatchFilter(bool bRegex, GitRev *pRev, std::tr1::wregex &pat);
306 bool ShouldShowFilter(GitRev *pRev, const std::map<CGitHash, std::set<CGitHash>> &commitChildren);
307 void ShowGraphColumn(bool bShow);
308 CString GetTagInfo(GitRev* pLogEntry);
310 CFindDlg *m_pFindDialog;
311 static const UINT m_FindDialogMessage;
312 void OnFind();
314 static const UINT m_ScrollToMessage;
315 static const UINT m_RebaseActionMessage;
317 inline int ShownCountWithStopped() const { return (int)m_arShownList.GetCount() + (m_bStrictStopped ? 1 : 0); }
318 int FetchLogAsync(void * data=NULL);
319 CThreadSafePtrArray m_arShownList;
320 void Refresh(BOOL IsCleanFilter=TRUE);
321 void RecalculateShownList(CThreadSafePtrArray * pShownlist);
322 void Clear();
324 DWORD m_SelectedFilters;
325 FilterShow m_ShowFilter;
326 bool m_bFilterWithRegex;
327 CLogDataVector m_logEntries;
328 void RemoveFilter();
329 void StartFilter();
330 bool ValidateRegexp(LPCTSTR regexp_str, std::tr1::wregex& pat, bool bMatchCase = false );
331 CString m_sFilterText;
333 __time64_t m_From;
334 __time64_t m_To;
336 CTGitPath m_Path;
337 int m_ShowMask;
338 CGitHash m_lastSelectedHash;
339 int m_ShowRefMask;
341 void GetTimeRange(CTime &oldest,CTime &latest);
342 virtual void GetParentHashes(GitRev *pRev, GIT_REV_LIST &parentHash);
343 virtual void ContextMenuAction(int cmd,int FirstSelect, int LastSelect, CMenu * menu)=0;
344 void ReloadHashMap()
346 m_HashMap.clear();
348 if (g_Git.GetMapHashToFriendName(m_HashMap))
349 MessageBox(g_Git.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR);
351 m_CurrentBranch=g_Git.GetCurrentBranch();
353 if (g_Git.GetHash(m_HeadHash, _T("HEAD")))
355 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash. Quitting...")), _T("TortoiseGit"), MB_ICONERROR);
356 ExitProcess(1);
359 m_wcRev.m_ParentHash.clear();
360 m_wcRev.m_ParentHash.push_back(m_HeadHash);
362 FetchRemoteList();
363 FetchTrackingBranchList();
365 void SafeTerminateThread()
367 if (m_LoadingThread!=NULL && InterlockedExchange(&m_bExitThread, TRUE) == FALSE)
369 DWORD ret = WAIT_TIMEOUT;
370 for (int i = 0; i < 200 && m_bThreadRunning; ++i)
371 ret =::WaitForSingleObject(m_LoadingThread->m_hThread, 100);
372 if (ret == WAIT_TIMEOUT && m_bThreadRunning)
373 ::TerminateThread(m_LoadingThread, 0);
374 m_LoadingThread = NULL;
378 bool IsInWorkingThread()
380 return (AfxGetThread() == m_LoadingThread);
383 void SetRange(const CString& range)
385 m_sRange = range;
388 CString GetRange() const { return m_sRange; }
390 bool HasFilterText() const { return !m_sFilterText.IsEmpty() && m_sFilterText != _T("!"); }
392 int m_nSearchIndex;
394 volatile LONG m_bExitThread;
395 CWinThread* m_LoadingThread;
396 MAP_HASH_NAME m_HashMap;
397 std::map<CString, std::pair<CString, CString>> m_TrackingMap;
399 public:
400 CString m_ColumnRegKey;
401 CComCriticalSection m_critSec_AsyncDiff;
403 protected:
404 typedef struct {
405 CString name;
406 COLORREF color;
407 CString simplifiedName;
408 bool singleRemote;
409 bool hasTracking;
410 bool sameName;
411 bool annotatedTag;
412 } REFLABEL;
414 DECLARE_MESSAGE_MAP()
415 afx_msg void OnDestroy();
416 virtual afx_msg void OnNMCustomdrawLoglist(NMHDR *pNMHDR, LRESULT *pResult);
417 virtual afx_msg void OnLvnGetdispinfoLoglist(NMHDR *pNMHDR, LRESULT *pResult);
418 afx_msg LRESULT OnFindDialogMessage(WPARAM wParam, LPARAM lParam);
419 afx_msg LRESULT OnScrollToMessage(WPARAM wParam, LPARAM lParam);
420 afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
421 afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
422 afx_msg LRESULT OnLoad(WPARAM wParam, LPARAM lParam);
423 afx_msg void OnHdnBegintrack(NMHDR *pNMHDR, LRESULT *pResult);
424 afx_msg void OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult);
425 afx_msg void OnColumnResized(NMHDR *pNMHDR, LRESULT *pResult);
426 afx_msg void OnColumnMoved(NMHDR *pNMHDR, LRESULT *pResult);
427 void OnNMDblclkLoglist(NMHDR * /*pNMHDR*/, LRESULT *pResult);
428 afx_msg void OnLvnOdfinditemLoglist(NMHDR *pNMHDR, LRESULT *pResult);
429 void PreSubclassWindow();
430 virtual BOOL PreTranslateMessage(MSG* pMsg);
431 static UINT LogThreadEntry(LPVOID pVoid);
432 UINT LogThread();
433 bool IsOnStash(int index);
434 bool IsStash(const GitRev * pSelLogEntry);
435 void FetchRemoteList();
436 void FetchTrackingBranchList();
437 void FetchLastLogInfo();
438 void FetchFullLogInfo(CString &from, CString &to);
439 void FillBackGround(HDC hdc, DWORD_PTR Index, CRect &rect);
440 void DrawTagBranchMessage(HDC hdc, CRect &rect, INT_PTR index, std::vector<REFLABEL> &refList);
441 void DrawTagBranch(HDC hdc, CDC &W_Dc, HTHEME hTheme, CRect &rect, CRect &rt, LVITEM &rItem, GitRev* data, std::vector<REFLABEL> &refList);
442 void DrawGraph(HDC,CRect &rect,INT_PTR index);
444 void paintGraphLane(HDC hdc,int laneHeight, int type, int x1, int x2,
445 const COLORREF& col,const COLORREF& activeColor, int top) ;
446 void DrawLine(HDC hdc, int x1, int y1, int x2, int y2){::MoveToEx(hdc,x1,y1,NULL);::LineTo(hdc,x2,y2);}
448 * Save column widths to the registry
450 void SaveColumnWidths(); // save col widths to the registry
452 BOOL IsEntryInDateRange(int i);
454 int GetHeadIndex();
456 std::vector<GitRev*> m_AsynDiffList;
457 CComCriticalSection m_AsynDiffListLock;
458 HANDLE m_AsyncDiffEvent;
459 volatile LONG m_AsyncThreadExit;
460 CWinThread* m_DiffingThread;
462 static int DiffAsync(GitRev *rev, void *data)
464 ULONGLONG offset=((CGitLogListBase*)data)->m_LogCache.GetOffset(rev->m_CommitHash);
465 if(!offset)
467 ((CGitLogListBase*)data)->m_AsynDiffListLock.Lock();
468 ((CGitLogListBase*)data)->m_AsynDiffList.push_back(rev);
469 ((CGitLogListBase*)data)->m_AsynDiffListLock.Unlock();
470 ::SetEvent(((CGitLogListBase*)data)->m_AsyncDiffEvent);
472 else
474 if(((CGitLogListBase*)data)->m_LogCache.LoadOneItem(*rev,offset))
476 ((CGitLogListBase*)data)->m_AsynDiffListLock.Lock();
477 ((CGitLogListBase*)data)->m_AsynDiffList.push_back(rev);
478 ((CGitLogListBase*)data)->m_AsynDiffListLock.Unlock();
479 ::SetEvent(((CGitLogListBase*)data)->m_AsyncDiffEvent);
481 InterlockedExchange(&rev->m_IsDiffFiles, TRUE);
482 if(rev->m_IsDiffFiles && rev->m_IsCommitParsed)
483 InterlockedExchange(&rev->m_IsFull, TRUE);
485 return 0;
488 static UINT AsyncThread(LPVOID data)
490 return ((CGitLogListBase*)data)->AsyncDiffThread();
493 int AsyncDiffThread();
494 bool m_AsyncThreadExited;
496 int SafeGetAction(GitRev *rev, int **ptr = nullptr)
500 int *p = &rev->GetAction(this);
501 if (ptr)
502 *ptr = p;
503 return *p;
505 catch (const char* msg)
507 MessageBox(_T("Could not get action of commit ") + rev->m_CommitHash.ToString() + _T(".\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
508 return 0;
512 public:
513 void SafeTerminateAsyncDiffThread()
515 if(m_DiffingThread!=NULL && m_AsyncThreadExit != TRUE)
517 m_AsyncThreadExit = TRUE;
518 ::SetEvent(m_AsyncDiffEvent);
519 DWORD ret = WAIT_TIMEOUT;
520 // do not block here, but process messages and ask until the thread ends
521 while (ret == WAIT_TIMEOUT && !m_AsyncThreadExited)
523 MSG msg;
524 if (::PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE))
525 AfxGetThread()->PumpMessage(); // process messages, so that GetTopIndex and so on in the thread work
526 ret = ::WaitForSingleObject(m_DiffingThread->m_hThread, 100);
528 m_DiffingThread = NULL;
532 protected:
533 CComCriticalSection m_critSec;
535 HICON m_hModifiedIcon;
536 HICON m_hReplacedIcon;
537 HICON m_hAddedIcon;
538 HICON m_hDeletedIcon;
539 HICON m_hFetchIcon;
541 HFONT m_boldFont;
543 CRegDWORD m_regMaxBugIDColWidth;
545 void *m_ProcData;
546 CStoreSelection* m_pStoreSelection;
548 CColors m_Colors;
550 CString m_CurrentBranch;
551 CGitHash m_HeadHash;
553 COLORREF m_LineColors[Lanes::COLORS_NUM];
554 DWORD m_LineWidth;
555 DWORD m_NodeSize;
556 DWORD m_DateFormat; // DATE_SHORTDATE or DATE_LONGDATE
557 bool m_bRelativeTimes; // Show relative times
558 GIT_LOG m_DllGitLog;
559 CString m_SingleRemote;
560 bool m_bTagsBranchesOnRightSide;
561 bool m_bSymbolizeRefNames;
562 bool m_bIncludeBoundaryCommits;
564 ColumnManager m_ColumnManager;
565 DWORD m_dwDefaultColumns;