Make more use of theApp.m_pMainWnd
[TortoiseGit.git] / src / TortoiseProc / GitDiff.cpp
blob905a0f705f00ca235b947368dde9f06be3bb070e
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2018 - TortoiseGit
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.
20 #include "stdafx.h"
21 #include "GitDiff.h"
22 #include "TortoiseProc.h"
23 #include "AppUtils.h"
24 #include "gittype.h"
25 #include "resource.h"
26 #include "MessageBox.h"
27 #include "FileDiffDlg.h"
28 #include "SubmoduleDiffDlg.h"
29 #include "TempFile.h"
31 int CGitDiff::SubmoduleDiffNull(HWND hWnd, const CTGitPath* pPath, const CString& rev1)
33 CString newsub;
34 CString newhash;
36 CString cmd;
37 if (rev1 != GIT_REV_ZERO)
38 cmd.Format(L"git.exe ls-tree \"%s\" -- \"%s\"", (LPCTSTR)rev1, (LPCTSTR)pPath->GetGitPathString());
39 else
40 cmd.Format(L"git.exe ls-files -s -- \"%s\"", (LPCTSTR)pPath->GetGitPathString());
42 CString output, err;
43 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
45 CMessageBox::Show(hWnd, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
46 return -1;
49 int start = output.Find(L' ');
50 if(start>0)
52 if (rev1 != GIT_REV_ZERO) // in ls-files the hash is in the second column; in ls-tree it's in the third one
53 start = output.Find(L' ', start + 1);
54 if(start>0)
55 newhash=output.Mid(start + 1, GIT_HASH_SIZE * 2);
57 CGit subgit;
58 subgit.m_CurrentDir = g_Git.CombinePath(pPath);
59 int encode=CAppUtils::GetLogOutputEncode(&subgit);
61 cmd.Format(L"git.exe log -n1 --pretty=format:\"%%s\" %s --", (LPCTSTR)newhash);
62 bool toOK = !subgit.Run(cmd,&newsub,encode);
64 bool dirty = false;
65 if (rev1 == GIT_REV_ZERO && !(pPath->m_Action & CTGitPath::LOGACTIONS_DELETED))
67 CString dirtyList;
68 subgit.Run(L"git.exe status --porcelain", &dirtyList, encode);
69 dirty = !dirtyList.IsEmpty();
72 CSubmoduleDiffDlg submoduleDiffDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
73 if (GetExplorerHWND() == hWnd)
74 theApp.m_pMainWnd = &submoduleDiffDlg;
75 if (pPath->m_Action & CTGitPath::LOGACTIONS_DELETED)
76 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), false, newhash, newsub, toOK, GIT_REV_ZERO, L"", false, dirty, DeleteSubmodule);
77 else
78 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), false, GIT_REV_ZERO, L"", true, newhash, newsub, toOK, dirty, NewSubmodule);
79 submoduleDiffDlg.DoModal();
80 if (submoduleDiffDlg.IsRefresh())
81 return 1;
83 return 0;
86 if (rev1 != GIT_REV_ZERO)
87 CMessageBox::Show(hWnd, L"ls-tree output format error", L"TortoiseGit", MB_OK | MB_ICONERROR);
88 else
89 CMessageBox::Show(hWnd, L"ls-files output format error", L"TortoiseGit", MB_OK | MB_ICONERROR);
90 return -1;
93 int CGitDiff::DiffNull(HWND hWnd, const CTGitPath* pPath, CString rev1, bool bIsAdd, int jumpToLine, bool bAlternative)
95 if (rev1 != GIT_REV_ZERO)
97 CGitHash rev1Hash;
98 if (g_Git.GetHash(rev1Hash, rev1)) // make sure we have a HASH here, otherwise filenames might be invalid
100 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get hash of \"" + rev1 + L"\"."), L"TortoiseGit", MB_ICONERROR);
101 return -1;
103 rev1 = rev1Hash.ToString();
105 CString file1;
106 CString nullfile;
107 CString cmd;
109 if(pPath->IsDirectory())
111 int result;
112 // refresh if result = 1
113 CTGitPath path = *pPath;
114 while ((result = SubmoduleDiffNull(hWnd, &path, rev1)) == 1)
115 path.SetFromGit(pPath->GetGitPathString());
116 return result;
119 if(rev1 != GIT_REV_ZERO )
121 file1 = CTempFiles::Instance().GetTempFilePath(false, *pPath, rev1).GetWinPathString();
122 if (g_Git.GetOneFile(rev1, *pPath, file1))
124 CString out;
125 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath->GetGitPathString(), (LPCTSTR)rev1, (LPCTSTR)file1);
126 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_OK);
127 return -1;
129 ::SetFileAttributes(file1, FILE_ATTRIBUTE_READONLY);
131 else
132 file1 = g_Git.CombinePath(pPath);
134 CString tempfile = CTempFiles::Instance().GetTempFilePath(false, *pPath, rev1).GetWinPathString();
135 ::SetFileAttributes(tempfile, FILE_ATTRIBUTE_READONLY);
137 CAppUtils::DiffFlags flags;
138 flags.bAlternativeTool = bAlternative;
139 if(bIsAdd)
140 CAppUtils::StartExtDiff(tempfile,file1,
141 pPath->GetGitPathString(),
142 pPath->GetGitPathString() + L':' + rev1.Left(g_Git.GetShortHASHLength()),
143 g_Git.CombinePath(pPath), g_Git.CombinePath(pPath),
144 GIT_REV_ZERO, rev1
145 , flags, jumpToLine);
146 else
147 CAppUtils::StartExtDiff(file1,tempfile,
148 pPath->GetGitPathString() + L':' + rev1.Left(g_Git.GetShortHASHLength()),
149 pPath->GetGitPathString(),
150 g_Git.CombinePath(pPath), g_Git.CombinePath(pPath),
151 rev1, GIT_REV_ZERO
152 , flags, jumpToLine);
154 return 0;
157 int CGitDiff::SubmoduleDiff(HWND hWnd, const CTGitPath* pPath, const CTGitPath* /*pPath2*/, const CString& rev1, const CString& rev2, bool /*blame*/, bool /*unified*/)
159 CString oldhash;
160 CString newhash;
161 bool dirty = false;
162 CString cmd;
163 bool isWorkingCopy = false;
164 if( rev2 == GIT_REV_ZERO || rev1 == GIT_REV_ZERO )
166 oldhash = GIT_REV_ZERO;
167 newhash = GIT_REV_ZERO;
169 CString rev;
170 if( rev2 != GIT_REV_ZERO )
171 rev = rev2;
172 if( rev1 != GIT_REV_ZERO )
173 rev = rev1;
175 isWorkingCopy = true;
177 cmd.Format(L"git.exe diff %s -- \"%s\"",
178 (LPCTSTR)rev, (LPCTSTR)pPath->GetGitPathString());
180 CString output, err;
181 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
183 CMessageBox::Show(hWnd, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
184 return -1;
187 if (output.IsEmpty())
189 output.Empty();
190 err.Empty();
191 // also compare against index
192 cmd.Format(L"git.exe diff -- \"%s\"", (LPCTSTR)pPath->GetGitPathString());
193 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
195 CMessageBox::Show(hWnd, output + L'\n' + err, L"TortoiseGit", MB_OK | MB_ICONERROR);
196 return -1;
199 if (output.IsEmpty())
201 CMessageBox::Show(hWnd, IDS_ERR_EMPTYDIFF, IDS_APPNAME, MB_OK | MB_ICONERROR);
202 return -1;
204 else if (CMessageBox::Show(hWnd, IDS_SUBMODULE_EMPTYDIFF, IDS_APPNAME, 1, IDI_QUESTION, IDS_MSGBOX_YES, IDS_MSGBOX_NO) == 1)
206 CString sCmd;
207 sCmd.Format(L"/command:subupdate /bkpath:\"%s\"", (LPCTSTR)g_Git.m_CurrentDir);
208 CAppUtils::RunTortoiseGitProc(sCmd);
210 return -1;
213 int start =0;
214 int oldstart = output.Find(L"-Subproject commit", start);
215 if(oldstart<0)
217 CMessageBox::Show(hWnd, L"Subproject Diff Format error", L"TortoiseGit", MB_OK | MB_ICONERROR);
218 return -1;
220 oldhash = output.Mid(oldstart + (int)wcslen(L"-Subproject commit") + 1, GIT_HASH_SIZE * 2);
221 start = 0;
222 int newstart = output.Find(L"+Subproject commit",start);
223 if (newstart < 0)
225 CMessageBox::Show(hWnd, L"Subproject Diff Format error", L"TortoiseGit", MB_OK | MB_ICONERROR);
226 return -1;
228 newhash = output.Mid(newstart + (int)wcslen(L"+Subproject commit") + 1, GIT_HASH_SIZE * 2);
229 dirty = output.Mid(newstart + (int)wcslen(L"+Subproject commit") + GIT_HASH_SIZE * 2 + 1) == L"-dirty\n";
231 else
233 cmd.Format(L"git.exe diff-tree -r -z %s %s -- \"%s\"",
234 (LPCTSTR)rev2, (LPCTSTR)rev1, (LPCTSTR)pPath->GetGitPathString());
236 BYTE_VECTOR bytes, errBytes;
237 if(g_Git.Run(cmd, &bytes, &errBytes))
239 CString err;
240 CGit::StringAppend(&err, &errBytes[0], CP_UTF8);
241 CMessageBox::Show(hWnd, err, L"TortoiseGit", MB_OK | MB_ICONERROR);
242 return -1;
245 if (bytes.size() < 15 + 2 * GIT_HASH_SIZE + 1 + 2 * GIT_HASH_SIZE)
247 CMessageBox::Show(hWnd, L"git diff-tree gives invalid output", L"TortoiseGit", MB_OK | MB_ICONERROR);
248 return -1;
250 CGit::StringAppend(&oldhash, &bytes[15], CP_UTF8, 2 * GIT_HASH_SIZE);
251 CGit::StringAppend(&newhash, &bytes[15 + 2 * GIT_HASH_SIZE + 1], CP_UTF8, 2 * GIT_HASH_SIZE);
255 CString oldsub;
256 CString newsub;
257 bool oldOK = false, newOK = false;
259 CGit subgit;
260 subgit.m_CurrentDir = g_Git.CombinePath(pPath);
261 ChangeType changeType = Unknown;
263 if (pPath->HasAdminDir())
264 GetSubmoduleChangeType(subgit, oldhash, newhash, oldOK, newOK, changeType, oldsub, newsub);
266 CSubmoduleDiffDlg submoduleDiffDlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
267 if (GetExplorerHWND() == hWnd)
268 theApp.m_pMainWnd = &submoduleDiffDlg;
269 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), isWorkingCopy, oldhash, oldsub, oldOK, newhash, newsub, newOK, dirty, changeType);
270 submoduleDiffDlg.DoModal();
271 if (submoduleDiffDlg.IsRefresh())
272 return 1;
274 return 0;
277 void CGitDiff::GetSubmoduleChangeType(CGit& subgit, const CGitHash& oldhash, const CGitHash& newhash, bool& oldOK, bool& newOK, ChangeType& changeType, CString& oldsub, CString& newsub)
279 CString cmd;
280 int encode = CAppUtils::GetLogOutputEncode(&subgit);
281 int oldTime = 0, newTime = 0;
283 if (!oldhash.IsEmpty())
285 CString cmdout, cmderr;
286 cmd.Format(L"git.exe log -n1 --pretty=format:\"%%ct %%s\" %s --", (LPCTSTR)oldhash.ToString());
287 oldOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
288 if (oldOK)
290 int pos = cmdout.Find(L' ');
291 oldTime = _wtoi(cmdout.Left(pos));
292 oldsub = cmdout.Mid(pos + 1);
294 else
295 oldsub = cmderr;
297 if (!newhash.IsEmpty())
299 CString cmdout, cmderr;
300 cmd.Format(L"git.exe log -n1 --pretty=format:\"%%ct %%s\" %s --", (LPCTSTR)newhash.ToString());
301 newOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
302 if (newOK)
304 int pos = cmdout.Find(L' ');
305 newTime = _wtoi(cmdout.Left(pos));
306 newsub = cmdout.Mid(pos + 1);
308 else
309 newsub = cmderr;
312 if (oldhash.IsEmpty())
314 oldOK = true;
315 changeType = NewSubmodule;
317 else if (newhash.IsEmpty())
319 newOK = true;
320 changeType = DeleteSubmodule;
322 else if (oldhash != newhash)
324 bool ffNewer = subgit.IsFastForward(oldhash, newhash);
325 if (!ffNewer)
327 bool ffOlder = subgit.IsFastForward(newhash, oldhash);
328 if (!ffOlder)
330 if (newTime > oldTime)
331 changeType = NewerTime;
332 else if (newTime < oldTime)
333 changeType = OlderTime;
334 else
335 changeType = SameTime;
337 else
338 changeType = Rewind;
340 else
341 changeType = FastForward;
343 else if (oldhash == newhash)
344 changeType = Identical;
346 if (!oldOK || !newOK)
347 changeType = Unknown;
350 int CGitDiff::Diff(HWND hWnd, const CTGitPath* pPath, const CTGitPath* pPath2, CString rev1, CString rev2, bool /*blame*/, bool /*unified*/, int jumpToLine, bool bAlternativeTool, bool mustExist)
352 // make sure we have HASHes here, otherwise filenames might be invalid
353 if (rev1 != GIT_REV_ZERO)
355 CGitHash rev1Hash;
356 if (g_Git.GetHash(rev1Hash, rev1))
358 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get hash of \"" + rev1 + L"\"."), L"TortoiseGit", MB_ICONERROR);
359 return -1;
361 rev1 = rev1Hash.ToString();
363 if (rev2 != GIT_REV_ZERO)
365 CGitHash rev2Hash;
366 if (g_Git.GetHash(rev2Hash, rev2))
368 MessageBox(hWnd, g_Git.GetGitLastErr(L"Could not get hash of \"" + rev2 + L"\"."), L"TortoiseGit", MB_ICONERROR);
369 return -1;
371 rev2 = rev2Hash.ToString();
374 CString file1;
375 CString title1;
376 CString cmd;
378 if(pPath->IsDirectory() || pPath2->IsDirectory())
380 int result;
381 // refresh if result = 1
382 CTGitPath path = *pPath;
383 CTGitPath path2 = *pPath2;
384 while ((result = SubmoduleDiff(hWnd, &path, &path2, rev1, rev2)) == 1)
386 path.SetFromGit(pPath->GetGitPathString());
387 path2.SetFromGit(pPath2->GetGitPathString());
389 return result;
392 if(rev1 != GIT_REV_ZERO )
394 // use original file extension, an external diff tool might need it
395 file1 = CTempFiles::Instance().GetTempFilePath(false, *pPath, rev1).GetWinPathString();
396 title1 = pPath->GetGitPathString() + L": " + rev1.Left(g_Git.GetShortHASHLength());
397 auto ret = g_Git.GetOneFile(rev1, *pPath, file1);
398 if (ret && !(!mustExist && ret == GIT_ENOTFOUND))
400 CString out;
401 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath->GetGitPathString(), (LPCTSTR)rev1, (LPCTSTR)file1);
402 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_OK);
403 return -1;
405 ::SetFileAttributes(file1, FILE_ATTRIBUTE_READONLY);
407 else
409 file1 = g_Git.CombinePath(pPath);
410 title1.Format(IDS_DIFF_WCNAME, (LPCTSTR)pPath->GetGitPathString());
411 if (!PathFileExists(file1))
413 CString sMsg;
414 sMsg.Format(IDS_PROC_DIFFERROR_FILENOTINWORKINGTREE, (LPCTSTR)file1);
415 if (MessageBox(hWnd, sMsg, L"TortoiseGit", MB_ICONEXCLAMATION | MB_YESNO) != IDYES)
416 return 1;
417 if (!CCommonAppUtils::FileOpenSave(file1, nullptr, IDS_SELECTFILE, IDS_COMMONFILEFILTER, true))
418 return 1;
419 title1 = file1;
423 CString file2;
424 CString title2;
425 if(rev2 != GIT_REV_ZERO)
427 CTGitPath fileName = *pPath2;
428 if (pPath2->m_Action & CTGitPath::LOGACTIONS_REPLACED)
429 fileName = CTGitPath(pPath2->GetGitOldPathString());
431 file2 = CTempFiles::Instance().GetTempFilePath(false, fileName, rev2).GetWinPathString();
432 title2 = fileName.GetGitPathString() + L": " + rev2.Left(g_Git.GetShortHASHLength());
433 auto ret = g_Git.GetOneFile(rev2, fileName, file2);
434 if (ret && !(!mustExist && ret == GIT_ENOTFOUND))
436 CString out;
437 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath2->GetGitPathString(), (LPCTSTR)rev2, (LPCTSTR)file2);
438 CMessageBox::Show(hWnd, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", MB_OK);
439 return -1;
441 ::SetFileAttributes(file2, FILE_ATTRIBUTE_READONLY);
443 else
445 file2 = g_Git.CombinePath(pPath2);
446 title2.Format(IDS_DIFF_WCNAME, (LPCTSTR)pPath2->GetGitPathString());
449 CAppUtils::DiffFlags flags;
450 flags.bAlternativeTool = bAlternativeTool;
451 CAppUtils::StartExtDiff(file2,file1,
452 title2,
453 title1,
454 g_Git.CombinePath(pPath2),
455 g_Git.CombinePath(pPath),
456 rev2,
457 rev1,
458 flags, jumpToLine);
460 return 0;
463 int CGitDiff::DiffCommit(HWND hWnd, const CTGitPath& path, const GitRev* r1, const GitRev* r2, bool bAlternative)
465 return DiffCommit(hWnd, path, path, r1, r2, bAlternative);
468 int CGitDiff::DiffCommit(HWND hWnd, const CTGitPath& path1, const CTGitPath& path2, const GitRev* r1, const GitRev* r2, bool bAlternative)
470 if (path1.GetWinPathString().IsEmpty())
472 CFileDiffDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
473 if (GetExplorerHWND() == hWnd)
474 theApp.m_pMainWnd = &dlg;
475 dlg.SetDiff(nullptr, *r2, *r1);
476 dlg.DoModal();
478 else if (path1.IsDirectory())
480 CFileDiffDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
481 if (GetExplorerHWND() == hWnd)
482 theApp.m_pMainWnd = &dlg;
483 dlg.SetDiff(&path1, *r2, *r1);
484 dlg.DoModal();
486 else
487 Diff(hWnd, &path1, &path2, r1->m_CommitHash.ToString(), r2->m_CommitHash.ToString(), false, false, 0, bAlternative);
488 return 0;
491 int CGitDiff::DiffCommit(HWND hWnd, const CTGitPath& path, const CString& r1, const CString& r2, bool bAlternative)
493 return DiffCommit(hWnd, path, path, r1, r2, bAlternative);
496 int CGitDiff::DiffCommit(HWND hWnd, const CTGitPath& path1, const CTGitPath& path2, const CString& r1, const CString& r2, bool bAlternative)
498 if (path1.GetWinPathString().IsEmpty())
500 CFileDiffDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
501 if (GetExplorerHWND() == hWnd)
502 theApp.m_pMainWnd = &dlg;
503 dlg.SetDiff(nullptr, r2, r1);
504 dlg.DoModal();
506 else if (path1.IsDirectory())
508 CFileDiffDlg dlg(GetExplorerHWND() == hWnd ? nullptr : CWnd::FromHandle(hWnd));
509 if (GetExplorerHWND() == hWnd)
510 theApp.m_pMainWnd = &dlg;
511 dlg.SetDiff(&path1, r2, r1);
512 dlg.DoModal();
514 else
515 Diff(hWnd, &path1, &path2, r1, r2, false, false, 0, bAlternative);
516 return 0;