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
);
72 CSubmoduleDiffDlg submoduleDiffDlg
;
73 submoduleDiffDlg
.SetDiff(pPath
->GetWinPath(), false, oldhash
, oldsub
, true, newhash
, newsub
, toOK
);
74 submoduleDiffDlg
.DoModal();
79 if (rev1
!= GIT_REV_ZERO
)
80 CMessageBox::Show(NULL
, _T("ls-tree output format error"), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
82 CMessageBox::Show(NULL
, _T("ls-files output format error"), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
86 int CGitDiff::DiffNull(CTGitPath
*pPath
, git_revnum_t rev1
,bool bIsAdd
)
89 GetTempPath(temppath
);
90 rev1
= g_Git
.GetHash(rev1
); // make sure we have a HASH here, otherwise filenames might be invalid
95 if(pPath
->IsDirectory())
98 return SubmoduleDiffNull(pPath
,rev1
);
101 if(rev1
!= GIT_REV_ZERO
)
103 TCHAR szTempName
[MAX_PATH
];
104 GetTempFileName(temppath
, pPath
->GetBaseFilename(), 0, szTempName
);
105 CString
temp(szTempName
);
106 DeleteFile(szTempName
);
107 CreateDirectory(szTempName
, NULL
);
108 file1
.Format(_T("%s\\%s-%s%s"),
110 pPath
->GetBaseFilename(),
111 rev1
.Left(g_Git
.GetShortHASHLength()),
112 pPath
->GetFileExtension());
114 g_Git
.GetOneFile(rev1
,*pPath
,file1
);
118 file1
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
121 // preserve FileExtension, needed especially for diffing deleted images (detection on new filename extension)
122 CString tempfile
=::GetTempFile() + pPath
->GetFileExtension();
123 CStdioFile
file(tempfile
,CFile::modeReadWrite
|CFile::modeCreate
);
124 //file.WriteString();
126 ::SetFileAttributes(tempfile
, FILE_ATTRIBUTE_READONLY
);
128 CAppUtils::DiffFlags flags
;
131 CAppUtils::StartExtDiff(tempfile
,file1
,
132 pPath
->GetGitPathString(),
133 pPath
->GetGitPathString() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength())
136 CAppUtils::StartExtDiff(file1
,tempfile
,
137 pPath
->GetGitPathString() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength())
138 ,pPath
->GetGitPathString(),flags
);
143 int CGitDiff::SubmoduleDiff(CTGitPath
* pPath
,CTGitPath
* /*pPath2*/, git_revnum_t rev1
, git_revnum_t rev2
, bool /*blame*/, bool /*unified*/)
148 bool isWorkingCopy
= false;
149 if( rev2
== GIT_REV_ZERO
|| rev1
== GIT_REV_ZERO
)
151 oldhash
= GIT_REV_ZERO
;
152 newhash
= GIT_REV_ZERO
;
155 if( rev2
!= GIT_REV_ZERO
)
157 if( rev1
!= GIT_REV_ZERO
)
160 isWorkingCopy
= true;
162 cmd
.Format(_T("git.exe diff %s -- \"%s\""),
163 rev
,pPath
->GetGitPathString());
166 if (g_Git
.Run(cmd
, &output
, &err
, CP_UTF8
))
168 CMessageBox::Show(NULL
, output
+ L
"\n" + err
, _T("TortoiseGit"), MB_OK
|MB_ICONERROR
);
172 int oldstart
= output
.Find(_T("-Subproject commit"),start
);
175 CMessageBox::Show(NULL
,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
178 oldhash
= output
.Mid(oldstart
+ CString(_T("-Subproject commit")).GetLength()+1,40);
180 int newstart
= output
.Find(_T("+Subproject commit"),start
);
183 CMessageBox::Show(NULL
,_T("Subproject Diff Format error") ,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
186 newhash
= output
.Mid(newstart
+ CString(_T("+Subproject commit")).GetLength()+1,40);
191 cmd
.Format(_T("git.exe diff-tree -r -z %s %s -- \"%s\""),
192 rev2
,rev1
,pPath
->GetGitPathString());
194 BYTE_VECTOR bytes
, errBytes
;
195 if(g_Git
.Run(cmd
, &bytes
, &errBytes
))
198 g_Git
.StringAppend(&err
, &errBytes
[0], CP_UTF8
);
199 CMessageBox::Show(NULL
,err
,_T("TortoiseGit"),MB_OK
|MB_ICONERROR
);
203 g_Git
.StringAppend(&oldhash
, &bytes
[15], CP_UTF8
, 40);
204 g_Git
.StringAppend(&newhash
, &bytes
[15+41], CP_UTF8
, 40);
210 bool oldOK
= false, newOK
= false;
213 subgit
.m_CurrentDir
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
215 if(pPath
->HasAdminDir())
217 int encode
=CAppUtils::GetLogOutputEncode(&subgit
);
219 if(oldhash
!= GIT_REV_ZERO
)
221 cmd
.Format(_T("git log -n1 --pretty=format:\"%%s\" %s"),oldhash
);
222 oldOK
= !subgit
.Run(cmd
,&oldsub
,encode
);
224 if(newsub
!= GIT_REV_ZERO
)
226 cmd
.Format(_T("git log -n1 --pretty=format:\"%%s\" %s"),newhash
);
227 newOK
= !subgit
.Run(cmd
,&newsub
,encode
);
231 CSubmoduleDiffDlg submoduleDiffDlg
;
232 submoduleDiffDlg
.SetDiff(pPath
->GetWinPath(), isWorkingCopy
, oldhash
, oldsub
, oldOK
, newhash
, newsub
, newOK
);
233 submoduleDiffDlg
.DoModal();
238 int CGitDiff::Diff(CTGitPath
* pPath
,CTGitPath
* pPath2
, git_revnum_t rev1
, git_revnum_t rev2
, bool /*blame*/, bool /*unified*/)
241 GetTempPath(temppath
);
243 // make sure we have HASHes here, otherwise filenames might be invalid
244 rev1
= g_Git
.GetHash(rev1
);
245 rev2
= g_Git
.GetHash(rev2
);
251 if(pPath
->IsDirectory() || pPath2
->IsDirectory())
253 return SubmoduleDiff(pPath
,pPath2
,rev1
,rev2
);
256 if(rev1
!= GIT_REV_ZERO
)
258 TCHAR szTempName
[MAX_PATH
];
259 GetTempFileName(temppath
, pPath
->GetBaseFilename(), 0, szTempName
);
260 CString
temp(szTempName
);
261 DeleteFile(szTempName
);
262 CreateDirectory(szTempName
, NULL
);
263 // use original file extension, an external diff tool might need it
264 file1
.Format(_T("%s\\%s-%s-right%s"),
266 pPath
->GetBaseFilename(),
267 rev1
.Left(g_Git
.GetShortHASHLength()),
268 pPath
->GetFileExtension());
269 title1
= pPath
->GetFileOrDirectoryName() + _T(":") + rev1
.Left(g_Git
.GetShortHASHLength());
270 g_Git
.GetOneFile(rev1
,*pPath
,file1
);
271 ::SetFileAttributes(file1
, FILE_ATTRIBUTE_READONLY
);
275 file1
=g_Git
.m_CurrentDir
+_T("\\")+pPath
->GetWinPathString();
276 title1
.Format( IDS_DIFF_WCNAME
, pPath
->GetFileOrDirectoryName() );
277 if (!PathFileExists(file1
))
280 sMsg
.Format(IDS_PROC_DIFFERROR_FILENOTINWORKINGTREE
, file1
);
281 if (MessageBox(NULL
, sMsg
, _T("TortoiseGit"), MB_ICONEXCLAMATION
| MB_OKCANCEL
) == IDCANCEL
)
283 if (!CCommonAppUtils::FileOpenSave(file1
, NULL
, IDS_DIFF_WCNAME
, IDS_COMMONFILEFILTER
, true))
285 title1
.Format(IDS_DIFF_WCNAME
, CTGitPath(file1
).GetUIFileOrDirectoryName());
291 if(rev2
!= GIT_REV_ZERO
)
293 TCHAR szTempName
[MAX_PATH
];
294 GetTempFileName(temppath
, pPath2
->GetBaseFilename(), 0, szTempName
);
295 CString
temp(szTempName
);
296 DeleteFile(szTempName
);
297 CreateDirectory(szTempName
, NULL
);
298 CTGitPath fileName
= *pPath2
;
299 if (rev1
== GIT_REV_ZERO
&& pPath2
->m_Action
& CTGitPath::LOGACTIONS_REPLACED
)
300 fileName
= CTGitPath(pPath2
->GetGitOldPathString());
302 // use original file extension, an external diff tool might need it
303 file2
.Format(_T("%s\\%s-%s-left%s"),
305 fileName
.GetBaseFilename(),
306 rev2
.Left(g_Git
.GetShortHASHLength()),
307 fileName
.GetFileExtension());
308 title2
= fileName
.GetFileOrDirectoryName() + _T(":") + rev2
.Left(g_Git
.GetShortHASHLength());
309 g_Git
.GetOneFile(rev2
, fileName
, file2
);
310 ::SetFileAttributes(file2
, FILE_ATTRIBUTE_READONLY
);
314 file2
=g_Git
.m_CurrentDir
+_T("\\")+pPath2
->GetWinPathString();
315 title2
.Format( IDS_DIFF_WCNAME
, pPath2
->GetFileOrDirectoryName() );
318 if (pPath
->m_Action
== pPath
->LOGACTIONS_ADDED
)
320 CGitDiff::DiffNull(pPath
, rev1
, true);
322 else if (pPath
->m_Action
== pPath
->LOGACTIONS_DELETED
)
324 CGitDiff::DiffNull(pPath
, rev2
, false);
328 CAppUtils::DiffFlags flags
;
329 CAppUtils::StartExtDiff(file2
,file1
,
337 int CGitDiff::DiffCommit(CTGitPath
&path
, GitRev
*r1
, GitRev
*r2
)
339 return DiffCommit(path
, path
, r1
, r2
);
342 int CGitDiff::DiffCommit(CTGitPath path1
, CTGitPath path2
, GitRev
*r1
, GitRev
*r2
)
344 if (path1
.GetWinPathString().IsEmpty())
347 dlg
.SetDiff(NULL
, *r1
, *r2
);
350 else if (path1
.IsDirectory())
353 dlg
.SetDiff(&path1
, *r1
, *r2
);
358 Diff(&path1
, &path2
, r1
->m_CommitHash
.ToString(), r2
->m_CommitHash
.ToString());
363 int CGitDiff::DiffCommit(CTGitPath
&path
, CString r1
, CString r2
)
365 return DiffCommit(path
, path
, r1
, r2
);
369 int CGitDiff::DiffCommit(CTGitPath path1
, CTGitPath path2
, CString r1
, CString r2
)
371 if (path1
.GetWinPathString().IsEmpty())
374 dlg
.SetDiff(NULL
, r1
, r2
);
377 else if (path1
.IsDirectory())
380 dlg
.SetDiff(&path1
, r1
, r2
);
385 Diff(&path1
, &path2
, r1
, r2
);