1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2020 - 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 // GitRefCompareList.cpp : implementation file
24 #include "GitRefCompareList.h"
26 #include "UnicodeUtils.h"
29 #include "../TortoiseShell/resource.h"
30 #include "LoglistCommonResource.h"
33 IMPLEMENT_DYNAMIC(CGitRefCompareList
, CHintCtrl
<CListCtrl
>)
35 BEGIN_MESSAGE_MAP(CGitRefCompareList
, CHintCtrl
<CListCtrl
>)
37 ON_NOTIFY(HDN_ITEMCLICKA
, 0, OnHdnItemclick
)
38 ON_NOTIFY(HDN_ITEMCLICKW
, 0, OnHdnItemclick
)
41 BOOL
CGitRefCompareList::m_bSortLogical
= FALSE
;
53 IDGITRCLH_HIDEUNCHANGED
= 1,
56 CGitRefCompareList::CGitRefCompareList()
57 : CHintCtrl
<CListCtrl
>()
68 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
70 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
71 m_bHideUnchanged
= CRegDWORD(L
"Software\\TortoiseGit\\RefCompareHideUnchanged", FALSE
);
74 void CGitRefCompareList::Init()
77 colRef
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_REF
)));
78 colRefType
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_REF
)));
79 colChange
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_CHANGETYPE
)));
80 colOldHash
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_OLDHASH
)));
81 colOldMessage
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_OLDMESSAGE
)));
82 colNewHash
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_NEWHASH
)));
83 colNewMessage
= InsertColumn(index
++,CString(MAKEINTRESOURCE(IDS_NEWMESSAGE
)));
84 for (int i
= 0; i
< index
; ++i
)
85 SetColumnWidth(i
, LVSCW_AUTOSIZE_USEHEADER
);
86 SetColumnWidth(colRef
, CDPIAware::Instance().ScaleX(130));
88 CImageList
*imagelist
= new CImageList();
89 imagelist
->Create(IDB_BITMAP_REFTYPE
, 16, 3, RGB(255, 255, 255));
90 SetImageList(imagelist
, LVSIL_SMALL
);
92 SetWindowTheme(m_hWnd
, L
"Explorer", nullptr);
95 int CGitRefCompareList::AddEntry(git_repository
* repo
, const CString
& ref
, const CGitHash
* oldHash
, const CGitHash
* newHash
)
99 entry
.shortName
= CGit::GetShortName(ref
, &entry
.refType
);
101 entry
.oldHash
= oldHash
->ToString(g_Git
.GetShortHASHLength());
103 entry
.newHash
= newHash
->ToString(g_Git
.GetShortHASHLength());
105 CAutoCommit oldCommit
;
108 if (!git_commit_lookup(oldCommit
.GetPointer(), repo
, *oldHash
))
109 entry
.oldMessage
= GetCommitMessage(oldCommit
);
112 CAutoCommit newCommit
;
115 if (!git_commit_lookup(newCommit
.GetPointer(), repo
, *newHash
))
116 entry
.newMessage
= GetCommitMessage(newCommit
);
119 if (oldHash
&& newHash
)
121 if (*oldHash
== *newHash
)
123 entry
.change
.LoadString(IDS_SAME
);
124 entry
.changeType
= ChangeType::Same
;
128 size_t ahead
= 0, behind
= 0;
129 if (!git_graph_ahead_behind(&ahead
, &behind
, repo
, *newHash
, *oldHash
))
132 if (ahead
> 0 && behind
== 0)
134 entry
.change
.Format(IDS_FORWARDN
, ahead
);
135 entry
.changeType
= ChangeType::FastForward
;
137 else if (ahead
== 0 && behind
> 0)
139 entry
.change
.Format(IDS_REWINDN
, behind
);
140 entry
.changeType
= ChangeType::Rewind
;
144 git_time_t oldTime
= git_commit_committer(oldCommit
)->when
.time
;
145 git_time_t newTime
= git_commit_committer(newCommit
)->when
.time
;
146 if (oldTime
< newTime
)
148 entry
.change
.LoadString(IDS_SUBMODULEDIFF_NEWERTIME
);
149 entry
.changeType
= ChangeType::NewerTime
;
151 else if (oldTime
> newTime
)
153 entry
.change
.LoadString(IDS_SUBMODULEDIFF_OLDERTIME
);
154 entry
.changeType
= ChangeType::OlderTime
;
158 entry
.change
.LoadString(IDS_SUBMODULEDIFF_SAMETIME
);
159 entry
.changeType
= ChangeType::SameTime
;
167 entry
.change
.LoadString(IDS_DELETED
);
168 entry
.changeType
= ChangeType::Deleted
;
172 entry
.change
.LoadString(IDS_NEW
);
173 entry
.changeType
= ChangeType::New
;
176 m_RefList
.push_back(entry
);
177 return static_cast<int>(m_RefList
.size()) - 1;
180 inline static bool StringComparePredicate(bool sortLogical
, const CString
& e1
, const CString
& e2
)
183 return StrCmpLogicalW(e1
, e2
) < 0;
184 return e1
.Compare(e2
) < 0;
187 static CString
RefTypeString(CGit::REF_TYPE reftype
)
192 case CGit::REF_TYPE::LOCAL_BRANCH
:
193 type
.LoadString(IDS_PROC_BRANCH
);
195 case CGit::REF_TYPE::REMOTE_BRANCH
:
196 type
.LoadString(IDS_PROC_REMOTEBRANCH
);
198 case CGit::REF_TYPE::ANNOTATED_TAG
:
199 case CGit::REF_TYPE::TAG
:
200 type
.LoadString(IDS_PROC_TAG
);
206 void CGitRefCompareList::Show()
210 pleaseWait
.LoadString(IDS_PROGRESSWAIT
);
211 ShowText(pleaseWait
, true);
216 if (m_nSortedColumn
>= 0)
218 auto predicate
= [](bool sortLogical
, int sortColumn
, const RefEntry
& e1
, const RefEntry
& e2
)
223 return StringComparePredicate(sortLogical
, e1
.shortName
, e2
.shortName
);
226 return StringComparePredicate(false, RefTypeString(e1
.refType
), RefTypeString(e2
.refType
));
229 return StringComparePredicate(false, e1
.change
, e2
.change
);
232 return e1
.oldHash
.Compare(e2
.oldHash
) < 0;
235 return StringComparePredicate(sortLogical
, e1
.oldMessage
, e2
.oldMessage
);
238 return e1
.newHash
.Compare(e2
.newHash
) < 0;
241 return StringComparePredicate(sortLogical
, e1
.newMessage
, e2
.newMessage
);
248 std::stable_sort(m_RefList
.begin(), m_RefList
.end(), std::bind(predicate
, m_bSortLogical
, m_nSortedColumn
, std::placeholders::_1
, std::placeholders::_2
));
250 std::stable_sort(m_RefList
.begin(), m_RefList
.end(), std::bind(predicate
, m_bSortLogical
, m_nSortedColumn
, std::placeholders::_2
, std::placeholders::_1
));
253 std::sort(m_RefList
.begin(), m_RefList
.end(), SortPredicate
);
257 for (const auto& entry
: m_RefList
)
260 if (entry
.changeType
== ChangeType::Same
&& m_bHideUnchanged
)
264 if (entry
.refType
== CGit::REF_TYPE::LOCAL_BRANCH
)
266 else if (entry
.refType
== CGit::REF_TYPE::REMOTE_BRANCH
)
268 else if (entry
.refType
== CGit::REF_TYPE::ANNOTATED_TAG
|| entry
.refType
== CGit::REF_TYPE::TAG
)
270 InsertItem(index
, entry
.shortName
, nImage
);
271 SetItemText(index
, colRefType
, RefTypeString(entry
.refType
));
272 SetItemText(index
, colChange
, entry
.change
);
273 SetItemText(index
, colOldHash
, entry
.oldHash
);
274 SetItemText(index
, colOldMessage
, entry
.oldMessage
);
275 SetItemText(index
, colNewHash
, entry
.newHash
);
276 SetItemText(index
, colNewMessage
, entry
.newMessage
);
277 SetItemData(index
, listIdx
);
281 auto pHeader
= GetHeaderCtrl();
282 HDITEM HeaderItem
= { 0 };
283 HeaderItem
.mask
= HDI_FORMAT
;
284 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
286 pHeader
->GetItem(i
, &HeaderItem
);
287 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
288 pHeader
->SetItem(i
, &HeaderItem
);
290 if (m_nSortedColumn
>= 0)
292 pHeader
->GetItem(m_nSortedColumn
, &HeaderItem
);
293 HeaderItem
.fmt
|= (m_bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
294 pHeader
->SetItem(m_nSortedColumn
, &HeaderItem
);
301 empty
.LoadString(IDS_COMPAREREV_NODIFF
);
302 ShowText(empty
, true);
308 void CGitRefCompareList::Clear()
314 void CGitRefCompareList::OnHdnItemclick(NMHDR
*pNMHDR
, LRESULT
*pResult
)
316 auto phdr
= reinterpret_cast<LPNMHEADER
>(pNMHDR
);
319 if (m_RefList
.empty())
322 if (m_nSortedColumn
== phdr
->iItem
)
323 m_bAscending
= !m_bAscending
;
326 m_nSortedColumn
= phdr
->iItem
;
331 void CGitRefCompareList::OnContextMenu(CWnd
*pWnd
, CPoint point
)
335 OnContextMenuList(pWnd
, point
);
337 else if (pWnd
== GetHeaderCtrl())
339 OnContextMenuHeader(pWnd
, point
);
343 void CGitRefCompareList::OnContextMenuList(CWnd
* /*pWnd*/, CPoint point
)
345 int selIndex
= GetSelectionMark();
349 int index
= static_cast<int>(GetItemData(selIndex
));
350 if (index
< 0 || static_cast<size_t>(index
) >= m_RefList
.size())
353 CString refName
= m_RefList
[index
].fullName
;
354 CString oldHash
= m_RefList
[index
].oldHash
;
355 CString newHash
= m_RefList
[index
].newHash
;
357 popup
.CreatePopupMenu();
359 if (!oldHash
.IsEmpty())
361 logStr
.Format(IDS_SHOWLOG_OF
, static_cast<LPCTSTR
>(oldHash
));
362 popup
.AppendMenuIcon(IDGITRCL_OLDLOG
, logStr
, IDI_LOG
);
364 if (!newHash
.IsEmpty() && oldHash
!= newHash
)
366 logStr
.Format(IDS_SHOWLOG_OF
, static_cast<LPCTSTR
>(newHash
));
367 popup
.AppendMenuIcon(IDGITRCL_NEWLOG
, logStr
, IDI_LOG
);
369 if (!oldHash
.IsEmpty() && !newHash
.IsEmpty() && oldHash
!= newHash
)
370 popup
.AppendMenuIcon(IDGITRCL_COMPARE
, IDS_LOG_POPUP_COMPAREWITHPREVIOUS
, IDI_DIFF
);
371 popup
.AppendMenuIcon(IDGITRCL_REFLOG
, IDS_MENUREFLOG
, IDI_LOG
);
373 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
374 AfxGetApp()->DoWaitCursor(1);
377 case IDGITRCL_OLDLOG
:
378 case IDGITRCL_NEWLOG
:
381 sCmd
.Format(L
"/command:log /path:\"%s\" /endrev:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), cmd
== IDGITRCL_OLDLOG
? static_cast<LPCTSTR
>(oldHash
) : static_cast<LPCTSTR
>(newHash
));
382 CAppUtils::RunTortoiseGitProc(sCmd
);
385 case IDGITRCL_COMPARE
:
388 sCmd
.Format(L
"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(oldHash
), static_cast<LPCTSTR
>(newHash
));
389 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
390 sCmd
+= L
" /alternative";
391 CAppUtils::RunTortoiseGitProc(sCmd
);
394 case IDGITRCL_REFLOG
:
397 sCmd
.Format(L
"/command:reflog /path:\"%s\" /ref:\"%s\"", static_cast<LPCTSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCTSTR
>(refName
));
398 CAppUtils::RunTortoiseGitProc(sCmd
);
402 AfxGetApp()->DoWaitCursor(-1);
405 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
408 text
.LoadString(nTextID
);
409 menu
.AppendMenu(MF_STRING
| (enabled
? MF_ENABLED
: MF_DISABLED
) | (checked
? MF_CHECKED
: MF_UNCHECKED
), nItemID
, text
);
412 void CGitRefCompareList::OnContextMenuHeader(CWnd
* /*pWnd*/, CPoint point
)
415 if (popup
.CreatePopupMenu())
417 AppendMenuChecked(popup
, IDS_HIDEUNCHANGED
, IDGITRCLH_HIDEUNCHANGED
, m_bHideUnchanged
);
419 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
422 case IDGITRCLH_HIDEUNCHANGED
:
423 m_bHideUnchanged
= !m_bHideUnchanged
;
430 CString
CGitRefCompareList::GetCommitMessage(git_commit
*commit
)
432 int encode
= CP_UTF8
;
433 const char *encodingString
= git_commit_message_encoding(commit
);
434 if (encodingString
!= nullptr)
435 encode
= CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString
));
437 CString message
= CUnicodeUtils::GetUnicode(git_commit_message(commit
), encode
);
439 message
= message
.Tokenize(L
"\n", start
);
443 bool CGitRefCompareList::SortPredicate(const RefEntry
&e1
, const RefEntry
&e2
)
445 if (e1
.changeType
< e2
.changeType
)
447 if (e1
.changeType
> e2
.changeType
)
450 return StrCmpLogicalW(e1
.fullName
, e2
.fullName
) < 0;
451 return e1
.fullName
.Compare(e2
.fullName
) < 0;
454 ULONG
CGitRefCompareList::GetGestureStatus(CPoint
/*ptTouch*/)