OSDN Git Service

Keep hidden items (#1377)
[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 #include "OptionsDiffOptions.h"
18 #include "UniFile.h"
19 #include "codepage_detect.h"
20 #include "OptionsMgr.h"
21 #include "OptionsDef.h"
22 #include "ClipBoard.h"
23
24 #ifdef _DEBUG
25 #define new DEBUG_NEW
26 #endif
27
28 /**
29  * @brief Default constructor.
30  */
31 CPatchTool::CPatchTool() : m_bOpenToEditor(false), m_bCopyToClipbard(false)
32 {
33 }
34
35 /**
36  * @brief Default destructor.
37  */
38 CPatchTool::~CPatchTool() = default;
39
40 /** 
41  * @brief Adds files to list for patching.
42  * @param [in] file1 First file to add.
43  * @param [in] file2 Second file to add.
44  */
45 void CPatchTool::AddFiles(const String &file1, const String &file2)
46 {
47         PATCHFILES tFiles;
48         tFiles.lfile = file1;
49         tFiles.rfile = file2;
50
51         // TODO: Read and add file's timestamps
52         m_fileList.push_back(tFiles);
53 }
54
55 /**
56  * @brief Add files with alternative paths.
57  * This function adds files with alternative paths. Alternative path is the
58  * one that is added to the patch file. So while @p file1 and @p file2 are
59  * paths in disk (can be temp file names), @p altPath1 and @p altPath2 are
60  * "visible " paths printed to the patch file.
61  * @param [in] file1 First path in disk.
62  * @param [in] altPath1 First path as printed to the patch file.
63  * @param [in] file2 Second path in disk.
64  * @param [in] altPath2 Second path as printed to the patch file.
65  */
66 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
67                 const String &file2, const String &altPath2)
68 {
69         PATCHFILES tFiles;
70         tFiles.lfile = file1;
71         tFiles.rfile = file2;
72         tFiles.pathLeft = altPath1;
73         tFiles.pathRight = altPath2;
74
75         // TODO: Read and add file's timestamps
76         m_fileList.push_back(tFiles);
77 }
78
79 /** 
80  * @brief Create a patch from files given.
81  * @note Files can be given using AddFiles() or selecting using
82  * CPatchDlg.
83  */
84 int CPatchTool::CreatePatch()
85 {
86         DIFFSTATUS status;
87         int retVal = 0;
88
89         CPatchDlg dlgPatch;
90
91         // If files already inserted, add them to dialog
92     for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
93     {
94         dlgPatch.AddItem(*iter);
95         }
96
97         if (ShowDialog(&dlgPatch))
98         {
99                 bool bResult = true;
100
101                 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
102                 {
103                         LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
104                         return 0;
105                 }
106
107                 // Select patch create -mode
108                 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
109                 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
110                 m_diffWrapper.SetPrediffer(nullptr);
111
112                 size_t fileCount = dlgPatch.GetItemCount();
113
114                 std::vector<PATCHFILES> fileList;
115                 for (size_t index = 0; index < fileCount; index++)
116                 {
117                         const PATCHFILES& tFiles = dlgPatch.GetItemAt(index);
118                         if (paths::DoesPathExist(tFiles.lfile) == paths::IS_EXISTING_DIR && paths::DoesPathExist(tFiles.rfile) == paths::IS_EXISTING_DIR)
119                         {
120                                 // Walk given folders recursively and adds found files into patch list
121                                 String lfile = tFiles.lfile;
122                                 String rfile = tFiles.rfile;
123                                 paths::normalize(lfile);
124                                 paths::normalize(rfile);
125                                 PathContext paths((tFiles.pathLeft.empty() ? _T("") : paths::GetParentPath(lfile)),
126                                                                   (tFiles.pathRight.empty() ? _T("") : paths::GetParentPath(rfile)));
127                                 String subdir[2] = { (tFiles.pathLeft.empty() ? lfile : tFiles.pathLeft),
128                                                                          (tFiles.pathRight.empty() ? rfile : tFiles.pathRight) };
129                                 GetItemsForPatchList(paths, subdir, &fileList);
130                         }
131                         else {
132                                 fileList.push_back(tFiles);
133                         }
134                 }
135                 fileCount = fileList.size();
136
137                 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
138                 m_diffWrapper.SetAppendFiles(true);
139
140                 bool bShowedBinaryMessage = false;
141                 int writeFileCount = 0;
142
143                 for (size_t index = 0; index < fileCount; index++)
144                 {
145                         const PATCHFILES& tFiles = fileList[index];
146                         String filename1 = tFiles.lfile.length() == 0 ? _T("NUL") : tFiles.lfile;
147                         String filename2 = tFiles.rfile.length() == 0 ? _T("NUL") : tFiles.rfile;
148                         
149                         // Set up DiffWrapper
150                         m_diffWrapper.SetPaths(PathContext(filename1, filename2), false);
151                         m_diffWrapper.SetAlternativePaths(PathContext(tFiles.pathLeft, tFiles.pathRight));
152                         m_diffWrapper.SetCompareFiles(PathContext(tFiles.lfile, tFiles.rfile));
153                         bool bDiffSuccess = m_diffWrapper.RunFileDiff();
154                         m_diffWrapper.GetDiffStatus(&status);
155
156                         if (!bDiffSuccess)
157                         {
158                                 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
159                                 bResult = false;
160                                 break;
161                         }
162                         else if (status.bBinaries)
163                         {
164                                 if (!bShowedBinaryMessage)
165                                 {
166                                         LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
167                                         bShowedBinaryMessage = true;
168                                 }
169                         }
170                         else if (status.bPatchFileFailed)
171                         {
172                                 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
173                                 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
174                                 bResult = false;
175                                 break;
176                         }
177                         else
178                         {
179                                 writeFileCount++;
180                         }
181
182                 }
183                 
184                 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
185
186                 if (bResult && writeFileCount > 0)
187                 {
188                         LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
189                                             IDS_DIFF_SUCCEEDED);
190                         
191                         m_sPatchFile = dlgPatch.m_fileResult;
192                         m_bOpenToEditor = dlgPatch.m_openToEditor;
193                         m_bCopyToClipbard = dlgPatch.m_copyToClipboard;
194                         retVal = 1;
195                 }
196         }
197         dlgPatch.ClearItems();
198         if (retVal)
199         {
200                 if (m_bOpenToEditor)
201                         CMergeApp::OpenFileToExternalEditor(m_sPatchFile);
202                 if (m_bCopyToClipbard)
203                 {
204                         UniMemFile file;
205                         if (file.OpenReadOnly(m_sPatchFile))
206                         {
207                                 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
208                                 FileTextEncoding encoding = codepage_detect::Guess(m_sPatchFile, iGuessEncodingType);
209                                 file.SetUnicoding(encoding.m_unicoding);
210                                 file.SetCodepage(encoding.m_codepage);
211                                 file.SetBom(encoding.m_bom);
212                                 if (encoding.m_bom)
213                                         file.ReadBom();
214                                 String lines;
215                                 file.ReadStringAll(lines);
216                                 file.Close();
217                                 PutToClipboard(lines, AfxGetMainWnd()->m_hWnd);
218                         }
219                 }
220         }
221         return retVal;
222 }
223
224 /** 
225  * @brief Show patch options dialog and check options selected.
226  * @return `true` if user wants to create a patch (didn't cancel dialog).
227  */
228 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
229 {
230         DIFFOPTIONS diffOptions = {0};
231         PATCHOPTIONS patchOptions;
232         bool bRetVal = true;
233
234         if (pDlgPatch->DoModal() == IDOK)
235         {
236                 // There must be one filepair
237                 if (pDlgPatch->GetItemCount() < 1)
238                         bRetVal = false;
239
240                 // These two are from dropdown list - can't be wrong
241                 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
242                 patchOptions.nContext = pDlgPatch->m_contextLines;
243
244                 // Checkbox - can't be wrong
245                 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
246                 m_diffWrapper.SetPatchOptions(&patchOptions);
247
248                 // These are from checkboxes and radiobuttons - can't be wrong
249                 m_diffWrapper.SetAppendFiles(pDlgPatch->m_appendFile);
250                 Options::DiffOptions::Load(GetOptionsMgr(), diffOptions);
251                 m_diffWrapper.SetOptions(&diffOptions);
252         }
253         else
254                 return false;
255
256         return bRetVal;
257 }
258
259 /**
260  * @brief Add one compare item to patch list.
261  * @param [in] sDir1 Left subdirectory.
262  * @param [in] sDir2 Right subdirectory.
263  * @param [in] ent1 Left item data to add.
264  * @param [in] ent2 Right item data to add.
265  * @param [out] fileList Patch files list.
266  */
267 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
268 {
269         if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
270                 return;
271
272         static const TCHAR backslash[] = _T("\\");
273
274         PATCHFILES tFiles;
275
276         if (ent1 != nullptr)
277         {
278                 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
279
280                 String pathLeft = _T("");
281                 if (!sDir1.empty())
282                         pathLeft = sDir1 + backslash;
283                 pathLeft += ent1->filename.get();
284                 tFiles.pathLeft = std::move(pathLeft);
285         }
286
287         if (ent2 != nullptr)
288         {
289                 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
290
291                 String pathRight = _T("");
292                 if (!sDir2.empty())
293                         pathRight = sDir2 + backslash;
294                 pathRight += ent2->filename.get();
295
296                 tFiles.pathRight = std::move(pathRight);
297         }
298
299         fileList->push_back(tFiles);
300 }
301
302 /**
303  * @brief This function walks given folders and adds found files into patch list.
304  * We walk all subfolders and add the files they contain into list.
305  *
306  * @param [in] paths Root paths of compare
307  * @param [in] subdir Subdirectories under root path
308  * @param [out] fileList Patch files list
309  * @return 1 normally, 0 if no directories and files exist.
310  * @remark This function was written based on DirScan_GetItems() in DirScan.cpp
311  */
312 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
313 {
314         static const TCHAR backslash[] = _T("\\");
315         int nDirs = paths.GetSize();
316
317         String sDir[2];
318         String subprefix[2];
319
320         std::copy(paths.begin(), paths.end(), sDir);
321
322         if (!subdir[0].empty())
323         {
324                 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
325                 {
326                         sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
327                         subprefix[nIndex] = subdir[nIndex] + backslash;
328                 }
329         }
330
331         DirItemArray dirs[2], aFiles[2];
332         for (int nIndex = 0; nIndex < nDirs; nIndex++)
333                 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
334
335         {
336                 int nIndex;
337                 for (nIndex = 0; nIndex < nDirs; nIndex++)
338                         if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
339                 if (nIndex == nDirs)
340                         return 0;
341         }
342
343         // Handle directories
344         // i points to current directory in left list (dirs[0])
345         // j points to current directory in right list (dirs[1])
346         DirItemArray::size_type i = 0, j = 0;
347         while (true)
348         {
349                 if (i >= dirs[0].size() && j >= dirs[1].size())
350                         break;
351
352                 unsigned nDiffCode = DIFFCODE::DIR;
353                 // Comparing directories leftDirs[i].name to rightDirs[j].name
354                 if (i < dirs[0].size() && (j == dirs[1].size() || collstr(dirs[0][i].filename, dirs[1][j].filename, false) < 0))
355                 {
356                         nDiffCode |= DIFFCODE::FIRST;
357                 }
358                 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
359                 {
360                         nDiffCode |= DIFFCODE::SECOND;
361                 }
362                 else
363                 {
364                         nDiffCode |= DIFFCODE::BOTH;
365                 }
366
367                 String leftnewsub = (nDiffCode & DIFFCODE::FIRST) ? subprefix[0] + dirs[0][i].filename.get() : subprefix[0] + dirs[1][j].filename.get();
368                 String rightnewsub = (nDiffCode & DIFFCODE::SECOND) ? subprefix[1] + dirs[1][j].filename.get() : subprefix[1] + dirs[0][i].filename.get();
369
370                 // Scan recursively all subdirectories too
371                 String newsubdir[2] = { leftnewsub, rightnewsub };
372                 GetItemsForPatchList(paths, newsubdir, fileList);
373
374                 if (nDiffCode & DIFFCODE::FIRST)
375                         i++;
376                 if (nDiffCode & DIFFCODE::SECOND)
377                         j++;
378         }
379
380         // Handle files
381         // i points to current file in left list (aFiles[0])
382         // j points to current file in right list (aFiles[1])
383         i = 0, j = 0;
384         while (true)
385         {
386                 // Comparing file aFiles[0][i].name to aFiles[1][j].name
387                 if (i < aFiles[0].size() && (j == aFiles[1].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) < 0))
388                 {
389                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
390                         ++i;
391                         continue;
392                 }
393                 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
394                 {
395                         AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
396                         ++j;
397                         continue;
398                 }
399                 if (i < aFiles[0].size())
400                 {
401                         assert(j < aFiles[1].size());
402
403                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);
404                         ++i;
405                         ++j;
406                         continue;
407                 }
408                 break;
409         }
410
411         return 1;
412 }