1 // SPDX-License-Identifier: GPL-2.0-or-later
5 * @brief Code file routines
10 #include "UnicodeString.h"
11 #include "DiffWrapper.h"
12 #include "PathContext.h"
16 #include "DirTravel.h"
23 * @brief Default constructor.
25 CPatchTool::CPatchTool() : m_bOpenToEditor(false)
30 * @brief Default destructor.
32 CPatchTool::~CPatchTool()
37 * @brief Adds files to list for patching.
38 * @param [in] file1 First file to add.
39 * @param [in] file2 Second file to add.
41 void CPatchTool::AddFiles(const String &file1, const String &file2)
47 // TODO: Read and add file's timestamps
48 m_fileList.push_back(tFiles);
52 * @brief Add files with alternative paths.
53 * This function adds files with alternative paths. Alternative path is the
54 * one that is added to the patch file. So while @p file1 and @p file2 are
55 * paths in disk (can be temp file names), @p altPath1 and @p altPath2 are
56 * "visible " paths printed to the patch file.
57 * @param [in] file1 First path in disk.
58 * @param [in] altPath1 First path as printed to the patch file.
59 * @param [in] file2 Second path in disk.
60 * @param [in] altPath2 Second path as printed to the patch file.
62 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
63 const String &file2, const String &altPath2)
68 tFiles.pathLeft = altPath1;
69 tFiles.pathRight = altPath2;
71 // TODO: Read and add file's timestamps
72 m_fileList.push_back(tFiles);
76 * @brief Create a patch from files given.
77 * @note Files can be given using AddFiles() or selecting using
80 int CPatchTool::CreatePatch()
87 // If files already inserted, add them to dialog
88 for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
90 dlgPatch.AddItem(*iter);
93 if (ShowDialog(&dlgPatch))
97 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
99 LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
103 // Select patch create -mode
104 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
105 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
106 m_diffWrapper.SetPrediffer(nullptr);
108 size_t fileCount = dlgPatch.GetItemCount();
110 std::vector<PATCHFILES> fileList;
111 for (size_t index = 0; index < fileCount; index++)
113 const PATCHFILES& tFiles = dlgPatch.GetItemAt(index);
114 if (paths::DoesPathExist(tFiles.lfile) == paths::IS_EXISTING_DIR && paths::DoesPathExist(tFiles.rfile) == paths::IS_EXISTING_DIR)
116 // Walk given folders recursively and adds found files into patch list
117 String lfile = tFiles.lfile;
118 String rfile = tFiles.rfile;
119 paths::normalize(lfile);
120 paths::normalize(rfile);
121 PathContext paths((tFiles.pathLeft.empty() ? _T("") : paths::GetParentPath(lfile)),
122 (tFiles.pathRight.empty() ? _T("") : paths::GetParentPath(rfile)));
123 String subdir[2] = { (tFiles.pathLeft.empty() ? lfile : tFiles.pathLeft),
124 (tFiles.pathRight.empty() ? rfile : tFiles.pathRight) };
125 GetItemsForPatchList(paths, subdir, &fileList);
128 fileList.push_back(tFiles);
131 fileCount = fileList.size();
133 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
134 m_diffWrapper.SetAppendFiles(true);
136 bool bShowedBinaryMessage = false;
137 int writeFileCount = 0;
139 for (size_t index = 0; index < fileCount; index++)
141 const PATCHFILES& tFiles = fileList[index];
142 String filename1 = tFiles.lfile.length() == 0 ? _T("NUL") : tFiles.lfile;
143 String filename2 = tFiles.rfile.length() == 0 ? _T("NUL") : tFiles.rfile;
145 // Set up DiffWrapper
146 m_diffWrapper.SetPaths(PathContext(filename1, filename2), false);
147 m_diffWrapper.SetAlternativePaths(PathContext(tFiles.pathLeft, tFiles.pathRight));
148 m_diffWrapper.SetCompareFiles(PathContext(tFiles.lfile, tFiles.rfile));
149 bool bDiffSuccess = m_diffWrapper.RunFileDiff();
150 m_diffWrapper.GetDiffStatus(&status);
154 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
158 else if (status.bBinaries)
160 if (!bShowedBinaryMessage)
162 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
163 bShowedBinaryMessage = true;
166 else if (status.bPatchFileFailed)
168 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
169 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
180 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
182 if (bResult && writeFileCount > 0)
184 LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
187 m_sPatchFile = dlgPatch.m_fileResult;
188 m_bOpenToEditor = dlgPatch.m_openToEditor;
192 dlgPatch.ClearItems();
196 CMergeApp::OpenFileToExternalEditor(m_sPatchFile);
202 * @brief Show patch options dialog and check options selected.
203 * @return `true` if user wants to create a patch (didn't cancel dialog).
205 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
207 DIFFOPTIONS diffOptions = {0};
208 PATCHOPTIONS patchOptions;
211 if (pDlgPatch->DoModal() == IDOK)
213 // There must be one filepair
214 if (pDlgPatch->GetItemCount() < 1)
217 // These two are from dropdown list - can't be wrong
218 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
219 patchOptions.nContext = pDlgPatch->m_contextLines;
221 // Checkbox - can't be wrong
222 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
223 m_diffWrapper.SetPatchOptions(&patchOptions);
225 // These are from checkboxes and radiobuttons - can't be wrong
226 diffOptions.nIgnoreWhitespace = pDlgPatch->m_whitespaceCompare;
227 diffOptions.bIgnoreBlankLines = pDlgPatch->m_ignoreBlanks;
228 m_diffWrapper.SetAppendFiles(pDlgPatch->m_appendFile);
230 // Use this because non-sensitive setting can't write
231 // patch file EOLs correctly
232 diffOptions.bIgnoreEol = pDlgPatch->m_ignoreEOLDifference;
234 diffOptions.bIgnoreCase = pDlgPatch->m_ignoreCase;
235 diffOptions.nDiffAlgorithm = pDlgPatch->m_diffAlgorithm;
236 diffOptions.bIndentHeuristic = pDlgPatch->m_indentHeuristic;
237 m_diffWrapper.SetOptions(&diffOptions);
246 * @brief Add one compare item to patch list.
247 * @param [in] sDir1 Left subdirectory.
248 * @param [in] sDir2 Right subdirectory.
249 * @param [in] ent1 Left item data to add.
250 * @param [in] ent2 Right item data to add.
251 * @param [out] fileList Patch files list.
253 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
255 if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
258 static const TCHAR backslash[] = _T("\\");
264 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
266 String pathLeft = _T("");
268 pathLeft = sDir1 + backslash;
269 pathLeft += ent1->filename.get();
270 tFiles.pathLeft = pathLeft;
275 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
277 String pathRight = _T("");
279 pathRight = sDir2 + backslash;
280 pathRight += ent2->filename.get();
282 tFiles.pathRight = pathRight;
285 fileList->push_back(tFiles);
289 * @brief This function walks given folders and adds found files into patch list.
290 * We walk all subfolders and add the files they contain into list.
292 * @param [in] paths Root paths of compare
293 * @param [in] subdir Subdirectories under root path
294 * @param [out] fileList Patch files list
295 * @return 1 normally, 0 if no directories and files exist.
296 * @remark This function was written based on DirScan_GetItems() in DirScan.cpp
298 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
300 static const TCHAR backslash[] = _T("\\");
301 int nDirs = paths.GetSize();
306 std::copy(paths.begin(), paths.end(), sDir);
308 if (!subdir[0].empty())
310 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
312 sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
313 subprefix[nIndex] = subdir[nIndex] + backslash;
317 DirItemArray dirs[2], aFiles[2];
318 for (int nIndex = 0; nIndex < nDirs; nIndex++)
319 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
323 for (nIndex = 0; nIndex < nDirs; nIndex++)
324 if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
329 // Handle directories
330 // i points to current directory in left list (dirs[0])
331 // j points to current directory in right list (dirs[1])
332 DirItemArray::size_type i = 0, j = 0;
335 if (i >= dirs[0].size() && j >= dirs[1].size())
338 unsigned nDiffCode = DIFFCODE::DIR;
339 // Comparing directories leftDirs[i].name to rightDirs[j].name
340 if (i < dirs[0].size() && (j == dirs[1].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, false) < 0))
342 nDiffCode |= DIFFCODE::FIRST;
344 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
346 nDiffCode |= DIFFCODE::SECOND;
350 nDiffCode |= DIFFCODE::BOTH;
353 String leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
354 String rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
356 // Scan recursively all subdirectories too
357 String newsubdir[2] = { leftnewsub, rightnewsub };
358 GetItemsForPatchList(paths, newsubdir, fileList);
360 if (nDiffCode & DIFFCODE::FIRST)
362 if (nDiffCode & DIFFCODE::SECOND)
367 // i points to current file in left list (aFiles[0])
368 // j points to current file in right list (aFiles[1])
372 // Comparing file aFiles[0][i].name to aFiles[1][j].name
373 if (i < aFiles[0].size() && (j == aFiles[1].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) < 0))
375 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
379 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
381 AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
385 if (i < aFiles[0].size())
387 assert(j < aFiles[1].size());
389 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);