TortoiseGitMerge: Accept more patch files
[TortoiseGit.git] / src / Git / GitPatch.cpp
blob48104ba32fc8d900c2f12254008f991c3c11e99b
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.
20 #include "stdafx.h"
21 #include "resource.h"
22 #include "GitPatch.h"
23 #include "UnicodeUtils.h"
24 #include "SysProgressDlg.h"
25 #include "DirFileEnum.h"
26 #include "GitAdminDir.h"
27 #include "StringUtils.h"
29 #include "AppUtils.h"
31 #define STRIP_LIMIT 10
33 GitPatch::GitPatch()
34 : m_nStrip(0)
35 , m_bSuccessfullyPatched(false)
36 , m_nRejected(0)
37 , m_pProgDlg(NULL)
38 , m_patch()
42 GitPatch::~GitPatch()
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);
52 return 0;
55 m_errorStr.Empty();
56 m_patchfile = patchfile;
57 m_targetpath = targetpath;
58 m_testPath.Empty();
60 m_patchfile.Replace('\\', '/');
61 m_targetpath.Replace('\\', '/');
63 if (pPprogDlg)
65 pPprogDlg->SetTitle(IDS_APPNAME);
66 pPprogDlg->FormatNonPathLine(1, IDS_PATCH_PROGTITLE);
67 pPprogDlg->SetShowProgressBar(false);
68 pPprogDlg->ShowModeless(AfxGetMainWnd());
69 m_pProgDlg = pPprogDlg;
72 m_filePaths.clear();
73 m_nRejected = 0;
74 m_nStrip = 0;
76 // Read and try to apply patch
77 if (m_patch.OpenUnifiedDiffFile(m_patchfile) == FALSE)
79 m_errorStr = m_patch.GetErrorMessage();
80 m_filePaths.clear();
81 return 0;
83 if (!ApplyPatches())
85 m_filePaths.clear();
86 return 0;
89 m_pProgDlg = NULL;
91 if ((m_nRejected > ((int)m_filePaths.size() / 3)) && !m_testPath.IsEmpty())
93 m_nStrip++;
94 bool found = false;
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())
101 found = true;
102 m_nStrip--;
103 break;
106 if (found)
107 break;
111 if (m_nStrip == STRIP_LIMIT)
112 m_filePaths.clear();
113 else if (m_nStrip > 0)
115 m_filePaths.clear();
116 m_nRejected = 0;
118 if (!ApplyPatches())
120 m_filePaths.clear();
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))
132 return false;
135 return true;
138 bool GitPatch::PatchFile(int nIndex, CString &datapath)
140 CString sFilePath = m_patch.GetFullPath(datapath, nIndex);
141 CString sTempFile = CTempFiles::Instance().GetTempFilePathString();
143 PathRejects pr;
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);
149 if (m_pProgDlg)
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
157 //again...
158 CString sVersion = m_patch.GetRevision(nIndex);
160 CString sBaseFile;
161 if (sVersion == _T("0000000") || sFilePath == _T("NUL"))
162 sBaseFile = CTempFiles::Instance().GetTempFilePathString();
163 else
165 if (sVersion.IsEmpty())
167 m_errorStr.Empty();
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))
174 m_errorStr.Empty();
175 m_errorStr.Format(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND, (LPCTSTR)sVersion, (LPCTSTR)sFilePath);
177 return false;
181 if (m_pProgDlg)
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)
187 pr.rejects = 0;
188 else
190 pr.rejects = 1;
191 // rejectsPath should hold the absolute path to the reject files, but we do not support reject files ATM; also see changes FilePatchesDlg
192 pr.rejectsPath = m_patch.GetErrorMessage();
195 TRACE(_T("comparing %s and %s\nagainst the base file %s\n"), (LPCTSTR)sTempFile, (LPCTSTR)sFilePath, (LPCTSTR)sBaseFile);
197 else
199 //"dry run" was successful, so save the patched file somewhere...
200 pr.rejects = 0;
201 TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR)sFilePath, (LPCTSTR)sTempFile);
204 pr.resultPath = sTempFile;
205 pr.content = true;
206 pr.props = false;
207 // only add this entry if it hasn't been added already
208 bool bExists = false;
209 for (auto it = m_filePaths.rbegin(); it != m_filePaths.rend(); ++it)
211 if (it->path.Compare(pr.path) == 0)
213 bExists = true;
214 break;
217 if (!bExists)
218 m_filePaths.push_back(pr);
219 m_nRejected += pr.rejects;
221 return true;
224 CString GitPatch::GetPatchRejects(int nIndex) const
226 if (nIndex < 0)
227 return _T("");
228 if (nIndex < (int)m_filePaths.size())
230 return m_filePaths[nIndex].rejectsPath;
233 return _T("");
236 bool GitPatch::PatchPath(const CString& path)
238 m_errorStr.Empty();
240 m_patchfile.Replace('\\', '/');
241 m_targetpath.Replace('\\', '/');
243 m_filetopatch = path.Mid(m_targetpath.GetLength()+1);
244 m_filetopatch.Replace('\\', '/');
246 m_nRejected = 0;
248 m_errorStr = _T("NOT IMPLEMENTED");
249 return false;
253 int GitPatch::GetPatchResult(const CString& sPath, CString& sSavePath, CString& sRejectPath) const
255 for (std::vector<PathRejects>::const_iterator it = m_filePaths.begin(); it != m_filePaths.end(); ++it)
257 if (Strip(it->path).CompareNoCase(sPath)==0)
259 sSavePath = it->resultPath;
260 if (it->rejects > 0)
261 sRejectPath = it->rejectsPath;
262 else
263 sRejectPath.Empty();
264 return it->rejects;
267 return -1;
270 CString GitPatch::CheckPatchPath(const CString& path)
272 // first check if the path already matches
273 if (CountMatches(path) > (GetNumberOfFiles() / 3))
274 return path;
276 CSysProgressDlg progress;
277 CString tmp;
278 progress.SetTitle(IDS_PATCH_SEARCHPATHTITLE);
279 progress.SetShowProgressBar(false);
280 tmp.LoadString(IDS_PATCH_SEARCHPATHLINE1);
281 progress.SetLine(1, tmp);
282 progress.ShowModeless(AfxGetMainWnd());
284 // now go up the tree and try again
285 CString upperpath = path;
286 while (upperpath.ReverseFind('\\')>0)
288 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
289 progress.SetLine(2, upperpath, true);
290 if (progress.HasUserCancelled())
291 return path;
292 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
293 return upperpath;
295 // still no match found. So try sub folders
296 bool isDir = false;
297 CString subpath;
298 CDirFileEnum filefinder(path);
299 while (filefinder.NextFile(subpath, &isDir))
301 if (progress.HasUserCancelled())
302 return path;
303 if (!isDir)
304 continue;
305 if (g_GitAdminDir.IsAdminDirPath(subpath))
306 continue;
307 progress.SetLine(2, subpath, true);
308 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
309 return subpath;
312 // if a patch file only contains newly added files
313 // we can't really find the correct path.
314 // But: we can compare paths strings without the filenames
315 // and check if at least those match
316 upperpath = path;
317 while (upperpath.ReverseFind('\\')>0)
319 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
320 progress.SetLine(2, upperpath, true);
321 if (progress.HasUserCancelled())
322 return path;
323 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
324 return upperpath;
327 return path;
330 int GitPatch::CountMatches(const CString& path) const
332 int matches = 0;
333 for (int i=0; i<GetNumberOfFiles(); ++i)
335 CString temp = GetStrippedPath(i);
336 temp.Replace('/', '\\');
337 if ((PathIsRelative(temp)) ||
338 ((temp.GetLength() > 1) && (temp[0]=='\\') && (temp[1]!='\\')) )
339 temp = path + _T("\\")+ temp;
340 if (PathFileExists(temp))
341 matches++;
343 return matches;
346 int GitPatch::CountDirMatches(const CString& path) const
348 int matches = 0;
349 for (int i=0; i<GetNumberOfFiles(); ++i)
351 CString temp = GetStrippedPath(i);
352 temp.Replace('/', '\\');
353 if (PathIsRelative(temp))
354 temp = path + _T("\\")+ temp;
355 // remove the filename
356 temp = temp.Left(temp.ReverseFind('\\'));
357 if (PathFileExists(temp))
358 matches++;
360 return matches;
363 CString GitPatch::GetStrippedPath(int nIndex) const
365 if (nIndex < 0)
366 return _T("");
367 if (nIndex < (int)m_filePaths.size())
369 CString filepath = Strip(GetFilePath(nIndex));
370 return filepath;
373 return _T("");
376 CString GitPatch::Strip(const CString& filename) const
378 CString s = filename;
379 if ( m_nStrip>0 )
381 // Remove windows drive letter "c:"
382 if (s.GetLength()>2 && s[1]==':')
384 s = s.Mid(2);
387 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
389 // "/home/ts/my-working-copy/dir/file.txt"
390 // "home/ts/my-working-copy/dir/file.txt"
391 // "ts/my-working-copy/dir/file.txt"
392 // "my-working-copy/dir/file.txt"
393 // "dir/file.txt"
394 int p = s.FindOneOf(_T("/\\"));
395 if (p < 0)
397 s.Empty();
398 break;
400 s = s.Mid(p+1);
403 return s;
406 bool GitPatch::RemoveFile(const CString& /*path*/)
408 // Delete file in Git
409 // not necessary now, because TGit doesn't support the "missing file" status
410 return true;