1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or (at
6 // your option) any later version.
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 /////////////////////////////////////////////////////////////////////////////
20 * @brief Code file routines
24 #include "PatchTool.h"
25 #include "UnicodeString.h"
26 #include "DiffWrapper.h"
27 #include "PathContext.h"
31 #include "DirTravel.h"
38 * @brief Default constructor.
40 CPatchTool::CPatchTool() : m_bOpenToEditor(false)
45 * @brief Default destructor.
47 CPatchTool::~CPatchTool()
52 * @brief Adds files to list for patching.
53 * @param [in] file1 First file to add.
54 * @param [in] file2 Second file to add.
56 void CPatchTool::AddFiles(const String &file1, const String &file2)
62 // TODO: Read and add file's timestamps
63 m_fileList.push_back(tFiles);
67 * @brief Add files with alternative paths.
68 * This function adds files with alternative paths. Alternative path is the
69 * one that is added to the patch file. So while @p file1 and @p file2 are
70 * paths in disk (can be temp file names), @p altPath1 and @p altPath2 are
71 * "visible " paths printed to the patch file.
72 * @param [in] file1 First path in disk.
73 * @param [in] altPath1 First path as printed to the patch file.
74 * @param [in] file2 Second path in disk.
75 * @param [in] altPath2 Second path as printed to the patch file.
77 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
78 const String &file2, const String &altPath2)
83 tFiles.pathLeft = altPath1;
84 tFiles.pathRight = altPath2;
86 // TODO: Read and add file's timestamps
87 m_fileList.push_back(tFiles);
91 * @brief Create a patch from files given.
92 * @note Files can be given using AddFiles() or selecting using
95 int CPatchTool::CreatePatch()
102 // If files already inserted, add them to dialog
103 for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
105 dlgPatch.AddItem(*iter);
108 if (ShowDialog(&dlgPatch))
112 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
114 LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
118 // Select patch create -mode
119 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
120 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
121 m_diffWrapper.SetPrediffer(nullptr);
123 size_t fileCount = dlgPatch.GetItemCount();
125 std::vector<PATCHFILES> fileList;
126 for (size_t index = 0; index < fileCount; index++)
128 const PATCHFILES& tFiles = dlgPatch.GetItemAt(index);
129 if (paths::DoesPathExist(tFiles.lfile) == paths::IS_EXISTING_DIR && paths::DoesPathExist(tFiles.rfile) == paths::IS_EXISTING_DIR)
131 // Walk given folders recursively and adds found files into patch list
132 String lfile = tFiles.lfile;
133 String rfile = tFiles.rfile;
134 paths::normalize(lfile);
135 paths::normalize(rfile);
136 PathContext paths((tFiles.pathLeft.empty() ? _T("") : paths::GetParentPath(lfile)),
137 (tFiles.pathRight.empty() ? _T("") : paths::GetParentPath(rfile)));
138 String subdir[2] = { (tFiles.pathLeft.empty() ? lfile : tFiles.pathLeft),
139 (tFiles.pathRight.empty() ? rfile : tFiles.pathRight) };
140 GetItemsForPatchList(paths, subdir, &fileList);
143 fileList.push_back(tFiles);
146 fileCount = fileList.size();
148 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
149 m_diffWrapper.SetAppendFiles(true);
151 bool bShowedBinaryMessage = false;
152 int writeFileCount = 0;
154 for (size_t index = 0; index < fileCount; index++)
156 const PATCHFILES& tFiles = fileList[index];
157 String filename1 = tFiles.lfile.length() == 0 ? _T("NUL") : tFiles.lfile;
158 String filename2 = tFiles.rfile.length() == 0 ? _T("NUL") : tFiles.rfile;
160 // Set up DiffWrapper
161 m_diffWrapper.SetPaths(PathContext(filename1, filename2), false);
162 m_diffWrapper.SetAlternativePaths(PathContext(tFiles.pathLeft, tFiles.pathRight));
163 m_diffWrapper.SetCompareFiles(PathContext(tFiles.lfile, tFiles.rfile));
164 bool bDiffSuccess = m_diffWrapper.RunFileDiff();
165 m_diffWrapper.GetDiffStatus(&status);
169 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
173 else if (status.bBinaries)
175 if (!bShowedBinaryMessage)
177 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
178 bShowedBinaryMessage = true;
181 else if (status.bPatchFileFailed)
183 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
184 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
195 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
197 if (bResult && writeFileCount > 0)
199 LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
202 m_sPatchFile = dlgPatch.m_fileResult;
203 m_bOpenToEditor = dlgPatch.m_openToEditor;
207 dlgPatch.ClearItems();
211 theApp.OpenFileToExternalEditor(m_sPatchFile);
217 * @brief Show patch options dialog and check options selected.
218 * @return `true` if user wants to create a patch (didn't cancel dialog).
220 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
222 DIFFOPTIONS diffOptions = {0};
223 PATCHOPTIONS patchOptions;
226 if (pDlgPatch->DoModal() == IDOK)
228 // There must be one filepair
229 if (pDlgPatch->GetItemCount() < 1)
232 // These two are from dropdown list - can't be wrong
233 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
234 patchOptions.nContext = pDlgPatch->m_contextLines;
236 // Checkbox - can't be wrong
237 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
238 m_diffWrapper.SetPatchOptions(&patchOptions);
240 // These are from checkboxes and radiobuttons - can't be wrong
241 diffOptions.nIgnoreWhitespace = pDlgPatch->m_whitespaceCompare;
242 diffOptions.bIgnoreBlankLines = pDlgPatch->m_ignoreBlanks;
243 m_diffWrapper.SetAppendFiles(pDlgPatch->m_appendFile);
245 // Use this because non-sensitive setting can't write
246 // patch file EOLs correctly
247 diffOptions.bIgnoreEol = pDlgPatch->m_ignoreEOLDifference;
249 diffOptions.bIgnoreCase = !pDlgPatch->m_caseSensitive;
250 diffOptions.nDiffAlgorithm = pDlgPatch->m_diffAlgorithm;
251 diffOptions.bIndentHeuristic = pDlgPatch->m_indentHeuristic;
252 m_diffWrapper.SetOptions(&diffOptions);
261 * @brief Add one compare item to patch list.
262 * @param [in] sDir1 Left subdirectory.
263 * @param [in] sDir2 Right subdirectory.
264 * @param [in] ent1 Left item data to add.
265 * @param [in] ent2 Right item data to add.
266 * @param [out] fileList Patch files list.
268 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
270 if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
273 static const TCHAR backslash[] = _T("\\");
279 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
281 String pathLeft = _T("");
283 pathLeft = sDir1 + backslash;
284 pathLeft += ent1->filename.get();
285 tFiles.pathLeft = pathLeft;
290 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
292 String pathRight = _T("");
294 pathRight = sDir2 + backslash;
295 pathRight += ent2->filename.get();
297 tFiles.pathRight = pathRight;
300 fileList->push_back(tFiles);
304 * @brief This function walks given folders and adds found files into patch list.
305 * We walk all subfolders and add the files they contain into list.
307 * @param [in] paths Root paths of compare
308 * @param [in] subdir Subdirectories under root path
309 * @param [out] fileList Patch files list
310 * @return 1 normally, 0 if no directories and files exist.
311 * @remark This function was written based on DirScan_GetItems() in DirScan.cpp
313 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
315 static const TCHAR backslash[] = _T("\\");
316 int nDirs = paths.GetSize();
321 std::copy(paths.begin(), paths.end(), sDir);
323 if (!subdir[0].empty())
325 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
327 sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
328 subprefix[nIndex] = subdir[nIndex] + backslash;
332 DirItemArray dirs[2], aFiles[2];
333 for (int nIndex = 0; nIndex < nDirs; nIndex++)
334 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
338 for (nIndex = 0; nIndex < nDirs; nIndex++)
339 if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
344 // Handle directories
345 // i points to current directory in left list (dirs[0])
346 // j points to current directory in right list (dirs[1])
347 DirItemArray::size_type i = 0, j = 0;
350 if (i >= dirs[0].size() && j >= dirs[1].size())
353 unsigned nDiffCode = DIFFCODE::DIR;
354 // Comparing directories leftDirs[i].name to rightDirs[j].name
355 if (i < dirs[0].size() && (j == dirs[1].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, false) < 0))
357 nDiffCode |= DIFFCODE::FIRST;
359 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
361 nDiffCode |= DIFFCODE::SECOND;
365 nDiffCode |= DIFFCODE::BOTH;
368 String leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
369 String rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
371 // Scan recursively all subdirectories too
372 String newsubdir[2] = { leftnewsub, rightnewsub };
373 GetItemsForPatchList(paths, newsubdir, fileList);
375 if (nDiffCode & DIFFCODE::FIRST)
377 if (nDiffCode & DIFFCODE::SECOND)
382 // i points to current file in left list (aFiles[0])
383 // j points to current file in right list (aFiles[1])
387 // Comparing file aFiles[0][i].name to aFiles[1][j].name
388 if (i < aFiles[0].size() && (j == aFiles[1].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) < 0))
390 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
394 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
396 AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
400 if (i < aFiles[0].size())
402 assert(j < aFiles[1].size());
404 AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);