1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2018 - 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"
32 IMPLEMENT_DYNAMIC(CGitRefCompareList
, CHintCtrl
<CListCtrl
>)
34 BEGIN_MESSAGE_MAP(CGitRefCompareList
, CHintCtrl
<CListCtrl
>)
36 ON_NOTIFY(HDN_ITEMCLICKA
, 0, OnHdnItemclick
)
37 ON_NOTIFY(HDN_ITEMCLICKW
, 0, OnHdnItemclick
)
40 BOOL
CGitRefCompareList::m_bSortLogical
= FALSE
;
52 IDGITRCLH_HIDEUNCHANGED
= 1,
55 CGitRefCompareList::CGitRefCompareList()
56 : CHintCtrl
<CListCtrl
>()
67 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
69 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE
);
70 m_bHideUnchanged
= CRegDWORD(L
"Software\\TortoiseGit\\RefCompareHideUnchanged", FALSE
);
73 void CGitRefCompareList::Init()
76 colRef
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_REF
)));
77 colRefType
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_REF
)));
78 colChange
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_CHANGETYPE
)));
79 colOldHash
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_OLDHASH
)));
80 colOldMessage
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_OLDMESSAGE
)));
81 colNewHash
= InsertColumn(index
++, CString(MAKEINTRESOURCE(IDS_NEWHASH
)));
82 colNewMessage
= InsertColumn(index
++,CString(MAKEINTRESOURCE(IDS_NEWMESSAGE
)));
83 for (int i
= 0; i
< index
; ++i
)
84 SetColumnWidth(i
, LVSCW_AUTOSIZE_USEHEADER
);
85 SetColumnWidth(colRef
, 130);
87 CImageList
*imagelist
= new CImageList();
88 imagelist
->Create(IDB_BITMAP_REFTYPE
, 16, 3, RGB(255, 255, 255));
89 SetImageList(imagelist
, LVSIL_SMALL
);
91 SetWindowTheme(m_hWnd
, L
"Explorer", nullptr);
94 int CGitRefCompareList::AddEntry(git_repository
* repo
, const CString
& ref
, const CGitHash
* oldHash
, const CGitHash
* newHash
)
98 entry
.shortName
= CGit::GetShortName(ref
, &entry
.refType
);
100 entry
.oldHash
= oldHash
->ToString().Left(g_Git
.GetShortHASHLength());
102 entry
.newHash
= newHash
->ToString().Left(g_Git
.GetShortHASHLength());
104 CAutoCommit oldCommit
;
107 if (!git_commit_lookup(oldCommit
.GetPointer(), repo
, *oldHash
))
108 entry
.oldMessage
= GetCommitMessage(oldCommit
);
111 CAutoCommit newCommit
;
114 if (!git_commit_lookup(newCommit
.GetPointer(), repo
, *newHash
))
115 entry
.newMessage
= GetCommitMessage(newCommit
);
118 if (oldHash
&& newHash
)
120 if (*oldHash
== *newHash
)
122 entry
.change
.LoadString(IDS_SAME
);
123 entry
.changeType
= ChangeType::Same
;
127 size_t ahead
= 0, behind
= 0;
128 if (!git_graph_ahead_behind(&ahead
, &behind
, repo
, *newHash
, *oldHash
))
131 if (ahead
> 0 && behind
== 0)
133 entry
.change
.Format(IDS_FORWARDN
, ahead
);
134 entry
.changeType
= ChangeType::FastForward
;
136 else if (ahead
== 0 && behind
> 0)
138 entry
.change
.Format(IDS_REWINDN
, behind
);
139 entry
.changeType
= ChangeType::Rewind
;
143 git_time_t oldTime
= git_commit_committer(oldCommit
)->when
.time
;
144 git_time_t newTime
= git_commit_committer(newCommit
)->when
.time
;
145 if (oldTime
< newTime
)
147 entry
.change
.LoadString(IDS_SUBMODULEDIFF_NEWERTIME
);
148 entry
.changeType
= ChangeType::NewerTime
;
150 else if (oldTime
> newTime
)
152 entry
.change
.LoadString(IDS_SUBMODULEDIFF_OLDERTIME
);
153 entry
.changeType
= ChangeType::OlderTime
;
157 entry
.change
.LoadString(IDS_SUBMODULEDIFF_SAMETIME
);
158 entry
.changeType
= ChangeType::SameTime
;
166 entry
.change
.LoadString(IDS_DELETED
);
167 entry
.changeType
= ChangeType::Deleted
;
171 entry
.change
.LoadString(IDS_NEW
);
172 entry
.changeType
= ChangeType::New
;
175 m_RefList
.push_back(entry
);
176 return (int)m_RefList
.size() - 1;
179 inline static bool StringComparePredicate(bool sortLogical
, const CString
& e1
, const CString
& e2
)
182 return StrCmpLogicalW(e1
, e2
) < 0;
183 return e1
.Compare(e2
) < 0;
186 static CString
RefTypeString(CGit::REF_TYPE reftype
)
191 case CGit::REF_TYPE::LOCAL_BRANCH
:
192 type
.LoadString(IDS_PROC_BRANCH
);
194 case CGit::REF_TYPE::REMOTE_BRANCH
:
195 type
.LoadString(IDS_PROC_REMOTEBRANCH
);
197 case CGit::REF_TYPE::ANNOTATED_TAG
:
198 case CGit::REF_TYPE::TAG
:
199 type
.LoadString(IDS_PROC_TAG
);
205 void CGitRefCompareList::Show()
209 pleaseWait
.LoadString(IDS_PROGRESSWAIT
);
210 ShowText(pleaseWait
, true);
215 if (m_nSortedColumn
>= 0)
217 auto predicate
= [](bool sortLogical
, int sortColumn
, const RefEntry
& e1
, const RefEntry
& e2
)
222 return StringComparePredicate(sortLogical
, e1
.shortName
, e2
.shortName
);
225 return StringComparePredicate(false, RefTypeString(e1
.refType
), RefTypeString(e2
.refType
));
228 return StringComparePredicate(false, e1
.change
, e2
.change
);
231 return e1
.oldHash
.Compare(e2
.oldHash
) < 0;
234 return StringComparePredicate(sortLogical
, e1
.oldMessage
, e2
.oldMessage
);
237 return e1
.newHash
.Compare(e2
.newHash
) < 0;
240 return StringComparePredicate(sortLogical
, e1
.newMessage
, e2
.newMessage
);
247 std::stable_sort(m_RefList
.begin(), m_RefList
.end(), std::bind(predicate
, m_bSortLogical
, m_nSortedColumn
, std::placeholders::_1
, std::placeholders::_2
));
249 std::stable_sort(m_RefList
.begin(), m_RefList
.end(), std::bind(predicate
, m_bSortLogical
, m_nSortedColumn
, std::placeholders::_2
, std::placeholders::_1
));
252 std::sort(m_RefList
.begin(), m_RefList
.end(), SortPredicate
);
256 for (const auto& entry
: m_RefList
)
259 if (entry
.changeType
== ChangeType::Same
&& m_bHideUnchanged
)
263 if (entry
.refType
== CGit::REF_TYPE::LOCAL_BRANCH
)
265 else if (entry
.refType
== CGit::REF_TYPE::REMOTE_BRANCH
)
267 else if (entry
.refType
== CGit::REF_TYPE::ANNOTATED_TAG
|| entry
.refType
== CGit::REF_TYPE::TAG
)
269 InsertItem(index
, entry
.shortName
, nImage
);
270 SetItemText(index
, colRefType
, RefTypeString(entry
.refType
));
271 SetItemText(index
, colChange
, entry
.change
);
272 SetItemText(index
, colOldHash
, entry
.oldHash
);
273 SetItemText(index
, colOldMessage
, entry
.oldMessage
);
274 SetItemText(index
, colNewHash
, entry
.newHash
);
275 SetItemText(index
, colNewMessage
, entry
.newMessage
);
276 SetItemData(index
, listIdx
);
280 auto pHeader
= GetHeaderCtrl();
281 HDITEM HeaderItem
= { 0 };
282 HeaderItem
.mask
= HDI_FORMAT
;
283 for (int i
= 0; i
< pHeader
->GetItemCount(); ++i
)
285 pHeader
->GetItem(i
, &HeaderItem
);
286 HeaderItem
.fmt
&= ~(HDF_SORTDOWN
| HDF_SORTUP
);
287 pHeader
->SetItem(i
, &HeaderItem
);
289 if (m_nSortedColumn
>= 0)
291 pHeader
->GetItem(m_nSortedColumn
, &HeaderItem
);
292 HeaderItem
.fmt
|= (m_bAscending
? HDF_SORTUP
: HDF_SORTDOWN
);
293 pHeader
->SetItem(m_nSortedColumn
, &HeaderItem
);
300 empty
.LoadString(IDS_COMPAREREV_NODIFF
);
301 ShowText(empty
, true);
307 void CGitRefCompareList::Clear()
313 void CGitRefCompareList::OnHdnItemclick(NMHDR
*pNMHDR
, LRESULT
*pResult
)
315 auto phdr
= reinterpret_cast<LPNMHEADER
>(pNMHDR
);
318 if (m_RefList
.empty())
321 if (m_nSortedColumn
== phdr
->iItem
)
322 m_bAscending
= !m_bAscending
;
325 m_nSortedColumn
= phdr
->iItem
;
330 void CGitRefCompareList::OnContextMenu(CWnd
*pWnd
, CPoint point
)
334 OnContextMenuList(pWnd
, point
);
336 else if (pWnd
== GetHeaderCtrl())
338 OnContextMenuHeader(pWnd
, point
);
342 void CGitRefCompareList::OnContextMenuList(CWnd
* /*pWnd*/, CPoint point
)
344 int selIndex
= GetSelectionMark();
348 int index
= (int)GetItemData(selIndex
);
349 if (index
< 0 || (size_t)index
>= m_RefList
.size())
352 CString refName
= m_RefList
[index
].fullName
;
353 CString oldHash
= m_RefList
[index
].oldHash
;
354 CString newHash
= m_RefList
[index
].newHash
;
356 popup
.CreatePopupMenu();
358 if (!oldHash
.IsEmpty())
360 logStr
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)oldHash
);
361 popup
.AppendMenuIcon(IDGITRCL_OLDLOG
, logStr
, IDI_LOG
);
363 if (!newHash
.IsEmpty() && oldHash
!= newHash
)
365 logStr
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)newHash
);
366 popup
.AppendMenuIcon(IDGITRCL_NEWLOG
, logStr
, IDI_LOG
);
368 if (!oldHash
.IsEmpty() && !newHash
.IsEmpty() && oldHash
!= newHash
)
369 popup
.AppendMenuIcon(IDGITRCL_COMPARE
, IDS_LOG_POPUP_COMPAREWITHPREVIOUS
, IDI_DIFF
);
370 popup
.AppendMenuIcon(IDGITRCL_REFLOG
, IDS_MENUREFLOG
, IDI_LOG
);
372 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
373 AfxGetApp()->DoWaitCursor(1);
376 case IDGITRCL_OLDLOG
:
377 case IDGITRCL_NEWLOG
:
380 sCmd
.Format(L
"/command:log /path:\"%s\" /endrev:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, cmd
== IDGITRCL_OLDLOG
? (LPCTSTR
)oldHash
: (LPCTSTR
)newHash
);
381 CAppUtils::RunTortoiseGitProc(sCmd
);
384 case IDGITRCL_COMPARE
:
387 sCmd
.Format(L
"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)oldHash
, (LPCTSTR
)newHash
);
388 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
389 sCmd
+= L
" /alternative";
390 CAppUtils::RunTortoiseGitProc(sCmd
);
393 case IDGITRCL_REFLOG
:
396 sCmd
.Format(L
"/command:reflog /path:\"%s\" /ref:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)refName
);
397 CAppUtils::RunTortoiseGitProc(sCmd
);
401 AfxGetApp()->DoWaitCursor(-1);
404 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
407 text
.LoadString(nTextID
);
408 menu
.AppendMenu(MF_STRING
| (enabled
? MF_ENABLED
: MF_DISABLED
) | (checked
? MF_CHECKED
: MF_UNCHECKED
), nItemID
, text
);
411 void CGitRefCompareList::OnContextMenuHeader(CWnd
* /*pWnd*/, CPoint point
)
414 if (popup
.CreatePopupMenu())
416 AppendMenuChecked(popup
, IDS_HIDEUNCHANGED
, IDGITRCLH_HIDEUNCHANGED
, m_bHideUnchanged
);
418 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
421 case IDGITRCLH_HIDEUNCHANGED
:
422 m_bHideUnchanged
= !m_bHideUnchanged
;
429 CString
CGitRefCompareList::GetCommitMessage(git_commit
*commit
)
431 int encode
= CP_UTF8
;
432 const char *encodingString
= git_commit_message_encoding(commit
);
433 if (encodingString
!= nullptr)
434 encode
= CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString
));
436 CString message
= CUnicodeUtils::GetUnicode(git_commit_message(commit
), encode
);
438 message
= message
.Tokenize(L
"\n", start
);
442 bool CGitRefCompareList::SortPredicate(const RefEntry
&e1
, const RefEntry
&e2
)
444 if (e1
.changeType
< e2
.changeType
)
446 if (e1
.changeType
> e2
.changeType
)
449 return StrCmpLogicalW(e1
.fullName
, e2
.fullName
) < 0;
450 return e1
.fullName
.Compare(e2
.fullName
) < 0;
453 ULONG
CGitRefCompareList::GetGestureStatus(CPoint
/*ptTouch*/)