1
// TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2012-2013, 2015-2019, 2023 - 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
41 int GitPatch::Init(const CString
& patchfile
, const CString
& targetpath
, CSysProgressDlg
*pPprogDlg
)
43 if (patchfile
.IsEmpty() || targetpath
.IsEmpty())
45 m_errorStr
.LoadString(IDS_ERR_PATCHPATHS
);
50 m_patchfile
= patchfile
;
51 m_targetpath
= targetpath
;
54 m_patchfile
.Replace('\\', '/');
55 m_targetpath
.Replace('\\', '/');
59 pPprogDlg
->SetTitle(IDS_APPNAME
);
60 pPprogDlg
->FormatNonPathLine(1, IDS_PATCH_PROGTITLE
);
61 pPprogDlg
->SetShowProgressBar(false);
62 pPprogDlg
->ShowModeless(AfxGetMainWnd());
63 m_pProgDlg
= pPprogDlg
;
70 // Read and try to apply patch
71 if (m_patch
.OpenUnifiedDiffFile(m_patchfile
) == FALSE
)
73 m_errorStr
= m_patch
.GetErrorMessage();
85 if ((m_nRejected
> (static_cast<int>(m_filePaths
.size()) / 3)) && !m_testPath
.IsEmpty())
87 for (m_nStrip
= 0; m_nStrip
< STRIP_LIMIT
; ++m_nStrip
)
89 if (std::any_of(m_filePaths
.cbegin(), m_filePaths
.cend(), [this](auto& filepath
) { return Strip(filepath
.path
).IsEmpty(); }))
97 if (m_nStrip
== STRIP_LIMIT
)
99 else if (m_nStrip
> 0)
107 return static_cast<int>(m_filePaths
.size());
110 bool GitPatch::ApplyPatches()
112 for (int i
= 0; i
< m_patch
.GetNumberOfFiles(); ++i
)
114 if (!PatchFile(i
, m_targetpath
))
121 bool GitPatch::PatchFile(int nIndex
, CString
&datapath
)
123 CString sFilePath
= m_patch
.GetFullPath(datapath
, nIndex
);
124 CString sTempFile
= CTempFiles::Instance().GetTempFilePathString();
127 m_testPath
= m_patch
.GetFilename2(nIndex
);
128 pr
.path
= m_patch
.GetFilename2(nIndex
);
129 if (pr
.path
== L
"NUL")
130 pr
.path
= m_patch
.GetFilename(nIndex
);
133 m_pProgDlg
->FormatPathLine(2, IDS_PATCH_PATHINGFILE
, static_cast<LPCWSTR
>(pr
.path
));
135 //first, do a "dry run" of patching against the file in place...
136 if (!m_patch
.PatchFile(m_nStrip
, nIndex
, datapath
, sTempFile
))
138 //patching not successful, so retrieve the
139 //base file from version control and try
141 CString sVersion
= m_patch
.GetRevision(nIndex
);
144 if ((sVersion
.GetLength() >= 7 && wcsncmp(sVersion
, GIT_REV_ZERO
, sVersion
.GetLength()) == 0) || sFilePath
== L
"NUL")
145 sBaseFile
= CTempFiles::Instance().GetTempFilePathString();
148 if (sVersion
.IsEmpty())
150 m_errorStr
.Format(IDS_ERR_MAINFRAME_FILECONFLICTNOVERSION
, static_cast<LPCWSTR
>(sFilePath
));
151 return false; // cannot apply patch which does not apply cleanly w/o git information in patch file.
153 sBaseFile
= CTempFiles::Instance().GetTempFilePathString();
154 if (!CAppUtils::GetVersionedFile(sFilePath
, sVersion
, sBaseFile
, m_pProgDlg
))
156 m_errorStr
.FormatMessage(IDS_ERR_MAINFRAME_FILEVERSIONNOTFOUND
, static_cast<LPCWSTR
>(sVersion
), static_cast<LPCWSTR
>(sFilePath
));
163 m_pProgDlg
->FormatPathLine(2, IDS_PATCH_PATHINGFILE
, static_cast<LPCWSTR
>(pr
.path
));
165 const int patchtry
= m_patch
.PatchFile(m_nStrip
, nIndex
, datapath
, sTempFile
, sBaseFile
, true);
167 if (patchtry
== TRUE
)
170 pr
.basePath
= sBaseFile
;
175 // rejectsPath should hold the absolute path to the reject files, but we do not support reject files ATM; also see changes FilePatchesDlg
176 pr
.rejectsPath
= m_patch
.GetErrorMessage();
179 TRACE(L
"comparing %s and %s\nagainst the base file %s\n", static_cast<LPCWSTR
>(sTempFile
), static_cast<LPCWSTR
>(sFilePath
), static_cast<LPCWSTR
>(sBaseFile
));
183 //"dry run" was successful, so save the patched file somewhere...
185 TRACE(L
"comparing %s\nwith the patched result %s\n", static_cast<LPCWSTR
>(sFilePath
), static_cast<LPCWSTR
>(sTempFile
));
188 pr
.resultPath
= sTempFile
;
191 // only add this entry if it hasn't been added already
192 if (std::none_of(m_filePaths
.crbegin(), m_filePaths
.crend(), [&pr
](auto& filepath
) { return filepath
.path
.Compare(pr
.path
) == 0; }))
193 m_filePaths
.push_back(pr
);
194 m_nRejected
+= pr
.rejects
;
199 CString
GitPatch::GetPatchRejects(int nIndex
) const
203 if (nIndex
< static_cast<int>(m_filePaths
.size()))
204 return m_filePaths
[nIndex
].rejectsPath
;
209 bool GitPatch::PatchPath(const CString
& path
)
213 m_patchfile
.Replace(L
'\\', L
'/');
214 m_targetpath
.Replace(L
'\\', L
'/');
216 m_filetopatch
= path
.Mid(m_targetpath
.GetLength()+1);
217 m_filetopatch
.Replace(L
'\\', L
'/');
221 m_errorStr
= L
"NOT IMPLEMENTED";
226 int GitPatch::GetPatchResult(const CString
& sPath
, CString
& sSavePath
, CString
& sRejectPath
, CString
&sBasePath
) const
228 auto it
= std::find_if(m_filePaths
.cbegin(), m_filePaths
.cend(), [this, &sPath
](auto& filePath
) { return Strip(filePath
.path
).CompareNoCase(sPath
) == 0; });
229 if (it
!= m_filePaths
.cend())
231 sSavePath
= (*it
).resultPath
;
232 sBasePath
= (*it
).basePath
;
233 if ((*it
).rejects
> 0)
234 sRejectPath
= (*it
).rejectsPath
;
237 return (*it
).rejects
;
242 CString
GitPatch::CheckPatchPath(const CString
& path
)
244 // first check if the path already matches
245 if (CountMatches(path
) > (GetNumberOfFiles() / 3))
248 CSysProgressDlg progress
;
250 progress
.SetTitle(IDS_PATCH_SEARCHPATHTITLE
);
251 progress
.SetShowProgressBar(false);
252 tmp
.LoadString(IDS_PATCH_SEARCHPATHLINE1
);
253 progress
.SetLine(1, tmp
);
254 progress
.ShowModeless(AfxGetMainWnd());
256 // now go up the tree and try again
257 CString upperpath
= path
;
259 while ((trimPos
= upperpath
.ReverseFind(L
'\\')) > 0)
261 upperpath
.Truncate(trimPos
);
262 progress
.SetLine(2, upperpath
, true);
263 if (progress
.HasUserCancelled())
265 if (CountMatches(upperpath
) > (GetNumberOfFiles()/3))
268 // still no match found. So try sub folders
269 CDirFileEnum
filefinder(path
);
270 while (auto file
= filefinder
.NextFile())
272 if (progress
.HasUserCancelled())
274 if (!file
->IsDirectory())
276 CString subpath
= file
->GetFilePath();
277 if (GitAdminDir::IsAdminDirPath(subpath
))
279 progress
.SetLine(2, subpath
, true);
280 if (CountMatches(subpath
) > (GetNumberOfFiles()/3))
284 // if a patch file only contains newly added files
285 // we can't really find the correct path.
286 // But: we can compare paths strings without the filenames
287 // and check if at least those match
289 while ((trimPos
= upperpath
.ReverseFind(L
'\\')) > 0)
291 upperpath
.Truncate(trimPos
);
292 progress
.SetLine(2, upperpath
, true);
293 if (progress
.HasUserCancelled())
295 if (CountDirMatches(upperpath
) > (GetNumberOfFiles()/3))
302 int GitPatch::CountMatches(const CString
& path
) const
305 for (int i
=0; i
<GetNumberOfFiles(); ++i
)
307 CString temp
= GetStrippedPath(i
);
308 temp
.Replace('/', '\\');
309 if (PathIsRelative(temp
) || ((temp
.GetLength() > 1) && (temp
[0] == L
'\\') && (temp
[1] != L
'\\')))
310 temp
= path
+ L
'\\' + temp
;
311 if (PathFileExists(temp
))
317 int GitPatch::CountDirMatches(const CString
& path
) const
320 for (int i
=0; i
<GetNumberOfFiles(); ++i
)
322 CString temp
= GetStrippedPath(i
);
323 temp
.Replace(L
'/', L
'\\');
324 if (PathIsRelative(temp
))
325 temp
= path
+ L
'\\' + temp
;
326 // remove the filename
327 temp
.Truncate(max(0, temp
.ReverseFind(L
'\\')));
328 if (PathFileExists(temp
))
334 CString
GitPatch::GetStrippedPath(int nIndex
) const
338 if (nIndex
< static_cast<int>(m_filePaths
.size()))
340 CString filepath
= Strip(GetFilePath(nIndex
));
347 CString
GitPatch::Strip(const CString
& filename
) const
349 CString s
= filename
;
352 // Remove windows drive letter "c:"
353 if (s
.GetLength() > 2 && s
[1] == L
':')
356 for (int nStrip
= 1; nStrip
<= m_nStrip
; ++nStrip
)
358 // "/home/ts/my-working-copy/dir/file.txt"
359 // "home/ts/my-working-copy/dir/file.txt"
360 // "ts/my-working-copy/dir/file.txt"
361 // "my-working-copy/dir/file.txt"
363 const int p
= s
.FindOneOf(L
"/\\");
375 bool GitPatch::RemoveFile(const CString
& /*path*/)
377 // Delete file in Git
378 // not necessary now, because TGit doesn't support the "missing file" status