CPatch: New memory management
[TortoiseGit.git] / src / Git / GitPatch.cpp
blob9ea8e226780250d18f9c8c2c6850ac031f1afc88
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2012-2013, 2015-2018 - 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(nullptr)
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 = nullptr;
91 if ((m_nRejected > ((int)m_filePaths.size() / 3)) && !m_testPath.IsEmpty())
93 ++m_nStrip;
94 for (m_nStrip = 0; m_nStrip < STRIP_LIMIT; ++m_nStrip)
96 if (std::any_of(m_filePaths.cbegin(), m_filePaths.cend(), [this](auto& filepath) { return Strip(filepath.path).IsEmpty(); }))
98 --m_nStrip;
99 break;
104 if (m_nStrip == STRIP_LIMIT)
105 m_filePaths.clear();
106 else if (m_nStrip > 0)
108 m_filePaths.clear();
109 m_nRejected = 0;
111 if (!ApplyPatches())
112 m_filePaths.clear();
114 return (int)m_filePaths.size();
117 bool GitPatch::ApplyPatches()
119 for (int i = 0; i < m_patch.GetNumberOfFiles(); ++i)
121 if (!PatchFile(i, m_targetpath))
122 return false;
125 return true;
128 bool GitPatch::PatchFile(int nIndex, CString &datapath)
130 CString sFilePath = m_patch.GetFullPath(datapath, nIndex);
131 CString sTempFile = CTempFiles::Instance().GetTempFilePathString();
133 PathRejects pr;
134 m_testPath = m_patch.GetFilename2(nIndex);
135 pr.path = m_patch.GetFilename2(nIndex);
136 if (pr.path == L"NUL")
137 pr.path = m_patch.GetFilename(nIndex);
139 if (m_pProgDlg)
140 m_pProgDlg->FormatPathLine(2, IDS_PATCH_PATHINGFILE, (LPCTSTR)pr.path);
142 //first, do a "dry run" of patching against the file in place...
143 if (!m_patch.PatchFile(m_nStrip, nIndex, datapath, sTempFile))
145 //patching not successful, so retrieve the
146 //base file from version control and try
147 //again...
148 CString sVersion = m_patch.GetRevision(nIndex);
150 CString sBaseFile;
151 if ((sVersion.GetLength() >= 7 && wcsncmp(sVersion, GIT_REV_ZERO, sVersion.GetLength()) == 0) || sFilePath == L"NUL")
152 sBaseFile = CTempFiles::Instance().GetTempFilePathString();
153 else
155 if (sVersion.IsEmpty())
157 m_errorStr.Format(IDS_ERR_MAINFRAME_FILECONFLICTNOVERSION, (LPCTSTR)sFilePath);
158 return false; // cannot apply patch which does not apply cleanly w/o git information in patch file.
160 sBaseFile = CTempFiles::Instance().GetTempFilePathString();
161 if (!CAppUtils::GetVersionedFile(sFilePath, sVersion, sBaseFile, m_pProgDlg))
163 m_errorStr.FormatMessage(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND, (LPCTSTR)sVersion, (LPCTSTR)sFilePath);
165 return false;
169 if (m_pProgDlg)
170 m_pProgDlg->FormatPathLine(2, IDS_PATCH_PATHINGFILE, (LPCTSTR)pr.path);
172 int patchtry = m_patch.PatchFile(m_nStrip, nIndex, datapath, sTempFile, sBaseFile, true);
174 if (patchtry == TRUE)
176 pr.rejects = 0;
177 pr.basePath = sBaseFile;
179 else
181 pr.rejects = 1;
182 // rejectsPath should hold the absolute path to the reject files, but we do not support reject files ATM; also see changes FilePatchesDlg
183 pr.rejectsPath = m_patch.GetErrorMessage();
186 TRACE(L"comparing %s and %s\nagainst the base file %s\n", (LPCTSTR)sTempFile, (LPCTSTR)sFilePath, (LPCTSTR)sBaseFile);
188 else
190 //"dry run" was successful, so save the patched file somewhere...
191 pr.rejects = 0;
192 TRACE(L"comparing %s\nwith the patched result %s\n", (LPCTSTR)sFilePath, (LPCTSTR)sTempFile);
195 pr.resultPath = sTempFile;
196 pr.content = true;
197 pr.props = false;
198 // only add this entry if it hasn't been added already
199 if (std::none_of(m_filePaths.crbegin(), m_filePaths.crend(), [&pr](auto& filepath) { return filepath.path.Compare(pr.path) == 0; }))
200 m_filePaths.push_back(pr);
201 m_nRejected += pr.rejects;
203 return true;
206 CString GitPatch::GetPatchRejects(int nIndex) const
208 if (nIndex < 0)
209 return L"";
210 if (nIndex < (int)m_filePaths.size())
211 return m_filePaths[nIndex].rejectsPath;
213 return L"";
216 bool GitPatch::PatchPath(const CString& path)
218 m_errorStr.Empty();
220 m_patchfile.Replace(L'\\', L'/');
221 m_targetpath.Replace(L'\\', L'/');
223 m_filetopatch = path.Mid(m_targetpath.GetLength()+1);
224 m_filetopatch.Replace(L'\\', L'/');
226 m_nRejected = 0;
228 m_errorStr = L"NOT IMPLEMENTED";
229 return false;
233 int GitPatch::GetPatchResult(const CString& sPath, CString& sSavePath, CString& sRejectPath, CString &sBasePath) const
235 auto it = std::find_if(m_filePaths.cbegin(), m_filePaths.cend(), [this, &sPath](auto& filePath) { return Strip(filePath.path).CompareNoCase(sPath) == 0; });
236 if (it != m_filePaths.cend())
238 sSavePath = (*it).resultPath;
239 sBasePath = (*it).basePath;
240 if ((*it).rejects > 0)
241 sRejectPath = (*it).rejectsPath;
242 else
243 sRejectPath.Empty();
244 return (*it).rejects;
246 return -1;
249 CString GitPatch::CheckPatchPath(const CString& path)
251 // first check if the path already matches
252 if (CountMatches(path) > (GetNumberOfFiles() / 3))
253 return path;
255 CSysProgressDlg progress;
256 CString tmp;
257 progress.SetTitle(IDS_PATCH_SEARCHPATHTITLE);
258 progress.SetShowProgressBar(false);
259 tmp.LoadString(IDS_PATCH_SEARCHPATHLINE1);
260 progress.SetLine(1, tmp);
261 progress.ShowModeless(AfxGetMainWnd());
263 // now go up the tree and try again
264 CString upperpath = path;
265 int trimPos;
266 while ((trimPos = upperpath.ReverseFind(L'\\')) > 0)
268 upperpath.Truncate(trimPos);
269 progress.SetLine(2, upperpath, true);
270 if (progress.HasUserCancelled())
271 return path;
272 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
273 return upperpath;
275 // still no match found. So try sub folders
276 bool isDir = false;
277 CString subpath;
278 CDirFileEnum filefinder(path);
279 while (filefinder.NextFile(subpath, &isDir))
281 if (progress.HasUserCancelled())
282 return path;
283 if (!isDir)
284 continue;
285 if (GitAdminDir::IsAdminDirPath(subpath))
286 continue;
287 progress.SetLine(2, subpath, true);
288 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
289 return subpath;
292 // if a patch file only contains newly added files
293 // we can't really find the correct path.
294 // But: we can compare paths strings without the filenames
295 // and check if at least those match
296 upperpath = path;
297 while ((trimPos = upperpath.ReverseFind(L'\\')) > 0)
299 upperpath.Truncate(trimPos);
300 progress.SetLine(2, upperpath, true);
301 if (progress.HasUserCancelled())
302 return path;
303 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
304 return upperpath;
307 return path;
310 int GitPatch::CountMatches(const CString& path) const
312 int matches = 0;
313 for (int i=0; i<GetNumberOfFiles(); ++i)
315 CString temp = GetStrippedPath(i);
316 temp.Replace('/', '\\');
317 if (PathIsRelative(temp) || ((temp.GetLength() > 1) && (temp[0] == L'\\') && (temp[1] != L'\\')))
318 temp = path + L'\\' + temp;
319 if (PathFileExists(temp))
320 ++matches;
322 return matches;
325 int GitPatch::CountDirMatches(const CString& path) const
327 int matches = 0;
328 for (int i=0; i<GetNumberOfFiles(); ++i)
330 CString temp = GetStrippedPath(i);
331 temp.Replace(L'/', L'\\');
332 if (PathIsRelative(temp))
333 temp = path + L'\\' + temp;
334 // remove the filename
335 temp.Truncate(max(0, temp.ReverseFind(L'\\')));
336 if (PathFileExists(temp))
337 ++matches;
339 return matches;
342 CString GitPatch::GetStrippedPath(int nIndex) const
344 if (nIndex < 0)
345 return L"";
346 if (nIndex < (int)m_filePaths.size())
348 CString filepath = Strip(GetFilePath(nIndex));
349 return filepath;
352 return L"";
355 CString GitPatch::Strip(const CString& filename) const
357 CString s = filename;
358 if ( m_nStrip>0 )
360 // Remove windows drive letter "c:"
361 if (s.GetLength() > 2 && s[1] == L':')
362 s = s.Mid(2);
364 for (int nStrip = 1; nStrip <= m_nStrip; ++nStrip)
366 // "/home/ts/my-working-copy/dir/file.txt"
367 // "home/ts/my-working-copy/dir/file.txt"
368 // "ts/my-working-copy/dir/file.txt"
369 // "my-working-copy/dir/file.txt"
370 // "dir/file.txt"
371 int p = s.FindOneOf(L"/\\");
372 if (p < 0)
374 s.Empty();
375 break;
377 s = s.Mid(p+1);
380 return s;
383 bool GitPatch::RemoveFile(const CString& /*path*/)
385 // Delete file in Git
386 // not necessary now, because TGit doesn't support the "missing file" status
387 return true;