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 (!FileTransform::Prediffing(m_infoPrediffer.get(), strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp))
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->m_PluginName.c_str());
440 AppErrorMessageBox(sError);
441 // don't use any more this prediffer
442 m_infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
443 m_infoPrediffer->m_PluginName.erase();
446 // We use the same plugin for both files, so it must be defined before
448 assert(m_infoPrediffer->m_PluginOrPredifferMode == PLUGIN_MODE::PLUGIN_MANUAL);
452 struct change *script = nullptr;
453 struct change *script10 = nullptr;
454 struct change *script12 = nullptr;
455 DiffFileData diffdata, diffdata10, diffdata12;
456 int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
458 if (aFiles.GetSize() == 2)
460 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
461 // This opens & fstats both files (if it succeeds)
462 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
467 // Compare the files, if no error was found.
468 // Last param (bin_file) is `nullptr` since we don't
469 // (yet) need info about binary sides.
470 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
472 // We don't anymore create diff-files for every rescan.
473 // User can create patch-file whenever one wants to.
474 // We don't need to waste time. But lets keep this as
475 // debugging aid. Sometimes it is very useful to see
476 // what differences diff-engine sees!
478 // throw the diff into a temp file
479 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
480 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
482 if (_tfopen_s(&outfile, path.c_str(), _T("w+")) == 0)
484 print_normal_script(script);
492 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
493 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
495 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
500 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
502 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
507 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
510 // First determine what happened during comparison
511 // If there were errors or files were binaries, don't bother
512 // creating diff-lists or patches
514 // diff_2_files set bin_flag to -1 if different binary
515 // diff_2_files set bin_flag to +1 if same binary
517 file_data * inf = diffdata.m_inf;
518 file_data * inf10 = diffdata10.m_inf;
519 file_data * inf12 = diffdata12.m_inf;
521 if (aFiles.GetSize() == 2)
525 m_status.bBinaries = true;
527 m_status.Identical = IDENTLEVEL::ALL;
529 m_status.Identical = IDENTLEVEL::NONE;
532 { // text files according to diffutils, so change script exists
533 m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
534 m_status.bBinaries = false;
536 m_status.bMissingNL[0] = !!inf[0].missing_newline;
537 m_status.bMissingNL[1] = !!inf[1].missing_newline;
541 m_status.Identical = IDENTLEVEL::NONE;
542 if (bin_flag10 != 0 || bin_flag12 != 0)
544 m_status.bBinaries = true;
545 if (bin_flag10 != -1 && bin_flag12 != -1)
546 m_status.Identical = IDENTLEVEL::ALL;
547 else if (bin_flag10 != -1)
548 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
549 else if (bin_flag12 != -1)
550 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
552 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
555 { // text files according to diffutils, so change script exists
556 m_status.bBinaries = false;
557 if (script10 == nullptr && script12 == nullptr)
558 m_status.Identical = IDENTLEVEL::ALL;
559 else if (script10 == nullptr)
560 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
561 else if (script12 == nullptr)
562 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
564 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
566 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
567 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
568 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
573 if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
575 WritePatchFile(script, &inf[0]);
578 // Go through diffs adding them to WinMerge's diff list
579 // This is done on every WinMerge's doc rescan!
580 if (!m_status.bBinaries && m_bUseDiffList)
582 if (aFiles.GetSize() == 2)
583 LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
585 LoadWinMergeDiffsFromDiffUtilsScript3(
587 diffdata10.m_inf, diffdata12.m_inf);
590 // cleanup the script
591 if (aFiles.GetSize() == 2)
592 FreeDiffUtilsScript(script);
595 FreeDiffUtilsScript(script10);
596 FreeDiffUtilsScript(script12);
599 // Done with diffutils filedata
600 if (aFiles.GetSize() == 2)
610 if (m_bPluginsEnabled)
612 // Delete temp files transformation functions possibly created
613 for (file = 0; file < aFiles.GetSize(); file++)
615 if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
619 TFile(strFileTemp[file]).remove();
623 LogErrorStringUTF8(e.displayText());
625 strFileTemp[file].erase();
633 * @brief Add diff to external diff-list
635 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
640 dr.begin[0] = begin0;
642 dr.begin[1] = begin1;
647 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
648 pDiffList->AddDiff(dr);
650 catch (std::exception& e)
652 AppErrorMessageBox(ucr::toTString(e.what()));
656 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
660 pDiffList->AddDiff(dr);
662 catch (std::exception& e)
664 AppErrorMessageBox(ucr::toTString(e.what()));
669 * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
670 * @param [in] leftBufferLines size of array pane left
671 * @param [in] rightBufferLines size of array pane right
672 * @param [in] left on whitch side we have to insert
673 * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
675 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
678 const int count = m_pDiffList->GetSize();
681 m_pDiffList->GetDiff(count - 1, dr);
683 for (int file = 0; file < nFiles; file++)
685 if (!bMissingNL[file])
689 m_pDiffList->SetDiff(count - 1, dr);
693 // we have to create the DIFF
694 for (int file = 0; file < nFiles; file++)
696 dr.end[file] = bufferLines[file] - 1;
697 if (bMissingNL[file])
698 dr.begin[file] = dr.end[file];
700 dr.begin[file] = dr.end[file] + 1;
702 assert(dr.begin[0] == dr.begin[file]);
704 if (bIgnoreBlankLines)
707 AddDiffRange(m_pDiffList, dr);
712 * @brief Returns status-data from diff-engine last run
714 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
716 std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
720 * @brief Formats command-line for diff-engine last run (like it was called from command-line)
722 String CDiffWrapper::FormatSwitchString() const
726 switch (m_options.m_outputStyle)
728 case DIFF_OUTPUT_NORMAL:
731 case DIFF_OUTPUT_CONTEXT:
732 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
734 case DIFF_OUTPUT_UNIFIED:
735 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
741 case DIFF_OUTPUT_FORWARD_ED:
744 case DIFF_OUTPUT_RCS:
747 case DIFF_OUTPUT_IFDEF:
750 case DIFF_OUTPUT_SDIFF:
756 if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
757 m_options.m_contextLines > 0)
759 TCHAR tmpNum[5] = {0};
760 _itot_s(m_options.m_contextLines, tmpNum, 10);
764 if (ignore_all_space_flag > 0)
765 switches += _T(" -w");
767 if (ignore_blank_lines_flag > 0)
768 switches += _T(" -B");
770 if (ignore_case_flag > 0)
771 switches += _T(" -i");
773 if (ignore_space_change_flag > 0)
774 switches += _T(" -b");
780 * @brief Enables/disables patch-file appending.
781 * If the file for patch already exists then the patch will be appended to
783 * @param [in] bAppendFiles If true patch will be appended to existing file.
785 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
787 m_bAppendFiles = bAppendFiles;
791 * @brief Compare two files using diffutils.
793 * Compare two files (in DiffFileData param) using diffutils. Run diffutils
794 * inside SEH so we can trap possible error and exceptions. If error or
795 * execption is trapped, return compare failure.
796 * @param [out] diffs Pointer to list of change structs where diffdata is stored.
797 * @param [in] diffData files to compare.
798 * @param [out] bin_status used to return binary status from compare.
799 * @param [out] bin_file Returns which file was binary file as bitmap.
800 So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
801 info is not needed (faster compare since diffutils don't bother checking
802 second file if first is binary).
803 * @return true when compare succeeds, false if error happened during compare.
804 * @note This function is used in file compare, not folder compare. Similar
805 * folder compare function is in DiffFileData.cpp.
807 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
808 int * bin_status, int * bin_file) const
814 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
816 unsigned xdl_flags = make_xdl_flags(m_options);
817 *diffs = diff_2_files_xdiff(diffData->m_inf, (m_pMovedLines[0] != nullptr), xdl_flags);
818 files[0] = diffData->m_inf[0];
819 files[1] = diffData->m_inf[1];
823 // Diff files. depth is zero because we are not comparing dirs
824 *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
825 (m_pMovedLines[0] != nullptr), bin_file);
827 CopyDiffutilTextStats(diffData->m_inf, diffData);
829 catch (SE_Exception&)
838 * @brief Free script (the diffutils linked list of differences)
841 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
843 if (script == nullptr) return;
844 struct change *e=nullptr, *p=nullptr;
845 // cleanup the script
846 for (e = script; e != nullptr; e = p)
855 * @brief Match regular expression list against given difference.
856 * This function matches the regular expression list against the difference
857 * (given as start line and end line). Matching the diff requires that all
858 * lines in difference match.
859 * @param [in] StartPos First line of the difference.
860 * @param [in] endPos Last line of the difference.
861 * @param [in] FileNo File to match.
862 * return true if any of the expressions matches.
864 bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) const
866 if (m_pFilterList == nullptr)
868 throw "CDiffWrapper::RegExpFilter() called when "
869 "filterlist doesn't exist (=nullptr)";
872 bool linesMatch = true; // set to false when non-matching line is found.
875 while (line <= EndPos && linesMatch)
877 size_t len = pinf->linbuf[line + 1] - pinf->linbuf[line];
878 const char *string = pinf->linbuf[line];
879 size_t stringlen = linelen(string, len);
880 if (!m_pFilterList->Match(std::string(string, stringlen)))
891 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
894 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
896 //Logic needed for Ignore comment option
897 PostFilterContext ctxt;
899 struct change *next = script;
901 while (next != nullptr)
903 /* Find a set of changes that belong together. */
904 struct change *thisob = next;
905 struct change *end = find_change(next);
907 /* Disconnect them from the rest of the changes,
908 making them a hunk, and remember the rest for next iteration. */
912 debug_script(thisob);
915 /* Print thisob hunk. */
916 //(*printfun) (thisob);
918 /* Determine range of line numbers involved in each file. */
919 int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
920 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
921 if (deletes || inserts || thisob->trivial)
923 OP_TYPE op = OP_NONE;
924 if (deletes && inserts)
926 else if (deletes || inserts)
931 /* Print the lines that the first file has. */
932 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
933 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
934 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
936 // Store information about these blocks in moved line info
937 if (GetDetectMovedBlocks())
939 if (thisob->match0>=0)
941 assert(thisob->inserted > 0);
942 for (int i=0; i<thisob->inserted; ++i)
944 int line0 = i+thisob->match0 + (trans_a0-first0-1);
945 int line1 = i+thisob->line1 + (trans_a1-first1-1);
946 GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
949 if (thisob->match1>=0)
951 assert(thisob->deleted > 0);
952 for (int i=0; i<thisob->deleted; ++i)
954 int line0 = i+thisob->line0 + (trans_a0-first0-1);
955 int line1 = i+thisob->match1 + (trans_a1-first1-1);
956 GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
960 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
961 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
963 if (m_options.m_filterCommentsLines ||
964 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
965 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, file_data_ary);
967 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
969 // Match lines against regular expression filters
970 // Our strategy is that every line in both sides must
971 // match regexp before we mark difference as ignored.
973 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &file_data_ary[0]);
975 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &file_data_ary[1]);
976 if (match1 && match2)
980 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
983 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
987 /* Reconnect the script so it will all be freed properly. */
994 Comp02Functor(const file_data * inf10, const file_data * inf12) :
995 inf10_(inf10), inf12_(inf12)
998 bool operator()(const DiffRangeInfo &dr3)
1000 int line0 = dr3.begin[0];
1001 int line2 = dr3.begin[2];
1002 int line0end = dr3.end[0];
1003 int line2end = dr3.end[2];
1004 if (line0end - line0 != line2end - line2)
1006 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1007 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1008 for (int i = 0; i < line0end - line0 + 1; ++i)
1010 const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1011 const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1012 if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1017 const file_data *inf10_;
1018 const file_data *inf12_;
1022 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1025 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1026 struct change * script10,
1027 struct change * script12,
1028 const file_data * inf10,
1029 const file_data * inf12)
1031 DiffList diff10, diff12;
1035 for (int file = 0; file < 2; file++)
1037 struct change *next = nullptr;
1038 int trans_a0, trans_b0, trans_a1, trans_b1;
1039 int first0, last0, first1, last1, deletes, inserts;
1041 const file_data *pinf = nullptr;
1042 DiffList *pdiff = nullptr;
1043 PostFilterContext ctxt;
1047 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1048 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1051 while (next != nullptr)
1053 /* Find a set of changes that belong together. */
1054 struct change *thisob = next;
1055 struct change *end = find_change(next);
1057 /* Disconnect them from the rest of the changes,
1058 making them a hunk, and remember the rest for next iteration. */
1060 end->link = nullptr;
1062 debug_script(thisob);
1065 /* Print thisob hunk. */
1066 //(*printfun) (thisob);
1068 /* Determine range of line numbers involved in each file. */
1069 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1070 if (deletes || inserts || thisob->trivial)
1072 if (deletes && inserts)
1074 else if (deletes || inserts)
1079 /* Print the lines that the first file has. */
1080 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1081 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1083 // Store information about these blocks in moved line info
1084 if (GetDetectMovedBlocks())
1086 int index1 = 0; // defaults for (file == 0 /* diff10 */)
1088 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1089 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1090 if (file == 1 /* diff12 */)
1094 side1 = MovedLines::SIDE::LEFT;
1095 side2 = MovedLines::SIDE::RIGHT;
1097 if (index1 != -1 && index2 != -1)
1099 if (thisob->match0>=0)
1101 assert(thisob->inserted > 0);
1102 for (int i=0; i<thisob->inserted; ++i)
1104 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1105 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1106 GetMovedLines(index1)->Add(side1, line1, line0);
1109 if (thisob->match1>=0)
1111 assert(thisob->deleted > 0);
1112 for (int i=0; i<thisob->deleted; ++i)
1114 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1115 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1116 GetMovedLines(index2)->Add(side2, line0, line1);
1122 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1123 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1125 if (m_options.m_filterCommentsLines ||
1126 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
1127 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, pinf);
1129 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
1131 // Match lines against regular expression filters
1132 // Our strategy is that every line in both sides must
1133 // match regexp before we mark difference as ignored.
1134 bool match2 = false;
1135 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &pinf[0]);
1137 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &pinf[1]);
1138 if (match1 && match2)
1142 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1146 /* Reconnect the script so it will all be freed properly. */
1151 Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(),
1152 Comp02Functor(inf10, inf12),
1153 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1156 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1159 if (!m_sPatchFile.empty())
1161 const TCHAR *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1162 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1166 if (outfile == nullptr)
1168 m_status.bPatchFileFailed = true;
1173 switch (tOutput_style)
1176 case OUTPUT_CONTEXT:
1177 case OUTPUT_UNIFIED:
1180 case OUTPUT_FORWARD_ED:
1187 print_html_header();
1195 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1198 if (!m_sPatchFile.empty())
1200 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), _T("a+")) != 0)
1204 if (outfile == nullptr)
1206 m_status.bPatchFileFailed = true;
1211 switch (tOutput_style)
1214 case OUTPUT_CONTEXT:
1215 case OUTPUT_UNIFIED:
1218 case OUTPUT_FORWARD_ED:
1225 print_html_terminator();
1234 * @brief Write out a patch file.
1235 * Writes patch file using already computed diffutils script. Converts path
1236 * delimiters from \ to / since we want to keep compatibility with patch-tools.
1237 * @param [in] script list of changes.
1238 * @param [in] inf file_data table containing filenames
1240 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1242 file_data inf_patch[2] = { inf[0], inf[1] };
1244 // Get paths, primarily use alternative paths, only if they are empty
1245 // use full filepaths
1246 String path1(m_alternativePaths[0]);
1247 String path2(m_alternativePaths[1]);
1252 path1 = paths::ToUnixPath(path1);
1253 path2 = paths::ToUnixPath(path2);
1254 if ((inf_patch[0].linbuf && ucr::CheckForInvalidUtf8(inf_patch[0].buffer, inf_patch[0].buffered_chars)) ||
1255 (inf_patch[1].linbuf && ucr::CheckForInvalidUtf8(inf_patch[1].buffer, inf_patch[1].buffered_chars)))
1257 inf_patch[0].name = _strdup(ucr::toThreadCP(path1).c_str());
1258 inf_patch[1].name = _strdup(ucr::toThreadCP(path2).c_str());
1262 inf_patch[0].name = _strdup(ucr::toUTF8(path1).c_str());
1263 inf_patch[1].name = _strdup(ucr::toUTF8(path2).c_str());
1266 // If paths in m_s1File and m_s2File point to original files, then we can use
1267 // them to fix potentially meaningless stats from potentially temporary files,
1268 // resulting from whatever transforms may have taken place.
1269 // If not, then we can't help it, and hence assert that this won't happen.
1270 if (!m_bPathsAreTemp)
1272 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1273 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1281 if (!m_sPatchFile.empty())
1283 const TCHAR *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1284 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1288 if (outfile == nullptr)
1290 m_status.bPatchFileFailed = true;
1294 // Print "command line"
1295 if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1297 String switches = FormatSwitchString();
1298 _ftprintf(outfile, _T("diff%s %s %s\n"),
1300 path1 == _T("NUL") ? _T("/dev/null") : path1.c_str(),
1301 path2 == _T("NUL") ? _T("/dev/null") : path2.c_str());
1304 if (strcmp(inf[0].name, "NUL") == 0)
1306 free((void *)inf_patch[0].name);
1307 inf_patch[0].name = _strdup("/dev/null");
1309 if (strcmp(inf[1].name, "NUL") == 0)
1311 free((void *)inf_patch[1].name);
1312 inf_patch[1].name = _strdup("/dev/null");
1316 switch (output_style)
1319 print_normal_script(script);
1321 case OUTPUT_CONTEXT:
1322 print_context_header(inf_patch, 0);
1323 print_context_script(script, 0);
1325 case OUTPUT_UNIFIED:
1326 print_context_header(inf_patch, 1);
1327 print_context_script(script, 1);
1331 print_ed_script(script);
1333 case OUTPUT_FORWARD_ED:
1334 pr_forward_ed_script(script);
1337 print_rcs_script(script);
1340 print_ifdef_script(script);
1343 print_sdiff_script(script);
1347 print_html_diff_header(inf_patch);
1348 print_html_script(script);
1349 print_html_diff_terminator();
1355 free((void *)inf_patch[0].name);
1356 free((void *)inf_patch[1].name);
1360 * @brief Set line filters, given as one string.
1361 * @param [in] filterStr Filters.
1363 void CDiffWrapper::SetFilterList(const String& filterStr)
1365 // Remove filterlist if new filter is empty
1366 if (filterStr.empty())
1368 m_pFilterList.reset();
1372 // Adding new filter without previous filter
1373 if (m_pFilterList == nullptr)
1375 m_pFilterList.reset(new FilterList);
1378 m_pFilterList->RemoveAllFilters();
1380 std::string regexp_str = ucr::toUTF8(filterStr);
1382 // Add every "line" of regexps to regexp list
1383 StringTokenizer tokens(regexp_str, "\r\n");
1384 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1385 m_pFilterList->AddRegExp(*it);
1388 void CDiffWrapper::SetFilterList(const FilterList* pFilterList)
1391 m_pFilterList.reset();
1394 m_pFilterList.reset(new FilterList());
1395 *m_pFilterList = *pFilterList;
1399 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1401 return m_pSubstitutionList.get();
1404 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1406 m_pSubstitutionList = pSubstitutionList;
1409 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1411 m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1415 * @brief Copy text stat results from diffutils back into the FileTextStats structure
1417 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1419 myTextStats->ncrlfs = inf->count_crlfs;
1420 myTextStats->ncrs = inf->count_crs;
1421 myTextStats->nlfs = inf->count_lfs;
1422 myTextStats->nzeros = inf->count_zeros;
1426 * @brief Copy both left & right text stats results back into the DiffFileData text stats
1428 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1430 CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1431 CopyTextStats(&inf[1], &diffData->m_textStats[1]);