Make using alternative diff tool work by pressing the shift key
[TortoiseGit.git] / src / TortoiseGitBlame / LogListBlameAction.cpp
blobdadd65b622b8b789d054dd816421ff1aadb2a9a8
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013, 2015-2016 - TortoiseGit
4 // Copyright (C) 2011-2013 Sven Strickroth <email@cs-ware.de>
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "stdafx.h"
22 #include "GitBlameLogList.h"
23 #include "GitRev.h"
24 #include "TortoiseGitBlameDoc.h"
25 #include "TortoiseGitBlameView.h"
26 #include "MainFrm.h"
27 #include "CommonAppUtils.h"
29 IMPLEMENT_DYNAMIC(CGitBlameLogList, CHintCtrl<CListCtrl>)
31 void CGitBlameLogList::hideUnimplementedCommands()
33 m_ContextMenuMask |= GetContextMenuBit(ID_BLAMEPREVIOUS) | GetContextMenuBit(ID_LOG);
34 hideFromContextMenu(
35 GetContextMenuBit(ID_COMPAREWITHPREVIOUS) |
36 GetContextMenuBit(ID_GNUDIFF1) |
37 GetContextMenuBit(ID_BLAMEPREVIOUS) |
38 GetContextMenuBit(ID_COPYCLIPBOARD) |
39 GetContextMenuBit(ID_COPYHASH) |
40 GetContextMenuBit(ID_EXPORT) |
41 GetContextMenuBit(ID_CREATE_BRANCH) |
42 GetContextMenuBit(ID_CREATE_TAG) |
43 GetContextMenuBit(ID_SWITCHTOREV) |
44 GetContextMenuBit(ID_LOG) |
45 GetContextMenuBit(ID_REPOBROWSE)
46 , true);
49 void CGitBlameLogList::GetParentHashes(GitRevLoglist* pRev, GIT_REV_LIST& parentHash)
51 std::vector<CTGitPath> paths;
52 GetPaths(pRev->m_CommitHash, paths);
54 std::set<int> parentNos;
55 GetParentNumbers(pRev, paths, parentNos);
57 for (auto it = parentNos.cbegin(); it != parentNos.cend(); ++it)
59 int parentNo = *it;
60 parentHash.push_back(pRev->m_ParentHash[parentNo]);
64 void RunTortoiseGitProcWithCurrentRev(const CString& command, const GitRev* pRev, const CString &path = g_Git.m_CurrentDir)
66 ASSERT(pRev);
67 CString procCmd;
68 procCmd.Format(L"/command:%s /path:\"%s\" /rev:%s", (LPCTSTR)command, (LPCTSTR)path, (LPCTSTR)pRev->m_CommitHash.ToString());
69 CCommonAppUtils::RunTortoiseGitProc(procCmd);
72 void CGitBlameLogList::ContextMenuAction(int cmd, int /*FirstSelect*/, int /*LastSelect*/, CMenu * /*menu*/)
74 POSITION pos = GetFirstSelectedItemPosition();
75 int indexNext = GetNextSelectedItem(pos);
76 if (indexNext < 0)
77 return;
78 CTortoiseGitBlameView *pView = DYNAMIC_DOWNCAST(CTortoiseGitBlameView,((CMainFrame*)::AfxGetApp()->GetMainWnd())->GetActiveView());
80 GitRevLoglist* pRev = &this->m_logEntries.GetGitRevAt(indexNext);
82 switch (cmd & 0xFFFF)
84 case ID_BLAMEPREVIOUS:
86 int index = (cmd >> 16) & 0xFFFF;
87 if (index > 0)
88 index -= 1;
90 CGitHash parentHash;
91 std::vector<CString> parentFilenames;
92 GetParentHash(pRev, index, parentHash, parentFilenames);
93 for (size_t i = 0; i < parentFilenames.size(); ++i)
95 CString procCmd = _T("/path:\"") + pView->ResolveCommitFile(parentFilenames[i]) + _T("\" ");
96 procCmd += _T(" /command:blame");
97 procCmd += _T(" /endrev:") + parentHash.ToString();
99 CCommonAppUtils::RunTortoiseGitProc(procCmd);
102 break;
103 case ID_GNUDIFF1: // fallthrough
104 case ID_COMPAREWITHPREVIOUS:
106 int index = (cmd >> 16) & 0xFFFF;
107 if (index > 0)
108 index -= 1;
110 CGitHash parentHash;
111 std::vector<CString> parentFilenames;
112 GetParentHash(pRev, index, parentHash, parentFilenames);
113 for (size_t i = 0; i < parentFilenames.size(); ++i)
115 CString procCmd = _T("/path:\"") + pView->ResolveCommitFile(parentFilenames[i]) + _T("\" ");
116 procCmd += _T(" /command:diff");
117 procCmd += _T(" /startrev:") + pRev->m_CommitHash.ToString();
118 procCmd += _T(" /endrev:") + parentHash.ToString();
119 if ((cmd & 0xFFFF) == ID_GNUDIFF1)
120 procCmd += _T(" /unified");
121 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
122 procCmd += L" /alternative";
124 CCommonAppUtils::RunTortoiseGitProc(procCmd);
127 break;
128 case ID_COPYCLIPBOARD:
130 CopySelectionToClipBoard();
132 break;
133 case ID_COPYHASH:
135 CopySelectionToClipBoard(ID_COPY_HASH);
137 break;
138 case ID_EXPORT:
139 RunTortoiseGitProcWithCurrentRev(_T("export"), pRev);
140 break;
141 case ID_CREATE_BRANCH:
142 RunTortoiseGitProcWithCurrentRev(_T("branch"), pRev);
143 break;
144 case ID_CREATE_TAG:
145 RunTortoiseGitProcWithCurrentRev(_T("tag"), pRev);
146 break;
147 case ID_SWITCHTOREV:
148 RunTortoiseGitProcWithCurrentRev(_T("switch"), pRev);
149 break;
150 case ID_LOG:
152 CString procCmd;
153 procCmd.Format(L"/command:log /path:\"%s\" /endrev:%s /rev:%s", (LPCTSTR)((CMainFrame*)::AfxGetApp()->GetMainWnd())->GetActiveView()->GetDocument()->GetPathName(), (LPCTSTR)pRev->m_CommitHash.ToString(), (LPCTSTR)pRev->m_CommitHash.ToString());
154 CCommonAppUtils::RunTortoiseGitProc(procCmd);
156 break;
157 case ID_REPOBROWSE:
158 RunTortoiseGitProcWithCurrentRev(_T("repobrowser"), pRev, ((CMainFrame*)::AfxGetApp()->GetMainWnd())->GetActiveView()->GetDocument()->GetPathName());
159 break;
160 default:
161 //CMessageBox::Show(nullptr, _T("Have not implemented"), _T("TortoiseGit"), MB_OK);
162 break;
163 } // switch (cmd)
166 void CGitBlameLogList::GetPaths(const CGitHash& hash, std::vector<CTGitPath>& paths)
168 CTortoiseGitBlameView *pView = DYNAMIC_DOWNCAST(CTortoiseGitBlameView,((CMainFrame*)::AfxGetApp()->GetMainWnd())->GetActiveView());
169 if (pView)
172 std::set<CString> filenames;
173 int numberOfLines = pView->m_data.GetNumberOfLines();
174 for (int i = 0; i < numberOfLines; ++i)
176 if (pView->m_data.GetHash(i) == hash)
177 filenames.insert(pView->m_data.GetFilename(i));
179 for (auto it = filenames.cbegin(); it != filenames.cend(); ++it)
180 paths.emplace_back(*it);
182 if (paths.empty())
184 // in case the hash does not exist in the blame output but it exists in the log follow only the file
185 paths.push_back(pView->GetDocument()->m_GitPath);
190 void CGitBlameLogList::GetParentNumbers(GitRevLoglist* pRev, const std::vector<CTGitPath>& paths, std::set<int>& parentNos)
192 if (pRev->m_ParentHash.empty())
194 if (pRev->GetParentFromHash(pRev->m_CommitHash))
195 MessageBox(pRev->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
198 GIT_REV_LIST allParentHash;
199 CGitLogListBase::GetParentHashes(pRev, allParentHash);
203 const CTGitPathList& files = pRev->GetFiles(nullptr);
204 for (int j=0, j_size = files.GetCount(); j < j_size; ++j)
206 const CTGitPath &file = files[j];
207 for (auto it=paths.cbegin(); it != paths.cend(); ++it)
209 const CTGitPath& path = *it;
210 if (file.IsEquivalentTo(path))
212 if (!(file.m_ParentNo & MERGE_MASK))
214 int action = file.m_Action;
215 // ignore (action & CTGitPath::LOGACTIONS_ADDED), as then there is nothing to blame/diff
216 // ignore (action & CTGitPath::LOGACTIONS_DELETED), should never happen as the file must exist
217 if (action & (CTGitPath::LOGACTIONS_MODIFIED | CTGitPath::LOGACTIONS_REPLACED))
219 int parentNo = file.m_ParentNo & PARENT_MASK;
220 if (parentNo >= 0 && (size_t)parentNo < pRev->m_ParentHash.size())
221 parentNos.insert(parentNo);
228 catch (const char* msg)
230 MessageBox(_T("Could not get files of parents.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);
234 void CGitBlameLogList::GetParentHash(GitRevLoglist* pRev, int index, CGitHash& parentHash, std::vector<CString>& parentFilenames)
236 std::vector<CTGitPath> paths;
237 GetPaths(pRev->m_CommitHash, paths);
239 std::set<int> parentNos;
240 GetParentNumbers(pRev, paths, parentNos);
242 int parentNo = 0;
244 int i = 0;
245 for (auto it = parentNos.cbegin(); it != parentNos.cend(); ++it, ++i)
247 if (i == index)
249 parentNo = *it;
250 break;
254 parentHash = pRev->m_ParentHash[parentNo];
258 const CTGitPathList& files = pRev->GetFiles(nullptr);
259 for (int j = 0, j_size = files.GetCount(); j < j_size; ++j)
261 const CTGitPath &file = files[j];
262 for (auto it = paths.cbegin(); it != paths.cend(); ++it)
264 const CTGitPath& path = *it;
265 if (file.IsEquivalentTo(path))
267 if (!(file.m_ParentNo & MERGE_MASK))
269 int action = file.m_Action;
270 // ignore (action & CTGitPath::LOGACTIONS_ADDED), as then there is nothing to blame/diff
271 // ignore (action & CTGitPath::LOGACTIONS_DELETED), should never happen as the file must exist
272 if (action & (CTGitPath::LOGACTIONS_MODIFIED | CTGitPath::LOGACTIONS_REPLACED))
274 if (parentNo == (file.m_ParentNo & PARENT_MASK) && (size_t)parentNo < pRev->m_ParentHash.size())
275 parentFilenames.push_back( (action & CTGitPath::LOGACTIONS_REPLACED) ? file.GetGitOldPathString() : file.GetGitPathString());
282 catch (const char* msg)
284 MessageBox(_T("Could not get files of parents.\nlibgit reports:\n") + CString(msg), _T("TortoiseGit"), MB_ICONERROR);