Re-integrate the GPG signed commit into the unit tests
[TortoiseGit.git] / src / TortoiseProc / GitTagCompareList.cpp
blobb71f281d30c0ca50a9e5258108dad5f1dd3db33c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2015-2017 - 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 CGitHash tagHash;
159 git_oid_cpy((git_oid*)tagHash.m_hash, git_object_id(gitObject));
160 localTags.emplace_back(TGitRef{ tagname, tagHash });
165 std::sort(remoteTags.begin(), remoteTags.end(), std::bind(SortPredicate, m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
166 std::sort(localTags.begin(), localTags.end(), std::bind(SortPredicate, m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
168 auto remoteIt = remoteTags.cbegin();
169 auto localIt = localTags.cbegin();
171 while (remoteIt != remoteTags.cend() && localIt != localTags.cend())
173 if (SortPredicate(m_bSortLogical, remoteIt->name, localIt->name))
175 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
176 ++remoteIt;
177 continue;
180 if (remoteIt->name == localIt->name)
182 AddEntry(repo, remoteIt->name, &localIt->hash, &remoteIt->hash);
183 ++remoteIt;
185 else
186 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
188 ++localIt;
191 while (remoteIt != remoteTags.cend())
193 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
194 ++remoteIt;
197 while (localIt != localTags.cend())
199 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
200 ++localIt;
203 Show();
205 CHeaderCtrl * pHeader = GetHeaderCtrl();
206 HDITEM HeaderItem = { 0 };
207 HeaderItem.mask = HDI_FORMAT;
208 for (int i = 0; i < pHeader->GetItemCount(); ++i)
210 pHeader->GetItem(i, &HeaderItem);
211 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
212 pHeader->SetItem(i, &HeaderItem);
214 if (m_nSortedColumn >= 0)
216 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
217 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
218 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
221 return 0;
224 void CGitTagCompareList::AddEntry(git_repository* repo, const CString& tag, const CGitHash* myHash, const CGitHash* theirHash)
226 TagEntry entry;
227 entry.name = tag;
228 if (myHash)
229 entry.myHash = *myHash;
230 if (theirHash)
231 entry.theirHash = *theirHash;
233 CAutoCommit oldCommit;
234 if (myHash)
236 if (!git_commit_lookup(oldCommit.GetPointer(), repo, (const git_oid*)&myHash->m_hash))
237 entry.myMessage = CGitRefCompareList::GetCommitMessage(oldCommit);
240 CAutoCommit newCommit;
241 if (theirHash)
243 if (!git_commit_lookup(newCommit.GetPointer(), repo, (const git_oid*)&theirHash->m_hash))
244 entry.theirMessage = CGitRefCompareList::GetCommitMessage(newCommit);
247 if (myHash && theirHash)
249 if (*myHash == *theirHash)
250 entry.diffstate.LoadString(IDS_TAGCOMPARE_SAME);
251 else
252 entry.diffstate.LoadString(IDS_TAGCOMPARE_DIFFER);
254 else if (myHash)
255 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYLOCAL);
256 else
257 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYREMOTE);
258 m_TagList.emplace_back(entry);
261 void CGitTagCompareList::Show()
264 CString pleaseWait;
265 pleaseWait.LoadString(IDS_PROGRESSWAIT);
266 ShowText(pleaseWait, true);
268 SetRedraw(false);
269 DeleteAllItems();
271 if (m_nSortedColumn >= 0)
273 auto predicate = [](bool sortLogical, int sortColumn, const TagEntry& e1, const TagEntry& e2)
275 switch (sortColumn)
277 case 0:
278 return SortPredicate(sortLogical, e1.name, e2.name);
279 break;
280 case 1:
281 return SortPredicate(false, e1.diffstate, e2.diffstate);
282 break;
283 case 2:
284 return e1.myHash < e2.myHash;
285 break;
286 case 3:
287 return SortPredicate(sortLogical, e1.myMessage, e2.myMessage);
288 break;
289 case 4:
290 return e1.theirHash < e2.theirHash;
291 break;
292 case 5:
293 return SortPredicate(sortLogical, e1.theirMessage, e2.theirMessage);
294 break;
296 return false;
299 if (m_bAscending)
300 std::stable_sort(m_TagList.begin(), m_TagList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_1, std::placeholders::_2));
301 else
302 std::stable_sort(m_TagList.begin(), m_TagList.end(), std::bind(predicate, m_bSortLogical, m_nSortedColumn, std::placeholders::_2, std::placeholders::_1));
305 int index = 0;
306 for (const auto& entry : m_TagList)
308 if (entry.myHash == entry.theirHash && m_bHideEqual)
309 continue;
311 InsertItem(index, entry.name);
312 SetItemText(index, colDiff, entry.diffstate);
313 if (!entry.myHash.IsEmpty())
314 SetItemText(index, colMyHash, entry.myHash.ToString().Left(g_Git.GetShortHASHLength()));
315 SetItemText(index, colMyMessage, entry.myMessage);
316 if (!entry.theirHash.IsEmpty())
317 SetItemText(index, colTheirHash, entry.theirHash.ToString().Left(g_Git.GetShortHASHLength()));
318 SetItemText(index, colTheirMessage, entry.theirMessage);
319 ++index;
321 for (int i = 0; i < GetHeaderCtrl()->GetItemCount(); ++i)
322 SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
324 auto pHeader = GetHeaderCtrl();
325 HDITEM HeaderItem = { 0 };
326 HeaderItem.mask = HDI_FORMAT;
327 for (int i = 0; i < pHeader->GetItemCount(); ++i)
329 pHeader->GetItem(i, &HeaderItem);
330 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
331 pHeader->SetItem(i, &HeaderItem);
333 if (m_nSortedColumn >= 0)
335 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
336 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
337 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
339 SetRedraw(true);
341 if (!index)
343 CString empty;
344 empty.LoadString(IDS_COMPAREREV_NODIFF);
345 ShowText(empty, true);
347 else
348 ShowText(L"", true);
351 void CGitTagCompareList::OnHdnItemclick(NMHDR *pNMHDR, LRESULT *pResult)
353 auto phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
354 *pResult = 0;
356 if (m_TagList.empty())
357 return;
359 if (m_nSortedColumn == phdr->iItem)
360 m_bAscending = !m_bAscending;
361 else
362 m_bAscending = TRUE;
363 m_nSortedColumn = phdr->iItem;
365 Show();
368 void CGitTagCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
370 if (pWnd == this)
372 OnContextMenuList(pWnd, point);
374 else if (pWnd == GetHeaderCtrl())
376 OnContextMenuHeader(pWnd, point);
380 void CGitTagCompareList::OnContextMenuList(CWnd * /*pWnd*/, CPoint point)
382 int selIndex = GetSelectionMark();
383 if (selIndex < 0)
384 return;
386 CString tag = GetItemText(selIndex, colTag);
387 tag.Replace(L"^{}", L"");
388 CString myHash = GetItemText(selIndex, colMyHash);
389 CString theirHash = GetItemText(selIndex, colTheirHash);
390 CIconMenu popup;
391 popup.CreatePopupMenu();
392 CString logStr;
393 if (!myHash.IsEmpty())
395 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)myHash);
396 popup.AppendMenuIcon(IDGITRCL_MYLOG, logStr, IDI_LOG);
399 if (myHash != theirHash)
401 if (!theirHash.IsEmpty())
403 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)theirHash);
404 popup.AppendMenuIcon(IDGITRCL_THEIRLOG, logStr, IDI_LOG);
407 if (!myHash.IsEmpty() && !theirHash.IsEmpty())
408 popup.AppendMenuIcon(IDGITRCL_COMPARE, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
410 popup.AppendMenu(MF_SEPARATOR);
412 if (!theirHash.IsEmpty())
413 popup.AppendMenuIcon(IDGITRCL_FETCH, IDS_MENUFETCH, IDI_UPDATE);
415 if (!myHash.IsEmpty())
416 popup.AppendMenuIcon(IDGITRCL_PUSH, IDS_MENUPUSH, IDI_COMMIT);
419 popup.AppendMenu(MF_SEPARATOR);
421 if (!myHash.IsEmpty())
422 popup.AppendMenuIcon(IDGITRCL_DELETELOCAL, IDS_DELETE_LOCALTAG, IDI_DELETE);
424 if (!theirHash.IsEmpty())
425 popup.AppendMenuIcon(IDGITRCL_DELETEREMOTE, IDS_DELETE_REMOTETAG, IDI_DELETE);
427 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
428 switch (cmd)
430 case IDGITRCL_MYLOG:
431 case IDGITRCL_THEIRLOG:
433 CString sCmd;
434 sCmd.Format(L"/command:log /path:\"%s\" /endrev:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, cmd == IDGITRCL_MYLOG ? (LPCTSTR)myHash : (LPCTSTR)theirHash);
435 CAppUtils::RunTortoiseGitProc(sCmd);
436 break;
438 case IDGITRCL_COMPARE:
440 CString sCmd;
441 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)myHash, (LPCTSTR)theirHash);
442 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
443 sCmd += L" /alternative";
444 CAppUtils::RunTortoiseGitProc(sCmd);
445 break;
447 case IDGITRCL_DELETELOCAL:
449 CString csMessage;
450 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
451 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
452 return;
454 g_Git.DeleteRef(L"refs/tags/" + tag);
456 CString err;
457 if (Fill(m_remote, err))
458 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
459 break;
461 case IDGITRCL_DELETEREMOTE:
463 CString csMessage;
464 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
465 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
466 return;
467 CSysProgressDlg sysProgressDlg;
468 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
469 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
470 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
471 sysProgressDlg.SetShowProgressBar(false);
472 sysProgressDlg.ShowModal(this, true);
474 STRING_VECTOR list;
475 list.push_back(L"refs/tags/" + tag);
476 if (g_Git.DeleteRemoteRefs(m_remote, list))
478 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote tag.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
479 sysProgressDlg.Stop();
480 BringWindowToTop();
481 return;
484 CString err;
485 auto ret = Fill(m_remote, err);
486 sysProgressDlg.Stop();
487 if (ret)
488 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
490 BringWindowToTop();
491 break;
493 case IDGITRCL_PUSH:
495 CProgressDlg dlg;
496 dlg.m_GitCmd.Format(L"git.exe push --force \"%s\" refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag);
497 dlg.DoModal();
499 CString err;
500 if (Fill(m_remote, err))
501 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
503 break;
505 case IDGITRCL_FETCH:
507 CProgressDlg dlg;
508 dlg.m_GitCmd.Format(L"git.exe fetch \"%s\" refs/tags/%s:refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag, (LPCTSTR)tag);
509 dlg.DoModal();
511 CString err;
512 if (Fill(m_remote, err))
513 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
515 break;
520 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
522 CString text;
523 text.LoadString(nTextID);
524 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
527 void CGitTagCompareList::OnContextMenuHeader(CWnd * /*pWnd*/, CPoint point)
529 CMenu popup;
530 if (popup.CreatePopupMenu())
532 AppendMenuChecked(popup, IDS_HIDEUNCHANGED, IDGITRCLH_HIDEUNCHANGED, m_bHideEqual);
534 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
535 switch (selection)
537 case IDGITRCLH_HIDEUNCHANGED:
538 m_bHideEqual = !m_bHideEqual;
539 Show();
540 break;