OSDN Git Service

theApp is unnecessary because these methods are a static methods
[winmerge-jp/winmerge-jp.git] / Src / PatchTool.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  PatchTool.cpp
4  *
5  * @brief Code file routines
6  */
7
8 #include "StdAfx.h"
9 #include "PatchTool.h"
10 #include "UnicodeString.h"
11 #include "DiffWrapper.h"
12 #include "PathContext.h"
13 #include "PatchDlg.h"
14 #include "paths.h"
15 #include "Merge.h"
16 #include "DirTravel.h"
17
18 #ifdef _DEBUG
19 #define new DEBUG_NEW
20 #endif
21
22 /**
23  * @brief Default constructor.
24  */
25 CPatchTool::CPatchTool() : m_bOpenToEditor(false)
26 {
27 }
28
29 /**
30  * @brief Default destructor.
31  */
32 CPatchTool::~CPatchTool()
33 {
34 }
35
36 /** 
37  * @brief Adds files to list for patching.
38  * @param [in] file1 First file to add.
39  * @param [in] file2 Second file to add.
40  */
41 void CPatchTool::AddFiles(const String &file1, const String &file2)
42 {
43         PATCHFILES tFiles;
44         tFiles.lfile = file1;
45         tFiles.rfile = file2;
46
47         // TODO: Read and add file's timestamps
48         m_fileList.push_back(tFiles);
49 }
50
51 /**
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.
61  */
62 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
63                 const String &file2, const String &altPath2)
64 {
65         PATCHFILES tFiles;
66         tFiles.lfile = file1;
67         tFiles.rfile = file2;
68         tFiles.pathLeft = altPath1;
69         tFiles.pathRight = altPath2;
70
71         // TODO: Read and add file's timestamps
72         m_fileList.push_back(tFiles);
73 }
74
75 /** 
76  * @brief Create a patch from files given.
77  * @note Files can be given using AddFiles() or selecting using
78  * CPatchDlg.
79  */
80 int CPatchTool::CreatePatch()
81 {
82         DIFFSTATUS status;
83         int retVal = 0;
84
85         CPatchDlg dlgPatch;
86
87         // If files already inserted, add them to dialog
88     for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
89     {
90         dlgPatch.AddItem(*iter);
91         }
92
93         if (ShowDialog(&dlgPatch))
94         {
95                 bool bResult = true;
96
97                 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
98                 {
99                         LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
100                         return 0;
101                 }
102
103                 // Select patch create -mode
104                 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
105                 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
106                 m_diffWrapper.SetPrediffer(nullptr);
107
108                 size_t fileCount = dlgPatch.GetItemCount();
109
110                 std::vector<PATCHFILES> fileList;
111                 for (size_t index = 0; index < fileCount; index++)
112                 {
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)
115                         {
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);
126                         }
127                         else {
128                                 fileList.push_back(tFiles);
129                         }
130                 }
131                 fileCount = fileList.size();
132
133                 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
134                 m_diffWrapper.SetAppendFiles(true);
135
136                 bool bShowedBinaryMessage = false;
137                 int writeFileCount = 0;
138
139                 for (size_t index = 0; index < fileCount; index++)
140                 {
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;
144                         
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);
151
152                         if (!bDiffSuccess)
153                         {
154                                 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
155                                 bResult = false;
156                                 break;
157                         }
158                         else if (status.bBinaries)
159                         {
160                                 if (!bShowedBinaryMessage)
161                                 {
162                                         LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
163                                         bShowedBinaryMessage = true;
164                                 }
165                         }
166                         else if (status.bPatchFileFailed)
167                         {
168                                 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
169                                 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
170                                 bResult = false;
171                                 break;
172                         }
173                         else
174                         {
175                                 writeFileCount++;
176                         }
177
178                 }
179                 
180                 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
181
182                 if (bResult && writeFileCount > 0)
183                 {
184                         LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
185                                             IDS_DIFF_SUCCEEDED);
186                         
187                         m_sPatchFile = dlgPatch.m_fileResult;
188                         m_bOpenToEditor = dlgPatch.m_openToEditor;
189                         retVal = 1;
190                 }
191         }
192         dlgPatch.ClearItems();
193         if (retVal)
194         {
195                 if (m_bOpenToEditor)
196                         CMergeApp::OpenFileToExternalEditor(m_sPatchFile);
197         }
198         return retVal;
199 }
200
201 /** 
202  * @brief Show patch options dialog and check options selected.
203  * @return `true` if user wants to create a patch (didn't cancel dialog).
204  */
205 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
206 {
207         DIFFOPTIONS diffOptions = {0};
208         PATCHOPTIONS patchOptions;
209         bool bRetVal = true;
210
211         if (pDlgPatch->DoModal() == IDOK)
212         {
213                 // There must be one filepair
214                 if (pDlgPatch->GetItemCount() < 1)
215                         bRetVal = false;
216
217                 // These two are from dropdown list - can't be wrong
218                 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
219                 patchOptions.nContext = pDlgPatch->m_contextLines;
220
221                 // Checkbox - can't be wrong
222                 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
223                 m_diffWrapper.SetPatchOptions(&patchOptions);
224
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);
229
230                 // Use this because non-sensitive setting can't write
231                 // patch file EOLs correctly
232                 diffOptions.bIgnoreEol = pDlgPatch->m_ignoreEOLDifference;
233                 
234                 diffOptions.bIgnoreCase = pDlgPatch->m_ignoreCase;
235                 diffOptions.nDiffAlgorithm = pDlgPatch->m_diffAlgorithm;
236                 diffOptions.bIndentHeuristic = pDlgPatch->m_indentHeuristic;
237                 m_diffWrapper.SetOptions(&diffOptions);
238         }
239         else
240                 return false;
241
242         return bRetVal;
243 }
244
245 /**
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.
252  */
253 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
254 {
255         if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
256                 return;
257
258         static const TCHAR backslash[] = _T("\\");
259
260         PATCHFILES tFiles;
261
262         if (ent1 != nullptr)
263         {
264                 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
265
266                 String pathLeft = _T("");
267                 if (!sDir1.empty())
268                         pathLeft = sDir1 + backslash;
269                 pathLeft += ent1->filename.get();
270                 tFiles.pathLeft = pathLeft;
271         }
272
273         if (ent2 != nullptr)
274         {
275                 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
276
277                 String pathRight = _T("");
278                 if (!sDir2.empty())
279                         pathRight = sDir2 + backslash;
280                 pathRight += ent2->filename.get();
281
282                 tFiles.pathRight = pathRight;
283         }
284
285         fileList->push_back(tFiles);
286 }
287
288 /**
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.
291  *
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
297  */
298 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
299 {
300         static const TCHAR backslash[] = _T("\\");
301         int nDirs = paths.GetSize();
302
303         String sDir[2];
304         String subprefix[2];
305
306         std::copy(paths.begin(), paths.end(), sDir);
307
308         if (!subdir[0].empty())
309         {
310                 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
311                 {
312                         sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
313                         subprefix[nIndex] = subdir[nIndex] + backslash;
314                 }
315         }
316
317         DirItemArray dirs[2], aFiles[2];
318         for (int nIndex = 0; nIndex < nDirs; nIndex++)
319                 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
320
321         {
322                 int nIndex;
323                 for (nIndex = 0; nIndex < nDirs; nIndex++)
324                         if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
325                 if (nIndex == nDirs)
326                         return 0;
327         }
328
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;
333         while (true)
334         {
335                 if (i >= dirs[0].size() && j >= dirs[1].size())
336                         break;
337
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))
341                 {
342                         nDiffCode |= DIFFCODE::FIRST;
343                 }
344                 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
345                 {
346                         nDiffCode |= DIFFCODE::SECOND;
347                 }
348                 else
349                 {
350                         nDiffCode |= DIFFCODE::BOTH;
351                 }
352
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();
355
356                 // Scan recursively all subdirectories too
357                 String newsubdir[2] = { leftnewsub, rightnewsub };
358                 GetItemsForPatchList(paths, newsubdir, fileList);
359
360                 if (nDiffCode & DIFFCODE::FIRST)
361                         i++;
362                 if (nDiffCode & DIFFCODE::SECOND)
363                         j++;
364         }
365
366         // Handle files
367         // i points to current file in left list (aFiles[0])
368         // j points to current file in right list (aFiles[1])
369         i = 0, j = 0;
370         while (true)
371         {
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))
374                 {
375                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
376                         ++i;
377                         continue;
378                 }
379                 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
380                 {
381                         AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
382                         ++j;
383                         continue;
384                 }
385                 if (i < aFiles[0].size())
386                 {
387                         assert(j < aFiles[1].size());
388
389                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);
390                         ++i;
391                         ++j;
392                         continue;
393                 }
394                 break;
395         }
396
397         return 1;
398 }