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 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)
83 // character that ends a line. Currently this is always `\n'
90 CDiffWrapper::~CDiffWrapper() = default;
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 a string inside a string with another string.
295 * This function searches for a string inside another string an if found,
296 * replaces it with another string. Function can replace several instances
297 * of the string inside one string.
298 * @param [in,out] target A string containing another string to replace.
299 * @param [in] find A string to search and replace with another (@p replace).
300 * @param [in] replace A string used to replace original (@p find).
302 void Replace(std::string &target, const std::string &find, const std::string &replace)
304 const std::string::size_type find_len = find.length();
305 const std::string::size_type replace_len = replace.length();
306 std::string::size_type pos = 0;
307 while ((pos = target.find(find, pos)) != std::string::npos)
309 target.replace(pos, find_len, replace);
315 * @brief Replace the characters that matche characters specified in its arguments
316 * @param [in,out] str - A string containing another string to replace.
317 * @param [in] chars - characters to search for
318 * @param [in] rep - String to replace
320 static void ReplaceChars(std::string & str, const char* chars, const char *rep)
322 std::string::size_type pos = 0;
323 size_t replen = strlen(rep);
324 while ((pos = str.find_first_of(chars, pos)) != std::string::npos)
326 std::string::size_type posend = str.find_first_not_of(chars, pos);
327 if (posend != String::npos)
328 str.replace(pos, posend - pos, rep);
330 str.replace(pos, str.length() - pos, rep);
336 * @brief Remove blank lines
338 void RemoveBlankLines(std::string &str)
341 while (pos < str.length())
343 size_t posend = str.find_first_of("\r\n", pos);
344 if (posend != std::string::npos)
345 posend = str.find_first_not_of("\r\n", posend);
346 if (posend == std::string::npos)
347 posend = str.length();
348 if (is_blank_line(str.data() + pos, str.data() + posend))
349 str.erase(pos, posend - pos);
356 @brief The main entry for post filtering. Performs post-filtering, by setting comment blocks to trivial
357 @param [in] LineNumberLeft - First line number to read from left file
358 @param [in] QtyLinesLeft - Number of lines in the block for left file
359 @param [in] LineNumberRight - First line number to read from right file
360 @param [in] QtyLinesRight - Number of lines in the block for right file
361 @param [in,out] Op - This variable is set to trivial if block should be ignored.
363 void CDiffWrapper::PostFilter(PostFilterContext& ctxt, int LineNumberLeft, int QtyLinesLeft, int LineNumberRight,
364 int QtyLinesRight, OP_TYPE &Op, const file_data *file_data_ary) const
366 if (Op == OP_TRIVIAL)
369 std::string LineDataLeft, LineDataRight;
371 if (m_options.m_filterCommentsLines)
373 ctxt.dwCookieLeft = GetLastLineCookie(ctxt.dwCookieLeft,
374 ctxt.nParsedLineEndLeft + 1, LineNumberLeft - 1, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, m_pFilterCommentsDef);
375 ctxt.dwCookieRight = GetLastLineCookie(ctxt.dwCookieRight,
376 ctxt.nParsedLineEndRight + 1, LineNumberRight - 1, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, m_pFilterCommentsDef);
378 ctxt.nParsedLineEndLeft = LineNumberLeft + QtyLinesLeft - 1;
379 ctxt.nParsedLineEndRight = LineNumberRight + QtyLinesRight - 1;;
381 ctxt.dwCookieLeft = GetCommentsFilteredText(ctxt.dwCookieLeft,
382 LineNumberLeft, ctxt.nParsedLineEndLeft, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, LineDataLeft, m_pFilterCommentsDef);
383 ctxt.dwCookieRight = GetCommentsFilteredText(ctxt.dwCookieRight,
384 LineNumberRight, ctxt.nParsedLineEndRight, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, LineDataRight, m_pFilterCommentsDef);
388 LineDataLeft.assign(file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base],
389 file_data_ary[0].linbuf[LineNumberLeft + QtyLinesLeft + file_data_ary[0].linbuf_base]
390 - file_data_ary[0].linbuf[LineNumberLeft + file_data_ary[0].linbuf_base]);
391 LineDataRight.assign(file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base],
392 file_data_ary[1].linbuf[LineNumberRight + QtyLinesRight + file_data_ary[1].linbuf_base]
393 - file_data_ary[1].linbuf[LineNumberRight + file_data_ary[1].linbuf_base]);
396 if (m_pSubstitutionList)
398 LineDataLeft = m_pSubstitutionList->Subst(LineDataLeft);
399 LineDataRight = m_pSubstitutionList->Subst(LineDataRight);
402 if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_ALL)
404 //Ignore character case
405 ReplaceChars(LineDataLeft, " \t", "");
406 ReplaceChars(LineDataRight, " \t", "");
408 else if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_CHANGE)
410 //Ignore change in whitespace char count
411 ReplaceChars(LineDataLeft, " \t", " ");
412 ReplaceChars(LineDataRight, " \t", " ");
415 if (m_options.m_bIgnoreNumbers )
417 //Ignore number character case
418 ReplaceChars(LineDataLeft, "0123456789", "");
419 ReplaceChars(LineDataRight, "0123456789", "");
421 if (m_options.m_bIgnoreCase)
424 // std::transform(LineDataLeft.begin(), LineDataLeft.end(), LineDataLeft.begin(), ::toupper);
425 for (std::string::iterator pb = LineDataLeft.begin(), pe = LineDataLeft.end(); pb != pe; ++pb)
426 *pb = static_cast<char>(::toupper(*pb));
427 // std::transform(LineDataRight.begin(), LineDataRight.end(), LineDataRight.begin(), ::toupper);
428 for (std::string::iterator pb = LineDataRight.begin(), pe = LineDataRight.end(); pb != pe; ++pb)
429 *pb = static_cast<char>(::toupper(*pb));
431 if (m_options.m_bIgnoreEOLDifference)
433 Replace(LineDataLeft, "\r\n", "\n");
434 Replace(LineDataLeft, "\r", "\n");
435 Replace(LineDataRight, "\r\n", "\n");
436 Replace(LineDataRight, "\r", "\n");
438 if (m_options.m_bIgnoreBlankLines)
440 RemoveBlankLines(LineDataLeft);
441 RemoveBlankLines(LineDataRight);
443 if (LineDataLeft != LineDataRight)
445 //only difference is trival
450 * @brief Set source paths for diffing two files.
451 * Sets full paths to two files we are diffing. Paths can be actual user files
452 * or temporary copies of user files. Parameter @p tempPaths tells if paths
453 * are temporary paths that can be deleted.
454 * @param [in] files Files to compare
455 * @param [in] tempPaths Are given paths temporary (can be deleted)?.
457 void CDiffWrapper::SetPaths(const PathContext &tFiles,
461 m_bPathsAreTemp = tempPaths;
465 * @brief Runs diff-engine.
467 bool CDiffWrapper::RunFileDiff()
469 PathContext aFiles = m_files;
471 for (file = 0; file < m_files.GetSize(); file++)
472 aFiles[file] = paths::ToWindowsPath(aFiles[file]);
475 String strFileTemp[3];
476 std::copy(m_files.begin(), m_files.end(), strFileTemp);
478 m_options.SetToDiffUtils();
481 m_nDiffs = m_pDiffList->GetSize();
483 for (file = 0; file < aFiles.GetSize(); file++)
485 if (m_bPluginsEnabled)
487 // Do the preprocessing now, overwrite the temp files
488 // NOTE: FileTransform_UCS2ToUTF8() may create new temp
489 // files and return new names, those created temp files
490 // are deleted in end of function.
492 // this can only fail if the data can not be saved back (no more
493 // place on disk ???) What to do then ??
494 if (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
496 // display a message box
497 String sError = strutils::format(
498 _T("An error occurred while prediffing the file '%s' with the plugin '%s'. The prediffing is not applied any more."),
499 strFileTemp[file].c_str(),
500 m_infoPrediffer->GetPluginPipeline().c_str());
501 AppErrorMessageBox(sError);
502 // don't use any more this prediffer
503 m_infoPrediffer->ClearPluginPipeline();
508 struct change *script = nullptr;
509 struct change *script10 = nullptr;
510 struct change *script12 = nullptr;
511 DiffFileData diffdata, diffdata10, diffdata12;
512 int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
514 if (aFiles.GetSize() == 2)
516 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
517 // This opens & fstats both files (if it succeeds)
518 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
523 // Compare the files, if no error was found.
524 // Last param (bin_file) is `nullptr` since we don't
525 // (yet) need info about binary sides.
526 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
528 // We don't anymore create diff-files for every rescan.
529 // User can create patch-file whenever one wants to.
530 // We don't need to waste time. But lets keep this as
531 // debugging aid. Sometimes it is very useful to see
532 // what differences diff-engine sees!
534 // throw the diff into a temp file
535 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
536 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
538 if (_tfopen_s(&outfile, path.c_str(), _T("w+")) == 0)
540 print_normal_script(script);
548 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
549 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
551 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
556 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
558 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
563 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
566 // First determine what happened during comparison
567 // If there were errors or files were binaries, don't bother
568 // creating diff-lists or patches
570 // diff_2_files set bin_flag to -1 if different binary
571 // diff_2_files set bin_flag to +1 if same binary
573 file_data * inf = diffdata.m_inf;
574 file_data * inf10 = diffdata10.m_inf;
575 file_data * inf12 = diffdata12.m_inf;
577 if (aFiles.GetSize() == 2)
581 m_status.bBinaries = true;
583 m_status.Identical = IDENTLEVEL::ALL;
585 m_status.Identical = IDENTLEVEL::NONE;
588 { // text files according to diffutils, so change script exists
589 m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
590 m_status.bBinaries = false;
592 m_status.bMissingNL[0] = !!inf[0].missing_newline;
593 m_status.bMissingNL[1] = !!inf[1].missing_newline;
597 m_status.Identical = IDENTLEVEL::NONE;
598 if (bin_flag10 != 0 || bin_flag12 != 0)
600 m_status.bBinaries = true;
601 if (bin_flag10 != -1 && bin_flag12 != -1)
602 m_status.Identical = IDENTLEVEL::ALL;
603 else if (bin_flag10 != -1)
604 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
605 else if (bin_flag12 != -1)
606 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
608 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
611 { // text files according to diffutils, so change script exists
612 m_status.bBinaries = false;
613 if (script10 == nullptr && script12 == nullptr)
614 m_status.Identical = IDENTLEVEL::ALL;
615 else if (script10 == nullptr)
616 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
617 else if (script12 == nullptr)
618 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
620 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
622 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
623 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
624 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
629 if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
631 WritePatchFile(script, &inf[0]);
634 // Go through diffs adding them to WinMerge's diff list
635 // This is done on every WinMerge's doc rescan!
636 if (!m_status.bBinaries && m_bUseDiffList)
638 if (aFiles.GetSize() == 2)
639 LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
641 LoadWinMergeDiffsFromDiffUtilsScript3(
643 diffdata10.m_inf, diffdata12.m_inf);
646 // cleanup the script
647 if (aFiles.GetSize() == 2)
648 FreeDiffUtilsScript(script);
651 FreeDiffUtilsScript(script10);
652 FreeDiffUtilsScript(script12);
655 // Done with diffutils filedata
656 if (aFiles.GetSize() == 2)
666 if (m_bPluginsEnabled)
668 // Delete temp files transformation functions possibly created
669 for (file = 0; file < aFiles.GetSize(); file++)
671 if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
675 TFile(strFileTemp[file]).remove();
679 LogErrorStringUTF8(e.displayText());
681 strFileTemp[file].erase();
689 * @brief Add diff to external diff-list
691 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
696 dr.begin[0] = begin0;
698 dr.begin[1] = begin1;
703 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
704 pDiffList->AddDiff(dr);
706 catch (std::exception& e)
708 AppErrorMessageBox(ucr::toTString(e.what()));
712 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
716 pDiffList->AddDiff(dr);
718 catch (std::exception& e)
720 AppErrorMessageBox(ucr::toTString(e.what()));
725 * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
726 * @param [in] leftBufferLines size of array pane left
727 * @param [in] rightBufferLines size of array pane right
728 * @param [in] left on whitch side we have to insert
729 * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
731 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
734 const int count = m_pDiffList->GetSize();
737 m_pDiffList->GetDiff(count - 1, dr);
739 for (int file = 0; file < nFiles; file++)
741 if (!bMissingNL[file])
745 m_pDiffList->SetDiff(count - 1, dr);
749 // we have to create the DIFF
750 for (int file = 0; file < nFiles; file++)
752 dr.end[file] = bufferLines[file] - 1;
753 if (bMissingNL[file])
754 dr.begin[file] = dr.end[file];
756 dr.begin[file] = dr.end[file] + 1;
758 assert(dr.begin[0] == dr.begin[file]);
760 if (bIgnoreBlankLines)
763 AddDiffRange(m_pDiffList, dr);
768 * @brief Returns status-data from diff-engine last run
770 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
772 std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
776 * @brief Formats command-line for diff-engine last run (like it was called from command-line)
778 String CDiffWrapper::FormatSwitchString() const
782 switch (m_options.m_outputStyle)
784 case DIFF_OUTPUT_NORMAL:
787 case DIFF_OUTPUT_CONTEXT:
788 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
790 case DIFF_OUTPUT_UNIFIED:
791 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
797 case DIFF_OUTPUT_FORWARD_ED:
800 case DIFF_OUTPUT_RCS:
803 case DIFF_OUTPUT_IFDEF:
806 case DIFF_OUTPUT_SDIFF:
812 if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
813 m_options.m_contextLines > 0)
815 TCHAR tmpNum[5] = {0};
816 _itot_s(m_options.m_contextLines, tmpNum, 10);
820 if (ignore_all_space_flag > 0)
821 switches += _T(" -w");
823 if (ignore_blank_lines_flag > 0)
824 switches += _T(" -B");
826 if (ignore_case_flag > 0)
827 switches += _T(" -i");
829 if (ignore_space_change_flag > 0)
830 switches += _T(" -b");
836 * @brief Enables/disables patch-file appending.
837 * If the file for patch already exists then the patch will be appended to
839 * @param [in] bAppendFiles If true patch will be appended to existing file.
841 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
843 m_bAppendFiles = bAppendFiles;
847 * @brief Compare two files using diffutils.
849 * Compare two files (in DiffFileData param) using diffutils. Run diffutils
850 * inside SEH so we can trap possible error and exceptions. If error or
851 * execption is trapped, return compare failure.
852 * @param [out] diffs Pointer to list of change structs where diffdata is stored.
853 * @param [in] diffData files to compare.
854 * @param [out] bin_status used to return binary status from compare.
855 * @param [out] bin_file Returns which file was binary file as bitmap.
856 So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
857 info is not needed (faster compare since diffutils don't bother checking
858 second file if first is binary).
859 * @return true when compare succeeds, false if error happened during compare.
860 * @note This function is used in file compare, not folder compare. Similar
861 * folder compare function is in DiffFileData.cpp.
863 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
864 int * bin_status, int * bin_file) const
870 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
872 unsigned xdl_flags = make_xdl_flags(m_options);
873 *diffs = diff_2_files_xdiff(diffData->m_inf, (m_pMovedLines[0] != nullptr), xdl_flags);
874 files[0] = diffData->m_inf[0];
875 files[1] = diffData->m_inf[1];
879 // Diff files. depth is zero because we are not comparing dirs
880 *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
881 (m_pMovedLines[0] != nullptr), bin_file);
883 CopyDiffutilTextStats(diffData->m_inf, diffData);
885 catch (SE_Exception&)
894 * @brief Free script (the diffutils linked list of differences)
897 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
899 if (script == nullptr) return;
900 struct change *e=nullptr, *p=nullptr;
901 // cleanup the script
902 for (e = script; e != nullptr; e = p)
911 * @brief Match regular expression list against given difference.
912 * This function matches the regular expression list against the difference
913 * (given as start line and end line). Matching the diff requires that all
914 * lines in difference match.
915 * @param [in] StartPos First line of the difference.
916 * @param [in] endPos Last line of the difference.
917 * @param [in] FileNo File to match.
918 * return true if any of the expressions matches.
920 bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) const
922 if (m_pFilterList == nullptr)
924 throw "CDiffWrapper::RegExpFilter() called when "
925 "filterlist doesn't exist (=nullptr)";
928 bool linesMatch = true; // set to false when non-matching line is found.
931 while (line <= EndPos && linesMatch)
933 size_t len = pinf->linbuf[line + 1] - pinf->linbuf[line];
934 const char *string = pinf->linbuf[line];
935 size_t stringlen = linelen(string, len);
936 if (!m_pFilterList->Match(std::string(string, stringlen)))
947 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
950 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
952 //Logic needed for Ignore comment option
953 PostFilterContext ctxt;
955 struct change *next = script;
957 while (next != nullptr)
959 /* Find a set of changes that belong together. */
960 struct change *thisob = next;
961 struct change *end = find_change(next);
963 /* Disconnect them from the rest of the changes,
964 making them a hunk, and remember the rest for next iteration. */
968 debug_script(thisob);
971 /* Print thisob hunk. */
972 //(*printfun) (thisob);
974 /* Determine range of line numbers involved in each file. */
975 int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
976 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
977 if (deletes || inserts || thisob->trivial)
979 OP_TYPE op = OP_NONE;
980 if (deletes && inserts)
982 else if (deletes || inserts)
987 /* Print the lines that the first file has. */
988 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
989 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
990 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
992 // Store information about these blocks in moved line info
993 if (GetDetectMovedBlocks())
995 if (thisob->match0>=0)
997 assert(thisob->inserted > 0);
998 for (int i=0; i<thisob->inserted; ++i)
1000 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1001 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1002 GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
1005 if (thisob->match1>=0)
1007 assert(thisob->deleted > 0);
1008 for (int i=0; i<thisob->deleted; ++i)
1010 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1011 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1012 GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
1016 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1017 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1019 if (m_options.m_filterCommentsLines ||
1020 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
1021 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, file_data_ary);
1023 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
1025 // Match lines against regular expression filters
1026 // Our strategy is that every line in both sides must
1027 // match regexp before we mark difference as ignored.
1028 bool match2 = false;
1029 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &file_data_ary[0]);
1031 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &file_data_ary[1]);
1032 if (match1 && match2)
1036 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1038 if (QtyLinesLeft == QtyLinesRight)
1042 else if (QtyLinesLeft < QtyLinesRight)
1044 trans_a0 += QtyLinesLeft;
1045 trans_a1 += QtyLinesLeft;
1049 trans_a0 += QtyLinesRight;
1050 trans_a1 += QtyLinesRight;
1054 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1058 /* Reconnect the script so it will all be freed properly. */
1063 struct Comp02Functor
1065 Comp02Functor(const file_data * inf10, const file_data * inf12) :
1066 inf10_(inf10), inf12_(inf12)
1069 bool operator()(const DiffRangeInfo &dr3)
1071 int line0 = dr3.begin[0];
1072 int line2 = dr3.begin[2];
1073 int line0end = dr3.end[0];
1074 int line2end = dr3.end[2];
1075 if (line0end - line0 != line2end - line2)
1077 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1078 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1079 for (int i = 0; i < line0end - line0 + 1; ++i)
1081 const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1082 const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1083 if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1088 const file_data *inf10_;
1089 const file_data *inf12_;
1093 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1096 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1097 struct change * script10,
1098 struct change * script12,
1099 const file_data * inf10,
1100 const file_data * inf12)
1102 DiffList diff10, diff12;
1106 for (int file = 0; file < 2; file++)
1108 struct change *next = nullptr;
1109 int trans_a0, trans_b0, trans_a1, trans_b1;
1110 int first0, last0, first1, last1, deletes, inserts;
1112 const file_data *pinf = nullptr;
1113 DiffList *pdiff = nullptr;
1114 PostFilterContext ctxt;
1118 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1119 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1122 while (next != nullptr)
1124 /* Find a set of changes that belong together. */
1125 struct change *thisob = next;
1126 struct change *end = find_change(next);
1128 /* Disconnect them from the rest of the changes,
1129 making them a hunk, and remember the rest for next iteration. */
1131 end->link = nullptr;
1133 debug_script(thisob);
1136 /* Print thisob hunk. */
1137 //(*printfun) (thisob);
1139 /* Determine range of line numbers involved in each file. */
1140 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1141 if (deletes || inserts || thisob->trivial)
1143 if (deletes && inserts)
1145 else if (deletes || inserts)
1150 /* Print the lines that the first file has. */
1151 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1152 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1154 // Store information about these blocks in moved line info
1155 if (GetDetectMovedBlocks())
1157 int index1 = 0; // defaults for (file == 0 /* diff10 */)
1159 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1160 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1161 if (file == 1 /* diff12 */)
1165 side1 = MovedLines::SIDE::LEFT;
1166 side2 = MovedLines::SIDE::RIGHT;
1168 if (index1 != -1 && index2 != -1)
1170 if (thisob->match0>=0)
1172 assert(thisob->inserted > 0);
1173 for (int i=0; i<thisob->inserted; ++i)
1175 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1176 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1177 GetMovedLines(index1)->Add(side1, line1, line0);
1180 if (thisob->match1>=0)
1182 assert(thisob->deleted > 0);
1183 for (int i=0; i<thisob->deleted; ++i)
1185 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1186 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1187 GetMovedLines(index2)->Add(side2, line0, line1);
1193 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1194 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1196 if (m_options.m_filterCommentsLines ||
1197 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
1198 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, pinf);
1200 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
1202 // Match lines against regular expression filters
1203 // Our strategy is that every line in both sides must
1204 // match regexp before we mark difference as ignored.
1205 bool match2 = false;
1206 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &pinf[0]);
1208 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &pinf[1]);
1209 if (match1 && match2)
1213 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1217 /* Reconnect the script so it will all be freed properly. */
1222 Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(),
1223 Comp02Functor(inf10, inf12),
1224 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1227 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1230 if (!m_sPatchFile.empty())
1232 const TCHAR *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1233 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1237 if (outfile == nullptr)
1239 m_status.bPatchFileFailed = true;
1244 switch (tOutput_style)
1247 case OUTPUT_CONTEXT:
1248 case OUTPUT_UNIFIED:
1251 case OUTPUT_FORWARD_ED:
1258 print_html_header();
1266 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1269 if (!m_sPatchFile.empty())
1271 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), _T("a+")) != 0)
1275 if (outfile == nullptr)
1277 m_status.bPatchFileFailed = true;
1282 switch (tOutput_style)
1285 case OUTPUT_CONTEXT:
1286 case OUTPUT_UNIFIED:
1289 case OUTPUT_FORWARD_ED:
1296 print_html_terminator();
1305 * @brief Write out a patch file.
1306 * Writes patch file using already computed diffutils script. Converts path
1307 * delimiters from \ to / since we want to keep compatibility with patch-tools.
1308 * @param [in] script list of changes.
1309 * @param [in] inf file_data table containing filenames
1311 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1313 file_data inf_patch[2] = { inf[0], inf[1] };
1315 // Get paths, primarily use alternative paths, only if they are empty
1316 // use full filepaths
1317 String path1(m_alternativePaths[0]);
1318 String path2(m_alternativePaths[1]);
1323 path1 = paths::ToUnixPath(path1);
1324 path2 = paths::ToUnixPath(path2);
1325 if ((inf_patch[0].linbuf && ucr::CheckForInvalidUtf8(inf_patch[0].buffer, inf_patch[0].buffered_chars)) ||
1326 (inf_patch[1].linbuf && ucr::CheckForInvalidUtf8(inf_patch[1].buffer, inf_patch[1].buffered_chars)))
1328 inf_patch[0].name = _strdup(ucr::toThreadCP(path1).c_str());
1329 inf_patch[1].name = _strdup(ucr::toThreadCP(path2).c_str());
1333 inf_patch[0].name = _strdup(ucr::toUTF8(path1).c_str());
1334 inf_patch[1].name = _strdup(ucr::toUTF8(path2).c_str());
1337 // If paths in m_s1File and m_s2File point to original files, then we can use
1338 // them to fix potentially meaningless stats from potentially temporary files,
1339 // resulting from whatever transforms may have taken place.
1340 // If not, then we can't help it, and hence assert that this won't happen.
1341 if (!m_bPathsAreTemp)
1343 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1344 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1352 if (!m_sPatchFile.empty())
1354 const TCHAR *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1355 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1359 if (outfile == nullptr)
1361 m_status.bPatchFileFailed = true;
1365 // Print "command line"
1366 if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1368 String switches = FormatSwitchString();
1369 _ftprintf(outfile, _T("diff%s %s %s\n"),
1371 path1 == _T("NUL") ? _T("/dev/null") : path1.c_str(),
1372 path2 == _T("NUL") ? _T("/dev/null") : path2.c_str());
1375 if (strcmp(inf[0].name, "NUL") == 0)
1377 free((void *)inf_patch[0].name);
1378 inf_patch[0].name = _strdup("/dev/null");
1380 if (strcmp(inf[1].name, "NUL") == 0)
1382 free((void *)inf_patch[1].name);
1383 inf_patch[1].name = _strdup("/dev/null");
1387 switch (output_style)
1390 print_normal_script(script);
1392 case OUTPUT_CONTEXT:
1393 print_context_header(inf_patch, 0);
1394 print_context_script(script, 0);
1396 case OUTPUT_UNIFIED:
1397 print_context_header(inf_patch, 1);
1398 print_context_script(script, 1);
1402 print_ed_script(script);
1404 case OUTPUT_FORWARD_ED:
1405 pr_forward_ed_script(script);
1408 print_rcs_script(script);
1411 print_ifdef_script(script);
1414 print_sdiff_script(script);
1418 print_html_diff_header(inf_patch);
1419 print_html_script(script);
1420 print_html_diff_terminator();
1426 free((void *)inf_patch[0].name);
1427 free((void *)inf_patch[1].name);
1431 * @brief Set line filters, given as one string.
1432 * @param [in] filterStr Filters.
1434 void CDiffWrapper::SetFilterList(const String& filterStr)
1436 // Remove filterlist if new filter is empty
1437 if (filterStr.empty())
1439 m_pFilterList.reset();
1443 // Adding new filter without previous filter
1444 if (m_pFilterList == nullptr)
1446 m_pFilterList.reset(new FilterList);
1449 m_pFilterList->RemoveAllFilters();
1451 std::string regexp_str = ucr::toUTF8(filterStr);
1453 // Add every "line" of regexps to regexp list
1454 StringTokenizer tokens(regexp_str, "\r\n");
1455 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1456 m_pFilterList->AddRegExp(*it);
1459 void CDiffWrapper::SetFilterList(const FilterList* pFilterList)
1462 m_pFilterList.reset();
1465 m_pFilterList.reset(new FilterList());
1466 *m_pFilterList = *pFilterList;
1470 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1472 return m_pSubstitutionList.get();
1475 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1477 m_pSubstitutionList = std::move(pSubstitutionList);
1480 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1482 m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1486 * @brief Copy text stat results from diffutils back into the FileTextStats structure
1488 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1490 myTextStats->ncrlfs = inf->count_crlfs;
1491 myTextStats->ncrs = inf->count_crs;
1492 myTextStats->nlfs = inf->count_lfs;
1493 myTextStats->nzeros = inf->count_zeros;
1497 * @brief Copy both left & right text stats results back into the DiffFileData text stats
1499 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1501 CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1502 CopyTextStats(&inf[1], &diffData->m_textStats[1]);