1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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
23 #include "HintListCtrl.h"
25 #include "ProjectProperties.h"
28 #include "SplitterControl.h"
30 #include "MenuButton.h"
31 #include "LogDlgHelper.h"
32 #include "FilterEdit.h"
36 #include "GitLogCache.h"
38 #include "GitStatusListCtrl.h"
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
59 #define LOGFILTER_CASE 0x0200
61 #define LOGLIST_SHOWNOTHING 0x0000
62 #define LOGLIST_SHOWLOCALBRANCHES 0x0001
63 #define LOGLIST_SHOWREMOTEBRANCHES 0x0002
64 #define LOGLIST_SHOWTAGS 0x0004
65 #define LOGLIST_SHOWSTASH 0x0008
66 #define LOGLIST_SHOWBISECT 0x0010
67 #define LOGLIST_SHOWALLREFS 0xFFFF
69 //typedef void CALLBACK_PROCESS(void * data, int progress);
70 #define MSG_LOADED (WM_USER+110)
71 #define MSG_LOAD_PERCENTAGE (WM_USER+111)
72 #define MSG_REFLOG_CHANGED (WM_USER+112)
73 #define MSG_FETCHED_DIFF (WM_USER+113)
75 class SelectionHistory
77 #define HISTORYLENGTH 50
79 SelectionHistory(void)
82 lastselected
.reserve(HISTORYLENGTH
);
84 void Add(CGitHash
&hash
)
89 size_t size
= lastselected
.size();
91 // re-select last selected commit
92 if (size
> 0 && hash
== lastselected
[size
- 1])
95 if (location
!= size
- 1)
100 // go back and some commit was highlight
101 if (size
> 0 && location
!= size
- 1)
103 // Re-select current one, it may be a forked point.
104 if (hash
== lastselected
[location
])
106 // That is that discarding forward history when a forked entry is really coming.
107 // And user has the chance to Go Forward again in this situation.
108 // IOW, (hash != lastselected[location]) means user wants a forked history,
109 // and this change saves one step from old behavior.
112 // Discard forward history if any
113 while (lastselected
.size() - 1 > location
)
114 lastselected
.pop_back();
117 if (lastselected
.size() >= HISTORYLENGTH
)
118 lastselected
.erase(lastselected
.cbegin());
120 lastselected
.push_back(hash
);
121 location
= lastselected
.size() - 1;
123 BOOL
GoBack(CGitHash
& historyEntry
)
128 historyEntry
= lastselected
[--location
];
132 BOOL
GoForward(CGitHash
& historyEntry
)
134 if (location
>= lastselected
.size() - 1)
137 historyEntry
= lastselected
[++location
];
142 std::vector
<CGitHash
> lastselected
;
146 class CThreadSafePtrArray
: public CPtrArray
148 CComCriticalSection
*m_critSec
;
150 CThreadSafePtrArray(CComCriticalSection
*section
){ m_critSec
= section
;}
151 void * SafeGetAt(INT_PTR i
)
156 if( i
<0 || i
>=GetCount())
169 INT_PTR
SafeAdd(void *newElement
)
174 ret
= Add(newElement
);
191 class CGitLogListBase
: public CHintListCtrl
193 DECLARE_DYNAMIC(CGitLogListBase
)
197 virtual ~CGitLogListBase();
198 ProjectProperties m_ProjectProperties
;
200 CFilterData m_Filter
;
202 void UpdateProjectProperties()
204 m_ProjectProperties
.ReadProps();
206 if ((!m_ProjectProperties
.sUrl
.IsEmpty())||(!m_ProjectProperties
.sCheckRe
.IsEmpty()))
207 m_bShowBugtraqColumn
= true;
209 m_bShowBugtraqColumn
= false;
212 void ResetWcRev(bool refresh
= false)
215 m_wcRev
.GetSubject() = CString(MAKEINTRESOURCE(IDS_LOG_WORKINGDIRCHANGES
));
216 m_wcRev
.m_Mark
= _T('-');
217 m_wcRev
.GetBody() = CString(MAKEINTRESOURCE(IDS_LOG_FETCHINGSTATUS
));
218 m_wcRev
.m_CallDiffAsync
= DiffAsync
;
219 InterlockedExchange(&m_wcRev
.m_IsDiffFiles
, FALSE
);
220 if (refresh
&& m_bShowWC
)
221 m_arShownList
[0] = &m_wcRev
;
224 volatile LONG m_bNoDispUpdates
;
225 BOOL m_IsIDReplaceAction
;
227 void hideFromContextMenu(unsigned __int64 hideMask
, bool exclusivelyShow
);
228 BOOL m_IsRebaseReplaceGraph
;
229 BOOL m_bNoHightlightHead
;
231 void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct
);
233 BOOL m_bStrictStopped
;
234 BOOL m_bShowBugtraqColumn
;
237 unsigned __int64 m_ContextMenuMask
;
242 volatile LONG m_bThreadRunning
;
243 CLogCache m_LogCache
;
246 // don't forget to bump BLAME_COLUMN_VERSION in GitStatusListCtrlHelpers.cpp if you change columns
259 LOGLIST_COMMIT_EMAIL
,
263 LOGLIST_MESSAGE_MAX
=300,
264 LOGLIST_MESSAGE_MIN
=200,
266 GIT_LOG_GRAPH
= 1<< LOGLIST_GRAPH
,
267 GIT_LOG_REBASE
= 1<< LOGLIST_REBASE
,
268 GIT_LOG_ID
= 1<< LOGLIST_ID
,
269 GIT_LOG_HASH
= 1<< LOGLIST_HASH
,
270 GIT_LOG_ACTIONS
= 1<< LOGLIST_ACTION
,
271 GIT_LOG_MESSAGE
= 1<< LOGLIST_MESSAGE
,
272 GIT_LOG_AUTHOR
= 1<< LOGLIST_AUTHOR
,
273 GIT_LOG_DATE
= 1<< LOGLIST_DATE
,
274 GIT_LOG_EMAIL
= 1<< LOGLIST_EMAIL
,
275 GIT_LOG_COMMIT_NAME
= 1<< LOGLIST_COMMIT_NAME
,
276 GIT_LOG_COMMIT_EMAIL
= 1<< LOGLIST_COMMIT_EMAIL
,
277 GIT_LOG_COMMIT_DATE
= 1<< LOGLIST_COMMIT_DATE
,
278 GIT_LOGLIST_BUG
= 1<< LOGLIST_BUG
,
279 GIT_LOGLIST_SVNREV
= 1<< LOGLIST_SVNREV
,
284 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
285 ID_COMPARE
= 1, // compare revision with WC
287 ID_COMPARETWO
, // compare two revisions
291 ID_GNUDIFF1
, // compare with WC, unified
292 ID_GNUDIFF2
, // compare two revisions, unified
309 ID_COMPAREWITHPREVIOUS
,
325 ID_REFLOG_STASH_APPLY
,
327 ID_REBASE_TO_VERSION
,
335 ID_COPYCLIPBOARDMESSAGES
,
338 ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
,
353 FILTERSHOW_MERGEPOINTS
= 2,
354 FILTERSHOW_ANYCOMMIT
= 4,
355 FILTERSHOW_ALL
= FILTERSHOW_ANYCOMMIT
| FILTERSHOW_REFS
| FILTERSHOW_MERGEPOINTS
360 LOGACTIONS_REBASE_CURRENT
= 0x08000000,
361 LOGACTIONS_REBASE_PICK
= 0x04000000,
362 LOGACTIONS_REBASE_SQUASH
= 0x02000000,
363 LOGACTIONS_REBASE_EDIT
= 0x01000000,
364 LOGACTIONS_REBASE_DONE
= 0x00800000,
365 LOGACTIONS_REBASE_SKIP
= 0x00400000,
366 LOGACTIONS_REBASE_MASK
= 0x0FC00000,
367 LOGACTIONS_REBASE_MODE_MASK
= 0x07C00000,
369 inline unsigned __int64
GetContextMenuBit(int i
){ return ((unsigned __int64
)0x1)<<i
;}
370 static CString
GetRebaseActionName(int action
);
371 void InsertGitColumn();
372 void ResizeAllListCtrlCols();
373 void CopySelectionToClipBoard(int toCopy
= ID_COPY_ALL
);
374 void DiffSelectedRevWithPrevious();
375 bool IsSelectionContinuous();
377 int FillGitLog(CTGitPath
*path
, CString
*range
= NULL
, int infomask
= CGit::LOG_INFO_STAT
| CGit::LOG_INFO_FILESTATE
| CGit::LOG_INFO_SHOW_MERGEDFILE
);
378 int FillGitLog(std::set
<CGitHash
>& hashes
);
379 BOOL
IsMatchFilter(bool bRegex
, GitRev
*pRev
, std::tr1::wregex
&pat
);
380 bool ShouldShowFilter(GitRev
*pRev
, const std::map
<CGitHash
, std::set
<CGitHash
>> &commitChildren
);
381 void ShowGraphColumn(bool bShow
);
382 CString
GetTagInfo(GitRev
* pLogEntry
);
384 CFindDlg
*m_pFindDialog
;
385 static const UINT m_FindDialogMessage
;
388 static const UINT m_ScrollToMessage
;
389 static const UINT m_RebaseActionMessage
;
391 inline int ShownCountWithStopped() const { return (int)m_arShownList
.GetCount() + (m_bStrictStopped
? 1 : 0); }
392 int FetchLogAsync(void * data
=NULL
);
393 CThreadSafePtrArray m_arShownList
;
394 void Refresh(BOOL IsCleanFilter
=TRUE
);
395 void RecalculateShownList(CThreadSafePtrArray
* pShownlist
);
398 DWORD m_SelectedFilters
;
399 FilterShow m_ShowFilter
;
400 bool m_bFilterWithRegex
;
401 bool m_bFilterCaseSensitively
;
402 CLogDataVector m_logEntries
;
405 bool ValidateRegexp(LPCTSTR regexp_str
, std::tr1::wregex
& pat
, bool bMatchCase
= false );
406 CString m_sFilterText
;
413 CGitHash m_lastSelectedHash
;
414 SelectionHistory m_selectionHistory
;
415 CGitHash m_highlight
;
418 void GetTimeRange(CTime
&oldest
,CTime
&latest
);
419 virtual void GetParentHashes(GitRev
*pRev
, GIT_REV_LIST
&parentHash
);
420 virtual void ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
* menu
)=0;
425 if (g_Git
.GetMapHashToFriendName(m_HashMap
))
426 MessageBox(g_Git
.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR
);
428 m_CurrentBranch
=g_Git
.GetCurrentBranch();
430 if (g_Git
.GetHash(m_HeadHash
, _T("HEAD")))
432 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash. Quitting...")), _T("TortoiseGit"), MB_ICONERROR
);
436 m_wcRev
.m_ParentHash
.clear();
437 m_wcRev
.m_ParentHash
.push_back(m_HeadHash
);
440 FetchTrackingBranchList();
442 void SafeTerminateThread()
444 if (m_LoadingThread
!=NULL
&& InterlockedExchange(&m_bExitThread
, TRUE
) == FALSE
)
446 DWORD ret
= WAIT_TIMEOUT
;
447 for (int i
= 0; i
< 200 && m_bThreadRunning
; ++i
)
448 ret
=::WaitForSingleObject(m_LoadingThread
->m_hThread
, 100);
449 if (ret
== WAIT_TIMEOUT
&& m_bThreadRunning
)
450 ::TerminateThread(m_LoadingThread
, 0);
451 m_LoadingThread
= NULL
;
455 bool IsInWorkingThread()
457 return (AfxGetThread() == m_LoadingThread
);
460 void SetRange(const CString
& range
)
465 CString
GetRange() const { return m_sRange
; }
467 bool HasFilterText() const { return !m_sFilterText
.IsEmpty() && m_sFilterText
!= _T("!"); }
471 volatile LONG m_bExitThread
;
472 CWinThread
* m_LoadingThread
;
473 MAP_HASH_NAME m_HashMap
;
474 std::map
<CString
, std::pair
<CString
, CString
>> m_TrackingMap
;
477 CString m_ColumnRegKey
;
478 CComCriticalSection m_critSec_AsyncDiff
;
484 CString simplifiedName
;
491 DECLARE_MESSAGE_MAP()
492 afx_msg
void OnDestroy();
493 virtual afx_msg
void OnNMCustomdrawLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
494 virtual afx_msg
void OnLvnGetdispinfoLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
495 afx_msg LRESULT
OnFindDialogMessage(WPARAM wParam
, LPARAM lParam
);
496 afx_msg LRESULT
OnScrollToMessage(WPARAM wParam
, LPARAM lParam
);
497 afx_msg
int OnCreate(LPCREATESTRUCT lpCreateStruct
);
498 afx_msg
void OnContextMenu(CWnd
* pWnd
, CPoint point
);
499 afx_msg LRESULT
OnLoad(WPARAM wParam
, LPARAM lParam
);
500 afx_msg
void OnHdnBegintrack(NMHDR
*pNMHDR
, LRESULT
*pResult
);
501 afx_msg
void OnHdnItemchanging(NMHDR
*pNMHDR
, LRESULT
*pResult
);
502 afx_msg
void OnColumnResized(NMHDR
*pNMHDR
, LRESULT
*pResult
);
503 afx_msg
void OnColumnMoved(NMHDR
*pNMHDR
, LRESULT
*pResult
);
504 void OnNMDblclkLoglist(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
);
505 afx_msg
void OnLvnOdfinditemLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
506 void PreSubclassWindow();
507 virtual BOOL
PreTranslateMessage(MSG
* pMsg
);
508 static UINT
LogThreadEntry(LPVOID pVoid
);
510 bool IsOnStash(int index
);
511 bool IsStash(const GitRev
* pSelLogEntry
);
512 void FetchRemoteList();
513 void FetchTrackingBranchList();
514 void FetchLastLogInfo();
515 void FetchFullLogInfo(CString
&from
, CString
&to
);
517 virtual afx_msg BOOL
OnToolTipText(UINT id
, NMHDR
* pNMHDR
, LRESULT
* pResult
);
518 virtual INT_PTR
OnToolHitTest(CPoint point
, TOOLINFO
* pTI
) const;
519 CString
GetToolTipText(int nItem
, int nSubItem
);
521 void FillBackGround(HDC hdc
, DWORD_PTR Index
, CRect
&rect
);
522 void DrawTagBranchMessage(HDC hdc
, CRect
&rect
, INT_PTR index
, std::vector
<REFLABEL
> &refList
);
523 void DrawTagBranch(HDC hdc
, CDC
&W_Dc
, HTHEME hTheme
, CRect
&rect
, CRect
&rt
, LVITEM
&rItem
, GitRev
* data
, std::vector
<REFLABEL
> &refList
);
524 void DrawGraph(HDC
,CRect
&rect
,INT_PTR index
);
526 void paintGraphLane(HDC hdc
,int laneHeight
, int type
, int x1
, int x2
,
527 const COLORREF
& col
,const COLORREF
& activeColor
, int top
) ;
528 void DrawLine(HDC hdc
, int x1
, int y1
, int x2
, int y2
){::MoveToEx(hdc
,x1
,y1
,NULL
);::LineTo(hdc
,x2
,y2
);}
530 * Save column widths to the registry
532 void SaveColumnWidths(); // save col widths to the registry
534 BOOL
IsEntryInDateRange(int i
);
538 std::vector
<GitRev
*> m_AsynDiffList
;
539 CComCriticalSection m_AsynDiffListLock
;
540 HANDLE m_AsyncDiffEvent
;
541 volatile LONG m_AsyncThreadExit
;
542 CWinThread
* m_DiffingThread
;
544 static int DiffAsync(GitRev
*rev
, void *data
)
546 ULONGLONG offset
=((CGitLogListBase
*)data
)->m_LogCache
.GetOffset(rev
->m_CommitHash
);
549 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Lock();
550 ((CGitLogListBase
*)data
)->m_AsynDiffList
.push_back(rev
);
551 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Unlock();
552 ::SetEvent(((CGitLogListBase
*)data
)->m_AsyncDiffEvent
);
556 if(((CGitLogListBase
*)data
)->m_LogCache
.LoadOneItem(*rev
,offset
))
558 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Lock();
559 ((CGitLogListBase
*)data
)->m_AsynDiffList
.push_back(rev
);
560 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Unlock();
561 ::SetEvent(((CGitLogListBase
*)data
)->m_AsyncDiffEvent
);
563 InterlockedExchange(&rev
->m_IsDiffFiles
, TRUE
);
564 if(rev
->m_IsDiffFiles
&& rev
->m_IsCommitParsed
)
565 InterlockedExchange(&rev
->m_IsFull
, TRUE
);
570 static UINT
AsyncThread(LPVOID data
)
572 return ((CGitLogListBase
*)data
)->AsyncDiffThread();
575 int AsyncDiffThread();
576 bool m_AsyncThreadExited
;
578 int SafeGetAction(GitRev
*rev
, int **ptr
= nullptr)
582 int *p
= &rev
->GetAction(this);
587 catch (const char* msg
)
589 MessageBox(_T("Could not get action of commit ") + rev
->m_CommitHash
.ToString() + _T(".\nlibgit reports:\n") + CString(msg
), _T("TortoiseGit"), MB_ICONERROR
);
595 void SafeTerminateAsyncDiffThread()
597 if(m_DiffingThread
!=NULL
&& m_AsyncThreadExit
!= TRUE
)
599 m_AsyncThreadExit
= TRUE
;
600 ::SetEvent(m_AsyncDiffEvent
);
601 DWORD ret
= WAIT_TIMEOUT
;
602 // do not block here, but process messages and ask until the thread ends
603 while (ret
== WAIT_TIMEOUT
&& !m_AsyncThreadExited
)
606 if (::PeekMessage(&msg
, NULL
, 0,0, PM_NOREMOVE
))
607 AfxGetThread()->PumpMessage(); // process messages, so that GetTopIndex and so on in the thread work
608 ret
= ::WaitForSingleObject(m_DiffingThread
->m_hThread
, 100);
610 m_DiffingThread
= NULL
;
615 CComCriticalSection m_critSec
;
617 HICON m_hModifiedIcon
;
618 HICON m_hReplacedIcon
;
620 HICON m_hDeletedIcon
;
625 HFONT m_boldItalicsFont
;
627 CRegDWORD m_regMaxBugIDColWidth
;
630 CStoreSelection
* m_pStoreSelection
;
634 CString m_CurrentBranch
;
637 COLORREF m_LineColors
[Lanes::COLORS_NUM
];
640 DWORD m_DateFormat
; // DATE_SHORTDATE or DATE_LONGDATE
641 bool m_bRelativeTimes
; // Show relative times
643 CString m_SingleRemote
;
644 bool m_bTagsBranchesOnRightSide
;
645 bool m_bSymbolizeRefNames
;
646 bool m_bIncludeBoundaryCommits
;
648 ColumnManager m_ColumnManager
;
649 DWORD m_dwDefaultColumns
;
651 typedef HRESULT (WINAPI
*FNDRAWTHEMETEXTEX
) (HTHEME
, HDC
, int, int, LPCWSTR
, int, DWORD
, LPRECT
, const DTTOPTS
*);
653 FNDRAWTHEMETEXTEX pfnDrawThemeTextEx
;
654 TCHAR m_wszTip
[8192];