Fix reverting to parent for renamed file
[TortoiseGit.git] / src / Git / GitPatch.cpp
blob0503576f2aea2190a8d4492012c2fa52c0fe0cc9
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)
188 pr.rejects = 0;
189 pr.basePath = sBaseFile;
191 else
193 pr.rejects = 1;
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);
200 else
202 //"dry run" was successful, so save the patched file somewhere...
203 pr.rejects = 0;
204 TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR)sFilePath, (LPCTSTR)sTempFile);
207 pr.resultPath = sTempFile;
208 pr.content = true;
209 pr.props = false;
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)
216 bExists = true;
217 break;
220 if (!bExists)
221 m_filePaths.push_back(pr);
222 m_nRejected += pr.rejects;
224 return true;
227 CString GitPatch::GetPatchRejects(int nIndex) const
229 if (nIndex < 0)
230 return _T("");
231 if (nIndex < (int)m_filePaths.size())
233 return m_filePaths[nIndex].rejectsPath;
236 return _T("");
239 bool GitPatch::PatchPath(const CString& path)
241 m_errorStr.Empty();
243 m_patchfile.Replace('\\', '/');
244 m_targetpath.Replace('\\', '/');
246 m_filetopatch = path.Mid(m_targetpath.GetLength()+1);
247 m_filetopatch.Replace('\\', '/');
249 m_nRejected = 0;
251 m_errorStr = _T("NOT IMPLEMENTED");
252 return false;
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;
264 if (it->rejects > 0)
265 sRejectPath = it->rejectsPath;
266 else
267 sRejectPath.Empty();
268 return it->rejects;
271 return -1;
274 CString GitPatch::CheckPatchPath(const CString& path)
276 // first check if the path already matches
277 if (CountMatches(path) > (GetNumberOfFiles() / 3))
278 return path;
280 CSysProgressDlg progress;
281 CString tmp;
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())
295 return path;
296 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
297 return upperpath;
299 // still no match found. So try sub folders
300 bool isDir = false;
301 CString subpath;
302 CDirFileEnum filefinder(path);
303 while (filefinder.NextFile(subpath, &isDir))
305 if (progress.HasUserCancelled())
306 return path;
307 if (!isDir)
308 continue;
309 if (g_GitAdminDir.IsAdminDirPath(subpath))
310 continue;
311 progress.SetLine(2, subpath, true);
312 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
313 return subpath;
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
320 upperpath = path;
321 while (upperpath.ReverseFind('\\')>0)
323 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
324 progress.SetLine(2, upperpath, true);
325 if (progress.HasUserCancelled())
326 return path;
327 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
328 return upperpath;
331 return path;
334 int GitPatch::CountMatches(const CString& path) const
336 int matches = 0;
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))
345 ++matches;
347 return matches;
350 int GitPatch::CountDirMatches(const CString& path) const
352 int matches = 0;
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))
362 ++matches;
364 return matches;
367 CString GitPatch::GetStrippedPath(int nIndex) const
369 if (nIndex < 0)
370 return _T("");
371 if (nIndex < (int)m_filePaths.size())
373 CString filepath = Strip(GetFilePath(nIndex));
374 return filepath;
377 return _T("");
380 CString GitPatch::Strip(const CString& filename) const
382 CString s = filename;
383 if ( m_nStrip>0 )
385 // Remove windows drive letter "c:"
386 if (s.GetLength()>2 && s[1]==':')
388 s = s.Mid(2);
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"
397 // "dir/file.txt"
398 int p = s.FindOneOf(_T("/\\"));
399 if (p < 0)
401 s.Empty();
402 break;
404 s = s.Mid(p+1);
407 return s;
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
414 return true;