Reload TGitPath instances when pressing F5 to refresh Submodule Diff Dialog
[TortoiseGit.git] / src / TortoiseProc / GitDiff.cpp
blob961284b4ea40dd19e544a60306eb231128ecbca2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2013 - 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.
19 #include "stdafx.h"
20 #include "GitDiff.h"
21 #include "AppUtils.h"
22 #include "git.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(CTGitPath *pPath, 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\""), rev1, pPath->GetGitPathString());
46 else
47 cmd.Format(_T("git.exe ls-files -s -- \"%s\""), pPath->GetGitPathString());
49 CString output, err;
50 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
52 CMessageBox::Show(NULL, 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.m_CurrentDir+_T("\\")+pPath->GetWinPathString();
67 int encode=CAppUtils::GetLogOutputEncode(&subgit);
69 cmd.Format(_T("git.exe log -n1 --pretty=format:\"%%s\" %s"),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, CSubmoduleDiffDlg::NewSubmodule);
82 submoduleDiffDlg.DoModal();
83 if (submoduleDiffDlg.IsRefresh())
84 return 1;
86 return 0;
89 if (rev1 != GIT_REV_ZERO)
90 CMessageBox::Show(NULL, _T("ls-tree output format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
91 else
92 CMessageBox::Show(NULL, _T("ls-files output format error"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
93 return -1;
96 int CGitDiff::DiffNull(CTGitPath *pPath, git_revnum_t rev1,bool bIsAdd)
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(NULL, 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 while ((result = SubmoduleDiffNull(pPath, rev1)) == 1)
120 CString sPath = pPath->GetGitPathString();
121 pPath->SetFromGit(sPath); // pass by reference
123 return result;
126 if(rev1 != GIT_REV_ZERO )
128 TCHAR szTempName[MAX_PATH];
129 GetTempFileName(temppath, pPath->GetBaseFilename(), 0, szTempName);
130 CString temp(szTempName);
131 DeleteFile(szTempName);
132 CreateDirectory(szTempName, NULL);
133 file1.Format(_T("%s\\%s-%s%s"),
134 temp,
135 pPath->GetBaseFilename(),
136 rev1.Left(g_Git.GetShortHASHLength()),
137 pPath->GetFileExtension());
139 g_Git.GetOneFile(rev1,*pPath,file1);
141 else
143 file1=g_Git.m_CurrentDir+_T("\\")+pPath->GetWinPathString();
146 // preserve FileExtension, needed especially for diffing deleted images (detection on new filename extension)
147 CString tempfile=::GetTempFile() + pPath->GetFileExtension();
148 CStdioFile file(tempfile,CFile::modeReadWrite|CFile::modeCreate );
149 //file.WriteString();
150 file.Close();
151 ::SetFileAttributes(tempfile, FILE_ATTRIBUTE_READONLY);
153 CAppUtils::DiffFlags flags;
155 if(bIsAdd)
156 CAppUtils::StartExtDiff(tempfile,file1,
157 pPath->GetGitPathString(),
158 pPath->GetGitPathString() + _T(":") + rev1.Left(g_Git.GetShortHASHLength()),
159 g_Git.m_CurrentDir + _T("\\") + pPath->GetWinPathString(), g_Git.m_CurrentDir + _T("\\") + pPath->GetWinPathString(),
160 git_revnum_t(GIT_REV_ZERO), rev1
161 , flags);
162 else
163 CAppUtils::StartExtDiff(file1,tempfile,
164 pPath->GetGitPathString() + _T(":") + rev1.Left(g_Git.GetShortHASHLength()),
165 pPath->GetGitPathString(),
166 g_Git.m_CurrentDir + _T("\\") + pPath->GetWinPathString(), g_Git.m_CurrentDir + _T("\\") + pPath->GetWinPathString(),
167 rev1, git_revnum_t(GIT_REV_ZERO)
168 , flags);
170 return 0;
173 int CGitDiff::SubmoduleDiff(CTGitPath * pPath,CTGitPath * /*pPath2*/, git_revnum_t rev1, git_revnum_t rev2, bool /*blame*/, bool /*unified*/)
175 CString oldhash;
176 CString newhash;
177 bool dirty = false;
178 CString cmd;
179 bool isWorkingCopy = false;
180 if( rev2 == GIT_REV_ZERO || rev1 == GIT_REV_ZERO )
182 oldhash = GIT_REV_ZERO;
183 newhash = GIT_REV_ZERO;
185 CString rev;
186 if( rev2 != GIT_REV_ZERO )
187 rev = rev2;
188 if( rev1 != GIT_REV_ZERO )
189 rev = rev1;
191 isWorkingCopy = true;
193 cmd.Format(_T("git.exe diff %s -- \"%s\""),
194 rev,pPath->GetGitPathString());
196 CString output, err;
197 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
199 CMessageBox::Show(NULL, output + L"\n" + err, _T("TortoiseGit"), MB_OK|MB_ICONERROR);
200 return -1;
203 if (output.IsEmpty())
205 output.Empty();
206 err.Empty();
207 // also compare against index
208 cmd.Format(_T("git.exe diff \"%s\""), pPath->GetGitPathString());
209 if (g_Git.Run(cmd, &output, &err, CP_UTF8))
211 CMessageBox::Show(NULL, output + _T("\n") + err, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
212 return -1;
215 if (output.IsEmpty())
217 CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_ERR_EMPTYDIFF)), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
218 return -1;
220 else if (CMessageBox::Show(NULL, CString(MAKEINTRESOURCE(IDS_SUBMODULE_EMPTYDIFF)), _T("TortoiseGit"), 1, IDI_QUESTION, CString(MAKEINTRESOURCE(IDS_MSGBOX_YES)), CString(MAKEINTRESOURCE(IDS_MSGBOX_NO))) == 1)
222 CString sCmd;
223 sCmd.Format(_T("/command:subupdate /bkpath:\"%s\""), g_Git.m_CurrentDir);
224 CAppUtils::RunTortoiseGitProc(sCmd);
226 return -1;
229 int start =0;
230 int oldstart = output.Find(_T("-Subproject commit"),start);
231 if(oldstart<0)
233 CMessageBox::Show(NULL,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
234 return -1;
236 oldhash = output.Mid(oldstart+ CString(_T("-Subproject commit")).GetLength()+1,40);
237 start = 0;
238 int newstart = output.Find(_T("+Subproject commit"),start);
239 if(oldstart<0)
241 CMessageBox::Show(NULL,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
242 return -1;
244 newhash = output.Mid(newstart+ CString(_T("+Subproject commit")).GetLength()+1,40);
245 dirty = output.Mid(newstart + CString(_T("+Subproject commit")).GetLength() + 41) == _T("-dirty\n");
247 else
249 cmd.Format(_T("git.exe diff-tree -r -z %s %s -- \"%s\""),
250 rev2,rev1,pPath->GetGitPathString());
252 BYTE_VECTOR bytes, errBytes;
253 if(g_Git.Run(cmd, &bytes, &errBytes))
255 CString err;
256 g_Git.StringAppend(&err, &errBytes[0], CP_UTF8);
257 CMessageBox::Show(NULL,err,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
258 return -1;
261 g_Git.StringAppend(&oldhash, &bytes[15], CP_UTF8, 40);
262 g_Git.StringAppend(&newhash, &bytes[15+41], CP_UTF8, 40);
266 CString oldsub;
267 CString newsub;
268 bool oldOK = false, newOK = false;
270 CGit subgit;
271 subgit.m_CurrentDir=g_Git.m_CurrentDir+_T("\\")+pPath->GetWinPathString();
272 CSubmoduleDiffDlg::ChangeType changeType = CSubmoduleDiffDlg::Unknown;
274 if(pPath->HasAdminDir())
276 int encode=CAppUtils::GetLogOutputEncode(&subgit);
277 int oldTime = 0, newTime = 0;
279 if(oldhash != GIT_REV_ZERO)
281 CString cmdout, cmderr;
282 cmd.Format(_T("git log -n1 --pretty=format:\"%%ct %%s\" %s"), oldhash);
283 oldOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
284 if (oldOK)
286 int pos = cmdout.Find(_T(" "));
287 oldTime = _ttoi(cmdout.Left(pos));
288 oldsub = cmdout.Mid(pos + 1);
290 else
291 oldsub = cmderr;
293 if (newhash != GIT_REV_ZERO)
295 CString cmdout, cmderr;
296 cmd.Format(_T("git log -n1 --pretty=format:\"%%ct %%s\" %s"), newhash);
297 newOK = !subgit.Run(cmd, &cmdout, &cmderr, encode);
298 if (newOK)
300 int pos = cmdout.Find(_T(" "));
301 newTime = _ttoi(cmdout.Left(pos));
302 newsub = cmdout.Mid(pos + 1);
304 else
305 newsub = cmderr;
308 if (oldhash == GIT_REV_ZERO)
310 oldOK = true;
311 changeType = CSubmoduleDiffDlg::NewSubmodule;
313 else if (newhash == GIT_REV_ZERO)
315 newOK = true;
316 changeType = CSubmoduleDiffDlg::DeleteSubmodule;
318 else if (oldhash != newhash)
320 bool ffNewer = false, ffOlder = false;
321 ffNewer = subgit.IsFastForward(oldhash, newhash);
322 if (!ffNewer)
324 ffOlder = subgit.IsFastForward(newhash, oldhash);
325 if (!ffOlder)
327 if (newTime > oldTime)
328 changeType = CSubmoduleDiffDlg::NewerTime;
329 else if (newTime < oldTime)
330 changeType = CSubmoduleDiffDlg::OlderTime;
331 else
332 changeType = CSubmoduleDiffDlg::SameTime;
334 else
335 changeType = CSubmoduleDiffDlg::Rewind;
337 else
338 changeType = CSubmoduleDiffDlg::FastForward;
342 if (!oldOK || !newOK)
343 changeType = CSubmoduleDiffDlg::Unknown;
345 CSubmoduleDiffDlg submoduleDiffDlg;
346 submoduleDiffDlg.SetDiff(pPath->GetWinPath(), isWorkingCopy, oldhash, oldsub, oldOK, newhash, newsub, newOK, dirty, changeType);
347 submoduleDiffDlg.DoModal();
348 if (submoduleDiffDlg.IsRefresh())
349 return 1;
351 return 0;
354 int CGitDiff::Diff(CTGitPath * pPath,CTGitPath * pPath2, git_revnum_t rev1, git_revnum_t rev2, bool /*blame*/, bool /*unified*/)
356 CString temppath;
357 GetTempPath(temppath);
359 // make sure we have HASHes here, otherwise filenames might be invalid
360 if (rev1 != GIT_REV_ZERO)
362 CGitHash rev1Hash;
363 if (g_Git.GetHash(rev1Hash, rev1))
365 MessageBox(NULL, g_Git.GetGitLastErr(_T("Could not get hash of \"") + rev1 + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
366 return -1;
368 rev1 = rev1Hash.ToString();
370 if (rev2 != GIT_REV_ZERO)
372 CGitHash rev2Hash;
373 if (g_Git.GetHash(rev2Hash, rev2))
375 MessageBox(NULL, g_Git.GetGitLastErr(_T("Could not get hash of \"") + rev2 + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
376 return -1;
378 rev2 = rev2Hash.ToString();
381 CString file1;
382 CString title1;
383 CString cmd;
385 if(pPath->IsDirectory() || pPath2->IsDirectory())
387 int result;
388 // refresh if result = 1
389 while ((result = SubmoduleDiff(pPath, pPath2, rev1, rev2)) == 1)
391 CString sPath = pPath->GetGitPathString();
392 pPath->SetFromGit(sPath); // pass by reference
393 CString sPath2 = pPath2->GetGitPathString();
394 pPath2->SetFromGit(sPath2); // pass by reference
396 return result;
399 if(rev1 != GIT_REV_ZERO )
401 TCHAR szTempName[MAX_PATH];
402 GetTempFileName(temppath, pPath->GetBaseFilename(), 0, szTempName);
403 CString temp(szTempName);
404 DeleteFile(szTempName);
405 CreateDirectory(szTempName, NULL);
406 // use original file extension, an external diff tool might need it
407 file1.Format(_T("%s\\%s-%s-right%s"),
408 temp,
409 pPath->GetBaseFilename(),
410 rev1.Left(g_Git.GetShortHASHLength()),
411 pPath->GetFileExtension());
412 title1 = pPath->GetFileOrDirectoryName() + _T(":") + rev1.Left(g_Git.GetShortHASHLength());
413 g_Git.GetOneFile(rev1,*pPath,file1);
414 ::SetFileAttributes(file1, FILE_ATTRIBUTE_READONLY);
416 else
418 file1=g_Git.m_CurrentDir+_T("\\")+pPath->GetWinPathString();
419 title1.Format( IDS_DIFF_WCNAME, pPath->GetFileOrDirectoryName() );
420 if (!PathFileExists(file1))
422 CString sMsg;
423 sMsg.Format(IDS_PROC_DIFFERROR_FILENOTINWORKINGTREE, file1);
424 if (MessageBox(NULL, sMsg, _T("TortoiseGit"), MB_ICONEXCLAMATION | MB_YESNO) == IDNO)
425 return 1;
426 if (!CCommonAppUtils::FileOpenSave(file1, NULL, IDS_DIFF_WCNAME, IDS_COMMONFILEFILTER, true))
427 return 1;
428 title1.Format(IDS_DIFF_WCNAME, CTGitPath(file1).GetUIFileOrDirectoryName());
432 CString file2;
433 CString title2;
434 if(rev2 != GIT_REV_ZERO)
436 TCHAR szTempName[MAX_PATH];
437 GetTempFileName(temppath, pPath2->GetBaseFilename(), 0, szTempName);
438 CString temp(szTempName);
439 DeleteFile(szTempName);
440 CreateDirectory(szTempName, NULL);
441 CTGitPath fileName = *pPath2;
442 if (rev1 == GIT_REV_ZERO && pPath2->m_Action & CTGitPath::LOGACTIONS_REPLACED)
443 fileName = CTGitPath(pPath2->GetGitOldPathString());
445 // use original file extension, an external diff tool might need it
446 file2.Format(_T("%s\\%s-%s-left%s"),
447 temp,
448 fileName.GetBaseFilename(),
449 rev2.Left(g_Git.GetShortHASHLength()),
450 fileName.GetFileExtension());
451 title2 = fileName.GetFileOrDirectoryName() + _T(":") + rev2.Left(g_Git.GetShortHASHLength());
452 g_Git.GetOneFile(rev2, fileName, file2);
453 ::SetFileAttributes(file2, FILE_ATTRIBUTE_READONLY);
455 else
457 file2=g_Git.m_CurrentDir+_T("\\")+pPath2->GetWinPathString();
458 title2.Format( IDS_DIFF_WCNAME, pPath2->GetFileOrDirectoryName() );
461 if (pPath->m_Action == pPath->LOGACTIONS_ADDED)
463 CGitDiff::DiffNull(pPath, rev1, true);
465 else if (pPath->m_Action == pPath->LOGACTIONS_DELETED)
467 CGitDiff::DiffNull(pPath, rev2, false);
469 else
471 CAppUtils::DiffFlags flags;
472 CAppUtils::StartExtDiff(file2,file1,
473 title2,
474 title1,
475 g_Git.m_CurrentDir + _T("\\") + pPath2->GetWinPathString(),
476 g_Git.m_CurrentDir + _T("\\") + pPath->GetWinPathString(),
477 rev2,
478 rev1,
479 flags);
481 return 0;
484 int CGitDiff::DiffCommit(CTGitPath &path, GitRev *r1, GitRev *r2)
486 return DiffCommit(path, path, r1, r2);
489 int CGitDiff::DiffCommit(CTGitPath path1, CTGitPath path2, GitRev *r1, GitRev *r2)
491 if (path1.GetWinPathString().IsEmpty())
493 CFileDiffDlg dlg;
494 dlg.SetDiff(NULL, *r1, *r2);
495 dlg.DoModal();
497 else if (path1.IsDirectory())
499 CFileDiffDlg dlg;
500 dlg.SetDiff(&path1, *r1, *r2);
501 dlg.DoModal();
503 else
505 Diff(&path1, &path2, r1->m_CommitHash.ToString(), r2->m_CommitHash.ToString());
507 return 0;
510 int CGitDiff::DiffCommit(CTGitPath &path, CString r1, CString r2)
512 return DiffCommit(path, path, r1, r2);
516 int CGitDiff::DiffCommit(CTGitPath path1, CTGitPath path2, CString r1, CString r2)
518 if (path1.GetWinPathString().IsEmpty())
520 CFileDiffDlg dlg;
521 dlg.SetDiff(NULL, r1, r2);
522 dlg.DoModal();
524 else if (path1.IsDirectory())
526 CFileDiffDlg dlg;
527 dlg.SetDiff(&path1, r1, r2);
528 dlg.DoModal();
530 else
532 Diff(&path1, &path2, r1, r2);
534 return 0;