Use correct length for buffer
[TortoiseGit.git] / src / TortoiseProc / GitDiff.cpp
blob4f313226ed7a6e6648e202b285323f460617acc7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2016 - 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 "AppUtils.h"
23 #include "gittype.h"
24 #include "resource.h"
25 #include "MessageBox.h"
26 #include "FileDiffDlg.h"
27 #include "SubmoduleDiffDlg.h"
29 CGitDiff::CGitDiff(void)
33 CGitDiff::~CGitDiff(void)
36 int CGitDiff::SubmoduleDiffNull(const CTGitPath * pPath, const git_revnum_t &rev1)
38 CString oldhash = GIT_REV_ZERO;
39 CString oldsub ;
40 CString newsub;
41 CString newhash;
43 CString cmd;
44 if (rev1 != GIT_REV_ZERO)
45 cmd.Format(_T("git.exe ls-tree \"%s\" -- \"%s\""), (LPCTSTR)rev1, (LPCTSTR)pPath->GetGitPathString());
46 else
47 cmd.Format(_T("git.exe ls-files -s -- \"%s\""), (LPCTSTR)pPath->GetGitPathString());
49 CString output, err;
50 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
52 CMessageBox::Show(nullptr, output + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
53 return -1;
56 int start=0;
57 start=output.Find(_T(' '),start);
58 if(start>0)
60 if (rev1 != GIT_REV_ZERO) // in ls-files the hash is in the second column; in ls-tree it's in the third one
61 start = output.Find(_T(' '), start + 1);
62 if(start>0)
63 newhash=output.Mid(start+1, 40);
65 CGit subgit;
66 subgit.m_CurrentDir = g_Git.CombinePath(pPath);
67 int encode=CAppUtils::GetLogOutputEncode(&subgit);
69 cmd.Format(_T("git.exe log -n1 --pretty=format:\"%%s\" %s --"), (LPCTSTR)newhash);
70 bool toOK = !subgit.Run(cmd,&newsub,encode);
72 bool dirty = false;
73 if (rev1 == GIT_REV_ZERO)
75 CString dirtyList;
76 subgit.Run(_T("git.exe status --porcelain"), &dirtyList, encode);
77 dirty = !dirtyList.IsEmpty();
80 CSubmoduleDiffDlg submoduleDiffDlg;
81 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), false, oldhash, oldsub, true, newhash, newsub, toOK, dirty, NewSubmodule);
82 submoduleDiffDlg.DoModal();
83 if (submoduleDiffDlg.IsRefresh())
84 return 1;
86 return 0;
89 if (rev1 != GIT_REV_ZERO)
90 CMessageBox::Show(nullptr, _T("ls-tree output format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
91 else
92 CMessageBox::Show(nullptr, _T("ls-files output format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
93 return -1;
96 int CGitDiff::DiffNull(const CTGitPath *pPath, git_revnum_t rev1, bool bIsAdd, int jumpToLine)
98 CString temppath;
99 GetTempPath(temppath);
100 if (rev1 != GIT_REV_ZERO)
102 CGitHash rev1Hash;
103 if (g_Git.GetHash(rev1Hash, rev1)) // make sure we have a HASH here, otherwise filenames might be invalid
105 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get hash of \"") + rev1 + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
106 return -1;
108 rev1 = rev1Hash.ToString();
110 CString file1;
111 CString nullfile;
112 CString cmd;
114 if(pPath->IsDirectory())
116 int result;
117 // refresh if result = 1
118 CTGitPath path = *pPath;
119 while ((result = SubmoduleDiffNull(&path, rev1)) == 1)
120 path.SetFromGit(pPath->GetGitPathString());
121 return result;
124 if(rev1 != GIT_REV_ZERO )
126 TCHAR szTempName[MAX_PATH] = {0};
127 GetTempFileName(temppath, pPath->GetBaseFilename(), 0, szTempName);
128 CString temp(szTempName);
129 DeleteFile(szTempName);
130 CreateDirectory(szTempName, nullptr);
131 file1.Format(_T("%s\\%s-%s%s"),
132 (LPCTSTR)temp,
133 (LPCTSTR)pPath->GetBaseFilename(),
134 (LPCTSTR)rev1.Left(g_Git.GetShortHASHLength()),
135 (LPCTSTR)pPath->GetFileExtension());
137 if (g_Git.GetOneFile(rev1, *pPath, file1))
139 CString out;
140 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath->GetGitPathString(), (LPCTSTR)rev1, (LPCTSTR)file1);
141 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_OK);
142 return -1;
145 else
146 file1 = g_Git.CombinePath(pPath);
148 // preserve FileExtension, needed especially for diffing deleted images (detection on new filename extension)
149 CString tempfile=::GetTempFile() + pPath->GetFileExtension();
150 CStdioFile file(tempfile,CFile::modeReadWrite|CFile::modeCreate );
151 //file.WriteString();
152 file.Close();
153 ::SetFileAttributes(tempfile, FILE_ATTRIBUTE_READONLY);
155 CAppUtils::DiffFlags flags;
157 if(bIsAdd)
158 CAppUtils::StartExtDiff(tempfile,file1,
159 pPath->GetGitPathString(),
160 pPath->GetGitPathString() + _T(":") + rev1.Left(g_Git.GetShortHASHLength()),
161 g_Git.CombinePath(pPath), g_Git.CombinePath(pPath),
162 git_revnum_t(GIT_REV_ZERO), rev1
163 , flags, jumpToLine);
164 else
165 CAppUtils::StartExtDiff(file1,tempfile,
166 pPath->GetGitPathString() + _T(":") + rev1.Left(g_Git.GetShortHASHLength()),
167 pPath->GetGitPathString(),
168 g_Git.CombinePath(pPath), g_Git.CombinePath(pPath),
169 rev1, git_revnum_t(GIT_REV_ZERO)
170 , flags, jumpToLine);
172 return 0;
175 int CGitDiff::SubmoduleDiff(const CTGitPath * pPath, const CTGitPath * /*pPath2*/, const git_revnum_t &rev1, const git_revnum_t &rev2, bool /*blame*/, bool /*unified*/)
177 CString oldhash;
178 CString newhash;
179 bool dirty = false;
180 CString cmd;
181 bool isWorkingCopy = false;
182 if( rev2 == GIT_REV_ZERO || rev1 == GIT_REV_ZERO )
184 oldhash = GIT_REV_ZERO;
185 newhash = GIT_REV_ZERO;
187 CString rev;
188 if( rev2 != GIT_REV_ZERO )
189 rev = rev2;
190 if( rev1 != GIT_REV_ZERO )
191 rev = rev1;
193 isWorkingCopy = true;
195 cmd.Format(_T("git.exe diff %s -- \"%s\""),
196 (LPCTSTR)rev, (LPCTSTR)pPath->GetGitPathString());
198 CString output, err;
199 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
201 CMessageBox::Show(nullptr, output + L"\n" + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
202 return -1;
205 if (output.IsEmpty())
207 output.Empty();
208 err.Empty();
209 // also compare against index
210 cmd.Format(_T("git.exe diff -- \"%s\""), (LPCTSTR)pPath->GetGitPathString());
211 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
213 CMessageBox::Show(nullptr, output + _T("\n") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
214 return -1;
217 if (output.IsEmpty())
219 CMessageBox::Show(nullptr, IDS_ERR_EMPTYDIFF, IDS_APPNAME, MB_OK | MB_ICONERROR);
220 return -1;
222 else if (CMessageBox::Show(nullptr, IDS_SUBMODULE_EMPTYDIFF, IDS_APPNAME, 1, IDI_QUESTION, IDS_MSGBOX_YES, IDS_MSGBOX_NO) == 1)
224 CString sCmd;
225 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR)g_Git.m_CurrentDir);
226 CAppUtils::RunTortoiseGitProc(sCmd);
228 return -1;
231 int start =0;
232 int oldstart = output.Find(_T("-Subproject commit"),start);
233 if(oldstart<0)
235 CMessageBox::Show(nullptr, _T("Subproject Diff Format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
236 return -1;
238 oldhash = output.Mid(oldstart+ CString(_T("-Subproject commit")).GetLength()+1,40);
239 start = 0;
240 int newstart = output.Find(_T("+Subproject commit"),start);
241 if (newstart < 0)
243 CMessageBox::Show(nullptr, _T("Subproject Diff Format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
244 return -1;
246 newhash = output.Mid(newstart+ CString(_T("+Subproject commit")).GetLength()+1,40);
247 dirty = output.Mid(newstart + CString(_T("+Subproject commit")).GetLength() + 41) == _T("-dirty\n");
249 else
251 cmd.Format(_T("git.exe diff-tree -r -z %s %s -- \"%s\""),
252 (LPCTSTR)rev2, (LPCTSTR)rev1, (LPCTSTR)pPath->GetGitPathString());
254 BYTE_VECTOR bytes, errBytes;
255 if(g_Git.Run(cmd, &bytes, &errBytes))
257 CString err;
258 CGit::StringAppend(&err, &errBytes[0], CP_UTF8);
259 CMessageBox::Show(nullptr, err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
260 return -1;
263 if (bytes.size() < 15 + 2 * GIT_HASH_SIZE + 1 + 2 * GIT_HASH_SIZE)
265 CMessageBox::Show(nullptr, _T("git diff-tree gives invalid output"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
266 return -1;
268 CGit::StringAppend(&oldhash, &bytes[15], CP_UTF8, 2 * GIT_HASH_SIZE);
269 CGit::StringAppend(&newhash, &bytes[15 + 2 * GIT_HASH_SIZE + 1], CP_UTF8, 2 * GIT_HASH_SIZE);
273 CString oldsub;
274 CString newsub;
275 bool oldOK = false, newOK = false;
277 CGit subgit;
278 subgit.m_CurrentDir = g_Git.CombinePath(pPath);
279 ChangeType changeType = Unknown;
281 if (pPath->HasAdminDir())
282 GetSubmoduleChangeType(subgit, oldhash, newhash, oldOK, newOK, changeType, oldsub, newsub);
284 CSubmoduleDiffDlg submoduleDiffDlg;
285 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), isWorkingCopy, oldhash, oldsub, oldOK, newhash, newsub, newOK, dirty, changeType);
286 submoduleDiffDlg.DoModal();
287 if (submoduleDiffDlg.IsRefresh())
288 return 1;
290 return 0;
293 void CGitDiff::GetSubmoduleChangeType(CGit& subgit, const CString& oldhash, const CString& newhash, bool& oldOK, bool& newOK, ChangeType& changeType, CString& oldsub, CString& newsub)
295 CString cmd;
296 int encode = CAppUtils::GetLogOutputEncode(&subgit);
297 int oldTime = 0, newTime = 0;
299 if (oldhash != GIT_REV_ZERO)
301 CString cmdout, cmderr;
302 cmd.Format(_T("git.exe log -n1 --pretty=format:\"%%ct %%s\" %s --"), (LPCTSTR)oldhash);
303 oldOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
304 if (oldOK)
306 int pos = cmdout.Find(_T(" "));
307 oldTime = _ttoi(cmdout.Left(pos));
308 oldsub = cmdout.Mid(pos + 1);
310 else
311 oldsub = cmderr;
313 if (newhash != GIT_REV_ZERO)
315 CString cmdout, cmderr;
316 cmd.Format(_T("git.exe log -n1 --pretty=format:\"%%ct %%s\" %s --"), (LPCTSTR)newhash);
317 newOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
318 if (newOK)
320 int pos = cmdout.Find(_T(" "));
321 newTime = _ttoi(cmdout.Left(pos));
322 newsub = cmdout.Mid(pos + 1);
324 else
325 newsub = cmderr;
328 if (oldhash == GIT_REV_ZERO)
330 oldOK = true;
331 changeType = NewSubmodule;
333 else if (newhash == GIT_REV_ZERO)
335 newOK = true;
336 changeType = DeleteSubmodule;
338 else if (oldhash != newhash)
340 bool ffNewer = false, ffOlder = false;
341 ffNewer = subgit.IsFastForward(oldhash, newhash);
342 if (!ffNewer)
344 ffOlder = subgit.IsFastForward(newhash, oldhash);
345 if (!ffOlder)
347 if (newTime > oldTime)
348 changeType = NewerTime;
349 else if (newTime < oldTime)
350 changeType = OlderTime;
351 else
352 changeType = SameTime;
354 else
355 changeType = Rewind;
357 else
358 changeType = FastForward;
360 else if (oldhash == newhash)
361 changeType = Identical;
363 if (!oldOK || !newOK)
364 changeType = Unknown;
367 int CGitDiff::Diff(const CTGitPath * pPath, const CTGitPath * pPath2, git_revnum_t rev1, git_revnum_t rev2, bool /*blame*/, bool /*unified*/, int jumpToLine)
369 CString temppath;
370 GetTempPath(temppath);
372 // make sure we have HASHes here, otherwise filenames might be invalid
373 if (rev1 != GIT_REV_ZERO)
375 CGitHash rev1Hash;
376 if (g_Git.GetHash(rev1Hash, rev1))
378 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get hash of \"") + rev1 + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
379 return -1;
381 rev1 = rev1Hash.ToString();
383 if (rev2 != GIT_REV_ZERO)
385 CGitHash rev2Hash;
386 if (g_Git.GetHash(rev2Hash, rev2))
388 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get hash of \"") + rev2 + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
389 return -1;
391 rev2 = rev2Hash.ToString();
394 CString file1;
395 CString title1;
396 CString cmd;
398 if(pPath->IsDirectory() || pPath2->IsDirectory())
400 int result;
401 // refresh if result = 1
402 CTGitPath path = *pPath;
403 CTGitPath path2 = *pPath2;
404 while ((result = SubmoduleDiff(&path, &path2, rev1, rev2)) == 1)
406 path.SetFromGit(pPath->GetGitPathString());
407 path2.SetFromGit(pPath2->GetGitPathString());
409 return result;
412 if(rev1 != GIT_REV_ZERO )
414 TCHAR szTempName[MAX_PATH] = {0};
415 GetTempFileName(temppath, pPath->GetBaseFilename(), 0, szTempName);
416 CString temp(szTempName);
417 DeleteFile(szTempName);
418 CreateDirectory(szTempName, nullptr);
419 // use original file extension, an external diff tool might need it
420 file1.Format(_T("%s\\%s-%s-right%s"),
421 (LPCTSTR)temp,
422 (LPCTSTR)pPath->GetBaseFilename(),
423 (LPCTSTR)rev1.Left(g_Git.GetShortHASHLength()),
424 (LPCTSTR)pPath->GetFileExtension());
425 title1 = pPath->GetGitPathString() + L": " + rev1.Left(g_Git.GetShortHASHLength());
426 if (g_Git.GetOneFile(rev1, *pPath, file1))
428 CString out;
429 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath->GetGitPathString(), (LPCTSTR)rev1, (LPCTSTR)file1);
430 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_OK);
431 return -1;
433 ::SetFileAttributes(file1, FILE_ATTRIBUTE_READONLY);
435 else
437 file1 = g_Git.CombinePath(pPath);
438 title1.Format(IDS_DIFF_WCNAME, (LPCTSTR)pPath->GetGitPathString());
439 if (!PathFileExists(file1))
441 CString sMsg;
442 sMsg.Format(IDS_PROC_DIFFERROR_FILENOTINWORKINGTREE, (LPCTSTR)file1);
443 if (MessageBox(nullptr, sMsg, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_YESNO) != IDYES)
444 return 1;
445 if (!CCommonAppUtils::FileOpenSave(file1, nullptr, IDS_SELECTFILE, IDS_COMMONFILEFILTER, true))
446 return 1;
447 title1 = file1;
451 CString file2;
452 CString title2;
453 if(rev2 != GIT_REV_ZERO)
455 TCHAR szTempName[MAX_PATH] = {0};
456 GetTempFileName(temppath, pPath2->GetBaseFilename(), 0, szTempName);
457 CString temp(szTempName);
458 DeleteFile(szTempName);
459 CreateDirectory(szTempName, nullptr);
460 CTGitPath fileName = *pPath2;
461 if (pPath2->m_Action & CTGitPath::LOGACTIONS_REPLACED)
462 fileName = CTGitPath(pPath2->GetGitOldPathString());
464 // use original file extension, an external diff tool might need it
465 file2.Format(_T("%s\\%s-%s-left%s"),
466 (LPCTSTR)temp,
467 (LPCTSTR)fileName.GetBaseFilename(),
468 (LPCTSTR)rev2.Left(g_Git.GetShortHASHLength()),
469 (LPCTSTR)fileName.GetFileExtension());
470 title2 = fileName.GetGitPathString() + L": " + rev2.Left(g_Git.GetShortHASHLength());
471 if (g_Git.GetOneFile(rev2, fileName, file2))
473 CString out;
474 out.Format(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)pPath2->GetGitPathString(), (LPCTSTR)rev2, (LPCTSTR)file2);
475 CMessageBox::Show(nullptr, g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), _T("TortoiseGit"), MB_OK);
476 return -1;
478 ::SetFileAttributes(file2, FILE_ATTRIBUTE_READONLY);
480 else
482 file2 = g_Git.CombinePath(pPath2);
483 title2.Format(IDS_DIFF_WCNAME, pPath2->GetGitPathString());
486 CAppUtils::DiffFlags flags;
487 CAppUtils::StartExtDiff(file2,file1,
488 title2,
489 title1,
490 g_Git.CombinePath(pPath2),
491 g_Git.CombinePath(pPath),
492 rev2,
493 rev1,
494 flags, jumpToLine);
496 return 0;
499 int CGitDiff::DiffCommit(const CTGitPath &path, const GitRev *r1, const GitRev *r2)
501 return DiffCommit(path, path, r1, r2);
504 int CGitDiff::DiffCommit(const CTGitPath &path1, const CTGitPath &path2, const GitRev *r1, const GitRev *r2)
506 if (path1.GetWinPathString().IsEmpty())
508 CFileDiffDlg dlg;
509 dlg.SetDiff(nullptr, *r2, *r1);
510 dlg.DoModal();
512 else if (path1.IsDirectory())
514 CFileDiffDlg dlg;
515 dlg.SetDiff(&path1, *r2, *r1);
516 dlg.DoModal();
518 else
519 Diff(&path1, &path2, r1->m_CommitHash.ToString(), r2->m_CommitHash.ToString());
520 return 0;
523 int CGitDiff::DiffCommit(const CTGitPath &path, const CString &r1, const CString &r2)
525 return DiffCommit(path, path, r1, r2);
528 int CGitDiff::DiffCommit(const CTGitPath &path1, const CTGitPath &path2, const CString &r1, const CString &r2)
530 if (path1.GetWinPathString().IsEmpty())
532 CFileDiffDlg dlg;
533 dlg.SetDiff(nullptr, r2, r1);
534 dlg.DoModal();
536 else if (path1.IsDirectory())
538 CFileDiffDlg dlg;
539 dlg.SetDiff(&path1, r2, r1);
540 dlg.DoModal();
542 else
543 Diff(&path1, &path2, r1, r2);
544 return 0;