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
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
>)
39 ON_NOTIFY(HDN_ITEMCLICKA
, 0, OnHdnItemclick
)
40 ON_NOTIFY(HDN_ITEMCLICKW
, 0, OnHdnItemclick
)
43 BOOL
CGitTagCompareList::m_bSortLogical
= FALSE
;
51 IDGITRCL_DELETEREMOTE
,
58 IDGITRCLH_HIDEUNCHANGED
= 1,
61 inline static bool SortPredicate(bool sortLogical
, const CString
& e1
, const CString
& e2
)
64 return StrCmpLogicalW(e1
, e2
) < 0;
65 return e1
.Compare(e2
) < 0;
68 CGitTagCompareList::CGitTagCompareList()
69 : CHintCtrl
<CListCtrl
>()
79 m_bSortLogical
= !CRegDWORD(L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER
);
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()
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
))
104 int CGitTagCompareList::Fill(const CString
& remote
, CString
& err
)
111 pleaseWait
.LoadString(IDS_PROGRESSWAIT
);
112 ShowText(pleaseWait
, true);
114 if (!g_Git
.m_IsUseLibGit2
)
116 err
= L
"Only available with libgit2 enabled";
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
);
127 CAutoRepository
repo(g_Git
.GetGitRepository());
130 err
= CGit::GetLibGit2LastErr(L
"Could not open repository.");
134 MAP_HASH_NAME hashMap
;
135 if (CGit::GetMapHashToFriendName(repo
, hashMap
))
137 err
= CGit::GetLibGit2LastErr(L
"Could not get all refs.");
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
)))
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
);
178 if (remoteIt
->name
== localIt
->name
)
180 AddEntry(repo
, remoteIt
->name
, &localIt
->hash
, &remoteIt
->hash
);
184 AddEntry(repo
, localIt
->name
, &localIt
->hash
, nullptr);
189 while (remoteIt
!= remoteTags
.cend())
191 AddEntry(repo
, remoteIt
->name
, nullptr, &remoteIt
->hash
);
195 while (localIt
!= localTags
.cend())
197 AddEntry(repo
, localIt
->name
, &localIt
->hash
, nullptr);
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
);
222 void CGitTagCompareList::AddEntry(git_repository
* repo
, const CString
& tag
, const CGitHash
* myHash
, const CGitHash
* theirHash
)
227 entry
.myHash
= *myHash
;
229 entry
.theirHash
= *theirHash
;
231 CAutoCommit oldCommit
;
234 if (!git_commit_lookup(oldCommit
.GetPointer(), repo
, *myHash
))
235 entry
.myMessage
= CGitRefCompareList::GetCommitMessage(oldCommit
);
238 CAutoCommit newCommit
;
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
);
250 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_DIFFER
);
253 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_ONLYLOCAL
);
255 entry
.diffstate
.LoadString(IDS_TAGCOMPARE_ONLYREMOTE
);
256 m_TagList
.emplace_back(entry
);
259 void CGitTagCompareList::Show()
263 pleaseWait
.LoadString(IDS_PROGRESSWAIT
);
264 ShowText(pleaseWait
, true);
269 if (m_nSortedColumn
>= 0)
271 auto predicate
= [](bool sortLogical
, int sortColumn
, const TagEntry
& e1
, const TagEntry
& e2
)
276 return SortPredicate(sortLogical
, e1
.name
, e2
.name
);
279 return SortPredicate(false, e1
.diffstate
, e2
.diffstate
);
282 return e1
.myHash
< e2
.myHash
;
285 return SortPredicate(sortLogical
, e1
.myMessage
, e2
.myMessage
);
288 return e1
.theirHash
< e2
.theirHash
;
291 return SortPredicate(sortLogical
, e1
.theirMessage
, e2
.theirMessage
);
298 std::stable_sort(m_TagList
.begin(), m_TagList
.end(), std::bind(predicate
, !!m_bSortLogical
, m_nSortedColumn
, std::placeholders::_1
, std::placeholders::_2
));
300 std::stable_sort(m_TagList
.begin(), m_TagList
.end(), std::bind(predicate
, !!m_bSortLogical
, m_nSortedColumn
, std::placeholders::_2
, std::placeholders::_1
));
304 for (const auto& entry
: m_TagList
)
306 if (entry
.myHash
== entry
.theirHash
&& m_bHideEqual
)
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
);
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
);
342 empty
.LoadString(IDS_COMPAREREV_NODIFF
);
343 ShowText(empty
, true);
349 void CGitTagCompareList::OnHdnItemclick(NMHDR
*pNMHDR
, LRESULT
*pResult
)
351 auto phdr
= reinterpret_cast<LPNMHEADER
>(pNMHDR
);
354 if (m_TagList
.empty())
357 if (m_nSortedColumn
== phdr
->iItem
)
358 m_bAscending
= !m_bAscending
;
361 m_nSortedColumn
= phdr
->iItem
;
366 void CGitTagCompareList::OnContextMenu(CWnd
*pWnd
, CPoint point
)
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();
384 CString tag
= GetItemText(selIndex
, colTag
);
385 tag
.Replace(L
"^{}", L
"");
386 CString myHash
= GetItemText(selIndex
, colMyHash
);
387 CString theirHash
= GetItemText(selIndex
, colTheirHash
);
389 popup
.CreatePopupMenu();
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);
429 case IDGITRCL_THEIRLOG
:
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
);
436 case IDGITRCL_COMPARE
:
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
);
445 case IDGITRCL_DELETELOCAL
:
448 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tag
);
449 if (MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
452 g_Git
.DeleteRef(L
"refs/tags/" + tag
);
455 if (Fill(m_remote
, err
))
456 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
459 case IDGITRCL_DELETEREMOTE
:
462 csMessage
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)tag
);
463 if (MessageBox(csMessage
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) != IDYES
)
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);
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();
483 auto ret
= Fill(m_remote
, err
);
484 sysProgressDlg
.Stop();
486 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
494 dlg
.m_GitCmd
.Format(L
"git.exe push --force \"%s\" refs/tags/%s", (LPCTSTR
)m_remote
, (LPCTSTR
)tag
);
498 if (Fill(m_remote
, err
))
499 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
506 dlg
.m_GitCmd
.Format(L
"git.exe fetch \"%s\" refs/tags/%s:refs/tags/%s", (LPCTSTR
)m_remote
, (LPCTSTR
)tag
, (LPCTSTR
)tag
);
510 if (Fill(m_remote
, err
))
511 MessageBox(err
, L
"TortoiseGit", MB_ICONERROR
);
518 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
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
)
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);
535 case IDGITRCLH_HIDEUNCHANGED
:
536 m_bHideEqual
= !m_bHideEqual
;
543 ULONG
CGitTagCompareList::GetGestureStatus(CPoint
/*ptTouch*/)