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
24 #include "GitTagCompareList.h"
25 #include "GitRefCompareList.h"
27 #include "UnicodeUtils.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
>)
41 BOOL
CGitTagCompareList::m_bSortLogical
= FALSE
;
49 IDGITRCL_DELETEREMOTE
,
56 IDGITRCLH_HIDEUNCHANGED
= 1,
59 static bool SortPredicate(bool sortLogical
, const CString
& e1
, const CString
& e2
)
62 return StrCmpLogicalW(e1
, e2
) < 0;
63 return e1
.Compare(e2
) < 0;
66 CGitTagCompareList::CGitTagCompareList()
67 : CHintCtrl
<CListCtrl
>()
75 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
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()
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
)
101 pleaseWait
.LoadString(IDS_PROGRESSWAIT
);
102 ShowText(pleaseWait
, true);
104 if (!g_Git
.m_IsUseLibGit2
)
106 err
= L
"Only available with libgit2 enabled";
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
);
117 CAutoRepository
repo(g_Git
.GetGitRepository());
120 err
= CGit::GetLibGit2LastErr(L
"Could not open repository.");
124 MAP_HASH_NAME hashMap
;
125 if (CGit::GetMapHashToFriendName(repo
, hashMap
))
127 err
= CGit::GetLibGit2LastErr(L
"Could not get all refs.");
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
)))
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
);
170 if (remoteIt
->name
== localIt
->name
)
172 AddEntry(repo
, remoteIt
->name
, &localIt
->hash
, &remoteIt
->hash
);
176 AddEntry(repo
, localIt
->name
, &localIt
->hash
, nullptr);
181 while (remoteIt
!= remoteTags
.cend())
183 AddEntry(repo
, remoteIt
->name
, nullptr, &remoteIt
->hash
);
187 while (localIt
!= localTags
.cend())
189 AddEntry(repo
, localIt
->name
, &localIt
->hash
, nullptr);
195 if (m_TagList
.empty())
198 empty
.LoadString(IDS_COMPAREREV_NODIFF
);
199 ShowText(empty
, true);
207 void CGitTagCompareList::AddEntry(git_repository
* repo
, const CString
& tag
, const CGitHash
* myHash
, const CGitHash
* theirHash
)
212 entry
.myHash
= *myHash
;
214 entry
.theirHash
= *theirHash
;
216 CAutoCommit oldCommit
;
219 if (!git_commit_lookup(oldCommit
.GetPointer(), repo
, (const git_oid
*)&myHash
->m_hash
))
220 entry
.myMessage
= CGitRefCompareList::GetCommitMessage(oldCommit
);
223 CAutoCommit newCommit
;
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
);
235 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_DIFFER
);
238 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_ONLYLOCAL
);
240 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_ONLYREMOTE
);
241 m_TagList
.emplace_back(entry
);
244 void CGitTagCompareList::Show()
248 for (const auto& entry
: m_TagList
)
250 if (entry
.myHash
== entry
.theirHash
&& m_bHideEqual
)
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
);
263 for (int i
= 0; i
< GetHeaderCtrl()->GetItemCount(); ++i
)
264 SetColumnWidth(i
, LVSCW_AUTOSIZE_USEHEADER
);
267 void CGitTagCompareList::OnContextMenu(CWnd
*pWnd
, CPoint point
)
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())
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
;
290 popup
.CreatePopupMenu();
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);
330 case IDGITRCL_THEIRLOG
:
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
);
337 case IDGITRCL_COMPARE
:
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
);
346 case IDGITRCL_DELETELOCAL
:
349 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tag
);
350 if (MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
353 g_Git
.DeleteRef(L
"refs/tags/" + tag
);
356 if (Fill(m_remote
, err
))
357 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
360 case IDGITRCL_DELETEREMOTE
:
363 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tag
);
364 if (MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
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);
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();
384 auto ret
= Fill(m_remote
, err
);
385 sysProgressDlg
.Stop();
387 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
395 dlg
.m_GitCmd
.Format(L
"git.exe push --force \"%s\" refs/tags/%s", (LPCTSTR
)m_remote
, (LPCTSTR
)tag
);
399 if (Fill(m_remote
, err
))
400 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
407 dlg
.m_GitCmd
.Format(L
"git.exe fetch \"%s\" refs/tags/%s:refs/tags/%s", (LPCTSTR
)m_remote
, (LPCTSTR
)tag
, (LPCTSTR
)tag
);
411 if (Fill(m_remote
, err
))
412 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
419 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
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
)
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);
436 case IDGITRCLH_HIDEUNCHANGED
:
437 m_bHideEqual
= !m_bHideEqual
;