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"
22 #include <Poco/Format.h>
23 #include <Poco/Debugger.h>
24 #include <Poco/StringTokenizer.h>
25 #include <Poco/Exception.h>
26 #include "DiffContext.h"
27 #include "coretools.h"
29 #include "MovedLines.h"
30 #include "FilterList.h"
33 #include "xdiff_gnudiff_compat.h"
34 #include "FileTransform.h"
36 #include "CompareOptions.h"
37 #include "FileTextStats.h"
38 #include "FolderCmp.h"
39 #include "Environment.h"
40 #include "PatchHTML.h"
41 #include "UnicodeString.h"
44 #include "Exceptions.h"
45 #include "parsers/crystallineparser.h"
46 #include "SyntaxColors.h"
48 #include "SubstitutionList.h"
49 #include "codepage_detect.h"
54 using Poco::StringTokenizer;
55 using Poco::Exception;
59 extern "C" int is_blank_line(char const* pch, char const* limit);
61 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats);
62 static void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData);
65 * @brief Default constructor.
66 * Initializes members.
68 CDiffWrapper::CDiffWrapper()
69 : m_pFilterCommentsDef(nullptr)
70 , m_bCreatePatchFile(false)
71 , m_bUseDiffList(false)
73 , m_bAppendFiles(false)
75 , m_infoPrediffer(nullptr)
76 , m_pDiffList(nullptr)
77 , m_bPathsAreTemp(false)
78 , m_pFilterList(nullptr)
79 , m_pSubstitutionList{nullptr}
80 , m_bPluginsEnabled(false)
82 , m_codepage(ucr::CP_UTF_8)
84 // character that ends a line. Currently this is always `\n'
91 CDiffWrapper::~CDiffWrapper() = default;
94 * @brief Enables/disables patch-file creation and sets filename.
95 * This function enables or disables patch file creation. When
96 * @p filename is empty, patch files are disabled.
97 * @param [in] filename Filename for patch file, or empty string.
99 void CDiffWrapper::SetCreatePatchFile(const String &filename)
101 if (filename.empty())
103 m_bCreatePatchFile = false;
104 m_sPatchFile.clear();
108 m_bCreatePatchFile = true;
109 m_sPatchFile = filename;
110 strutils::replace(m_sPatchFile, _T("/"), _T("\\"));
115 * @brief Enables/disabled DiffList creation ands sets DiffList.
116 * This function enables or disables DiffList creation. When
117 * @p diffList is `nullptr`, a difflist was not created. When valid
118 * DiffList pointer is given, compare results are stored into it.
119 * @param [in] diffList Pointer to DiffList getting compare results.
121 void CDiffWrapper::SetCreateDiffList(DiffList *diffList)
123 if (diffList == nullptr)
125 m_bUseDiffList = false;
126 m_pDiffList = nullptr;
130 m_bUseDiffList = true;
131 m_pDiffList = diffList;
136 * @brief Returns current set of options used by diff-engine.
137 * This function converts internally used diff-options to
138 * format used outside CDiffWrapper and returns them.
139 * @param [in,out] options Pointer to structure getting used options.
141 void CDiffWrapper::GetOptions(DIFFOPTIONS *options) const
143 assert(options != nullptr);
144 DIFFOPTIONS tmpOptions = {0};
145 m_options.GetAsDiffOptions(tmpOptions);
146 *options = tmpOptions;
150 * @brief Set options for Diff-engine.
151 * This function converts given options to format CDiffWrapper uses
152 * internally and stores them.
153 * @param [in] options Pointer to structure having new options.
155 void CDiffWrapper::SetOptions(const DIFFOPTIONS *options)
157 assert(options != nullptr);
158 m_options.SetFromDiffOptions(*options);
161 void CDiffWrapper::SetPrediffer(const PrediffingInfo * prediffer /*= nullptr*/)
163 // all flags are set correctly during the construction
164 m_infoPrediffer.reset(new PrediffingInfo);
166 if (prediffer != nullptr)
167 *m_infoPrediffer = *prediffer;
171 * @brief Set options used for patch-file creation.
172 * @param [in] options Pointer to structure having new options.
174 void CDiffWrapper::SetPatchOptions(const PATCHOPTIONS *options)
176 assert(options != nullptr);
177 m_options.m_contextLines = options->nContext;
179 switch (options->outputStyle)
182 m_options.m_outputStyle = DIFF_OUTPUT_NORMAL;
185 m_options.m_outputStyle = DIFF_OUTPUT_CONTEXT;
188 m_options.m_outputStyle = DIFF_OUTPUT_UNIFIED;
191 m_options.m_outputStyle = DIFF_OUTPUT_HTML;
194 throw "Unknown output style!";
198 m_bAddCmdLine = options->bAddCommandline;
202 * @brief Enables/disables moved block detection.
203 * @param [in] bDetectMovedBlocks If true moved blocks are detected.
205 void CDiffWrapper::SetDetectMovedBlocks(bool bDetectMovedBlocks)
207 if (bDetectMovedBlocks)
209 if (m_pMovedLines[0] == nullptr)
211 m_pMovedLines[0].reset(new MovedLines);
212 m_pMovedLines[1].reset(new MovedLines);
213 m_pMovedLines[2].reset(new MovedLines);
218 m_pMovedLines[0].reset();
219 m_pMovedLines[1].reset();
220 m_pMovedLines[2].reset();
224 static String convertToTString(const char* start, const char* end)
226 if (!ucr::CheckForInvalidUtf8(start, end - start))
228 return ucr::toTString(std::string(start, end));
234 ucr::maketstring(text, start, end - start, -1, &lossy);
239 static unsigned GetLastLineCookie(unsigned dwCookie, int startLine, int endLine, const char **linbuf, CrystalLineParser::TextDefinition* enuType)
243 for (int i = startLine; i <= endLine; ++i)
245 String text = convertToTString(linbuf[i], linbuf[i + 1]);
246 int nActualItems = 0;
247 std::vector<CrystalLineParser::TEXTBLOCK> blocks(text.length());
248 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), static_cast<int>(text.length()), blocks.data(), nActualItems);
253 static unsigned GetCommentsFilteredText(unsigned dwCookie, int startLine, int endLine, const char **linbuf, std::string& filtered, CrystalLineParser::TextDefinition* enuType)
256 for (int i = startLine; i <= endLine; ++i)
258 String text = convertToTString(linbuf[i], linbuf[i + 1]);
259 unsigned textlen = static_cast<unsigned>(text.size());
266 int nActualItems = 0;
267 std::vector<CrystalLineParser::TEXTBLOCK> blocks(textlen);
268 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), textlen, blocks.data(), nActualItems);
270 if (nActualItems == 0)
276 for (int j = 0; j < nActualItems; ++j)
278 CrystalLineParser::TEXTBLOCK& block = blocks[j];
279 if (block.m_nColorIndex != COLORINDEX_COMMENT)
281 unsigned blocklen = (j < nActualItems - 1) ? (blocks[j + 1].m_nCharPos - block.m_nCharPos) : textlen - block.m_nCharPos;
282 filteredT.append(text.c_str() + block.m_nCharPos, blocklen);
289 filtered = ucr::toUTF8(filteredT);
295 * @brief Replace a string inside a string with another string.
296 * This function searches for a string inside another string an if found,
297 * replaces it with another string. Function can replace several instances
298 * of the string inside one string.
299 * @param [in,out] target A string containing another string to replace.
300 * @param [in] find A string to search and replace with another (@p replace).
301 * @param [in] replace A string used to replace original (@p find).
303 void Replace(std::string &target, const std::string &find, const std::string &replace)
305 const std::string::size_type find_len = find.length();
306 const std::string::size_type replace_len = replace.length();
307 std::string::size_type pos = 0;
308 while ((pos = target.find(find, pos)) != std::string::npos)
310 target.replace(pos, find_len, replace);
316 * @brief Replace the characters that matche characters specified in its arguments
317 * @param [in,out] str - A string containing another string to replace.
318 * @param [in] chars - characters to search for
319 * @param [in] rep - String to replace
321 static void ReplaceChars(std::string & str, const char* chars, const char *rep)
323 std::string::size_type pos = 0;
324 size_t replen = strlen(rep);
325 while ((pos = str.find_first_of(chars, pos)) != std::string::npos)
327 std::string::size_type posend = str.find_first_not_of(chars, pos);
328 if (posend != String::npos)
329 str.replace(pos, posend - pos, rep);
331 str.replace(pos, str.length() - pos, rep);
337 * @brief Remove blank lines
339 void RemoveBlankLines(std::string &str)
342 while (pos < str.length())
344 size_t posend = str.find_first_of("\r\n", pos);
345 if (posend != std::string::npos)
346 posend = str.find_first_not_of("\r\n", posend);
347 if (posend == std::string::npos)
348 posend = str.length();
349 if (is_blank_line(str.data() + pos, str.data() + posend))
350 str.erase(pos, posend - pos);
357 @brief The main entry for post filtering. Performs post-filtering, by setting comment blocks to trivial
358 @param [in] LineNumberLeft - First line number to read from left file
359 @param [in] QtyLinesLeft - Number of lines in the block for left file
360 @param [in] LineNumberRight - First line number to read from right file
361 @param [in] QtyLinesRight - Number of lines in the block for right file
362 @param [in,out] Op - This variable is set to trivial if block should be ignored.
364 void CDiffWrapper::PostFilter(PostFilterContext& ctxt, int LineNumberLeft, int QtyLinesLeft, int LineNumberRight,
365 int QtyLinesRight, OP_TYPE &Op, const file_data *file_data_ary) const
367 if (Op == OP_TRIVIAL)
370 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
372 // Match lines against regular expression filters
373 // Our strategy is that every line in both sides must
374 // match regexp before we mark difference as ignored.
376 bool match1 = RegExpFilter(LineNumberLeft + file_data_ary[0].linbuf_base, LineNumberLeft + file_data_ary[0].linbuf_base + QtyLinesLeft - 1, &file_data_ary[0]);
378 match2 = RegExpFilter(LineNumberRight + file_data_ary[1].linbuf_base, LineNumberRight + file_data_ary[1].linbuf_base + QtyLinesRight - 1, &file_data_ary[1]);
379 if (match1 && match2)
386 std::string LineDataLeft, LineDataRight;
388 if (m_options.m_filterCommentsLines)
390 ctxt.dwCookieLeft = GetLastLineCookie(ctxt.dwCookieLeft,
391 ctxt.nParsedLineEndLeft + 1, LineNumberLeft - 1, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, m_pFilterCommentsDef);
392 ctxt.dwCookieRight = GetLastLineCookie(ctxt.dwCookieRight,
393 ctxt.nParsedLineEndRight + 1, LineNumberRight - 1, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, m_pFilterCommentsDef);
395 ctxt.nParsedLineEndLeft = LineNumberLeft + QtyLinesLeft - 1;
396 ctxt.nParsedLineEndRight = LineNumberRight + QtyLinesRight - 1;;
398 ctxt.dwCookieLeft = GetCommentsFilteredText(ctxt.dwCookieLeft,
399 LineNumberLeft, ctxt.nParsedLineEndLeft, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, LineDataLeft, m_pFilterCommentsDef);
400 ctxt.dwCookieRight = GetCommentsFilteredText(ctxt.dwCookieRight,
401 LineNumberRight, ctxt.nParsedLineEndRight, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, LineDataRight, m_pFilterCommentsDef);
405 LineDataLeft.assign(file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base],
406 file_data_ary[0].linbuf[LineNumberLeft + QtyLinesLeft + file_data_ary[0].linbuf_base]
407 - file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base]);
408 LineDataRight.assign(file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base],
409 file_data_ary[1].linbuf[LineNumberRight + QtyLinesRight + file_data_ary[1].linbuf_base]
410 - file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base]);
413 if (m_pSubstitutionList)
415 LineDataLeft = m_pSubstitutionList->Subst(LineDataLeft);
416 LineDataRight = m_pSubstitutionList->Subst(LineDataRight);
419 if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_ALL)
421 //Ignore character case
422 ReplaceChars(LineDataLeft, " \t", "");
423 ReplaceChars(LineDataRight, " \t", "");
425 else if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_CHANGE)
427 //Ignore change in whitespace char count
428 ReplaceChars(LineDataLeft, " \t", " ");
429 ReplaceChars(LineDataRight, " \t", " ");
432 if (m_options.m_bIgnoreNumbers )
434 //Ignore number character case
435 ReplaceChars(LineDataLeft, "0123456789", "");
436 ReplaceChars(LineDataRight, "0123456789", "");
438 if (m_options.m_bIgnoreCase)
441 // std::transform(LineDataLeft.begin(), LineDataLeft.end(), LineDataLeft.begin(), ::toupper);
442 for (std::string::iterator pb = LineDataLeft.begin(), pe = LineDataLeft.end(); pb != pe; ++pb)
443 *pb = static_cast<char>(::toupper(*pb));
444 // std::transform(LineDataRight.begin(), LineDataRight.end(), LineDataRight.begin(), ::toupper);
445 for (std::string::iterator pb = LineDataRight.begin(), pe = LineDataRight.end(); pb != pe; ++pb)
446 *pb = static_cast<char>(::toupper(*pb));
448 if (m_options.m_bIgnoreEOLDifference)
450 Replace(LineDataLeft, "\r\n", "\n");
451 Replace(LineDataLeft, "\r", "\n");
452 Replace(LineDataRight, "\r\n", "\n");
453 Replace(LineDataRight, "\r", "\n");
455 if (m_options.m_bIgnoreBlankLines)
457 RemoveBlankLines(LineDataLeft);
458 RemoveBlankLines(LineDataRight);
460 if (LineDataLeft != LineDataRight)
462 //only difference is trival
467 * @brief Set source paths for diffing two files.
468 * Sets full paths to two files we are diffing. Paths can be actual user files
469 * or temporary copies of user files. Parameter @p tempPaths tells if paths
470 * are temporary paths that can be deleted.
471 * @param [in] files Files to compare
472 * @param [in] tempPaths Are given paths temporary (can be deleted)?.
474 void CDiffWrapper::SetPaths(const PathContext &tFiles,
478 m_bPathsAreTemp = tempPaths;
482 * @brief Runs diff-engine.
484 bool CDiffWrapper::RunFileDiff()
486 PathContext aFiles = m_files;
488 for (file = 0; file < m_files.GetSize(); file++)
489 aFiles[file] = paths::ToWindowsPath(aFiles[file]);
492 String strFileTemp[3];
493 std::copy(m_files.begin(), m_files.end(), strFileTemp);
495 m_options.SetToDiffUtils();
498 m_nDiffs = m_pDiffList->GetSize();
500 for (file = 0; file < aFiles.GetSize(); file++)
502 if (m_bPluginsEnabled)
504 // Do the preprocessing now, overwrite the temp files
505 // NOTE: FileTransform_UCS2ToUTF8() may create new temp
506 // files and return new names, those created temp files
507 // are deleted in end of function.
509 // this can only fail if the data can not be saved back (no more
510 // place on disk ???) What to do then ??
511 if (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
513 // display a message box
514 String sError = strutils::format_string2(
515 _("An error occurred while prediffing the file '%1' with the plugin '%2'. The prediffing is not applied any more."),
516 strFileTemp[file].c_str(),
517 m_infoPrediffer->GetPluginPipeline().c_str());
518 AppErrorMessageBox(sError);
519 // don't use any more this prediffer
520 m_infoPrediffer->ClearPluginPipeline();
525 struct change *script = nullptr;
526 struct change *script10 = nullptr;
527 struct change *script12 = nullptr;
528 DiffFileData diffdata, diffdata10, diffdata12;
529 int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
531 if (aFiles.GetSize() == 2)
533 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
534 // This opens & fstats both files (if it succeeds)
535 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
540 // Compare the files, if no error was found.
541 // Last param (bin_file) is `nullptr` since we don't
542 // (yet) need info about binary sides.
543 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
545 // We don't anymore create diff-files for every rescan.
546 // User can create patch-file whenever one wants to.
547 // We don't need to waste time. But lets keep this as
548 // debugging aid. Sometimes it is very useful to see
549 // what differences diff-engine sees!
551 // throw the diff into a temp file
552 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
553 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
555 if (cio::tfopen_s(&outfile, path, _T("w+")) == 0)
557 print_normal_script(script);
565 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
566 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
568 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
573 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
575 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
580 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
583 // First determine what happened during comparison
584 // If there were errors or files were binaries, don't bother
585 // creating diff-lists or patches
587 // diff_2_files set bin_flag to -1 if different binary
588 // diff_2_files set bin_flag to +1 if same binary
590 file_data * inf = diffdata.m_inf;
591 file_data * inf10 = diffdata10.m_inf;
592 file_data * inf12 = diffdata12.m_inf;
594 if (aFiles.GetSize() == 2)
598 m_status.bBinaries = true;
600 m_status.Identical = IDENTLEVEL::ALL;
602 m_status.Identical = IDENTLEVEL::NONE;
605 { // text files according to diffutils, so change script exists
606 m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
607 m_status.bBinaries = false;
609 m_status.bMissingNL[0] = !!inf[0].missing_newline;
610 m_status.bMissingNL[1] = !!inf[1].missing_newline;
614 m_status.Identical = IDENTLEVEL::NONE;
615 if (bin_flag10 != 0 || bin_flag12 != 0)
617 m_status.bBinaries = true;
618 if (bin_flag10 != -1 && bin_flag12 != -1)
619 m_status.Identical = IDENTLEVEL::ALL;
620 else if (bin_flag10 != -1)
621 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
622 else if (bin_flag12 != -1)
623 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
625 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
628 { // text files according to diffutils, so change script exists
629 m_status.bBinaries = false;
630 if (script10 == nullptr && script12 == nullptr)
631 m_status.Identical = IDENTLEVEL::ALL;
632 else if (script10 == nullptr)
633 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
634 else if (script12 == nullptr)
635 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
637 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
639 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
640 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
641 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
646 if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
648 WritePatchFile(script, &inf[0]);
651 // Go through diffs adding them to WinMerge's diff list
652 // This is done on every WinMerge's doc rescan!
653 if (!m_status.bBinaries && m_bUseDiffList)
655 if (aFiles.GetSize() == 2)
656 LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
658 LoadWinMergeDiffsFromDiffUtilsScript3(
660 diffdata10.m_inf, diffdata12.m_inf);
663 // cleanup the script
664 if (aFiles.GetSize() == 2)
665 FreeDiffUtilsScript(script);
668 FreeDiffUtilsScript(script10);
669 FreeDiffUtilsScript(script12);
672 // Done with diffutils filedata
673 if (aFiles.GetSize() == 2)
683 if (m_bPluginsEnabled)
685 // Delete temp files transformation functions possibly created
686 for (file = 0; file < aFiles.GetSize(); file++)
688 if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
692 TFile(strFileTemp[file]).remove();
696 LogErrorStringUTF8(e.displayText());
698 strFileTemp[file].erase();
706 * @brief Add diff to external diff-list
708 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
713 dr.begin[0] = begin0;
715 dr.begin[1] = begin1;
720 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
721 pDiffList->AddDiff(dr);
723 catch (std::exception& e)
725 AppErrorMessageBox(ucr::toTString(e.what()));
729 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
733 pDiffList->AddDiff(dr);
735 catch (std::exception& e)
737 AppErrorMessageBox(ucr::toTString(e.what()));
742 * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
743 * @param [in] leftBufferLines size of array pane left
744 * @param [in] rightBufferLines size of array pane right
745 * @param [in] left on whitch side we have to insert
746 * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
748 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
751 const int count = m_pDiffList->GetSize();
754 m_pDiffList->GetDiff(count - 1, dr);
756 for (int file = 0; file < nFiles; file++)
758 if (!bMissingNL[file])
762 m_pDiffList->SetDiff(count - 1, dr);
766 // we have to create the DIFF
767 for (int file = 0; file < nFiles; file++)
769 dr.end[file] = bufferLines[file] - 1;
770 if (bMissingNL[file])
771 dr.begin[file] = dr.end[file];
773 dr.begin[file] = dr.end[file] + 1;
775 assert(dr.begin[0] == dr.begin[file]);
777 if (bIgnoreBlankLines)
780 AddDiffRange(m_pDiffList, dr);
785 * @brief Returns status-data from diff-engine last run
787 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
789 std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
793 * @brief Formats command-line for diff-engine last run (like it was called from command-line)
795 String CDiffWrapper::FormatSwitchString() const
799 switch (m_options.m_outputStyle)
801 case DIFF_OUTPUT_NORMAL:
804 case DIFF_OUTPUT_CONTEXT:
805 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
807 case DIFF_OUTPUT_UNIFIED:
808 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
814 case DIFF_OUTPUT_FORWARD_ED:
817 case DIFF_OUTPUT_RCS:
820 case DIFF_OUTPUT_IFDEF:
823 case DIFF_OUTPUT_SDIFF:
829 if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
830 m_options.m_contextLines > 0)
831 switches += strutils::to_str(m_options.m_contextLines);
833 if (ignore_all_space_flag > 0)
834 switches += _T(" -w");
836 if (ignore_blank_lines_flag > 0)
837 switches += _T(" -B");
839 if (ignore_case_flag > 0)
840 switches += _T(" -i");
842 if (ignore_space_change_flag > 0)
843 switches += _T(" -b");
849 * @brief Enables/disables patch-file appending.
850 * If the file for patch already exists then the patch will be appended to
852 * @param [in] bAppendFiles If true patch will be appended to existing file.
854 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
856 m_bAppendFiles = bAppendFiles;
860 * @brief Compare two files using diffutils.
862 * Compare two files (in DiffFileData param) using diffutils. Run diffutils
863 * inside SEH so we can trap possible error and exceptions. If error or
864 * execption is trapped, return compare failure.
865 * @param [out] diffs Pointer to list of change structs where diffdata is stored.
866 * @param [in] diffData files to compare.
867 * @param [out] bin_status used to return binary status from compare.
868 * @param [out] bin_file Returns which file was binary file as bitmap.
869 So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
870 info is not needed (faster compare since diffutils don't bother checking
871 second file if first is binary).
872 * @return true when compare succeeds, false if error happened during compare.
873 * @note This function is used in file compare, not folder compare. Similar
874 * folder compare function is in DiffFileData.cpp.
876 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
877 int * bin_status, int * bin_file) const
883 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
885 unsigned xdl_flags = make_xdl_flags(m_options);
886 *diffs = diff_2_files_xdiff(diffData->m_inf, (m_pMovedLines[0] != nullptr), xdl_flags);
887 files[0] = diffData->m_inf[0];
888 files[1] = diffData->m_inf[1];
892 // Diff files. depth is zero because we are not comparing dirs
893 *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
894 (m_pMovedLines[0] != nullptr), bin_file);
896 CopyDiffutilTextStats(diffData->m_inf, diffData);
898 catch (SE_Exception&)
907 * @brief Free script (the diffutils linked list of differences)
910 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
912 if (script == nullptr) return;
913 struct change *e=nullptr, *p=nullptr;
914 // cleanup the script
915 for (e = script; e != nullptr; e = p)
924 * @brief Match regular expression list against given difference.
925 * This function matches the regular expression list against the difference
926 * (given as start line and end line). Matching the diff requires that all
927 * lines in difference match.
928 * @param [in] StartPos First line of the difference.
929 * @param [in] endPos Last line of the difference.
930 * @param [in] FileNo File to match.
931 * return true if any of the expressions matches.
933 bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) const
935 if (m_pFilterList == nullptr)
937 throw "CDiffWrapper::RegExpFilter() called when "
938 "filterlist doesn't exist (=nullptr)";
941 bool linesMatch = true; // set to false when non-matching line is found.
944 while (line <= EndPos && linesMatch)
946 size_t len = pinf->linbuf[line + 1] - pinf->linbuf[line];
947 const char *string = pinf->linbuf[line];
948 size_t stringlen = linelen(string, len);
949 if (!m_pFilterList->Match(std::string(string, stringlen), m_codepage))
959 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
962 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
964 //Logic needed for Ignore comment option
965 PostFilterContext ctxt;
967 struct change *next = script;
969 const bool usefilters = m_options.m_filterCommentsLines ||
970 (m_pFilterList && m_pFilterList->HasRegExps()) ||
971 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
973 while (next != nullptr)
975 /* Find a set of changes that belong together. */
976 struct change *thisob = next;
977 struct change *end = find_change(next);
979 /* Disconnect them from the rest of the changes,
980 making them a hunk, and remember the rest for next iteration. */
984 debug_script(thisob);
987 /* Print thisob hunk. */
988 //(*printfun) (thisob);
990 /* Determine range of line numbers involved in each file. */
991 int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
992 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
993 if (deletes || inserts || thisob->trivial)
995 OP_TYPE op = OP_NONE;
996 if (deletes && inserts)
998 else if (deletes || inserts)
1003 /* Print the lines that the first file has. */
1004 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
1005 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
1006 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
1008 // Store information about these blocks in moved line info
1009 if (GetDetectMovedBlocks())
1011 if (thisob->match0>=0)
1013 assert(thisob->inserted > 0);
1014 for (int i=0; i<thisob->inserted; ++i)
1016 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1017 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1018 GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
1021 if (thisob->match1>=0)
1023 assert(thisob->deleted > 0);
1024 for (int i=0; i<thisob->deleted; ++i)
1026 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1027 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1028 GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
1032 const int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1033 const int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1035 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, file_data_ary);
1037 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1039 if (QtyLinesLeft == QtyLinesRight)
1043 else if (QtyLinesLeft < QtyLinesRight)
1045 trans_a0 += QtyLinesLeft;
1046 trans_a1 += QtyLinesLeft;
1050 trans_a0 += QtyLinesRight;
1051 trans_a1 += QtyLinesRight;
1055 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1059 /* Reconnect the script so it will all be freed properly. */
1064 struct Comp02Functor
1066 Comp02Functor(const file_data * inf10, const file_data * inf12) :
1067 inf10_(inf10), inf12_(inf12)
1070 bool operator()(const DiffRangeInfo &dr3)
1072 int line0 = dr3.begin[0];
1073 int line2 = dr3.begin[2];
1074 int line0end = dr3.end[0];
1075 int line2end = dr3.end[2];
1076 if (line0end - line0 != line2end - line2)
1078 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1079 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1080 for (int i = 0; i < line0end - line0 + 1; ++i)
1082 const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1083 const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1084 if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1089 const file_data *inf10_;
1090 const file_data *inf12_;
1094 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1097 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1098 struct change * script10,
1099 struct change * script12,
1100 const file_data * inf10,
1101 const file_data * inf12)
1103 DiffList diff10, diff12;
1107 const bool usefilters = m_options.m_filterCommentsLines ||
1108 (m_pFilterList && m_pFilterList->HasRegExps()) ||
1109 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
1111 for (int file = 0; file < 2; file++)
1113 struct change *next = nullptr;
1114 int trans_a0, trans_b0, trans_a1, trans_b1;
1115 int first0, last0, first1, last1, deletes, inserts;
1117 const file_data *pinf = nullptr;
1118 DiffList *pdiff = nullptr;
1119 PostFilterContext ctxt;
1123 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1124 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1127 while (next != nullptr)
1129 /* Find a set of changes that belong together. */
1130 struct change *thisob = next;
1131 struct change *end = find_change(next);
1133 /* Disconnect them from the rest of the changes,
1134 making them a hunk, and remember the rest for next iteration. */
1136 end->link = nullptr;
1138 debug_script(thisob);
1141 /* Print thisob hunk. */
1142 //(*printfun) (thisob);
1144 /* Determine range of line numbers involved in each file. */
1145 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1146 if (deletes || inserts || thisob->trivial)
1148 if (deletes && inserts)
1150 else if (deletes || inserts)
1155 /* Print the lines that the first file has. */
1156 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1157 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1159 // Store information about these blocks in moved line info
1160 if (GetDetectMovedBlocks())
1162 int index1 = 0; // defaults for (file == 0 /* diff10 */)
1164 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1165 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1166 if (file == 1 /* diff12 */)
1170 side1 = MovedLines::SIDE::LEFT;
1171 side2 = MovedLines::SIDE::RIGHT;
1173 if (index1 != -1 && index2 != -1)
1175 if (thisob->match0>=0)
1177 assert(thisob->inserted > 0);
1178 for (int i=0; i<thisob->inserted; ++i)
1180 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1181 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1182 GetMovedLines(index1)->Add(side1, line1, line0);
1185 if (thisob->match1>=0)
1187 assert(thisob->deleted > 0);
1188 for (int i=0; i<thisob->deleted; ++i)
1190 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1191 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1192 GetMovedLines(index2)->Add(side2, line0, line1);
1198 const int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1199 const int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1201 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, pinf);
1203 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1207 /* Reconnect the script so it will all be freed properly. */
1212 Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(),
1213 Comp02Functor(inf10, inf12),
1214 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1217 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1220 if (!m_sPatchFile.empty())
1222 const tchar_t *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1223 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1227 if (outfile == nullptr)
1229 m_status.bPatchFileFailed = true;
1234 switch (tOutput_style)
1237 case OUTPUT_CONTEXT:
1238 case OUTPUT_UNIFIED:
1241 case OUTPUT_FORWARD_ED:
1248 print_html_header();
1256 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1259 if (!m_sPatchFile.empty())
1261 if (cio::tfopen_s(&outfile, m_sPatchFile, _T("a+")) != 0)
1265 if (outfile == nullptr)
1267 m_status.bPatchFileFailed = true;
1272 switch (tOutput_style)
1275 case OUTPUT_CONTEXT:
1276 case OUTPUT_UNIFIED:
1279 case OUTPUT_FORWARD_ED:
1286 print_html_terminator();
1295 * @brief Write out a patch file.
1296 * Writes patch file using already computed diffutils script. Converts path
1297 * delimiters from \ to / since we want to keep compatibility with patch-tools.
1298 * @param [in] script list of changes.
1299 * @param [in] inf file_data table containing filenames
1301 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1303 file_data inf_patch[2] = { inf[0], inf[1] };
1305 // Get paths, primarily use alternative paths, only if they are empty
1306 // use full filepaths
1307 String path1(m_alternativePaths[0]);
1308 String path2(m_alternativePaths[1]);
1313 path1 = paths::ToUnixPath(path1);
1314 path2 = paths::ToUnixPath(path2);
1315 auto strdupPath = [](const String& path, const void *buffer, size_t buffered_chars) -> char*
1317 FileTextEncoding encoding = codepage_detect::Guess(_T(""), buffer, buffered_chars, 1);
1318 if (encoding.m_unicoding != ucr::NONE)
1319 encoding.SetUnicoding(ucr::UTF8);
1320 ucr::buffer buf(256);
1321 ucr::convert(ucr::CP_TCHAR, reinterpret_cast<const unsigned char *>(path.c_str()), static_cast<int>(path.size() * sizeof(tchar_t)), encoding.m_codepage, &buf);
1322 return strdup(reinterpret_cast<const char *>(buf.ptr));
1324 inf_patch[0].name = strdupPath(path1, inf_patch[0].buffer, inf_patch[0].buffered_chars);
1325 inf_patch[1].name = strdupPath(path2, inf_patch[1].buffer, inf_patch[1].buffered_chars);
1327 // If paths in m_s1File and m_s2File point to original files, then we can use
1328 // them to fix potentially meaningless stats from potentially temporary files,
1329 // resulting from whatever transforms may have taken place.
1330 // If not, then we can't help it, and hence assert that this won't happen.
1331 if (!m_bPathsAreTemp)
1333 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1334 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1342 if (!m_sPatchFile.empty())
1344 const tchar_t *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1345 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1349 if (outfile == nullptr)
1351 m_status.bPatchFileFailed = true;
1355 if (strcmp(inf[0].name, "NUL") == 0)
1357 free((void *)inf_patch[0].name);
1358 inf_patch[0].name = strdup("/dev/null");
1360 if (strcmp(inf[1].name, "NUL") == 0)
1362 free((void *)inf_patch[1].name);
1363 inf_patch[1].name = strdup("/dev/null");
1366 // Print "command line"
1367 if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1369 String switches = FormatSwitchString();
1370 fprintf(outfile, "diff%S %s %s\n",
1371 switches.c_str(), inf_patch[0].name, inf_patch[1].name);
1375 switch (output_style)
1378 print_normal_script(script);
1380 case OUTPUT_CONTEXT:
1381 print_context_header(inf_patch, 0);
1382 print_context_script(script, 0);
1384 case OUTPUT_UNIFIED:
1385 print_context_header(inf_patch, 1);
1386 print_context_script(script, 1);
1390 print_ed_script(script);
1392 case OUTPUT_FORWARD_ED:
1393 pr_forward_ed_script(script);
1396 print_rcs_script(script);
1399 print_ifdef_script(script);
1402 print_sdiff_script(script);
1406 print_html_diff_header(inf_patch);
1407 print_html_script(script);
1408 print_html_diff_terminator();
1414 free((void *)inf_patch[0].name);
1415 free((void *)inf_patch[1].name);
1419 * @brief Set line filters, given as one string.
1420 * @param [in] filterStr Filters.
1422 void CDiffWrapper::SetFilterList(const String& filterStr)
1424 // Remove filterlist if new filter is empty
1425 if (filterStr.empty())
1427 m_pFilterList.reset();
1431 // Adding new filter without previous filter
1432 if (m_pFilterList == nullptr)
1434 m_pFilterList.reset(new FilterList);
1437 m_pFilterList->RemoveAllFilters();
1439 std::string regexp_str = ucr::toUTF8(filterStr);
1441 // Add every "line" of regexps to regexp list
1442 StringTokenizer tokens(regexp_str, "\r\n");
1443 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1444 m_pFilterList->AddRegExp(*it);
1447 const FilterList* CDiffWrapper::GetFilterList() const
1449 return m_pFilterList.get();
1452 void CDiffWrapper::SetFilterList(std::shared_ptr<FilterList> pFilterList)
1454 m_pFilterList = std::move(pFilterList);
1457 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1459 return m_pSubstitutionList.get();
1462 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1464 m_pSubstitutionList = std::move(pSubstitutionList);
1467 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1469 m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1473 * @brief Copy text stat results from diffutils back into the FileTextStats structure
1475 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1477 myTextStats->ncrlfs = inf->count_crlfs;
1478 myTextStats->ncrs = inf->count_crs;
1479 myTextStats->nlfs = inf->count_lfs;
1480 myTextStats->nzeros = inf->count_zeros;
1484 * @brief Copy both left & right text stats results back into the DiffFileData text stats
1486 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1488 CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1489 CopyTextStats(&inf[1], &diffData->m_textStats[1]);