Fix some warnings
[TortoiseGit.git] / src / Git / GitPatch.cpp
bloba219f65f6488d12ff34bafb5daa7538da7a1a0af
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2012 - 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 sBaseFile = CTempFiles::Instance().GetTempFilePathString();
166 if (!CAppUtils::GetVersionedFile(sFilePath, sVersion, sBaseFile, m_pProgDlg))
168 m_errorStr.Empty();
169 m_errorStr.Format(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND, (LPCTSTR)sVersion, (LPCTSTR)sFilePath);
171 return false;
175 if (m_pProgDlg)
176 m_pProgDlg->FormatPathLine(2, IDS_PATCH_PATHINGFILE, pr.path);
178 int patchtry = m_patch.PatchFile(m_nStrip, nIndex, datapath, sTempFile, sBaseFile, true);
180 if (patchtry == TRUE)
181 pr.rejects = 0;
182 else
184 pr.rejects = 1;
185 // rejectsPath should hold the absolute path to the reject files, but we do not support reject files ATM; also see changes FilePatchesDlg
186 pr.rejectsPath = m_patch.GetErrorMessage();
189 TRACE(_T("comparing %s and %s\nagainst the base file %s\n"), (LPCTSTR)sTempFile, (LPCTSTR)sFilePath, (LPCTSTR)sBaseFile);
191 else
193 //"dry run" was successful, so save the patched file somewhere...
194 pr.rejects = 0;
195 TRACE(_T("comparing %s\nwith the patched result %s\n"), (LPCTSTR)sFilePath, (LPCTSTR)sTempFile);
198 pr.resultPath = sTempFile;
199 pr.content = true;
200 pr.props = false;
201 // only add this entry if it hasn't been added already
202 bool bExists = false;
203 for (auto it = m_filePaths.rbegin(); it != m_filePaths.rend(); ++it)
205 if (it->path.Compare(pr.path) == 0)
207 bExists = true;
208 break;
211 if (!bExists)
212 m_filePaths.push_back(pr);
213 m_nRejected += pr.rejects;
215 return true;
218 CString GitPatch::GetPatchRejects(int nIndex) const
220 if (nIndex < 0)
221 return _T("");
222 if (nIndex < (int)m_filePaths.size())
224 return m_filePaths[nIndex].rejectsPath;
227 return _T("");
230 bool GitPatch::PatchPath(const CString& path)
232 m_errorStr.Empty();
234 m_patchfile.Replace('\\', '/');
235 m_targetpath.Replace('\\', '/');
237 m_filetopatch = path.Mid(m_targetpath.GetLength()+1);
238 m_filetopatch.Replace('\\', '/');
240 m_nRejected = 0;
242 m_errorStr = _T("NOT IMPLEMENTED");
243 return false;
247 int GitPatch::GetPatchResult(const CString& sPath, CString& sSavePath, CString& sRejectPath) const
249 for (std::vector<PathRejects>::const_iterator it = m_filePaths.begin(); it != m_filePaths.end(); ++it)
251 if (Strip(it->path).CompareNoCase(sPath)==0)
253 sSavePath = it->resultPath;
254 if (it->rejects > 0)
255 sRejectPath = it->rejectsPath;
256 else
257 sRejectPath.Empty();
258 return it->rejects;
261 return -1;
264 CString GitPatch::CheckPatchPath(const CString& path)
266 // first check if the path already matches
267 if (CountMatches(path) > (GetNumberOfFiles() / 3))
268 return path;
270 CSysProgressDlg progress;
271 CString tmp;
272 progress.SetTitle(IDS_PATCH_SEARCHPATHTITLE);
273 progress.SetShowProgressBar(false);
274 tmp.LoadString(IDS_PATCH_SEARCHPATHLINE1);
275 progress.SetLine(1, tmp);
276 progress.ShowModeless(AfxGetMainWnd());
278 // now go up the tree and try again
279 CString upperpath = path;
280 while (upperpath.ReverseFind('\\')>0)
282 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
283 progress.SetLine(2, upperpath, true);
284 if (progress.HasUserCancelled())
285 return path;
286 if (CountMatches(upperpath) > (GetNumberOfFiles()/3))
287 return upperpath;
289 // still no match found. So try sub folders
290 bool isDir = false;
291 CString subpath;
292 CDirFileEnum filefinder(path);
293 while (filefinder.NextFile(subpath, &isDir))
295 if (progress.HasUserCancelled())
296 return path;
297 if (!isDir)
298 continue;
299 if (g_GitAdminDir.IsAdminDirPath(subpath))
300 continue;
301 progress.SetLine(2, subpath, true);
302 if (CountMatches(subpath) > (GetNumberOfFiles()/3))
303 return subpath;
306 // if a patch file only contains newly added files
307 // we can't really find the correct path.
308 // But: we can compare paths strings without the filenames
309 // and check if at least those match
310 upperpath = path;
311 while (upperpath.ReverseFind('\\')>0)
313 upperpath = upperpath.Left(upperpath.ReverseFind('\\'));
314 progress.SetLine(2, upperpath, true);
315 if (progress.HasUserCancelled())
316 return path;
317 if (CountDirMatches(upperpath) > (GetNumberOfFiles()/3))
318 return upperpath;
321 return path;
324 int GitPatch::CountMatches(const CString& path) const
326 int matches = 0;
327 for (int i=0; i<GetNumberOfFiles(); ++i)
329 CString temp = GetStrippedPath(i);
330 temp.Replace('/', '\\');
331 if ((PathIsRelative(temp)) ||
332 ((temp.GetLength() > 1) && (temp[0]=='\\') && (temp[1]!='\\')) )
333 temp = path + _T("\\")+ temp;
334 if (PathFileExists(temp))
335 matches++;
337 return matches;
340 int GitPatch::CountDirMatches(const CString& path) const
342 int matches = 0;
343 for (int i=0; i<GetNumberOfFiles(); ++i)
345 CString temp = GetStrippedPath(i);
346 temp.Replace('/', '\\');
347 if (PathIsRelative(temp))
348 temp = path + _T("\\")+ temp;
349 // remove the filename
350 temp = temp.Left(temp.ReverseFind('\\'));
351 if (PathFileExists(temp))
352 matches++;
354 return matches;
357 CString GitPatch::GetStrippedPath(int nIndex) const
359 if (nIndex < 0)
360 return _T("");
361 if (nIndex < (int)m_filePaths.size())
363 CString filepath = Strip(GetFilePath(nIndex));
364 return filepath;
367 return _T("");
370 CString GitPatch::Strip(const CString& filename) const
372 CString s = filename;
373 if ( m_nStrip>0 )
375 // Remove windows drive letter "c:"
376 if (s.GetLength()>2 && s[1]==':')
378 s = s.Mid(2);
381 for (int nStrip=1;nStrip<=m_nStrip;nStrip++)
383 // "/home/ts/my-working-copy/dir/file.txt"
384 // "home/ts/my-working-copy/dir/file.txt"
385 // "ts/my-working-copy/dir/file.txt"
386 // "my-working-copy/dir/file.txt"
387 // "dir/file.txt"
388 int p = s.FindOneOf(_T("/\\"));
389 if (p < 0)
391 s.Empty();
392 break;
394 s = s.Mid(p+1);
397 return s;
400 bool GitPatch::RemoveFile(const CString& /*path*/)
402 // Delete file in Git
403 // not necessary now, because TGit doesn't support the "missing file" status
404 return true;