OSDN Git Service

Merge pull request #331 from tjmprm77/feature/generate_patch_from_directories
[winmerge-jp/winmerge-jp.git] / Src / PatchTool.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    License (GPLv2+):
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.
7 //    
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.
12 //
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 /////////////////////////////////////////////////////////////////////////////
17 /** 
18  * @file  PatchTool.cpp
19  *
20  * @brief Code file routines
21  */
22
23 #include "StdAfx.h"
24 #include "PatchTool.h"
25 #include "UnicodeString.h"
26 #include "DiffWrapper.h"
27 #include "PathContext.h"
28 #include "PatchDlg.h"
29 #include "paths.h"
30 #include "Merge.h"
31 #include "DirTravel.h"
32
33 #ifdef _DEBUG
34 #define new DEBUG_NEW
35 #endif
36
37 /**
38  * @brief Default constructor.
39  */
40 CPatchTool::CPatchTool() : m_bOpenToEditor(false)
41 {
42 }
43
44 /**
45  * @brief Default destructor.
46  */
47 CPatchTool::~CPatchTool()
48 {
49 }
50
51 /** 
52  * @brief Adds files to list for patching.
53  * @param [in] file1 First file to add.
54  * @param [in] file2 Second file to add.
55  */
56 void CPatchTool::AddFiles(const String &file1, const String &file2)
57 {
58         PATCHFILES tFiles;
59         tFiles.lfile = file1;
60         tFiles.rfile = file2;
61
62         // TODO: Read and add file's timestamps
63         m_fileList.push_back(tFiles);
64 }
65
66 /**
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.
76  */
77 void CPatchTool::AddFiles(const String &file1, const String &altPath1,
78                 const String &file2, const String &altPath2)
79 {
80         PATCHFILES tFiles;
81         tFiles.lfile = file1;
82         tFiles.rfile = file2;
83         tFiles.pathLeft = altPath1;
84         tFiles.pathRight = altPath2;
85
86         // TODO: Read and add file's timestamps
87         m_fileList.push_back(tFiles);
88 }
89
90 /** 
91  * @brief Create a patch from files given.
92  * @note Files can be given using AddFiles() or selecting using
93  * CPatchDlg.
94  */
95 int CPatchTool::CreatePatch()
96 {
97         DIFFSTATUS status;
98         int retVal = 0;
99
100         CPatchDlg dlgPatch;
101
102         // If files already inserted, add them to dialog
103     for(std::vector<PATCHFILES>::iterator iter = m_fileList.begin(); iter != m_fileList.end(); ++iter)
104     {
105         dlgPatch.AddItem(*iter);
106         }
107
108         if (ShowDialog(&dlgPatch))
109         {
110                 bool bResult = true;
111
112                 if (!paths::CreateIfNeeded(paths::GetPathOnly(dlgPatch.m_fileResult)))
113                 {
114                         LangMessageBox(IDS_FOLDER_NOTEXIST, MB_OK | MB_ICONSTOP);
115                         return 0;
116                 }
117
118                 // Select patch create -mode
119                 m_diffWrapper.SetCreatePatchFile(dlgPatch.m_fileResult);
120                 m_diffWrapper.SetAppendFiles(dlgPatch.m_appendFile);
121                 m_diffWrapper.SetPrediffer(nullptr);
122
123                 size_t fileCount = dlgPatch.GetItemCount();
124
125                 std::vector<PATCHFILES> fileList;
126                 for (size_t index = 0; index < fileCount; index++)
127                 {
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)
130                         {
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);
141                         }
142                         else {
143                                 fileList.push_back(tFiles);
144                         }
145                 }
146                 fileCount = fileList.size();
147
148                 m_diffWrapper.WritePatchFileHeader(dlgPatch.m_outputStyle, dlgPatch.m_appendFile);
149                 m_diffWrapper.SetAppendFiles(true);
150
151                 bool bShowedBinaryMessage = false;
152                 int writeFileCount = 0;
153
154                 for (size_t index = 0; index < fileCount; index++)
155                 {
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;
159                         
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);
166
167                         if (!bDiffSuccess)
168                         {
169                                 LangMessageBox(IDS_FILEERROR, MB_ICONSTOP);
170                                 bResult = false;
171                                 break;
172                         }
173                         else if (status.bBinaries)
174                         {
175                                 if (!bShowedBinaryMessage)
176                                 {
177                                         LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING);
178                                         bShowedBinaryMessage = true;
179                                 }
180                         }
181                         else if (status.bPatchFileFailed)
182                         {
183                                 String errMsg = strutils::format_string1(_("Could not write to file %1."), dlgPatch.m_fileResult);
184                                 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
185                                 bResult = false;
186                                 break;
187                         }
188                         else
189                         {
190                                 writeFileCount++;
191                         }
192
193                 }
194                 
195                 m_diffWrapper.WritePatchFileTerminator(dlgPatch.m_outputStyle);
196
197                 if (bResult && writeFileCount > 0)
198                 {
199                         LangMessageBox(IDS_DIFF_SUCCEEDED, MB_ICONINFORMATION|MB_DONT_DISPLAY_AGAIN,
200                                             IDS_DIFF_SUCCEEDED);
201                         
202                         m_sPatchFile = dlgPatch.m_fileResult;
203                         m_bOpenToEditor = dlgPatch.m_openToEditor;
204                         retVal = 1;
205                 }
206         }
207         dlgPatch.ClearItems();
208         if (retVal)
209         {
210                 if (m_bOpenToEditor)
211                         theApp.OpenFileToExternalEditor(m_sPatchFile);
212         }
213         return retVal;
214 }
215
216 /** 
217  * @brief Show patch options dialog and check options selected.
218  * @return `true` if user wants to create a patch (didn't cancel dialog).
219  */
220 bool CPatchTool::ShowDialog(CPatchDlg *pDlgPatch)
221 {
222         DIFFOPTIONS diffOptions = {0};
223         PATCHOPTIONS patchOptions;
224         bool bRetVal = true;
225
226         if (pDlgPatch->DoModal() == IDOK)
227         {
228                 // There must be one filepair
229                 if (pDlgPatch->GetItemCount() < 1)
230                         bRetVal = false;
231
232                 // These two are from dropdown list - can't be wrong
233                 patchOptions.outputStyle = pDlgPatch->m_outputStyle;
234                 patchOptions.nContext = pDlgPatch->m_contextLines;
235
236                 // Checkbox - can't be wrong
237                 patchOptions.bAddCommandline = pDlgPatch->m_includeCmdLine;
238                 m_diffWrapper.SetPatchOptions(&patchOptions);
239
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);
244
245                 // Use this because non-sensitive setting can't write
246                 // patch file EOLs correctly
247                 diffOptions.bIgnoreEol = pDlgPatch->m_ignoreEOLDifference;
248                 
249                 diffOptions.bIgnoreCase = !pDlgPatch->m_caseSensitive;
250                 diffOptions.nDiffAlgorithm = pDlgPatch->m_diffAlgorithm;
251                 diffOptions.bIndentHeuristic = pDlgPatch->m_indentHeuristic;
252                 m_diffWrapper.SetOptions(&diffOptions);
253         }
254         else
255                 return false;
256
257         return bRetVal;
258 }
259
260 /**
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.
267  */
268 void CPatchTool::AddFilesToList(const String& sDir1, const String& sDir2, const DirItem* ent1, const DirItem* ent2, std::vector<PATCHFILES>* fileList)
269 {
270         if ((ent1 == nullptr && ent2 == nullptr) || fileList == nullptr)
271                 return;
272
273         static const TCHAR backslash[] = _T("\\");
274
275         PATCHFILES tFiles;
276
277         if (ent1 != nullptr)
278         {
279                 tFiles.lfile = ent1->path.get() + backslash + ent1->filename.get();
280
281                 String pathLeft = _T("");
282                 if (!sDir1.empty())
283                         pathLeft = sDir1 + backslash;
284                 pathLeft += ent1->filename.get();
285                 tFiles.pathLeft = pathLeft;
286         }
287
288         if (ent2 != nullptr)
289         {
290                 tFiles.rfile = ent2->path.get() + backslash + ent2->filename.get();
291
292                 String pathRight = _T("");
293                 if (!sDir2.empty())
294                         pathRight = sDir2 + backslash;
295                 pathRight += ent2->filename.get();
296
297                 tFiles.pathRight = pathRight;
298         }
299
300         fileList->push_back(tFiles);
301 }
302
303 /**
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.
306  *
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
312  */
313 int CPatchTool::GetItemsForPatchList(const PathContext& paths, const String subdir[], std::vector<PATCHFILES>* fileList)
314 {
315         static const TCHAR backslash[] = _T("\\");
316         int nDirs = paths.GetSize();
317
318         String sDir[2];
319         String subprefix[2];
320
321         std::copy(paths.begin(), paths.end(), sDir);
322
323         if (!subdir[0].empty())
324         {
325                 for (int nIndex = 0; nIndex < paths.GetSize(); nIndex++)
326                 {
327                         sDir[nIndex] = paths::ConcatPath(sDir[nIndex], subdir[nIndex]);
328                         subprefix[nIndex] = subdir[nIndex] + backslash;
329                 }
330         }
331
332         DirItemArray dirs[2], aFiles[2];
333         for (int nIndex = 0; nIndex < nDirs; nIndex++)
334                 LoadAndSortFiles(sDir[nIndex], &dirs[nIndex], &aFiles[nIndex], false);
335
336         {
337                 int nIndex;
338                 for (nIndex = 0; nIndex < nDirs; nIndex++)
339                         if (dirs[nIndex].size() != 0 || aFiles[nIndex].size() != 0) break;
340                 if (nIndex == nDirs)
341                         return 0;
342         }
343
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;
348         while (true)
349         {
350                 if (i >= dirs[0].size() && j >= dirs[1].size())
351                         break;
352
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))
356                 {
357                         nDiffCode |= DIFFCODE::FIRST;
358                 }
359                 else if (j < dirs[1].size() && (i == dirs[0].size() || collstr(dirs[1][j].filename, dirs[0][i].filename, false) < 0))
360                 {
361                         nDiffCode |= DIFFCODE::SECOND;
362                 }
363                 else
364                 {
365                         nDiffCode |= DIFFCODE::BOTH;
366                 }
367
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();
370
371                 // Scan recursively all subdirectories too
372                 String newsubdir[2] = { leftnewsub, rightnewsub };
373                 GetItemsForPatchList(paths, newsubdir, fileList);
374
375                 if (nDiffCode & DIFFCODE::FIRST)
376                         i++;
377                 if (nDiffCode & DIFFCODE::SECOND)
378                         j++;
379         }
380
381         // Handle files
382         // i points to current file in left list (aFiles[0])
383         // j points to current file in right list (aFiles[1])
384         i = 0, j = 0;
385         while (true)
386         {
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))
389                 {
390                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], nullptr, fileList);
391                         ++i;
392                         continue;
393                 }
394                 if (j < aFiles[1].size() && (i == aFiles[0].size() || collstr(aFiles[0][i].filename, aFiles[1][j].filename, false) > 0))
395                 {
396                         AddFilesToList(subdir[0], subdir[1], nullptr, &aFiles[1][j], fileList);
397                         ++j;
398                         continue;
399                 }
400                 if (i < aFiles[0].size())
401                 {
402                         assert(j < aFiles[1].size());
403
404                         AddFilesToList(subdir[0], subdir[1], &aFiles[0][i], &aFiles[1][j], fileList);
405                         ++i;
406                         ++j;
407                         continue;
408                 }
409                 break;
410         }
411
412         return 1;
413 }