1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * @file DiffWrapper.cpp
5 * @brief Code for DiffWrapper class
7 * @date Created: 2003-08-22
12 #include "DiffWrapper.h"
13 #include <sys/types.h>
24 #include <Poco/Format.h>
25 #include <Poco/Debugger.h>
26 #include <Poco/StringTokenizer.h>
27 #include <Poco/Exception.h>
28 #include "DiffContext.h"
29 #include "coretools.h"
31 #include "MovedLines.h"
32 #include "FilterList.h"
35 #include "xdiff_gnudiff_compat.h"
36 #include "FileTransform.h"
38 #include "CompareOptions.h"
39 #include "FileTextStats.h"
40 #include "FolderCmp.h"
41 #include "Environment.h"
42 #include "PatchHTML.h"
43 #include "UnicodeString.h"
46 #include "Exceptions.h"
47 #include "parsers/crystallineparser.h"
48 #include "SyntaxColors.h"
50 #include "SubstitutionList.h"
54 using Poco::StringTokenizer;
55 using Poco::Exception;
59 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats);
60 static void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData);
63 * @brief Default constructor.
64 * Initializes members.
66 CDiffWrapper::CDiffWrapper()
67 : m_pFilterCommentsDef(nullptr)
68 , m_bCreatePatchFile(false)
69 , m_bUseDiffList(false)
71 , m_bAppendFiles(false)
73 , m_infoPrediffer(nullptr)
74 , m_pDiffList(nullptr)
75 , m_bPathsAreTemp(false)
76 , m_pFilterList(nullptr)
77 , m_pSubstitutionList{nullptr}
78 , m_bPluginsEnabled(false)
81 // character that ends a line. Currently this is always `\n'
88 CDiffWrapper::~CDiffWrapper()
93 * @brief Enables/disables patch-file creation and sets filename.
94 * This function enables or disables patch file creation. When
95 * @p filename is empty, patch files are disabled.
96 * @param [in] filename Filename for patch file, or empty string.
98 void CDiffWrapper::SetCreatePatchFile(const String &filename)
100 if (filename.empty())
102 m_bCreatePatchFile = false;
103 m_sPatchFile.clear();
107 m_bCreatePatchFile = true;
108 m_sPatchFile = filename;
109 strutils::replace(m_sPatchFile, _T("/"), _T("\\"));
114 * @brief Enables/disabled DiffList creation ands sets DiffList.
115 * This function enables or disables DiffList creation. When
116 * @p diffList is `nullptr`, a difflist was not created. When valid
117 * DiffList pointer is given, compare results are stored into it.
118 * @param [in] diffList Pointer to DiffList getting compare results.
120 void CDiffWrapper::SetCreateDiffList(DiffList *diffList)
122 if (diffList == nullptr)
124 m_bUseDiffList = false;
125 m_pDiffList = nullptr;
129 m_bUseDiffList = true;
130 m_pDiffList = diffList;
135 * @brief Returns current set of options used by diff-engine.
136 * This function converts internally used diff-options to
137 * format used outside CDiffWrapper and returns them.
138 * @param [in,out] options Pointer to structure getting used options.
140 void CDiffWrapper::GetOptions(DIFFOPTIONS *options) const
142 assert(options != nullptr);
143 DIFFOPTIONS tmpOptions = {0};
144 m_options.GetAsDiffOptions(tmpOptions);
145 *options = tmpOptions;
149 * @brief Set options for Diff-engine.
150 * This function converts given options to format CDiffWrapper uses
151 * internally and stores them.
152 * @param [in] options Pointer to structure having new options.
154 void CDiffWrapper::SetOptions(const DIFFOPTIONS *options)
156 assert(options != nullptr);
157 m_options.SetFromDiffOptions(*options);
160 void CDiffWrapper::SetPrediffer(const PrediffingInfo * prediffer /*= nullptr*/)
162 // all flags are set correctly during the construction
163 m_infoPrediffer.reset(new PrediffingInfo);
165 if (prediffer != nullptr)
166 *m_infoPrediffer = *prediffer;
170 * @brief Set options used for patch-file creation.
171 * @param [in] options Pointer to structure having new options.
173 void CDiffWrapper::SetPatchOptions(const PATCHOPTIONS *options)
175 assert(options != nullptr);
176 m_options.m_contextLines = options->nContext;
178 switch (options->outputStyle)
181 m_options.m_outputStyle = DIFF_OUTPUT_NORMAL;
184 m_options.m_outputStyle = DIFF_OUTPUT_CONTEXT;
187 m_options.m_outputStyle = DIFF_OUTPUT_UNIFIED;
190 m_options.m_outputStyle = DIFF_OUTPUT_HTML;
193 throw "Unknown output style!";
197 m_bAddCmdLine = options->bAddCommandline;
201 * @brief Enables/disables moved block detection.
202 * @param [in] bDetectMovedBlocks If true moved blocks are detected.
204 void CDiffWrapper::SetDetectMovedBlocks(bool bDetectMovedBlocks)
206 if (bDetectMovedBlocks)
208 if (m_pMovedLines[0] == nullptr)
210 m_pMovedLines[0].reset(new MovedLines);
211 m_pMovedLines[1].reset(new MovedLines);
212 m_pMovedLines[2].reset(new MovedLines);
217 m_pMovedLines[0].reset();
218 m_pMovedLines[1].reset();
219 m_pMovedLines[2].reset();
223 static String convertToTString(const char* start, const char* end)
225 if (!ucr::CheckForInvalidUtf8(start, end - start))
227 return ucr::toTString(std::string(start, end));
233 ucr::maketstring(text, start, end - start, -1, &lossy);
238 static unsigned GetLastLineCookie(unsigned dwCookie, int startLine, int endLine, const char **linbuf, CrystalLineParser::TextDefinition* enuType)
242 for (int i = startLine; i <= endLine; ++i)
244 String text = convertToTString(linbuf[i], linbuf[i + 1]);
245 int nActualItems = 0;
246 std::vector<CrystalLineParser::TEXTBLOCK> blocks(text.length());
247 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), static_cast<int>(text.length()), blocks.data(), nActualItems);
252 static unsigned GetCommentsFilteredText(unsigned dwCookie, int startLine, int endLine, const char **linbuf, std::string& filtered, CrystalLineParser::TextDefinition* enuType)
255 for (int i = startLine; i <= endLine; ++i)
257 String text = convertToTString(linbuf[i], linbuf[i + 1]);
258 unsigned textlen = static_cast<unsigned>(text.size());
265 int nActualItems = 0;
266 std::vector<CrystalLineParser::TEXTBLOCK> blocks(textlen);
267 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), textlen, blocks.data(), nActualItems);
269 if (nActualItems == 0)
275 for (int j = 0; j < nActualItems; ++j)
277 CrystalLineParser::TEXTBLOCK& block = blocks[j];
278 if (block.m_nColorIndex != COLORINDEX_COMMENT)
280 unsigned blocklen = (j < nActualItems - 1) ? (blocks[j + 1].m_nCharPos - block.m_nCharPos) : textlen - block.m_nCharPos;
281 filteredT.append(text.c_str() + block.m_nCharPos, blocklen);
288 filtered = ucr::toUTF8(filteredT);
294 * @brief Replace spaces in a string
295 * @param [in] str - String to search
296 * @param [in] rep - String to replace
298 static void ReplaceSpaces(std::string & str, const char *rep)
300 std::string::size_type pos = 0;
301 size_t replen = strlen(rep);
302 while ((pos = str.find_first_of(" \t", pos)) != std::string::npos)
304 std::string::size_type posend = str.find_first_not_of(" \t", pos);
305 if (posend != String::npos)
306 str.replace(pos, posend - pos, rep);
308 str.replace(pos, 1, rep);
313 @brief The main entry for post filtering. Performs post-filtering, by setting comment blocks to trivial
314 @param [in] LineNumberLeft - First line number to read from left file
315 @param [in] QtyLinesLeft - Number of lines in the block for left file
316 @param [in] LineNumberRight - First line number to read from right file
317 @param [in] QtyLinesRight - Number of lines in the block for right file
318 @param [in,out] Op - This variable is set to trivial if block should be ignored.
320 void CDiffWrapper::PostFilter(PostFilterContext& ctxt, int LineNumberLeft, int QtyLinesLeft, int LineNumberRight,
321 int QtyLinesRight, OP_TYPE &Op, const file_data *file_data_ary) const
323 if (Op == OP_TRIVIAL)
326 std::string LineDataLeft, LineDataRight;
328 if (m_options.m_filterCommentsLines)
330 ctxt.dwCookieLeft = GetLastLineCookie(ctxt.dwCookieLeft,
331 ctxt.nParsedLineEndLeft + 1, LineNumberLeft - 1, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, m_pFilterCommentsDef);
332 ctxt.dwCookieRight = GetLastLineCookie(ctxt.dwCookieRight,
333 ctxt.nParsedLineEndRight + 1, LineNumberRight - 1, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, m_pFilterCommentsDef);
335 ctxt.nParsedLineEndLeft = LineNumberLeft + QtyLinesLeft - 1;
336 ctxt.nParsedLineEndRight = LineNumberRight + QtyLinesRight - 1;;
338 ctxt.dwCookieLeft = GetCommentsFilteredText(ctxt.dwCookieLeft,
339 LineNumberLeft, ctxt.nParsedLineEndLeft, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, LineDataLeft, m_pFilterCommentsDef);
340 ctxt.dwCookieRight = GetCommentsFilteredText(ctxt.dwCookieRight,
341 LineNumberRight, ctxt.nParsedLineEndRight, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, LineDataRight, m_pFilterCommentsDef);
345 LineDataLeft.assign(file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base],
346 file_data_ary[0].linbuf[LineNumberLeft + QtyLinesLeft + file_data_ary[0].linbuf_base]
347 - file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base]);
348 LineDataRight.assign(file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base],
349 file_data_ary[1].linbuf[LineNumberRight + QtyLinesRight + file_data_ary[1].linbuf_base]
350 - file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base]);
353 if (m_pSubstitutionList)
355 LineDataLeft = m_pSubstitutionList->Subst(LineDataLeft);
356 LineDataRight = m_pSubstitutionList->Subst(LineDataRight);
359 if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_ALL)
361 //Ignore character case
362 ReplaceSpaces(LineDataLeft, "");
363 ReplaceSpaces(LineDataRight, "");
365 else if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_CHANGE)
367 //Ignore change in whitespace char count
368 ReplaceSpaces(LineDataLeft, " ");
369 ReplaceSpaces(LineDataRight, " ");
372 if (m_options.m_bIgnoreCase)
375 // std::transform(LineDataLeft.begin(), LineDataLeft.end(), LineDataLeft.begin(), ::toupper);
376 for (std::string::iterator pb = LineDataLeft.begin(), pe = LineDataLeft.end(); pb != pe; ++pb)
377 *pb = static_cast<char>(::toupper(*pb));
378 // std::transform(LineDataRight.begin(), LineDataRight.end(), LineDataRight.begin(), ::toupper);
379 for (std::string::iterator pb = LineDataRight.begin(), pe = LineDataRight.end(); pb != pe; ++pb)
380 *pb = static_cast<char>(::toupper(*pb));
382 if (LineDataLeft != LineDataRight)
384 //only difference is trival
389 * @brief Set source paths for diffing two files.
390 * Sets full paths to two files we are diffing. Paths can be actual user files
391 * or temporary copies of user files. Parameter @p tempPaths tells if paths
392 * are temporary paths that can be deleted.
393 * @param [in] files Files to compare
394 * @param [in] tempPaths Are given paths temporary (can be deleted)?.
396 void CDiffWrapper::SetPaths(const PathContext &tFiles,
400 m_bPathsAreTemp = tempPaths;
404 * @brief Runs diff-engine.
406 bool CDiffWrapper::RunFileDiff()
408 PathContext aFiles = m_files;
410 for (file = 0; file < m_files.GetSize(); file++)
411 aFiles[file] = paths::ToWindowsPath(aFiles[file]);
414 String strFileTemp[3];
415 std::copy(m_files.begin(), m_files.end(), strFileTemp);
417 m_options.SetToDiffUtils();
420 m_nDiffs = m_pDiffList->GetSize();
422 for (file = 0; file < aFiles.GetSize(); file++)
424 if (m_bPluginsEnabled)
426 // Do the preprocessing now, overwrite the temp files
427 // NOTE: FileTransform_UCS2ToUTF8() may create new temp
428 // files and return new names, those created temp files
429 // are deleted in end of function.
431 // this can only fail if the data can not be saved back (no more
432 // place on disk ???) What to do then ??
433 if (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
435 // display a message box
436 String sError = strutils::format(
437 _T("An error occurred while prediffing the file '%s' with the plugin '%s'. The prediffing is not applied any more."),
438 strFileTemp[file].c_str(),
439 m_infoPrediffer->GetPluginPipeline().c_str());
440 AppErrorMessageBox(sError);
441 // don't use any more this prediffer
442 m_infoPrediffer->ClearPluginPipeline();
447 struct change *script = nullptr;
448 struct change *script10 = nullptr;
449 struct change *script12 = nullptr;
450 DiffFileData diffdata, diffdata10, diffdata12;
451 int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
453 if (aFiles.GetSize() == 2)
455 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
456 // This opens & fstats both files (if it succeeds)
457 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
462 // Compare the files, if no error was found.
463 // Last param (bin_file) is `nullptr` since we don't
464 // (yet) need info about binary sides.
465 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
467 // We don't anymore create diff-files for every rescan.
468 // User can create patch-file whenever one wants to.
469 // We don't need to waste time. But lets keep this as
470 // debugging aid. Sometimes it is very useful to see
471 // what differences diff-engine sees!
473 // throw the diff into a temp file
474 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
475 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
477 if (_tfopen_s(&outfile, path.c_str(), _T("w+")) == 0)
479 print_normal_script(script);
487 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
488 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
490 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
495 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
497 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
502 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
505 // First determine what happened during comparison
506 // If there were errors or files were binaries, don't bother
507 // creating diff-lists or patches
509 // diff_2_files set bin_flag to -1 if different binary
510 // diff_2_files set bin_flag to +1 if same binary
512 file_data * inf = diffdata.m_inf;
513 file_data * inf10 = diffdata10.m_inf;
514 file_data * inf12 = diffdata12.m_inf;
516 if (aFiles.GetSize() == 2)
520 m_status.bBinaries = true;
522 m_status.Identical = IDENTLEVEL::ALL;
524 m_status.Identical = IDENTLEVEL::NONE;
527 { // text files according to diffutils, so change script exists
528 m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
529 m_status.bBinaries = false;
531 m_status.bMissingNL[0] = !!inf[0].missing_newline;
532 m_status.bMissingNL[1] = !!inf[1].missing_newline;
536 m_status.Identical = IDENTLEVEL::NONE;
537 if (bin_flag10 != 0 || bin_flag12 != 0)
539 m_status.bBinaries = true;
540 if (bin_flag10 != -1 && bin_flag12 != -1)
541 m_status.Identical = IDENTLEVEL::ALL;
542 else if (bin_flag10 != -1)
543 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
544 else if (bin_flag12 != -1)
545 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
547 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
550 { // text files according to diffutils, so change script exists
551 m_status.bBinaries = false;
552 if (script10 == nullptr && script12 == nullptr)
553 m_status.Identical = IDENTLEVEL::ALL;
554 else if (script10 == nullptr)
555 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
556 else if (script12 == nullptr)
557 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
559 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
561 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
562 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
563 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
568 if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
570 WritePatchFile(script, &inf[0]);
573 // Go through diffs adding them to WinMerge's diff list
574 // This is done on every WinMerge's doc rescan!
575 if (!m_status.bBinaries && m_bUseDiffList)
577 if (aFiles.GetSize() == 2)
578 LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
580 LoadWinMergeDiffsFromDiffUtilsScript3(
582 diffdata10.m_inf, diffdata12.m_inf);
585 // cleanup the script
586 if (aFiles.GetSize() == 2)
587 FreeDiffUtilsScript(script);
590 FreeDiffUtilsScript(script10);
591 FreeDiffUtilsScript(script12);
594 // Done with diffutils filedata
595 if (aFiles.GetSize() == 2)
605 if (m_bPluginsEnabled)
607 // Delete temp files transformation functions possibly created
608 for (file = 0; file < aFiles.GetSize(); file++)
610 if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
614 TFile(strFileTemp[file]).remove();
618 LogErrorStringUTF8(e.displayText());
620 strFileTemp[file].erase();
628 * @brief Add diff to external diff-list
630 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
635 dr.begin[0] = begin0;
637 dr.begin[1] = begin1;
642 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
643 pDiffList->AddDiff(dr);
645 catch (std::exception& e)
647 AppErrorMessageBox(ucr::toTString(e.what()));
651 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
655 pDiffList->AddDiff(dr);
657 catch (std::exception& e)
659 AppErrorMessageBox(ucr::toTString(e.what()));
664 * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
665 * @param [in] leftBufferLines size of array pane left
666 * @param [in] rightBufferLines size of array pane right
667 * @param [in] left on whitch side we have to insert
668 * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
670 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
673 const int count = m_pDiffList->GetSize();
676 m_pDiffList->GetDiff(count - 1, dr);
678 for (int file = 0; file < nFiles; file++)
680 if (!bMissingNL[file])
684 m_pDiffList->SetDiff(count - 1, dr);
688 // we have to create the DIFF
689 for (int file = 0; file < nFiles; file++)
691 dr.end[file] = bufferLines[file] - 1;
692 if (bMissingNL[file])
693 dr.begin[file] = dr.end[file];
695 dr.begin[file] = dr.end[file] + 1;
697 assert(dr.begin[0] == dr.begin[file]);
699 if (bIgnoreBlankLines)
702 AddDiffRange(m_pDiffList, dr);
707 * @brief Returns status-data from diff-engine last run
709 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
711 std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
715 * @brief Formats command-line for diff-engine last run (like it was called from command-line)
717 String CDiffWrapper::FormatSwitchString() const
721 switch (m_options.m_outputStyle)
723 case DIFF_OUTPUT_NORMAL:
726 case DIFF_OUTPUT_CONTEXT:
727 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
729 case DIFF_OUTPUT_UNIFIED:
730 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
736 case DIFF_OUTPUT_FORWARD_ED:
739 case DIFF_OUTPUT_RCS:
742 case DIFF_OUTPUT_IFDEF:
745 case DIFF_OUTPUT_SDIFF:
751 if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
752 m_options.m_contextLines > 0)
754 TCHAR tmpNum[5] = {0};
755 _itot_s(m_options.m_contextLines, tmpNum, 10);
759 if (ignore_all_space_flag > 0)
760 switches += _T(" -w");
762 if (ignore_blank_lines_flag > 0)
763 switches += _T(" -B");
765 if (ignore_case_flag > 0)
766 switches += _T(" -i");
768 if (ignore_space_change_flag > 0)
769 switches += _T(" -b");
775 * @brief Enables/disables patch-file appending.
776 * If the file for patch already exists then the patch will be appended to
778 * @param [in] bAppendFiles If true patch will be appended to existing file.
780 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
782 m_bAppendFiles = bAppendFiles;
786 * @brief Compare two files using diffutils.
788 * Compare two files (in DiffFileData param) using diffutils. Run diffutils
789 * inside SEH so we can trap possible error and exceptions. If error or
790 * execption is trapped, return compare failure.
791 * @param [out] diffs Pointer to list of change structs where diffdata is stored.
792 * @param [in] diffData files to compare.
793 * @param [out] bin_status used to return binary status from compare.
794 * @param [out] bin_file Returns which file was binary file as bitmap.
795 So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
796 info is not needed (faster compare since diffutils don't bother checking
797 second file if first is binary).
798 * @return true when compare succeeds, false if error happened during compare.
799 * @note This function is used in file compare, not folder compare. Similar
800 * folder compare function is in DiffFileData.cpp.
802 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
803 int * bin_status, int * bin_file) const
809 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
811 unsigned xdl_flags = make_xdl_flags(m_options);
812 *diffs = diff_2_files_xdiff(diffData->m_inf, (m_pMovedLines[0] != nullptr), xdl_flags);
813 files[0] = diffData->m_inf[0];
814 files[1] = diffData->m_inf[1];
818 // Diff files. depth is zero because we are not comparing dirs
819 *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
820 (m_pMovedLines[0] != nullptr), bin_file);
822 CopyDiffutilTextStats(diffData->m_inf, diffData);
824 catch (SE_Exception&)
833 * @brief Free script (the diffutils linked list of differences)
836 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
838 if (script == nullptr) return;
839 struct change *e=nullptr, *p=nullptr;
840 // cleanup the script
841 for (e = script; e != nullptr; e = p)
850 * @brief Match regular expression list against given difference.
851 * This function matches the regular expression list against the difference
852 * (given as start line and end line). Matching the diff requires that all
853 * lines in difference match.
854 * @param [in] StartPos First line of the difference.
855 * @param [in] endPos Last line of the difference.
856 * @param [in] FileNo File to match.
857 * return true if any of the expressions matches.
859 bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) const
861 if (m_pFilterList == nullptr)
863 throw "CDiffWrapper::RegExpFilter() called when "
864 "filterlist doesn't exist (=nullptr)";
867 bool linesMatch = true; // set to false when non-matching line is found.
870 while (line <= EndPos && linesMatch)
872 size_t len = pinf->linbuf[line + 1] - pinf->linbuf[line];
873 const char *string = pinf->linbuf[line];
874 size_t stringlen = linelen(string, len);
875 if (!m_pFilterList->Match(std::string(string, stringlen)))
886 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
889 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
891 //Logic needed for Ignore comment option
892 PostFilterContext ctxt;
894 struct change *next = script;
896 while (next != nullptr)
898 /* Find a set of changes that belong together. */
899 struct change *thisob = next;
900 struct change *end = find_change(next);
902 /* Disconnect them from the rest of the changes,
903 making them a hunk, and remember the rest for next iteration. */
907 debug_script(thisob);
910 /* Print thisob hunk. */
911 //(*printfun) (thisob);
913 /* Determine range of line numbers involved in each file. */
914 int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
915 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
916 if (deletes || inserts || thisob->trivial)
918 OP_TYPE op = OP_NONE;
919 if (deletes && inserts)
921 else if (deletes || inserts)
926 /* Print the lines that the first file has. */
927 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
928 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
929 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
931 // Store information about these blocks in moved line info
932 if (GetDetectMovedBlocks())
934 if (thisob->match0>=0)
936 assert(thisob->inserted > 0);
937 for (int i=0; i<thisob->inserted; ++i)
939 int line0 = i+thisob->match0 + (trans_a0-first0-1);
940 int line1 = i+thisob->line1 + (trans_a1-first1-1);
941 GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
944 if (thisob->match1>=0)
946 assert(thisob->deleted > 0);
947 for (int i=0; i<thisob->deleted; ++i)
949 int line0 = i+thisob->line0 + (trans_a0-first0-1);
950 int line1 = i+thisob->match1 + (trans_a1-first1-1);
951 GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
955 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
956 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
958 if (m_options.m_filterCommentsLines ||
959 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
960 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, file_data_ary);
962 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
964 // Match lines against regular expression filters
965 // Our strategy is that every line in both sides must
966 // match regexp before we mark difference as ignored.
968 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &file_data_ary[0]);
970 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &file_data_ary[1]);
971 if (match1 && match2)
975 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces &&
976 QtyLinesLeft == QtyLinesRight)
979 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
983 /* Reconnect the script so it will all be freed properly. */
990 Comp02Functor(const file_data * inf10, const file_data * inf12) :
991 inf10_(inf10), inf12_(inf12)
994 bool operator()(const DiffRangeInfo &dr3)
996 int line0 = dr3.begin[0];
997 int line2 = dr3.begin[2];
998 int line0end = dr3.end[0];
999 int line2end = dr3.end[2];
1000 if (line0end - line0 != line2end - line2)
1002 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1003 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1004 for (int i = 0; i < line0end - line0 + 1; ++i)
1006 const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1007 const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1008 if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1013 const file_data *inf10_;
1014 const file_data *inf12_;
1018 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1021 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1022 struct change * script10,
1023 struct change * script12,
1024 const file_data * inf10,
1025 const file_data * inf12)
1027 DiffList diff10, diff12;
1031 for (int file = 0; file < 2; file++)
1033 struct change *next = nullptr;
1034 int trans_a0, trans_b0, trans_a1, trans_b1;
1035 int first0, last0, first1, last1, deletes, inserts;
1037 const file_data *pinf = nullptr;
1038 DiffList *pdiff = nullptr;
1039 PostFilterContext ctxt;
1043 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1044 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1047 while (next != nullptr)
1049 /* Find a set of changes that belong together. */
1050 struct change *thisob = next;
1051 struct change *end = find_change(next);
1053 /* Disconnect them from the rest of the changes,
1054 making them a hunk, and remember the rest for next iteration. */
1056 end->link = nullptr;
1058 debug_script(thisob);
1061 /* Print thisob hunk. */
1062 //(*printfun) (thisob);
1064 /* Determine range of line numbers involved in each file. */
1065 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1066 if (deletes || inserts || thisob->trivial)
1068 if (deletes && inserts)
1070 else if (deletes || inserts)
1075 /* Print the lines that the first file has. */
1076 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1077 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1079 // Store information about these blocks in moved line info
1080 if (GetDetectMovedBlocks())
1082 int index1 = 0; // defaults for (file == 0 /* diff10 */)
1084 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1085 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1086 if (file == 1 /* diff12 */)
1090 side1 = MovedLines::SIDE::LEFT;
1091 side2 = MovedLines::SIDE::RIGHT;
1093 if (index1 != -1 && index2 != -1)
1095 if (thisob->match0>=0)
1097 assert(thisob->inserted > 0);
1098 for (int i=0; i<thisob->inserted; ++i)
1100 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1101 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1102 GetMovedLines(index1)->Add(side1, line1, line0);
1105 if (thisob->match1>=0)
1107 assert(thisob->deleted > 0);
1108 for (int i=0; i<thisob->deleted; ++i)
1110 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1111 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1112 GetMovedLines(index2)->Add(side2, line0, line1);
1118 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1119 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1121 if (m_options.m_filterCommentsLines ||
1122 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
1123 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, pinf);
1125 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
1127 // Match lines against regular expression filters
1128 // Our strategy is that every line in both sides must
1129 // match regexp before we mark difference as ignored.
1130 bool match2 = false;
1131 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &pinf[0]);
1133 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &pinf[1]);
1134 if (match1 && match2)
1138 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1142 /* Reconnect the script so it will all be freed properly. */
1147 Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(),
1148 Comp02Functor(inf10, inf12),
1149 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1152 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1155 if (!m_sPatchFile.empty())
1157 const TCHAR *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1158 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1162 if (outfile == nullptr)
1164 m_status.bPatchFileFailed = true;
1169 switch (tOutput_style)
1172 case OUTPUT_CONTEXT:
1173 case OUTPUT_UNIFIED:
1176 case OUTPUT_FORWARD_ED:
1183 print_html_header();
1191 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1194 if (!m_sPatchFile.empty())
1196 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), _T("a+")) != 0)
1200 if (outfile == nullptr)
1202 m_status.bPatchFileFailed = true;
1207 switch (tOutput_style)
1210 case OUTPUT_CONTEXT:
1211 case OUTPUT_UNIFIED:
1214 case OUTPUT_FORWARD_ED:
1221 print_html_terminator();
1230 * @brief Write out a patch file.
1231 * Writes patch file using already computed diffutils script. Converts path
1232 * delimiters from \ to / since we want to keep compatibility with patch-tools.
1233 * @param [in] script list of changes.
1234 * @param [in] inf file_data table containing filenames
1236 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1238 file_data inf_patch[2] = { inf[0], inf[1] };
1240 // Get paths, primarily use alternative paths, only if they are empty
1241 // use full filepaths
1242 String path1(m_alternativePaths[0]);
1243 String path2(m_alternativePaths[1]);
1248 path1 = paths::ToUnixPath(path1);
1249 path2 = paths::ToUnixPath(path2);
1250 if ((inf_patch[0].linbuf && ucr::CheckForInvalidUtf8(inf_patch[0].buffer, inf_patch[0].buffered_chars)) ||
1251 (inf_patch[1].linbuf && ucr::CheckForInvalidUtf8(inf_patch[1].buffer, inf_patch[1].buffered_chars)))
1253 inf_patch[0].name = _strdup(ucr::toThreadCP(path1).c_str());
1254 inf_patch[1].name = _strdup(ucr::toThreadCP(path2).c_str());
1258 inf_patch[0].name = _strdup(ucr::toUTF8(path1).c_str());
1259 inf_patch[1].name = _strdup(ucr::toUTF8(path2).c_str());
1262 // If paths in m_s1File and m_s2File point to original files, then we can use
1263 // them to fix potentially meaningless stats from potentially temporary files,
1264 // resulting from whatever transforms may have taken place.
1265 // If not, then we can't help it, and hence assert that this won't happen.
1266 if (!m_bPathsAreTemp)
1268 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1269 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1277 if (!m_sPatchFile.empty())
1279 const TCHAR *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1280 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1284 if (outfile == nullptr)
1286 m_status.bPatchFileFailed = true;
1290 // Print "command line"
1291 if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1293 String switches = FormatSwitchString();
1294 _ftprintf(outfile, _T("diff%s %s %s\n"),
1296 path1 == _T("NUL") ? _T("/dev/null") : path1.c_str(),
1297 path2 == _T("NUL") ? _T("/dev/null") : path2.c_str());
1300 if (strcmp(inf[0].name, "NUL") == 0)
1302 free((void *)inf_patch[0].name);
1303 inf_patch[0].name = _strdup("/dev/null");
1305 if (strcmp(inf[1].name, "NUL") == 0)
1307 free((void *)inf_patch[1].name);
1308 inf_patch[1].name = _strdup("/dev/null");
1312 switch (output_style)
1315 print_normal_script(script);
1317 case OUTPUT_CONTEXT:
1318 print_context_header(inf_patch, 0);
1319 print_context_script(script, 0);
1321 case OUTPUT_UNIFIED:
1322 print_context_header(inf_patch, 1);
1323 print_context_script(script, 1);
1327 print_ed_script(script);
1329 case OUTPUT_FORWARD_ED:
1330 pr_forward_ed_script(script);
1333 print_rcs_script(script);
1336 print_ifdef_script(script);
1339 print_sdiff_script(script);
1343 print_html_diff_header(inf_patch);
1344 print_html_script(script);
1345 print_html_diff_terminator();
1351 free((void *)inf_patch[0].name);
1352 free((void *)inf_patch[1].name);
1356 * @brief Set line filters, given as one string.
1357 * @param [in] filterStr Filters.
1359 void CDiffWrapper::SetFilterList(const String& filterStr)
1361 // Remove filterlist if new filter is empty
1362 if (filterStr.empty())
1364 m_pFilterList.reset();
1368 // Adding new filter without previous filter
1369 if (m_pFilterList == nullptr)
1371 m_pFilterList.reset(new FilterList);
1374 m_pFilterList->RemoveAllFilters();
1376 std::string regexp_str = ucr::toUTF8(filterStr);
1378 // Add every "line" of regexps to regexp list
1379 StringTokenizer tokens(regexp_str, "\r\n");
1380 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1381 m_pFilterList->AddRegExp(*it);
1384 void CDiffWrapper::SetFilterList(const FilterList* pFilterList)
1387 m_pFilterList.reset();
1390 m_pFilterList.reset(new FilterList());
1391 *m_pFilterList = *pFilterList;
1395 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1397 return m_pSubstitutionList.get();
1400 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1402 m_pSubstitutionList = pSubstitutionList;
1405 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1407 m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1411 * @brief Copy text stat results from diffutils back into the FileTextStats structure
1413 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1415 myTextStats->ncrlfs = inf->count_crlfs;
1416 myTextStats->ncrs = inf->count_crs;
1417 myTextStats->nlfs = inf->count_lfs;
1418 myTextStats->nzeros = inf->count_zeros;
1422 * @brief Copy both left & right text stats results back into the DiffFileData text stats
1424 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1426 CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1427 CopyTextStats(&inf[1], &diffData->m_textStats[1]);