Move prune (all remotes) setting to git config page
[TortoiseGit.git] / src / TortoiseProc / GitRefCompareList.cpp
blob57c60855913a8dc05c77d3a2f387e666edde25f2
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
21 #include "stdafx.h"
22 #include "resource.h"
23 #include "Git.h"
24 #include "GitRefCompareList.h"
25 #include "registry.h"
26 #include "UnicodeUtils.h"
27 #include "IconMenu.h"
28 #include "AppUtils.h"
29 #include "../TortoiseShell/resource.h"
30 #include "LoglistCommonResource.h"
31 #include "DPIAware.h"
33 IMPLEMENT_DYNAMIC(CGitRefCompareList, CHintCtrl<CListCtrl>)
35 BEGIN_MESSAGE_MAP(CGitRefCompareList, CHintCtrl<CListCtrl>)
36 ON_WM_CONTEXTMENU()
37 ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHdnItemclick)
38 ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHdnItemclick)
39 END_MESSAGE_MAP()
41 BOOL CGitRefCompareList::m_bSortLogical = FALSE;
43 enum IDGITRCL
45 IDGITRCL_OLDLOG = 1,
46 IDGITRCL_NEWLOG,
47 IDGITRCL_COMPARE,
48 IDGITRCL_REFLOG,
51 enum IDGITRCLH
53 IDGITRCLH_HIDEUNCHANGED = 1,
56 CGitRefCompareList::CGitRefCompareList()
57 : CHintCtrl<CListCtrl>()
58 , colRef(0)
59 , colRefType(0)
60 , colChange(0)
61 , colOldHash(0)
62 , colOldMessage(0)
63 , colNewHash(0)
64 , colNewMessage(0)
65 , m_bAscending(false)
66 , m_nSortedColumn(-1)
68 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
69 if (m_bSortLogical)
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()
76 int index = 0;
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)
97 RefEntry entry;
98 entry.fullName = ref;
99 entry.shortName = CGit::GetShortName(ref, &entry.refType);
100 if (oldHash)
101 entry.oldHash = oldHash->ToString(g_Git.GetShortHASHLength());
102 if (newHash)
103 entry.newHash = newHash->ToString(g_Git.GetShortHASHLength());
105 CAutoCommit oldCommit;
106 if (oldHash)
108 if (!git_commit_lookup(oldCommit.GetPointer(), repo, *oldHash))
109 entry.oldMessage = GetCommitMessage(oldCommit);
112 CAutoCommit newCommit;
113 if (newHash)
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;
126 else
128 size_t ahead = 0, behind = 0;
129 if (!git_graph_ahead_behind(&ahead, &behind, repo, *newHash, *oldHash))
131 CString change;
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;
142 else
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;
156 else
158 entry.change.LoadString(IDS_SUBMODULEDIFF_SAMETIME);
159 entry.changeType = ChangeType::SameTime;
165 else if (oldHash)
167 entry.change.LoadString(IDS_DELETED);
168 entry.changeType = ChangeType::Deleted;
170 else if (newHash)
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)
182 if (sortLogical)
183 return StrCmpLogicalW(e1, e2) < 0;
184 return e1.Compare(e2) < 0;
187 static CString RefTypeString(CGit::REF_TYPE reftype)
189 CString type;
190 switch (reftype)
192 case CGit::REF_TYPE::LOCAL_BRANCH:
193 type.LoadString(IDS_PROC_BRANCH);
194 break;
195 case CGit::REF_TYPE::REMOTE_BRANCH:
196 type.LoadString(IDS_PROC_REMOTEBRANCH);
197 break;
198 case CGit::REF_TYPE::ANNOTATED_TAG:
199 case CGit::REF_TYPE::TAG:
200 type.LoadString(IDS_PROC_TAG);
201 break;
203 return type;
206 void CGitRefCompareList::Show()
209 CString pleaseWait;
210 pleaseWait.LoadString(IDS_PROGRESSWAIT);
211 ShowText(pleaseWait, true);
213 SetRedraw(false);
214 DeleteAllItems();
216 if (m_nSortedColumn >= 0)
218 auto predicate = [](bool sortLogical, int sortColumn, const RefEntry& e1, const RefEntry& e2)
220 switch (sortColumn)
222 case 0:
223 return StringComparePredicate(sortLogical, e1.shortName, e2.shortName);
224 break;
225 case 1:
226 return StringComparePredicate(false, RefTypeString(e1.refType), RefTypeString(e2.refType));
227 break;
228 case 2:
229 return StringComparePredicate(false, e1.change, e2.change);
230 break;
231 case 3:
232 return e1.oldHash.Compare(e2.oldHash) < 0;
233 break;
234 case 4:
235 return StringComparePredicate(sortLogical, e1.oldMessage, e2.oldMessage);
236 break;
237 case 5:
238 return e1.newHash.Compare(e2.newHash) < 0;
239 break;
240 case 6:
241 return StringComparePredicate(sortLogical, e1.newMessage, e2.newMessage);
242 break;
244 return false;
247 if (m_bAscending)
248 std::stable_sort(m_RefList.begin(), m_RefList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_1, std::placeholders::_2));
249 else
250 std::stable_sort(m_RefList.begin(), m_RefList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_2, std::placeholders::_1));
252 else
253 std::sort(m_RefList.begin(), m_RefList.end(), SortPredicate);
255 int index = 0;
256 int listIdx = -1;
257 for (const auto& entry : m_RefList)
259 ++listIdx;
260 if (entry.changeType == ChangeType::Same && m_bHideUnchanged)
261 continue;
263 int nImage = -1;
264 if (entry.refType == CGit::REF_TYPE::LOCAL_BRANCH)
265 nImage = 1;
266 else if (entry.refType == CGit::REF_TYPE::REMOTE_BRANCH)
267 nImage = 2;
268 else if (entry.refType == CGit::REF_TYPE::ANNOTATED_TAG || entry.refType == CGit::REF_TYPE::TAG)
269 nImage = 0;
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);
278 ++index;
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);
296 SetRedraw(true);
298 if (!index)
300 CString empty;
301 empty.LoadString(IDS_COMPAREREV_NODIFF);
302 ShowText(empty, true);
304 else
305 ShowText(L"", true);
308 void CGitRefCompareList::Clear()
310 m_RefList.clear();
311 DeleteAllItems();
314 void CGitRefCompareList::OnHdnItemclick(NMHDR *pNMHDR, LRESULT *pResult)
316 auto phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
317 *pResult = 0;
319 if (m_RefList.empty())
320 return;
322 if (m_nSortedColumn == phdr->iItem)
323 m_bAscending = !m_bAscending;
324 else
325 m_bAscending = TRUE;
326 m_nSortedColumn = phdr->iItem;
328 Show();
331 void CGitRefCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
333 if (pWnd == this)
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();
346 if (selIndex < 0)
347 return;
349 int index = static_cast<int>(GetItemData(selIndex));
350 if (index < 0 || static_cast<size_t>(index) >= m_RefList.size())
351 return;
353 CString refName = m_RefList[index].fullName;
354 CString oldHash = m_RefList[index].oldHash;
355 CString newHash = m_RefList[index].newHash;
356 CIconMenu popup;
357 popup.CreatePopupMenu();
358 CString logStr;
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);
375 switch (cmd)
377 case IDGITRCL_OLDLOG:
378 case IDGITRCL_NEWLOG:
380 CString sCmd;
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);
383 break;
385 case IDGITRCL_COMPARE:
387 CString sCmd;
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);
392 break;
394 case IDGITRCL_REFLOG:
396 CString sCmd;
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);
399 break;
402 AfxGetApp()->DoWaitCursor(-1);
405 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
407 CString text;
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)
414 CMenu popup;
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);
420 switch (selection)
422 case IDGITRCLH_HIDEUNCHANGED:
423 m_bHideUnchanged = !m_bHideUnchanged;
424 Show();
425 break;
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);
438 int start = 0;
439 message = message.Tokenize(L"\n", start);
440 return message;
443 bool CGitRefCompareList::SortPredicate(const RefEntry &e1, const RefEntry &e2)
445 if (e1.changeType < e2.changeType)
446 return true;
447 if (e1.changeType > e2.changeType)
448 return false;
449 if (m_bSortLogical)
450 return StrCmpLogicalW(e1.fullName, e2.fullName) < 0;
451 return e1.fullName.Compare(e2.fullName) < 0;
454 ULONG CGitRefCompareList::GetGestureStatus(CPoint /*ptTouch*/)
456 return 0;