1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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"
29 #include "LogDlgHelper.h"
30 #include "GitRevLoglist.h"
32 #include "GitLogCache.h"
34 #include "GitStatusListCtrl.h"
38 #define ICONITEMBORDER 5
40 #define GITLOG_START 0
41 #define GITLOG_START_ALL 1
42 #define GITLOG_END 100
44 #define LOGFILTER_ALL 0xFFFF
45 #define LOGFILTER_TOGGLE 0x8000
46 #define LOGFILTER_MESSAGES 0x0001
47 #define LOGFILTER_PATHS 0x0002
48 #define LOGFILTER_AUTHORS 0x0004
49 #define LOGFILTER_REVS 0x0008
50 #define LOGFILTER_REGEX 0x0010
51 #define LOGFILTER_BUGID 0x0020
52 #define LOGFILTER_SUBJECT 0x0040
53 #define LOGFILTER_REFNAME 0x0080
54 #define LOGFILTER_EMAILS 0x0100
55 #define LOGFILTER_NOTES 0x0200
56 #define LOGFILTER_ANNOTATEDTAG 0x0400
57 #define LOGFILTER_CASE 0x0800
59 #define LOGLIST_SHOWNOTHING 0x0000
60 #define LOGLIST_SHOWLOCALBRANCHES 0x0001
61 #define LOGLIST_SHOWREMOTEBRANCHES 0x0002
62 #define LOGLIST_SHOWTAGS 0x0004
63 #define LOGLIST_SHOWSTASH 0x0008
64 #define LOGLIST_SHOWBISECT 0x0010
65 #define LOGLIST_SHOWALLREFS 0xFFFF
67 //typedef void CALLBACK_PROCESS(void * data, int progress);
68 #define MSG_LOADED (WM_USER+110)
69 #define MSG_LOAD_PERCENTAGE (WM_USER+111)
70 #define MSG_REFLOG_CHANGED (WM_USER+112)
71 #define MSG_FETCHED_DIFF (WM_USER+113)
73 class SelectionHistory
75 #define HISTORYLENGTH 50
77 SelectionHistory(void)
80 lastselected
.reserve(HISTORYLENGTH
);
82 void Add(CGitHash
&hash
)
87 size_t size
= lastselected
.size();
89 // re-select last selected commit
90 if (size
> 0 && hash
== lastselected
[size
- 1])
93 if (location
!= size
- 1)
98 // go back and some commit was highlight
99 if (size
> 0 && location
!= size
- 1)
101 // Re-select current one, it may be a forked point.
102 if (hash
== lastselected
[location
])
104 // That is that discarding forward history when a forked entry is really coming.
105 // And user has the chance to Go Forward again in this situation.
106 // IOW, (hash != lastselected[location]) means user wants a forked history,
107 // and this change saves one step from old behavior.
110 // Discard forward history if any
111 while (lastselected
.size() - 1 > location
)
112 lastselected
.pop_back();
115 if (lastselected
.size() >= HISTORYLENGTH
)
116 lastselected
.erase(lastselected
.cbegin());
118 lastselected
.push_back(hash
);
119 location
= lastselected
.size() - 1;
121 BOOL
GoBack(CGitHash
& historyEntry
)
126 historyEntry
= lastselected
[--location
];
130 BOOL
GoForward(CGitHash
& historyEntry
)
132 if (location
>= lastselected
.size() - 1)
135 historyEntry
= lastselected
[++location
];
140 std::vector
<CGitHash
> lastselected
;
144 class CThreadSafePtrArray
: public std::vector
<GitRevLoglist
*>
146 CComCriticalSection
*m_critSec
;
148 CThreadSafePtrArray(CComCriticalSection
*section
){ m_critSec
= section
;}
149 GitRevLoglist
* SafeGetAt(size_t i
)
166 void SafeAdd(GitRevLoglist
* newElement
)
170 push_back(newElement
);
175 void SafeRemoveAt(size_t i
)
202 class CGitLogListBase
: public CHintListCtrl
204 DECLARE_DYNAMIC(CGitLogListBase
)
208 virtual ~CGitLogListBase();
209 ProjectProperties m_ProjectProperties
;
211 void UpdateProjectProperties()
213 m_ProjectProperties
.ReadProps();
215 if ((!m_ProjectProperties
.sUrl
.IsEmpty())||(!m_ProjectProperties
.sCheckRe
.IsEmpty()))
216 m_bShowBugtraqColumn
= true;
218 m_bShowBugtraqColumn
= false;
221 void ResetWcRev(bool refresh
= false)
224 m_wcRev
.GetSubject().LoadString(IDS_LOG_WORKINGDIRCHANGES
);
225 m_wcRev
.m_Mark
= _T('-');
226 m_wcRev
.GetBody().LoadString(IDS_LOG_FETCHINGSTATUS
);
227 m_wcRev
.m_CallDiffAsync
= DiffAsync
;
228 InterlockedExchange(&m_wcRev
.m_IsDiffFiles
, FALSE
);
229 if (refresh
&& m_bShowWC
)
230 m_arShownList
[0] = &m_wcRev
;
233 volatile LONG m_bNoDispUpdates
;
234 BOOL m_IsIDReplaceAction
;
236 void hideFromContextMenu(unsigned __int64 hideMask
, bool exclusivelyShow
);
237 BOOL m_IsRebaseReplaceGraph
;
238 BOOL m_bNoHightlightHead
;
240 void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct
);
242 BOOL m_bStrictStopped
;
243 BOOL m_bShowBugtraqColumn
;
246 bool m_bIsCherryPick
;
247 unsigned __int64 m_ContextMenuMask
;
251 GitRevLoglist m_wcRev
;
252 volatile LONG m_bThreadRunning
;
253 CLogCache m_LogCache
;
256 // don't forget to bump BLAME_COLUMN_VERSION in GitStatusListCtrlHelpers.cpp if you change columns
269 LOGLIST_COMMIT_EMAIL
,
273 LOGLIST_MESSAGE_MAX
=300,
274 LOGLIST_MESSAGE_MIN
=200,
276 GIT_LOG_GRAPH
= 1<< LOGLIST_GRAPH
,
277 GIT_LOG_REBASE
= 1<< LOGLIST_REBASE
,
278 GIT_LOG_ID
= 1<< LOGLIST_ID
,
279 GIT_LOG_HASH
= 1<< LOGLIST_HASH
,
280 GIT_LOG_ACTIONS
= 1<< LOGLIST_ACTION
,
281 GIT_LOG_MESSAGE
= 1<< LOGLIST_MESSAGE
,
282 GIT_LOG_AUTHOR
= 1<< LOGLIST_AUTHOR
,
283 GIT_LOG_DATE
= 1<< LOGLIST_DATE
,
284 GIT_LOG_EMAIL
= 1<< LOGLIST_EMAIL
,
285 GIT_LOG_COMMIT_NAME
= 1<< LOGLIST_COMMIT_NAME
,
286 GIT_LOG_COMMIT_EMAIL
= 1<< LOGLIST_COMMIT_EMAIL
,
287 GIT_LOG_COMMIT_DATE
= 1<< LOGLIST_COMMIT_DATE
,
288 GIT_LOGLIST_BUG
= 1<< LOGLIST_BUG
,
289 GIT_LOGLIST_SVNREV
= 1<< LOGLIST_SVNREV
,
294 // needs to start with 1, since 0 is the return value if *nothing* is clicked on in the context menu
295 ID_COMPARE
= 1, // compare revision with WC
297 ID_COMPARETWO
, // compare two revisions
301 ID_GNUDIFF1
, // compare with WC, unified
302 ID_GNUDIFF2
, // compare two revisions, unified
319 ID_COMPAREWITHPREVIOUS
,
335 ID_REFLOG_STASH_APPLY
,
337 ID_REBASE_TO_VERSION
,
345 ID_COPYCLIPBOARDMESSAGES
,
348 ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
,
366 FILTERSHOW_MERGEPOINTS
= 2,
367 FILTERSHOW_ANYCOMMIT
= 4,
368 FILTERSHOW_ALL
= FILTERSHOW_ANYCOMMIT
| FILTERSHOW_REFS
| FILTERSHOW_MERGEPOINTS
373 LOGACTIONS_REBASE_CURRENT
= 0x08000000,
374 LOGACTIONS_REBASE_PICK
= 0x04000000,
375 LOGACTIONS_REBASE_SQUASH
= 0x02000000,
376 LOGACTIONS_REBASE_EDIT
= 0x01000000,
377 LOGACTIONS_REBASE_DONE
= 0x00800000,
378 LOGACTIONS_REBASE_SKIP
= 0x00400000,
379 LOGACTIONS_REBASE_MASK
= 0x0FC00000,
380 LOGACTIONS_REBASE_MODE_MASK
= 0x07C00000,
382 inline unsigned __int64
GetContextMenuBit(int i
){ return ((unsigned __int64
)0x1)<<i
;}
383 static CString
GetRebaseActionName(int action
);
384 void InsertGitColumn();
385 void CopySelectionToClipBoard(int toCopy
= ID_COPY_ALL
);
386 void DiffSelectedRevWithPrevious();
387 bool IsSelectionContinuous();
389 int FillGitLog(CTGitPath
* path
, CString
* range
= nullptr, int infomask
= CGit::LOG_INFO_STAT
| CGit::LOG_INFO_FILESTATE
| CGit::LOG_INFO_SHOW_MERGEDFILE
);
390 int FillGitLog(std::set
<CGitHash
>& hashes
);
391 CString
MessageDisplayStr(GitRev
* pLogEntry
);
392 BOOL
IsMatchFilter(bool bRegex
, GitRevLoglist
* pRev
, std::tr1::wregex
& pat
);
393 bool ShouldShowFilter(GitRevLoglist
* pRev
, const std::map
<CGitHash
, std::set
<CGitHash
>>& commitChildren
);
394 void ShowGraphColumn(bool bShow
);
395 CString
GetTagInfo(GitRev
* pLogEntry
);
397 CFindDlg
*m_pFindDialog
;
398 static const UINT m_FindDialogMessage
;
401 static const UINT m_ScrollToMessage
;
402 static const UINT m_RebaseActionMessage
;
404 inline int ShownCountWithStopped() const { return (int)m_arShownList
.size() + (m_bStrictStopped
? 1 : 0); }
405 void FetchLogAsync(void* data
= nullptr);
406 CThreadSafePtrArray m_arShownList
;
407 void Refresh(BOOL IsCleanFilter
=TRUE
);
408 void RecalculateShownList(CThreadSafePtrArray
* pShownlist
);
411 DWORD m_SelectedFilters
;
412 FilterShow m_ShowFilter
;
413 bool m_bFilterWithRegex
;
414 bool m_bFilterCaseSensitively
;
415 CLogDataVector m_logEntries
;
418 bool ValidateRegexp(LPCTSTR regexp_str
, std::tr1::wregex
& pat
, bool bMatchCase
= false );
419 CString m_sFilterText
;
421 CFilterData m_Filter
;
425 CGitHash m_lastSelectedHash
;
426 SelectionHistory m_selectionHistory
;
427 CGitHash m_highlight
;
430 void GetTimeRange(CTime
&oldest
,CTime
&latest
);
431 virtual void GetParentHashes(GitRev
* pRev
, GIT_REV_LIST
& parentHash
);
432 virtual void ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
* menu
)=0;
435 m_RefLabelPosMap
.clear();
438 if (g_Git
.GetMapHashToFriendName(m_HashMap
))
439 MessageBox(g_Git
.GetGitLastErr(_T("Could not get all refs.")), _T("TortoiseGit"), MB_ICONERROR
);
441 m_CurrentBranch
=g_Git
.GetCurrentBranch();
443 if (g_Git
.GetHash(m_HeadHash
, _T("HEAD")))
445 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash. Quitting...")), _T("TortoiseGit"), MB_ICONERROR
);
449 m_wcRev
.m_ParentHash
.clear();
450 m_wcRev
.m_ParentHash
.push_back(m_HeadHash
);
453 FetchTrackingBranchList();
455 void StartAsyncDiffThread();
456 void StartLoadingThread();
457 void SafeTerminateThread()
459 if (m_LoadingThread
&& InterlockedExchange(&m_bExitThread
, TRUE
) == FALSE
)
461 DWORD ret
= WAIT_TIMEOUT
;
462 for (int i
= 0; i
< 200 && m_bThreadRunning
; ++i
)
463 ret
=::WaitForSingleObject(m_LoadingThread
->m_hThread
, 100);
464 if (ret
== WAIT_TIMEOUT
&& m_bThreadRunning
)
465 ::TerminateThread(m_LoadingThread
, 0);
466 m_LoadingThread
= nullptr;
470 bool IsInWorkingThread()
472 return (AfxGetThread() == m_LoadingThread
);
475 void SetRange(const CString
& range
)
480 CString
GetRange() const { return m_sRange
; }
482 bool HasFilterText() const { return !m_sFilterText
.IsEmpty() && m_sFilterText
!= _T("!"); }
486 volatile LONG m_bExitThread
;
487 CWinThread
* m_LoadingThread
;
488 MAP_HASH_NAME m_HashMap
;
489 std::map
<CString
, std::pair
<CString
, CString
>> m_TrackingMap
;
492 CString m_ColumnRegKey
;
498 CString simplifiedName
;
503 CGit::REF_TYPE refType
;
506 DECLARE_MESSAGE_MAP()
507 afx_msg
void OnDestroy();
508 virtual afx_msg
void OnNMCustomdrawLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
509 virtual afx_msg
void OnLvnGetdispinfoLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
510 afx_msg LRESULT
OnFindDialogMessage(WPARAM wParam
, LPARAM lParam
);
511 afx_msg LRESULT
OnScrollToMessage(WPARAM wParam
, LPARAM lParam
);
512 afx_msg
int OnCreate(LPCREATESTRUCT lpCreateStruct
);
513 afx_msg
void OnContextMenu(CWnd
* pWnd
, CPoint point
);
514 afx_msg LRESULT
OnLoad(WPARAM wParam
, LPARAM lParam
);
515 afx_msg
void OnHdnBegintrack(NMHDR
*pNMHDR
, LRESULT
*pResult
);
516 afx_msg
void OnHdnItemchanging(NMHDR
*pNMHDR
, LRESULT
*pResult
);
517 afx_msg
void OnColumnResized(NMHDR
*pNMHDR
, LRESULT
*pResult
);
518 afx_msg
void OnColumnMoved(NMHDR
*pNMHDR
, LRESULT
*pResult
);
519 void OnNMDblclkLoglist(NMHDR
* /*pNMHDR*/, LRESULT
*pResult
);
520 afx_msg
void OnLvnOdfinditemLoglist(NMHDR
*pNMHDR
, LRESULT
*pResult
);
521 void PreSubclassWindow();
522 virtual BOOL
PreTranslateMessage(MSG
* pMsg
);
523 static UINT
LogThreadEntry(LPVOID pVoid
);
525 bool IsOnStash(int index
);
526 bool IsStash(const GitRev
* pSelLogEntry
);
527 bool IsBisect(const GitRev
* pSelLogEntry
);
528 void FetchRemoteList();
529 void FetchTrackingBranchList();
530 void FetchLastLogInfo();
531 void FetchFullLogInfo(CString
&from
, CString
&to
);
533 virtual afx_msg BOOL
OnToolTipText(UINT id
, NMHDR
* pNMHDR
, LRESULT
* pResult
);
534 virtual INT_PTR
OnToolHitTest(CPoint point
, TOOLINFO
* pTI
) const;
535 CString
GetToolTipText(int nItem
, int nSubItem
);
537 /** Checks whether a referenfe label is under pt and returns the index/type
538 * pLogEntry IN: the entry of commit
539 * pt IN: the mouse position in client coordinate
540 * type IN: give the specific reference type, then check if it is the same reference type.
541 * OUT: give CGit::REF_TYPE::UNKNOWN for getting the real type it is.
542 * pShortname OUT: the short name of that reference label
543 * pIndex OUT: the index value of label of that entry
545 bool IsMouseOnRefLabel(const GitRevLoglist
* pLogEntry
, const POINT
& pt
, CGit::REF_TYPE
& type
, CString
* pShortname
= nullptr, size_t* pIndex
= nullptr);
546 bool IsMouseOnRefLabelFromPopupMenu(const GitRevLoglist
* pLogEntry
, const CPoint
& pt
, CGit::REF_TYPE
& type
, CString
* pShortname
= nullptr, size_t* pIndex
= nullptr);
548 void FillBackGround(HDC hdc
, DWORD_PTR Index
, CRect
&rect
);
549 void DrawTagBranchMessage(HDC hdc
, CRect
&rect
, INT_PTR index
, std::vector
<REFLABEL
> &refList
);
550 void DrawTagBranch(HDC hdc
, CDC
& W_Dc
, HTHEME hTheme
, CRect
& rect
, CRect
& rt
, LVITEM
& rItem
, GitRevLoglist
* data
, std::vector
<REFLABEL
>& refList
);
551 void DrawGraph(HDC
,CRect
&rect
,INT_PTR index
);
553 void paintGraphLane(HDC hdc
,int laneHeight
, int type
, int x1
, int x2
,
554 const COLORREF
& col
,const COLORREF
& activeColor
, int top
) ;
555 void DrawLine(HDC hdc
, int x1
, int y1
, int x2
, int y2
){ ::MoveToEx(hdc
, x1
, y1
, nullptr); ::LineTo(hdc
, x2
, y2
); }
557 * Save column widths to the registry
559 void SaveColumnWidths(); // save col widths to the registry
561 BOOL
IsEntryInDateRange(int i
);
565 std::vector
<GitRevLoglist
*> m_AsynDiffList
;
566 CComCriticalSection m_AsynDiffListLock
;
567 HANDLE m_AsyncDiffEvent
;
568 volatile LONG m_AsyncThreadExit
;
569 CWinThread
* m_DiffingThread
;
570 volatile LONG m_AsyncThreadRunning
;
572 static int DiffAsync(GitRevLoglist
* rev
, void* data
)
574 ULONGLONG offset
=((CGitLogListBase
*)data
)->m_LogCache
.GetOffset(rev
->m_CommitHash
);
575 if (!offset
|| ((CGitLogListBase
*)data
)->m_LogCache
.LoadOneItem(*rev
, offset
))
577 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Lock();
578 ((CGitLogListBase
*)data
)->m_AsynDiffList
.push_back(rev
);
579 ((CGitLogListBase
*)data
)->m_AsynDiffListLock
.Unlock();
580 ::SetEvent(((CGitLogListBase
*)data
)->m_AsyncDiffEvent
);
584 InterlockedExchange(&rev
->m_IsDiffFiles
, TRUE
);
585 if (!rev
->m_IsCommitParsed
)
587 InterlockedExchange(&rev
->m_IsFull
, TRUE
);
588 // we might need to signal that the changed files are now available
589 if (((CGitLogListBase
*)data
)->GetSelectedCount() == 1)
591 POSITION pos
= ((CGitLogListBase
*)data
)->GetFirstSelectedItemPosition();
592 int nItem
= ((CGitLogListBase
*)data
)->GetNextSelectedItem(pos
);
595 GitRevLoglist
* data2
= (GitRevLoglist
*)((CGitLogListBase
*)data
)->m_arShownList
.SafeGetAt(nItem
);
596 if (data2
&& data2
->m_CommitHash
== rev
->m_CommitHash
)
597 ((CGitLogListBase
*)data
)->GetParent()->PostMessage(WM_COMMAND
, MSG_FETCHED_DIFF
, 0);
603 static UINT
AsyncThread(LPVOID data
)
605 return ((CGitLogListBase
*)data
)->AsyncDiffThread();
608 int AsyncDiffThread();
611 void SafeTerminateAsyncDiffThread()
613 if (m_DiffingThread
&& InterlockedExchange(&m_AsyncThreadExit
, TRUE
) == FALSE
)
615 ::SetEvent(m_AsyncDiffEvent
);
616 DWORD ret
= WAIT_TIMEOUT
;
617 // do not block here, but process messages and ask until the thread ends
618 while (ret
== WAIT_TIMEOUT
&& m_AsyncThreadRunning
)
621 if (::PeekMessage(&msg
, nullptr, 0,0, PM_NOREMOVE
))
622 AfxGetThread()->PumpMessage(); // process messages, so that GetTopIndex and so on in the thread work
623 ret
= ::WaitForSingleObject(m_DiffingThread
->m_hThread
, 100);
625 m_DiffingThread
= nullptr;
626 InterlockedExchange(&m_AsyncThreadExit
, FALSE
);
631 CComCriticalSection m_critSec
;
633 HICON m_hModifiedIcon
;
634 HICON m_hReplacedIcon
;
635 HICON m_hConflictedIcon
;
637 HICON m_hDeletedIcon
;
642 CFont m_boldItalicsFont
;
644 CRegDWORD m_regMaxBugIDColWidth
;
650 CString m_CurrentBranch
;
653 COLORREF m_LineColors
[Lanes::COLORS_NUM
];
656 DWORD m_DateFormat
; // DATE_SHORTDATE or DATE_LONGDATE
657 bool m_bRelativeTimes
; // Show relative times
659 CString m_SingleRemote
;
660 bool m_bTagsBranchesOnRightSide
;
661 bool m_bFullCommitMessageOnLogLine
;
662 bool m_bSymbolizeRefNames
;
663 bool m_bIncludeBoundaryCommits
;
665 ColumnManager m_ColumnManager
;
666 DWORD m_dwDefaultColumns
;
667 TCHAR m_wszTip
[8192];
669 std::map
<CString
, CRect
> m_RefLabelPosMap
; // ref name vs. label position