1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2012-2013 - TortoiseGit
4 // Copyright (C) 2010-2012 - TortoiseSVN
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.
23 #include "UnicodeUtils.h"
24 #include "SysProgressDlg.h"
25 #include "DirFileEnum.h"
26 #include "GitAdminDir.h"
27 #include "StringUtils.h"
31 #define STRIP_LIMIT 10
35 , m_bSuccessfullyPatched(false)
46 int GitPatch::Init(const CString
& patchfile
, const CString
& targetpath
, CSysProgressDlg
*pPprogDlg
)
48 CTGitPath target
= CTGitPath(targetpath
);
49 if (patchfile
.IsEmpty() || targetpath
.IsEmpty())
51 m_errorStr
.LoadString(IDS_ERR_PATCHPATHS
);
56 m_patchfile
= patchfile
;
57 m_targetpath
= targetpath
;
60 m_patchfile
.Replace('\\', '/');
61 m_targetpath
.Replace('\\', '/');
65 pPprogDlg
->SetTitle(IDS_APPNAME
);
66 pPprogDlg
->FormatNonPathLine(1, IDS_PATCH_PROGTITLE
);
67 pPprogDlg
->SetShowProgressBar(false);
68 pPprogDlg
->ShowModeless(AfxGetMainWnd());
69 m_pProgDlg
= pPprogDlg
;
76 // Read and try to apply patch
77 if (m_patch
.OpenUnifiedDiffFile(m_patchfile
) == FALSE
)
79 m_errorStr
= m_patch
.GetErrorMessage();
91 if ((m_nRejected
> ((int)m_filePaths
.size() / 3)) && !m_testPath
.IsEmpty())
95 for (m_nStrip
= 0; m_nStrip
< STRIP_LIMIT
; ++m_nStrip
)
97 for (std::vector
<PathRejects
>::iterator it
= m_filePaths
.begin(); it
!= m_filePaths
.end(); ++it
)
99 if (Strip(it
->path
).IsEmpty())
111 if (m_nStrip
== STRIP_LIMIT
)
113 else if (m_nStrip
> 0)
123 return (int)m_filePaths
.size();
126 bool GitPatch::ApplyPatches()
129 for (int i
= 0; i
< m_patch
.GetNumberOfFiles(); ++i
)
131 if (!PatchFile(i
, m_targetpath
))
138 bool GitPatch::PatchFile(int nIndex
, CString
&datapath
)
140 CString sFilePath
= m_patch
.GetFullPath(datapath
, nIndex
);
141 CString sTempFile
= CTempFiles::Instance().GetTempFilePathString();
144 m_testPath
= m_patch
.GetFilename2(nIndex
);
145 pr
.path
= m_patch
.GetFilename2(nIndex
);
146 if (pr
.path
== _T("NUL"))
147 pr
.path
= m_patch
.GetFilename(nIndex
);
150 m_pProgDlg
->FormatPathLine(2, IDS_PATCH_PATHINGFILE
, pr
.path
);
152 //first, do a "dry run" of patching against the file in place...
153 if (!m_patch
.PatchFile(m_nStrip
, nIndex
, datapath
, sTempFile
))
155 //patching not successful, so retrieve the
156 //base file from version control and try
158 CString sVersion
= m_patch
.GetRevision(nIndex
);
161 if (sVersion
== _T("0000000") || sFilePath
== _T("NUL"))
162 sBaseFile
= CTempFiles::Instance().GetTempFilePathString();
165 if (sVersion
.IsEmpty())
168 m_errorStr
.Format(IDS_ERR_MAINFRAME_FILECONFLICTNOVERSION
, (LPCTSTR
)sFilePath
);
169 return false; // cannot apply patch which does not apply cleanly w/o git information in patch file.
171 sBaseFile
= CTempFiles::Instance().GetTempFilePathString();
172 if (!CAppUtils::GetVersionedFile(sFilePath
, sVersion
, sBaseFile
, m_pProgDlg
))
175 m_errorStr
.Format(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND
, (LPCTSTR
)sVersion
, (LPCTSTR
)sFilePath
);
182 m_pProgDlg
->FormatPathLine(2, IDS_PATCH_PATHINGFILE
, pr
.path
);
184 int patchtry
= m_patch
.PatchFile(m_nStrip
, nIndex
, datapath
, sTempFile
, sBaseFile
, true);
186 if (patchtry
== TRUE
)
189 pr
.basePath
= sBaseFile
;
194 // rejectsPath should hold the absolute path to the reject files, but we do not support reject files ATM; also see changes FilePatchesDlg
195 pr
.rejectsPath
= m_patch
.GetErrorMessage();
198 TRACE(_T("comparing %s and %s\nagainst the base file %s\n"), (LPCTSTR
)sTempFile
, (LPCTSTR
)sFilePath
, (LPCTSTR
)sBaseFile
);
202 //"dry run" was successful, so save the patched file somewhere...
204 TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR
)sFilePath
, (LPCTSTR
)sTempFile
);
207 pr
.resultPath
= sTempFile
;
210 // only add this entry if it hasn't been added already
211 bool bExists
= false;
212 for (auto it
= m_filePaths
.rbegin(); it
!= m_filePaths
.rend(); ++it
)
214 if (it
->path
.Compare(pr
.path
) == 0)
221 m_filePaths
.push_back(pr
);
222 m_nRejected
+= pr
.rejects
;
227 CString
GitPatch::GetPatchRejects(int nIndex
) const
231 if (nIndex
< (int)m_filePaths
.size())
233 return m_filePaths
[nIndex
].rejectsPath
;
239 bool GitPatch::PatchPath(const CString
& path
)
243 m_patchfile
.Replace('\\', '/');
244 m_targetpath
.Replace('\\', '/');
246 m_filetopatch
= path
.Mid(m_targetpath
.GetLength()+1);
247 m_filetopatch
.Replace('\\', '/');
251 m_errorStr
= _T("NOT IMPLEMENTED");
256 int GitPatch::GetPatchResult(const CString
& sPath
, CString
& sSavePath
, CString
& sRejectPath
, CString
&sBasePath
) const
258 for (std::vector
<PathRejects
>::const_iterator it
= m_filePaths
.begin(); it
!= m_filePaths
.end(); ++it
)
260 if (Strip(it
->path
).CompareNoCase(sPath
)==0)
262 sSavePath
= it
->resultPath
;
263 sBasePath
= it
->basePath
;
265 sRejectPath
= it
->rejectsPath
;
274 CString
GitPatch::CheckPatchPath(const CString
& path
)
276 // first check if the path already matches
277 if (CountMatches(path
) > (GetNumberOfFiles() / 3))
280 CSysProgressDlg progress
;
282 progress
.SetTitle(IDS_PATCH_SEARCHPATHTITLE
);
283 progress
.SetShowProgressBar(false);
284 tmp
.LoadString(IDS_PATCH_SEARCHPATHLINE1
);
285 progress
.SetLine(1, tmp
);
286 progress
.ShowModeless(AfxGetMainWnd());
288 // now go up the tree and try again
289 CString upperpath
= path
;
290 while (upperpath
.ReverseFind('\\')>0)
292 upperpath
= upperpath
.Left(upperpath
.ReverseFind('\\'));
293 progress
.SetLine(2, upperpath
, true);
294 if (progress
.HasUserCancelled())
296 if (CountMatches(upperpath
) > (GetNumberOfFiles()/3))
299 // still no match found. So try sub folders
302 CDirFileEnum
filefinder(path
);
303 while (filefinder
.NextFile(subpath
, &isDir
))
305 if (progress
.HasUserCancelled())
309 if (g_GitAdminDir
.IsAdminDirPath(subpath
))
311 progress
.SetLine(2, subpath
, true);
312 if (CountMatches(subpath
) > (GetNumberOfFiles()/3))
316 // if a patch file only contains newly added files
317 // we can't really find the correct path.
318 // But: we can compare paths strings without the filenames
319 // and check if at least those match
321 while (upperpath
.ReverseFind('\\')>0)
323 upperpath
= upperpath
.Left(upperpath
.ReverseFind('\\'));
324 progress
.SetLine(2, upperpath
, true);
325 if (progress
.HasUserCancelled())
327 if (CountDirMatches(upperpath
) > (GetNumberOfFiles()/3))
334 int GitPatch::CountMatches(const CString
& path
) const
337 for (int i
=0; i
<GetNumberOfFiles(); ++i
)
339 CString temp
= GetStrippedPath(i
);
340 temp
.Replace('/', '\\');
341 if ((PathIsRelative(temp
)) ||
342 ((temp
.GetLength() > 1) && (temp
[0]=='\\') && (temp
[1]!='\\')) )
343 temp
= path
+ _T("\\")+ temp
;
344 if (PathFileExists(temp
))
350 int GitPatch::CountDirMatches(const CString
& path
) const
353 for (int i
=0; i
<GetNumberOfFiles(); ++i
)
355 CString temp
= GetStrippedPath(i
);
356 temp
.Replace('/', '\\');
357 if (PathIsRelative(temp
))
358 temp
= path
+ _T("\\")+ temp
;
359 // remove the filename
360 temp
= temp
.Left(temp
.ReverseFind('\\'));
361 if (PathFileExists(temp
))
367 CString
GitPatch::GetStrippedPath(int nIndex
) const
371 if (nIndex
< (int)m_filePaths
.size())
373 CString filepath
= Strip(GetFilePath(nIndex
));
380 CString
GitPatch::Strip(const CString
& filename
) const
382 CString s
= filename
;
385 // Remove windows drive letter "c:"
386 if (s
.GetLength()>2 && s
[1]==':')
391 for (int nStrip
= 1; nStrip
<= m_nStrip
; ++nStrip
)
393 // "/home/ts/my-working-copy/dir/file.txt"
394 // "home/ts/my-working-copy/dir/file.txt"
395 // "ts/my-working-copy/dir/file.txt"
396 // "my-working-copy/dir/file.txt"
398 int p
= s
.FindOneOf(_T("/\\"));
410 bool GitPatch::RemoveFile(const CString
& /*path*/)
412 // Delete file in Git
413 // not necessary now, because TGit doesn't support the "missing file" status