1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2012 - 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.
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
;
44 if (rev1
!= GIT_REV_ZERO
)
45 cmd
.Format(_T("git.exe ls-tree \"%s\" -- \"%s\""), rev1
, pPath
->GetGitPathString());
47 cmd
.Format(_T("git.exe ls-files -s -- \"%s\""), pPath
->GetGitPathString());
50 if (g_Git
.Run(cmd
, &output
, &err
, CP_UTF8
))
52 CMessageBox::Show(NULL
, output
+ L
"\n" + err
, _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
57 start
=output
.Find(_T(' '),start
);
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);
63 newhash
=output
.Mid(start
+1, 40);
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
);
73 if (rev1
== GIT_REV_ZERO
)
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();
87 if (rev1
!= GIT_REV_ZERO
)
88 CMessageBox::Show(NULL
, _T("ls-tree output format error"), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
90 CMessageBox::Show(NULL
, _T("ls-files output format error"), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
94 int CGitDiff::DiffNull(CTGitPath
*pPath
, git_revnum_t rev1
,bool bIsAdd
)
97 GetTempPath(temppath
);
98 rev1
= g_Git
.GetHash(rev1
); // make sure we have a HASH here, otherwise filenames might be invalid
103 if(pPath
->IsDirectory())
106 return SubmoduleDiffNull(pPath
,rev1
);
109 if(rev1
!= GIT_REV_ZERO
)
111 TCHAR szTempName
[MAX_PATH
];
112 GetTempFileName(temppath
, pPath
->GetBaseFilename(), 0, szTempName
);
113 CString
temp(szTempName
);
114 DeleteFile(szTempName
);
115 CreateDirectory(szTempName
, NULL
);
116 file1
.Format(_T("%s\\%s-%s%s"),
118 pPath
->GetBaseFilename(),
119 rev1
.Left(g_Git
.GetShortHASHLength()),
120 pPath
->GetFileExtension());
122 g_Git
.GetOneFile(rev1
,*pPath
,file1
);
126 file1
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
129 // preserve FileExtension, needed especially for diffing deleted images (detection on new filename extension)
130 CString tempfile
=::GetTempFile() + pPath
->GetFileExtension();
131 CStdioFile
file(tempfile
,CFile::modeReadWrite
|CFile::modeCreate
);
132 //file.WriteString();
134 ::SetFileAttributes(tempfile
, FILE_ATTRIBUTE_READONLY
);
136 CAppUtils::DiffFlags flags
;
139 CAppUtils::StartExtDiff(tempfile
,file1
,
140 pPath
->GetGitPathString(),
141 pPath
->GetGitPathString() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength()),
142 g_Git
.m_CurrentDir
+ _T("\\") + pPath
->GetWinPathString(), g_Git
.m_CurrentDir
+ _T("\\") + pPath
->GetWinPathString(),
143 git_revnum_t(GIT_REV_ZERO
), rev1
146 CAppUtils::StartExtDiff(file1
,tempfile
,
147 pPath
->GetGitPathString() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength()),
148 pPath
->GetGitPathString(),
149 g_Git
.m_CurrentDir
+ _T("\\") + pPath
->GetWinPathString(), g_Git
.m_CurrentDir
+ _T("\\") + pPath
->GetWinPathString(),
150 rev1
, git_revnum_t(GIT_REV_ZERO
)
156 int CGitDiff::SubmoduleDiff(CTGitPath
* pPath
,CTGitPath
* /*pPath2*/, git_revnum_t rev1
, git_revnum_t rev2
, bool /*blame*/, bool /*unified*/)
162 bool isWorkingCopy
= false;
163 if( rev2
== GIT_REV_ZERO
|| rev1
== GIT_REV_ZERO
)
165 oldhash
= GIT_REV_ZERO
;
166 newhash
= GIT_REV_ZERO
;
169 if( rev2
!= GIT_REV_ZERO
)
171 if( rev1
!= GIT_REV_ZERO
)
174 isWorkingCopy
= true;
176 cmd
.Format(_T("git.exe diff %s -- \"%s\""),
177 rev
,pPath
->GetGitPathString());
180 if (g_Git
.Run(cmd
, &output
, &err
, CP_UTF8
))
182 CMessageBox::Show(NULL
, output
+ L
"\n" + err
, _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
186 int oldstart
= output
.Find(_T("-Subproject commit"),start
);
189 CMessageBox::Show(NULL
,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
192 oldhash
= output
.Mid(oldstart
+ CString(_T("-Subproject commit")).GetLength()+1,40);
194 int newstart
= output
.Find(_T("+Subproject commit"),start
);
197 CMessageBox::Show(NULL
,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
200 newhash
= output
.Mid(newstart
+ CString(_T("+Subproject commit")).GetLength()+1,40);
201 dirty
= output
.Mid(newstart
+ CString(_T("+Subproject commit")).GetLength() + 41) == _T("-dirty\n");
205 cmd
.Format(_T("git.exe diff-tree -r -z %s %s -- \"%s\""),
206 rev2
,rev1
,pPath
->GetGitPathString());
208 BYTE_VECTOR bytes
, errBytes
;
209 if(g_Git
.Run(cmd
, &bytes
, &errBytes
))
212 g_Git
.StringAppend(&err
, &errBytes
[0], CP_UTF8
);
213 CMessageBox::Show(NULL
,err
,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
217 g_Git
.StringAppend(&oldhash
, &bytes
[15], CP_UTF8
, 40);
218 g_Git
.StringAppend(&newhash
, &bytes
[15+41], CP_UTF8
, 40);
224 bool oldOK
= false, newOK
= false;
227 subgit
.m_CurrentDir
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
228 CSubmoduleDiffDlg::ChangeType changeType
= CSubmoduleDiffDlg::Unknown
;
230 if(pPath
->HasAdminDir())
232 int encode
=CAppUtils::GetLogOutputEncode(&subgit
);
233 int oldTime
= 0, newTime
= 0;
235 if(oldhash
!= GIT_REV_ZERO
)
237 CString cmdout
, cmderr
;
238 cmd
.Format(_T("git log -n1 --pretty=format:\"%%ct %%s\" %s"), oldhash
);
239 oldOK
= !subgit
.Run(cmd
, &cmdout
, &cmderr
, encode
);
242 int pos
= cmdout
.Find(_T(" "));
243 oldTime
= _ttoi(cmdout
.Left(pos
));
244 oldsub
= cmdout
.Mid(pos
+ 1);
249 if (newhash
!= GIT_REV_ZERO
)
251 CString cmdout
, cmderr
;
252 cmd
.Format(_T("git log -n1 --pretty=format:\"%%ct %%s\" %s"), newhash
);
253 newOK
= !subgit
.Run(cmd
, &cmdout
, &cmderr
, encode
);
256 int pos
= cmdout
.Find(_T(" "));
257 newTime
= _ttoi(cmdout
.Left(pos
));
258 newsub
= cmdout
.Mid(pos
+ 1);
264 if (oldhash
== GIT_REV_ZERO
)
267 changeType
= CSubmoduleDiffDlg::NewSubmodule
;
269 else if (newhash
== GIT_REV_ZERO
)
272 changeType
= CSubmoduleDiffDlg::DeleteSubmodule
;
274 else if (oldhash
!= newhash
)
276 bool ffNewer
= false, ffOlder
= false;
277 ffNewer
= subgit
.IsFastForward(oldhash
, newhash
);
280 ffOlder
= subgit
.IsFastForward(newhash
, oldhash
);
283 if (newTime
> oldTime
)
284 changeType
= CSubmoduleDiffDlg::NewerTime
;
285 else if (newTime
< oldTime
)
286 changeType
= CSubmoduleDiffDlg::OlderTime
;
288 changeType
= CSubmoduleDiffDlg::SameTime
;
291 changeType
= CSubmoduleDiffDlg::Rewind
;
294 changeType
= CSubmoduleDiffDlg::FastForward
;
298 CSubmoduleDiffDlg submoduleDiffDlg
;
299 submoduleDiffDlg
.SetDiff(pPath
->GetWinPath(), isWorkingCopy
, oldhash
, oldsub
, oldOK
, newhash
, newsub
, newOK
, dirty
, changeType
);
300 submoduleDiffDlg
.DoModal();
305 int CGitDiff::Diff(CTGitPath
* pPath
,CTGitPath
* pPath2
, git_revnum_t rev1
, git_revnum_t rev2
, bool /*blame*/, bool /*unified*/)
308 GetTempPath(temppath
);
310 // make sure we have HASHes here, otherwise filenames might be invalid
311 rev1
= g_Git
.GetHash(rev1
);
312 rev2
= g_Git
.GetHash(rev2
);
318 if(pPath
->IsDirectory() || pPath2
->IsDirectory())
320 return SubmoduleDiff(pPath
,pPath2
,rev1
,rev2
);
323 if(rev1
!= GIT_REV_ZERO
)
325 TCHAR szTempName
[MAX_PATH
];
326 GetTempFileName(temppath
, pPath
->GetBaseFilename(), 0, szTempName
);
327 CString
temp(szTempName
);
328 DeleteFile(szTempName
);
329 CreateDirectory(szTempName
, NULL
);
330 // use original file extension, an external diff tool might need it
331 file1
.Format(_T("%s\\%s-%s-right%s"),
333 pPath
->GetBaseFilename(),
334 rev1
.Left(g_Git
.GetShortHASHLength()),
335 pPath
->GetFileExtension());
336 title1
= pPath
->GetFileOrDirectoryName() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength());
337 g_Git
.GetOneFile(rev1
,*pPath
,file1
);
338 ::SetFileAttributes(file1
, FILE_ATTRIBUTE_READONLY
);
342 file1
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
343 title1
.Format( IDS_DIFF_WCNAME
, pPath
->GetFileOrDirectoryName() );
344 if (!PathFileExists(file1
))
347 sMsg
.Format(IDS_PROC_DIFFERROR_FILENOTINWORKINGTREE
, file1
);
348 if (MessageBox(NULL
, sMsg
, _T("TortoiseGit"), MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
350 if (!CCommonAppUtils::FileOpenSave(file1
, NULL
, IDS_DIFF_WCNAME
, IDS_COMMONFILEFILTER
, true))
352 title1
.Format(IDS_DIFF_WCNAME
, CTGitPath(file1
).GetUIFileOrDirectoryName());
358 if(rev2
!= GIT_REV_ZERO
)
360 TCHAR szTempName
[MAX_PATH
];
361 GetTempFileName(temppath
, pPath2
->GetBaseFilename(), 0, szTempName
);
362 CString
temp(szTempName
);
363 DeleteFile(szTempName
);
364 CreateDirectory(szTempName
, NULL
);
365 CTGitPath fileName
= *pPath2
;
366 if (rev1
== GIT_REV_ZERO
&& pPath2
->m_Action
& CTGitPath::LOGACTIONS_REPLACED
)
367 fileName
= CTGitPath(pPath2
->GetGitOldPathString());
369 // use original file extension, an external diff tool might need it
370 file2
.Format(_T("%s\\%s-%s-left%s"),
372 fileName
.GetBaseFilename(),
373 rev2
.Left(g_Git
.GetShortHASHLength()),
374 fileName
.GetFileExtension());
375 title2
= fileName
.GetFileOrDirectoryName() + _T(":") + rev2
.Left(g_Git
.GetShortHASHLength());
376 g_Git
.GetOneFile(rev2
, fileName
, file2
);
377 ::SetFileAttributes(file2
, FILE_ATTRIBUTE_READONLY
);
381 file2
=g_Git
.m_CurrentDir
+_T("\\")+pPath2
->GetWinPathString();
382 title2
.Format( IDS_DIFF_WCNAME
, pPath2
->GetFileOrDirectoryName() );
385 if (pPath
->m_Action
== pPath
->LOGACTIONS_ADDED
)
387 CGitDiff::DiffNull(pPath
, rev1
, true);
389 else if (pPath
->m_Action
== pPath
->LOGACTIONS_DELETED
)
391 CGitDiff::DiffNull(pPath
, rev2
, false);
395 CAppUtils::DiffFlags flags
;
396 CAppUtils::StartExtDiff(file2
,file1
,
399 g_Git
.m_CurrentDir
+ _T("\\") + pPath2
->GetWinPathString(),
400 g_Git
.m_CurrentDir
+ _T("\\") + pPath
->GetWinPathString(),
408 int CGitDiff::DiffCommit(CTGitPath
&path
, GitRev
*r1
, GitRev
*r2
)
410 return DiffCommit(path
, path
, r1
, r2
);
413 int CGitDiff::DiffCommit(CTGitPath path1
, CTGitPath path2
, GitRev
*r1
, GitRev
*r2
)
415 if (path1
.GetWinPathString().IsEmpty())
418 dlg
.SetDiff(NULL
, *r1
, *r2
);
421 else if (path1
.IsDirectory())
424 dlg
.SetDiff(&path1
, *r1
, *r2
);
429 Diff(&path1
, &path2
, r1
->m_CommitHash
.ToString(), r2
->m_CommitHash
.ToString());
434 int CGitDiff::DiffCommit(CTGitPath
&path
, CString r1
, CString r2
)
436 return DiffCommit(path
, path
, r1
, r2
);
440 int CGitDiff::DiffCommit(CTGitPath path1
, CTGitPath path2
, CString r1
, CString r2
)
442 if (path1
.GetWinPathString().IsEmpty())
445 dlg
.SetDiff(NULL
, r1
, r2
);
448 else if (path1
.IsDirectory())
451 dlg
.SetDiff(&path1
, r1
, r2
);
456 Diff(&path1
, &path2
, r1
, r2
);