Provide (experimental) clang-format file
[TortoiseGit.git] / src / TortoiseProc / GitTagCompareList.cpp
blob78983ef062e492e3611a412b9f8869196b9eb29d
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2015-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
21 #include "stdafx.h"
22 #include "resource.h"
23 #include "Git.h"
24 #include "GitTagCompareList.h"
25 #include "GitRefCompareList.h"
26 #include "registry.h"
27 #include "UnicodeUtils.h"
28 #include "IconMenu.h"
29 #include "AppUtils.h"
30 #include "../TortoiseShell/resource.h"
31 #include "LoglistCommonResource.h"
32 #include "SysProgressDlg.h"
33 #include "ProgressDlg.h"
35 IMPLEMENT_DYNAMIC(CGitTagCompareList, CHintCtrl<CListCtrl>)
37 BEGIN_MESSAGE_MAP(CGitTagCompareList, CHintCtrl<CListCtrl>)
38 ON_WM_CONTEXTMENU()
39 ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHdnItemclick)
40 ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHdnItemclick)
41 END_MESSAGE_MAP()
43 BOOL CGitTagCompareList::m_bSortLogical = FALSE;
45 enum IDGITRCL
47 IDGITRCL_MYLOG = 1,
48 IDGITRCL_THEIRLOG,
49 IDGITRCL_COMPARE,
50 IDGITRCL_DELETELOCAL,
51 IDGITRCL_DELETEREMOTE,
52 IDGITRCL_PUSH,
53 IDGITRCL_FETCH,
56 enum IDGITRCLH
58 IDGITRCLH_HIDEUNCHANGED = 1,
61 inline static bool SortPredicate(bool sortLogical, const CString& e1, const CString& e2)
63 if (sortLogical)
64 return StrCmpLogicalW(e1, e2) < 0;
65 return e1.Compare(e2) < 0;
68 CGitTagCompareList::CGitTagCompareList()
69 : CHintCtrl<CListCtrl>()
70 , colTag(0)
71 , colDiff(0)
72 , colMyHash(0)
73 , colMyMessage(0)
74 , colTheirHash(0)
75 , colTheirMessage(0)
76 , m_bAscending(false)
77 , m_nSortedColumn(-1)
79 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
80 if (m_bSortLogical)
81 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
82 m_bHideEqual = CRegDWORD(L"Software\\TortoiseGit\\TagCompareHideEqual", FALSE);
85 void CGitTagCompareList::Init()
87 int index = 0;
88 colTag = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_PROC_TAG)));
89 colDiff = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_STATUSLIST_COLSTATUS)));
90 colMyHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_LOCALHASH)));
91 colMyMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_LOCALMESSAGE)));
92 colTheirHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_REMOTEHASH)));
93 colTheirMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_REMOTEMESSAGE)));
95 SetWindowTheme(m_hWnd, L"Explorer", nullptr);
97 if (!!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_LOCAL_MACHINE) || !!CRegDWORD(L"Software\\TortoiseGit\\SortTagsReversed", 0, false, HKEY_CURRENT_USER))
99 m_bAscending = false;
100 m_nSortedColumn = 0;
104 int CGitTagCompareList::Fill(const CString& remote, CString& err)
106 m_remote = remote;
107 m_TagList.clear();
108 DeleteAllItems();
110 CString pleaseWait;
111 pleaseWait.LoadString(IDS_PROGRESSWAIT);
112 ShowText(pleaseWait, true);
114 if (!g_Git.m_IsUseLibGit2)
116 err = L"Only available with libgit2 enabled";
117 return -1;
120 REF_VECTOR remoteTags;
121 if (g_Git.GetRemoteTags(remote, remoteTags))
123 err = g_Git.GetGitLastErr(L"Could not retrieve remote tags.", CGit::GIT_CMD_FETCH);
124 return -1;
127 CAutoRepository repo(g_Git.GetGitRepository());
128 if (!repo)
130 err = CGit::GetLibGit2LastErr(L"Could not open repository.");
131 return -1;
134 MAP_HASH_NAME hashMap;
135 if (CGit::GetMapHashToFriendName(repo, hashMap))
137 err = CGit::GetLibGit2LastErr(L"Could not get all refs.");
138 return -1;
141 REF_VECTOR localTags;
142 for (auto it = hashMap.cbegin(); it != hashMap.cend(); ++it)
144 const auto& hash = it->first;
145 const auto& refs = it->second;
146 std::for_each(refs.cbegin(), refs.cend(), [&](const auto& ref)
148 if (CStringUtils::StartsWith(ref, L"refs/tags/"))
150 auto tagname = ref.Mid((int)wcslen(L"refs/tags/"));
151 localTags.emplace_back(TGitRef{ tagname, hash });
152 if (CStringUtils::EndsWith(tagname, L"^{}"))
154 tagname.Replace(L"^{}", L"");
155 CAutoObject gitObject;
156 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(tagname)))
157 return;
158 localTags.emplace_back(TGitRef{ tagname, git_object_id(gitObject) });
163 std::sort(remoteTags.begin(), remoteTags.end(), std::bind(SortPredicate, !!m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
164 std::sort(localTags.begin(), localTags.end(), std::bind(SortPredicate, !!m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
166 auto remoteIt = remoteTags.cbegin();
167 auto localIt = localTags.cbegin();
169 while (remoteIt != remoteTags.cend() && localIt != localTags.cend())
171 if (SortPredicate(!!m_bSortLogical, remoteIt->name, localIt->name))
173 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
174 ++remoteIt;
175 continue;
178 if (remoteIt->name == localIt->name)
180 AddEntry(repo, remoteIt->name, &localIt->hash, &remoteIt->hash);
181 ++remoteIt;
183 else
184 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
186 ++localIt;
189 while (remoteIt != remoteTags.cend())
191 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
192 ++remoteIt;
195 while (localIt != localTags.cend())
197 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
198 ++localIt;
201 Show();
203 CHeaderCtrl * pHeader = GetHeaderCtrl();
204 HDITEM HeaderItem = { 0 };
205 HeaderItem.mask = HDI_FORMAT;
206 for (int i = 0; i < pHeader->GetItemCount(); ++i)
208 pHeader->GetItem(i, &HeaderItem);
209 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
210 pHeader->SetItem(i, &HeaderItem);
212 if (m_nSortedColumn >= 0)
214 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
215 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
216 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
219 return 0;
222 void CGitTagCompareList::AddEntry(git_repository* repo, const CString& tag, const CGitHash* myHash, const CGitHash* theirHash)
224 TagEntry entry;
225 entry.name = tag;
226 if (myHash)
227 entry.myHash = *myHash;
228 if (theirHash)
229 entry.theirHash = *theirHash;
231 CAutoCommit oldCommit;
232 if (myHash)
234 if (!git_commit_lookup(oldCommit.GetPointer(), repo, *myHash))
235 entry.myMessage = CGitRefCompareList::GetCommitMessage(oldCommit);
238 CAutoCommit newCommit;
239 if (theirHash)
241 if (!git_commit_lookup(newCommit.GetPointer(), repo, *theirHash))
242 entry.theirMessage = CGitRefCompareList::GetCommitMessage(newCommit);
245 if (myHash && theirHash)
247 if (*myHash == *theirHash)
248 entry.diffstate.LoadString(IDS_TAGCOMPARE_SAME);
249 else
250 entry.diffstate.LoadString(IDS_TAGCOMPARE_DIFFER);
252 else if (myHash)
253 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYLOCAL);
254 else
255 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYREMOTE);
256 m_TagList.emplace_back(entry);
259 void CGitTagCompareList::Show()
262 CString pleaseWait;
263 pleaseWait.LoadString(IDS_PROGRESSWAIT);
264 ShowText(pleaseWait, true);
266 SetRedraw(false);
267 DeleteAllItems();
269 if (m_nSortedColumn >= 0)
271 auto predicate = [](bool sortLogical, int sortColumn, const TagEntry& e1, const TagEntry& e2)
273 switch (sortColumn)
275 case 0:
276 return SortPredicate(sortLogical, e1.name, e2.name);
277 break;
278 case 1:
279 return SortPredicate(false, e1.diffstate, e2.diffstate);
280 break;
281 case 2:
282 return e1.myHash < e2.myHash;
283 break;
284 case 3:
285 return SortPredicate(sortLogical, e1.myMessage, e2.myMessage);
286 break;
287 case 4:
288 return e1.theirHash < e2.theirHash;
289 break;
290 case 5:
291 return SortPredicate(sortLogical, e1.theirMessage, e2.theirMessage);
292 break;
294 return false;
297 if (m_bAscending)
298 std::stable_sort(m_TagList.begin(), m_TagList.end(), std::bind(predicate, !!m_bSortLogical, m_nSortedColumn, std::placeholders::_1, std::placeholders::_2));
299 else
300 std::stable_sort(m_TagList.begin(), m_TagList.end(), std::bind(predicate, !!m_bSortLogical, m_nSortedColumn, std::placeholders::_2, std::placeholders::_1));
303 int index = 0;
304 for (const auto& entry : m_TagList)
306 if (entry.myHash == entry.theirHash && m_bHideEqual)
307 continue;
309 InsertItem(index, entry.name);
310 SetItemText(index, colDiff, entry.diffstate);
311 if (!entry.myHash.IsEmpty())
312 SetItemText(index, colMyHash, entry.myHash.ToString().Left(g_Git.GetShortHASHLength()));
313 SetItemText(index, colMyMessage, entry.myMessage);
314 if (!entry.theirHash.IsEmpty())
315 SetItemText(index, colTheirHash, entry.theirHash.ToString().Left(g_Git.GetShortHASHLength()));
316 SetItemText(index, colTheirMessage, entry.theirMessage);
317 ++index;
319 for (int i = 0; i < GetHeaderCtrl()->GetItemCount(); ++i)
320 SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
322 auto pHeader = GetHeaderCtrl();
323 HDITEM HeaderItem = { 0 };
324 HeaderItem.mask = HDI_FORMAT;
325 for (int i = 0; i < pHeader->GetItemCount(); ++i)
327 pHeader->GetItem(i, &HeaderItem);
328 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
329 pHeader->SetItem(i, &HeaderItem);
331 if (m_nSortedColumn >= 0)
333 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
334 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
335 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
337 SetRedraw(true);
339 if (!index)
341 CString empty;
342 empty.LoadString(IDS_COMPAREREV_NODIFF);
343 ShowText(empty, true);
345 else
346 ShowText(L"", true);
349 void CGitTagCompareList::OnHdnItemclick(NMHDR *pNMHDR, LRESULT *pResult)
351 auto phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
352 *pResult = 0;
354 if (m_TagList.empty())
355 return;
357 if (m_nSortedColumn == phdr->iItem)
358 m_bAscending = !m_bAscending;
359 else
360 m_bAscending = TRUE;
361 m_nSortedColumn = phdr->iItem;
363 Show();
366 void CGitTagCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
368 if (pWnd == this)
370 OnContextMenuList(pWnd, point);
372 else if (pWnd == GetHeaderCtrl())
374 OnContextMenuHeader(pWnd, point);
378 void CGitTagCompareList::OnContextMenuList(CWnd * /*pWnd*/, CPoint point)
380 int selIndex = GetSelectionMark();
381 if (selIndex < 0)
382 return;
384 CString tag = GetItemText(selIndex, colTag);
385 tag.Replace(L"^{}", L"");
386 CString myHash = GetItemText(selIndex, colMyHash);
387 CString theirHash = GetItemText(selIndex, colTheirHash);
388 CIconMenu popup;
389 popup.CreatePopupMenu();
390 CString logStr;
391 if (!myHash.IsEmpty())
393 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)myHash);
394 popup.AppendMenuIcon(IDGITRCL_MYLOG, logStr, IDI_LOG);
397 if (myHash != theirHash)
399 if (!theirHash.IsEmpty())
401 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)theirHash);
402 popup.AppendMenuIcon(IDGITRCL_THEIRLOG, logStr, IDI_LOG);
405 if (!myHash.IsEmpty() && !theirHash.IsEmpty())
406 popup.AppendMenuIcon(IDGITRCL_COMPARE, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
408 popup.AppendMenu(MF_SEPARATOR);
410 if (!theirHash.IsEmpty())
411 popup.AppendMenuIcon(IDGITRCL_FETCH, IDS_MENUFETCH, IDI_UPDATE);
413 if (!myHash.IsEmpty())
414 popup.AppendMenuIcon(IDGITRCL_PUSH, IDS_MENUPUSH, IDI_COMMIT);
417 popup.AppendMenu(MF_SEPARATOR);
419 if (!myHash.IsEmpty())
420 popup.AppendMenuIcon(IDGITRCL_DELETELOCAL, IDS_DELETE_LOCALTAG, IDI_DELETE);
422 if (!theirHash.IsEmpty())
423 popup.AppendMenuIcon(IDGITRCL_DELETEREMOTE, IDS_DELETE_REMOTETAG, IDI_DELETE);
425 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
426 switch (cmd)
428 case IDGITRCL_MYLOG:
429 case IDGITRCL_THEIRLOG:
431 CString sCmd;
432 sCmd.Format(L"/command:log /path:\"%s\" /endrev:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, cmd == IDGITRCL_MYLOG ? (LPCTSTR)myHash : (LPCTSTR)theirHash);
433 CAppUtils::RunTortoiseGitProc(sCmd);
434 break;
436 case IDGITRCL_COMPARE:
438 CString sCmd;
439 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)myHash, (LPCTSTR)theirHash);
440 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
441 sCmd += L" /alternative";
442 CAppUtils::RunTortoiseGitProc(sCmd);
443 break;
445 case IDGITRCL_DELETELOCAL:
447 CString csMessage;
448 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
449 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
450 return;
452 g_Git.DeleteRef(L"refs/tags/" + tag);
454 CString err;
455 if (Fill(m_remote, err))
456 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
457 break;
459 case IDGITRCL_DELETEREMOTE:
461 CString csMessage;
462 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
463 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
464 return;
465 CSysProgressDlg sysProgressDlg;
466 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
467 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
468 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
469 sysProgressDlg.SetShowProgressBar(false);
470 sysProgressDlg.ShowModal(this, true);
472 STRING_VECTOR list;
473 list.push_back(L"refs/tags/" + tag);
474 if (g_Git.DeleteRemoteRefs(m_remote, list))
476 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote tag.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
477 sysProgressDlg.Stop();
478 BringWindowToTop();
479 return;
482 CString err;
483 auto ret = Fill(m_remote, err);
484 sysProgressDlg.Stop();
485 if (ret)
486 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
488 BringWindowToTop();
489 break;
491 case IDGITRCL_PUSH:
493 CProgressDlg dlg;
494 dlg.m_GitCmd.Format(L"git.exe push --force \"%s\" refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag);
495 dlg.DoModal();
497 CString err;
498 if (Fill(m_remote, err))
499 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
501 break;
503 case IDGITRCL_FETCH:
505 CProgressDlg dlg;
506 dlg.m_GitCmd.Format(L"git.exe fetch \"%s\" refs/tags/%s:refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag, (LPCTSTR)tag);
507 dlg.DoModal();
509 CString err;
510 if (Fill(m_remote, err))
511 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
513 break;
518 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
520 CString text;
521 text.LoadString(nTextID);
522 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
525 void CGitTagCompareList::OnContextMenuHeader(CWnd * /*pWnd*/, CPoint point)
527 CMenu popup;
528 if (popup.CreatePopupMenu())
530 AppendMenuChecked(popup, IDS_HIDEUNCHANGED, IDGITRCLH_HIDEUNCHANGED, m_bHideEqual);
532 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
533 switch (selection)
535 case IDGITRCLH_HIDEUNCHANGED:
536 m_bHideEqual = !m_bHideEqual;
537 Show();
538 break;
543 ULONG CGitTagCompareList::GetGestureStatus(CPoint /*ptTouch*/)
545 return 0;