Fix CrLF issues
[TortoiseGit.git] / src / TortoiseProc / GitTagCompareList.cpp
blob98da31d3fb4ab22d46d378c67965e445f2dc7b9f
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 END_MESSAGE_MAP()
41 BOOL CGitTagCompareList::m_bSortLogical = FALSE;
43 enum IDGITRCL
45 IDGITRCL_MYLOG = 1,
46 IDGITRCL_THEIRLOG,
47 IDGITRCL_COMPARE,
48 IDGITRCL_DELETELOCAL,
49 IDGITRCL_DELETEREMOTE,
50 IDGITRCL_PUSH,
51 IDGITRCL_FETCH,
54 enum IDGITRCLH
56 IDGITRCLH_HIDEUNCHANGED = 1,
59 static bool SortPredicate(bool sortLogical, const CString& e1, const CString& e2)
61 if (sortLogical)
62 return StrCmpLogicalW(e1, e2) < 0;
63 return e1.Compare(e2) < 0;
66 CGitTagCompareList::CGitTagCompareList()
67 : CHintCtrl<CListCtrl>()
68 , colTag(0)
69 , colDiff(0)
70 , colMyHash(0)
71 , colMyMessage(0)
72 , colTheirHash(0)
73 , colTheirMessage(0)
75 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
76 if (m_bSortLogical)
77 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
78 m_bHideEqual = CRegDWORD(L"Software\\TortoiseGit\\TagCompareHideEqual", FALSE);
81 void CGitTagCompareList::Init()
83 int index = 0;
84 colTag = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_PROC_TAG)));
85 colDiff = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_STATUSLIST_COLSTATUS)));
86 colMyHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_LOCALHASH)));
87 colMyMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_LOCALMESSAGE)));
88 colTheirHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_REMOTEHASH)));
89 colTheirMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_TAGCOMPARE_REMOTEMESSAGE)));
91 SetWindowTheme(m_hWnd, L"Explorer", nullptr);
94 int CGitTagCompareList::Fill(const CString& remote, CString& err)
96 m_remote = remote;
97 m_TagList.clear();
98 DeleteAllItems();
100 CString pleaseWait;
101 pleaseWait.LoadString(IDS_PROGRESSWAIT);
102 ShowText(pleaseWait, true);
104 if (!g_Git.m_IsUseLibGit2)
106 err = L"Only available with libgit2 enabled";
107 return -1;
110 REF_VECTOR remoteTags;
111 if (g_Git.GetRemoteTags(remote, remoteTags))
113 err = g_Git.GetGitLastErr(L"Could not retrieve remote tags.", CGit::GIT_CMD_FETCH);
114 return -1;
117 CAutoRepository repo(g_Git.GetGitRepository());
118 if (!repo)
120 err = CGit::GetLibGit2LastErr(L"Could not open repository.");
121 return -1;
124 MAP_HASH_NAME hashMap;
125 if (CGit::GetMapHashToFriendName(repo, hashMap))
127 err = CGit::GetLibGit2LastErr(L"Could not get all refs.");
128 return -1;
131 REF_VECTOR localTags;
132 for (auto it = hashMap.cbegin(); it != hashMap.cend(); ++it)
134 const auto& hash = it->first;
135 const auto& refs = it->second;
136 std::for_each(refs.cbegin(), refs.cend(), [&](const auto& ref)
138 if (CStringUtils::StartsWith(ref, L"refs/tags/"))
140 auto tagname = ref.Mid((int)wcslen(L"refs/tags/"));
141 localTags.emplace_back(TGitRef{ tagname, hash });
142 if (CStringUtils::EndsWith(tagname, L"^{}"))
144 tagname.Replace(L"^{}", L"");
145 CAutoObject gitObject;
146 if (git_revparse_single(gitObject.GetPointer(), repo, CUnicodeUtils::GetUTF8(tagname)))
147 return;
148 CGitHash tagHash;
149 git_oid_cpy((git_oid*)tagHash.m_hash, git_object_id(gitObject));
150 localTags.emplace_back(TGitRef{ tagname, tagHash });
155 std::sort(remoteTags.begin(), remoteTags.end(), std::bind(SortPredicate, m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
156 std::sort(localTags.begin(), localTags.end(), std::bind(SortPredicate, m_bSortLogical, std::placeholders::_1, std::placeholders::_2));
158 auto remoteIt = remoteTags.cbegin();
159 auto localIt = localTags.cbegin();
161 while (remoteIt != remoteTags.cend() && localIt != localTags.cend())
163 if (SortPredicate(m_bSortLogical, remoteIt->name, localIt->name))
165 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
166 ++remoteIt;
167 continue;
170 if (remoteIt->name == localIt->name)
172 AddEntry(repo, remoteIt->name, &localIt->hash, &remoteIt->hash);
173 ++remoteIt;
175 else
176 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
178 ++localIt;
181 while (remoteIt != remoteTags.cend())
183 AddEntry(repo, remoteIt->name, nullptr, &remoteIt->hash);
184 ++remoteIt;
187 while (localIt != localTags.cend())
189 AddEntry(repo, localIt->name, &localIt->hash, nullptr);
190 ++localIt;
193 Show();
195 if (m_TagList.empty())
197 CString empty;
198 empty.LoadString(IDS_COMPAREREV_NODIFF);
199 ShowText(empty, true);
201 else
202 ShowText(L"", true);
204 return 0;
207 void CGitTagCompareList::AddEntry(git_repository* repo, const CString& tag, const CGitHash* myHash, const CGitHash* theirHash)
209 TagEntry entry;
210 entry.name = tag;
211 if (myHash)
212 entry.myHash = *myHash;
213 if (theirHash)
214 entry.theirHash = *theirHash;
216 CAutoCommit oldCommit;
217 if (myHash)
219 if (!git_commit_lookup(oldCommit.GetPointer(), repo, (const git_oid*)&myHash->m_hash))
220 entry.myMessage = CGitRefCompareList::GetCommitMessage(oldCommit);
223 CAutoCommit newCommit;
224 if (theirHash)
226 if (!git_commit_lookup(newCommit.GetPointer(), repo, (const git_oid*)&theirHash->m_hash))
227 entry.theirMessage = CGitRefCompareList::GetCommitMessage(newCommit);
230 if (myHash && theirHash)
232 if (*myHash == *theirHash)
233 entry.diffstate.LoadString(IDS_TAGCOMPARE_SAME);
234 else
235 entry.diffstate.LoadString(IDS_TAGCOMPARE_DIFFER);
237 else if (myHash)
238 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYLOCAL);
239 else
240 entry.diffstate.LoadString(IDS_TAGCOMPARE_ONLYREMOTE);
241 m_TagList.emplace_back(entry);
244 void CGitTagCompareList::Show()
246 DeleteAllItems();
247 int index = 0;
248 for (const auto& entry : m_TagList)
250 if (entry.myHash == entry.theirHash && m_bHideEqual)
251 continue;
253 InsertItem(index, entry.name);
254 SetItemText(index, colDiff, entry.diffstate);
255 if (!entry.myHash.IsEmpty())
256 SetItemText(index, colMyHash, entry.myHash.ToString().Left(g_Git.GetShortHASHLength()));
257 SetItemText(index, colMyMessage, entry.myMessage);
258 if (!entry.theirHash.IsEmpty())
259 SetItemText(index, colTheirHash, entry.theirHash.ToString().Left(g_Git.GetShortHASHLength()));
260 SetItemText(index, colTheirMessage, entry.theirMessage);
261 index++;
263 for (int i = 0; i < GetHeaderCtrl()->GetItemCount(); ++i)
264 SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
267 void CGitTagCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
269 if (pWnd == this)
271 OnContextMenuList(pWnd, point);
273 else if (pWnd == GetHeaderCtrl())
275 OnContextMenuHeader(pWnd, point);
279 void CGitTagCompareList::OnContextMenuList(CWnd * /*pWnd*/, CPoint point)
281 int selIndex = GetSelectionMark();
282 if (selIndex < 0 || (size_t)selIndex >= m_TagList.size())
283 return;
285 CString tag = m_TagList[selIndex].name;
286 tag.Replace(L"^{}", L"");
287 CGitHash myHash = m_TagList[selIndex].myHash;
288 CGitHash theirHash = m_TagList[selIndex].theirHash;
289 CIconMenu popup;
290 popup.CreatePopupMenu();
291 CString logStr;
292 if (!myHash.IsEmpty())
294 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)myHash.ToString());
295 popup.AppendMenuIcon(IDGITRCL_MYLOG, logStr, IDI_LOG);
298 if (myHash != theirHash)
300 if (!theirHash.IsEmpty())
302 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)theirHash.ToString());
303 popup.AppendMenuIcon(IDGITRCL_THEIRLOG, logStr, IDI_LOG);
306 if (!myHash.IsEmpty() && !theirHash.IsEmpty())
307 popup.AppendMenuIcon(IDGITRCL_COMPARE, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
309 popup.AppendMenu(MF_SEPARATOR);
311 if (!theirHash.IsEmpty())
312 popup.AppendMenuIcon(IDGITRCL_FETCH, IDS_MENUFETCH, IDI_UPDATE);
314 if (!myHash.IsEmpty())
315 popup.AppendMenuIcon(IDGITRCL_PUSH, IDS_MENUPUSH, IDI_COMMIT);
318 popup.AppendMenu(MF_SEPARATOR);
320 if (!myHash.IsEmpty())
321 popup.AppendMenuIcon(IDGITRCL_DELETELOCAL, IDS_DELETE_LOCALTAG, IDI_DELETE);
323 if (!theirHash.IsEmpty())
324 popup.AppendMenuIcon(IDGITRCL_DELETEREMOTE, IDS_DELETE_REMOTETAG, IDI_DELETE);
326 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
327 switch (cmd)
329 case IDGITRCL_MYLOG:
330 case IDGITRCL_THEIRLOG:
332 CString sCmd;
333 sCmd.Format(L"/command:log /path:\"%s\" /endrev:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, cmd == IDGITRCL_MYLOG ? (LPCTSTR)myHash.ToString() : (LPCTSTR)theirHash.ToString());
334 CAppUtils::RunTortoiseGitProc(sCmd);
335 break;
337 case IDGITRCL_COMPARE:
339 CString sCmd;
340 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)myHash.ToString(), (LPCTSTR)theirHash.ToString());
341 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
342 sCmd += L" /alternative";
343 CAppUtils::RunTortoiseGitProc(sCmd);
344 break;
346 case IDGITRCL_DELETELOCAL:
348 CString csMessage;
349 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
350 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
351 return;
353 g_Git.DeleteRef(L"refs/tags/" + tag);
355 CString err;
356 if (Fill(m_remote, err))
357 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
358 break;
360 case IDGITRCL_DELETEREMOTE:
362 CString csMessage;
363 csMessage.Format(IDS_PROC_DELETEBRANCHTAG, (LPCTSTR)tag);
364 if (MessageBox(csMessage, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) != IDYES)
365 return;
366 CSysProgressDlg sysProgressDlg;
367 sysProgressDlg.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME)));
368 sysProgressDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS)));
369 sysProgressDlg.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT)));
370 sysProgressDlg.SetShowProgressBar(false);
371 sysProgressDlg.ShowModal(this, true);
373 STRING_VECTOR list;
374 list.push_back(L"refs/tags/" + tag);
375 if (g_Git.DeleteRemoteRefs(m_remote, list))
377 MessageBox(g_Git.GetGitLastErr(L"Could not delete remote tag.", CGit::GIT_CMD_PUSH), L"TortoiseGit", MB_OK | MB_ICONERROR);
378 sysProgressDlg.Stop();
379 BringWindowToTop();
380 return;
383 CString err;
384 auto ret = Fill(m_remote, err);
385 sysProgressDlg.Stop();
386 if (ret)
387 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
389 BringWindowToTop();
390 break;
392 case IDGITRCL_PUSH:
394 CProgressDlg dlg;
395 dlg.m_GitCmd.Format(L"git.exe push --force \"%s\" refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag);
396 dlg.DoModal();
398 CString err;
399 if (Fill(m_remote, err))
400 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
402 break;
404 case IDGITRCL_FETCH:
406 CProgressDlg dlg;
407 dlg.m_GitCmd.Format(L"git.exe fetch \"%s\" refs/tags/%s:refs/tags/%s", (LPCTSTR)m_remote, (LPCTSTR)tag, (LPCTSTR)tag);
408 dlg.DoModal();
410 CString err;
411 if (Fill(m_remote, err))
412 MessageBox(err, L"TortoiseGit", MB_ICONERROR);
414 break;
419 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
421 CString text;
422 text.LoadString(nTextID);
423 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
426 void CGitTagCompareList::OnContextMenuHeader(CWnd * /*pWnd*/, CPoint point)
428 CMenu popup;
429 if (popup.CreatePopupMenu())
431 AppendMenuChecked(popup, IDS_HIDEUNCHANGED, IDGITRCLH_HIDEUNCHANGED, m_bHideEqual);
433 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
434 switch (selection)
436 case IDGITRCLH_HIDEUNCHANGED:
437 m_bHideEqual = !m_bHideEqual;
438 Show();
439 break;