Make broader use of alternative tool selection
[TortoiseGit.git] / src / TortoiseProc / GitRefCompareList.cpp
blob349045e281e1c1309a449cb604b2b892b8ba2536
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013-2016 - 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 "GitRefCompareList.h"
24 #include "registry.h"
25 #include "UnicodeUtils.h"
26 #include "MessageBox.h"
27 #include "IconMenu.h"
28 #include "AppUtils.h"
29 #include "..\TortoiseShell\resource.h"
30 #include "LoglistCommonResource.h"
32 IMPLEMENT_DYNAMIC(CGitRefCompareList, CHintCtrl<CListCtrl>)
34 BEGIN_MESSAGE_MAP(CGitRefCompareList, CHintCtrl<CListCtrl>)
35 ON_WM_CONTEXTMENU()
36 END_MESSAGE_MAP()
38 BOOL CGitRefCompareList::m_bSortLogical = FALSE;
40 enum IDGITRCL
42 IDGITRCL_OLDLOG = 1,
43 IDGITRCL_NEWLOG,
44 IDGITRCL_COMPARE,
45 IDGITRCL_REFLOG,
48 enum IDGITRCLH
50 IDGITRCLH_HIDEUNCHANGED = 1,
53 CGitRefCompareList::CGitRefCompareList()
54 : CHintCtrl<CListCtrl>()
55 , colRef(0)
56 , colChange(0)
57 , colOldHash(0)
58 , colOldMessage(0)
59 , colNewHash(0)
60 , colNewMessage(0)
62 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_CURRENT_USER);
63 if (m_bSortLogical)
64 m_bSortLogical = !CRegDWORD(L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\NoStrCmpLogical", 0, false, HKEY_LOCAL_MACHINE);
65 m_bHideUnchanged = CRegDWORD(_T("Software\\TortoiseGit\\RefCompareHideUnchanged"), FALSE);
68 void CGitRefCompareList::Init()
70 int index = 0;
71 colRef = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_REF)));
72 colChange = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_CHANGETYPE)));
73 colOldHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_OLDHASH)));
74 colOldMessage = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_OLDMESSAGE)));
75 colNewHash = InsertColumn(index++, CString(MAKEINTRESOURCE(IDS_NEWHASH)));
76 colNewMessage = InsertColumn(index++,CString(MAKEINTRESOURCE(IDS_NEWMESSAGE)));
77 for (int i = 0; i < index; ++i)
78 SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
79 SetColumnWidth(colRef, 130);
81 CImageList *imagelist = new CImageList();
82 imagelist->Create(IDB_BITMAP_REFTYPE, 16, 3, RGB(255, 255, 255));
83 SetImageList(imagelist, LVSIL_SMALL);
85 SetWindowTheme(m_hWnd, L"Explorer", nullptr);
88 int CGitRefCompareList::AddEntry(git_repository* repo, const CString& ref, const CGitHash* oldHash, const CGitHash* newHash)
90 RefEntry entry;
91 entry.fullName = ref;
92 entry.shortName = CGit::GetShortName(ref, &entry.refType);
93 if (oldHash)
94 entry.oldHash = oldHash->ToString().Left(g_Git.GetShortHASHLength());
95 if (newHash)
96 entry.newHash = newHash->ToString().Left(g_Git.GetShortHASHLength());
98 CAutoCommit oldCommit;
99 if (oldHash)
101 if (!git_commit_lookup(oldCommit.GetPointer(), repo, (const git_oid *)&oldHash->m_hash))
102 entry.oldMessage = GetCommitMessage(oldCommit);
105 CAutoCommit newCommit;
106 if (newHash)
108 if (!git_commit_lookup(newCommit.GetPointer(), repo, (const git_oid *)&newHash->m_hash))
109 entry.newMessage = GetCommitMessage(newCommit);
112 if (oldHash && newHash)
114 if (*oldHash == *newHash)
116 entry.change.LoadString(IDS_SAME);
117 entry.changeType = ChangeType::Same;
119 else
121 size_t ahead = 0, behind = 0;
122 if (!git_graph_ahead_behind(&ahead, &behind, repo, (const git_oid *)&newHash->m_hash, (const git_oid *)&oldHash->m_hash))
124 CString change;
125 if (ahead > 0 && behind == 0)
127 entry.change.Format(IDS_FORWARDN, ahead);
128 entry.changeType = ChangeType::FastForward;
130 else if (ahead == 0 && behind > 0)
132 entry.change.Format(IDS_REWINDN, behind);
133 entry.changeType = ChangeType::Rewind;
135 else
137 git_time_t oldTime = git_commit_committer(oldCommit)->when.time;
138 git_time_t newTime = git_commit_committer(newCommit)->when.time;
139 if (oldTime < newTime)
141 entry.change.LoadString(IDS_SUBMODULEDIFF_NEWERTIME);
142 entry.changeType = ChangeType::NewerTime;
144 else if (oldTime > newTime)
146 entry.change.LoadString(IDS_SUBMODULEDIFF_OLDERTIME);
147 entry.changeType = ChangeType::OlderTime;
149 else
151 entry.change.LoadString(IDS_SUBMODULEDIFF_SAMETIME);
152 entry.changeType = ChangeType::SameTime;
158 else if (oldHash)
160 entry.change.LoadString(IDS_DELETED);
161 entry.changeType = ChangeType::Deleted;
163 else if (newHash)
165 entry.change.LoadString(IDS_NEW);
166 entry.changeType = ChangeType::New;
169 m_RefList.push_back(entry);
170 return (int)m_RefList.size() - 1;
173 void CGitRefCompareList::Show()
175 DeleteAllItems();
176 std::sort(m_RefList.begin(), m_RefList.end(), SortPredicate);
177 int index = 0;
178 for (const auto& entry : m_RefList)
180 if (entry.changeType == ChangeType::Same && m_bHideUnchanged)
181 continue;
183 int nImage = -1;
184 if (entry.refType == CGit::REF_TYPE::LOCAL_BRANCH)
185 nImage = 1;
186 else if (entry.refType == CGit::REF_TYPE::REMOTE_BRANCH)
187 nImage = 2;
188 else if (entry.refType == CGit::REF_TYPE::ANNOTATED_TAG || entry.refType == CGit::REF_TYPE::TAG)
189 nImage = 0;
190 InsertItem(index, entry.shortName, nImage);
191 SetItemText(index, colChange, entry.change);
192 SetItemText(index, colOldHash, entry.oldHash);
193 SetItemText(index, colOldMessage, entry.oldMessage);
194 SetItemText(index, colNewHash, entry.newHash);
195 SetItemText(index, colNewMessage, entry.newMessage);
196 index++;
200 void CGitRefCompareList::Clear()
202 m_RefList.clear();
203 DeleteAllItems();
206 void CGitRefCompareList::OnContextMenu(CWnd *pWnd, CPoint point)
208 if (pWnd == this)
210 OnContextMenuList(pWnd, point);
212 else if (pWnd == GetHeaderCtrl())
214 OnContextMenuHeader(pWnd, point);
218 void CGitRefCompareList::OnContextMenuList(CWnd * /*pWnd*/, CPoint point)
220 int selIndex = GetSelectionMark();
221 if (selIndex < 0 || (size_t)selIndex >= m_RefList.size())
222 return;
224 CString refName = m_RefList[selIndex].fullName;
225 CString oldHash = m_RefList[selIndex].oldHash;
226 CString newHash = m_RefList[selIndex].newHash;
227 CIconMenu popup;
228 popup.CreatePopupMenu();
229 CString logStr;
230 if (!oldHash.IsEmpty())
232 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)oldHash);
233 popup.AppendMenuIcon(IDGITRCL_OLDLOG, logStr, IDI_LOG);
235 if (!newHash.IsEmpty() && oldHash != newHash)
237 logStr.Format(IDS_SHOWLOG_OF, (LPCTSTR)newHash);
238 popup.AppendMenuIcon(IDGITRCL_NEWLOG, logStr, IDI_LOG);
240 if (!oldHash.IsEmpty() && !newHash.IsEmpty() && oldHash != newHash)
241 popup.AppendMenuIcon(IDGITRCL_COMPARE, IDS_LOG_POPUP_COMPAREWITHPREVIOUS, IDI_DIFF);
242 popup.AppendMenuIcon(IDGITRCL_REFLOG, IDS_MENUREFLOG, IDI_LOG);
244 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
245 AfxGetApp()->DoWaitCursor(1);
246 switch (cmd)
248 case IDGITRCL_OLDLOG:
249 case IDGITRCL_NEWLOG:
251 CString sCmd;
252 sCmd.Format(_T("/command:log /path:\"%s\" /endrev:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, cmd == IDGITRCL_OLDLOG ? (LPCTSTR)oldHash : (LPCTSTR)newHash);
253 CAppUtils::RunTortoiseGitProc(sCmd);
254 break;
256 case IDGITRCL_COMPARE:
258 CString sCmd;
259 sCmd.Format(_T("/command:showcompare /path:\"%s\" /revision1:\"%s\" /revision2:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)oldHash, (LPCTSTR)newHash);
260 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
261 sCmd += L" /alternative";
262 CAppUtils::RunTortoiseGitProc(sCmd);
263 break;
265 case IDGITRCL_REFLOG:
267 CString sCmd;
268 sCmd.Format(_T("/command:reflog /path:\"%s\" /ref:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir, (LPCTSTR)refName);
269 CAppUtils::RunTortoiseGitProc(sCmd);
270 break;
273 AfxGetApp()->DoWaitCursor(-1);
276 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
278 CString text;
279 text.LoadString(nTextID);
280 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
283 void CGitRefCompareList::OnContextMenuHeader(CWnd * /*pWnd*/, CPoint point)
285 CMenu popup;
286 if (popup.CreatePopupMenu())
288 AppendMenuChecked(popup, IDS_HIDEUNCHANGED, IDGITRCLH_HIDEUNCHANGED, m_bHideUnchanged);
290 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
291 switch (selection)
293 case IDGITRCLH_HIDEUNCHANGED:
294 m_bHideUnchanged = !m_bHideUnchanged;
295 Show();
296 break;
301 CString CGitRefCompareList::GetCommitMessage(git_commit *commit)
303 int encode = CP_UTF8;
304 const char *encodingString = git_commit_message_encoding(commit);
305 if (encodingString != nullptr)
306 encode = CUnicodeUtils::GetCPCode(CUnicodeUtils::GetUnicode(encodingString));
308 CString message = CUnicodeUtils::GetUnicode(git_commit_message(commit), encode);
309 int start = 0;
310 message = message.Tokenize(_T("\n"), start);
311 return message;
314 bool CGitRefCompareList::SortPredicate(const RefEntry &e1, const RefEntry &e2)
316 if (e1.changeType < e2.changeType)
317 return true;
318 if (e1.changeType > e2.changeType)
319 return false;
320 if (m_bSortLogical)
321 return StrCmpLogicalW(e1.fullName, e2.fullName) < 0;
322 return e1.fullName.Compare(e2.fullName) < 0;