Apply backgroundcolors.patch
[TortoiseGit.git] / src / TortoiseMerge / DiffData.cpp
blobab4ec70512f53aaa6ec548d884a25657106f68cd
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2023 - TortoiseGit
4 // Copyright (C) 2006-2017, 2020 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #pragma warning(push)
22 #include "diff.h"
23 #pragma warning(pop)
24 #include "TempFile.h"
25 #include "registry.h"
26 #include "resource.h"
27 #include "DiffData.h"
28 #include "UnicodeUtils.h"
29 #include "svn_dso.h"
30 #include "MovedBlocks.h"
32 #pragma warning(push)
33 #pragma warning(disable: 4702) // unreachable code
34 int CDiffData::abort_on_pool_failure (int /*retcode*/)
36 abort ();
37 return -1;
39 #pragma warning(pop)
41 CDiffData::CDiffData()
43 apr_initialize();
44 svn_dso_initialize2();
46 m_sPatchOriginal = L": original";
47 m_sPatchPatched = L": patched";
50 CDiffData::~CDiffData()
52 apr_terminate();
55 void CDiffData::SetMovedBlocks(bool bViewMovedBlocks/* = true*/)
57 m_bViewMovedBlocks = bViewMovedBlocks;
60 int CDiffData::GetLineCount() const
62 int count = m_arBaseFile.GetCount();
63 if (count < m_arTheirFile.GetCount())
64 count = m_arTheirFile.GetCount();
65 if (count < m_arYourFile.GetCount())
66 count = m_arYourFile.GetCount();
67 return count;
70 svn_diff_file_ignore_space_t CDiffData::GetIgnoreSpaceMode(IgnoreWS ignoreWs) const
72 switch (ignoreWs)
74 case IgnoreWS::None:
75 return svn_diff_file_ignore_space_none;
76 case IgnoreWS::AllWhiteSpaces:
77 return svn_diff_file_ignore_space_all;
78 case IgnoreWS::WhiteSpaces:
79 return svn_diff_file_ignore_space_change;
80 default:
81 return svn_diff_file_ignore_space_none;
85 svn_diff_file_options_t* CDiffData::CreateDiffFileOptions(IgnoreWS ignoreWs, bool bIgnoreEOL, apr_pool_t* pool)
87 svn_diff_file_options_t * options = svn_diff_file_options_create(pool);
88 options->ignore_eol_style = bIgnoreEOL;
89 options->ignore_space = GetIgnoreSpaceMode(ignoreWs);
90 return options;
93 bool CDiffData::HandleSvnError(svn_error_t * svnerr)
95 TRACE(L"diff-error in CDiffData::Load()\n");
96 TRACE(L"diff-error in CDiffData::Load()\n");
97 CStringA sMsg = CStringA(svnerr->message);
98 while (svnerr->child)
100 svnerr = svnerr->child;
101 sMsg += L'\n';
102 sMsg += CStringA(svnerr->message);
104 CString readableMsg = CUnicodeUtils::GetUnicode(sMsg);
105 m_sError.Format(IDS_ERR_DIFF_DIFF, static_cast<LPCWSTR>(readableMsg));
106 svn_error_clear(svnerr);
107 return false;
110 void CDiffData::TieMovedBlocks(int from, int to, apr_off_t length)
112 for(int i=0; i<length; i++, to++, from++)
114 int fromIndex = m_YourBaseLeft.FindLineNumber(from);
115 if (fromIndex < 0)
116 return;
117 int toIndex = m_YourBaseRight.FindLineNumber(to);
118 if (toIndex < 0)
119 return;
120 m_YourBaseLeft.SetMovedIndex(fromIndex, toIndex, true);
121 m_YourBaseRight.SetMovedIndex(toIndex, fromIndex, false);
123 toIndex = m_YourBaseBoth.FindLineNumber(to);
124 if (toIndex < 0)
125 return;
126 while((toIndex < m_YourBaseBoth.GetCount())&&
127 ((m_YourBaseBoth.GetState(toIndex) != DiffState::Added)&&
128 (!m_YourBaseBoth.IsMoved(toIndex))||(m_YourBaseBoth.IsMovedFrom(toIndex))||
129 (m_YourBaseBoth.GetLineNumber(toIndex) != to)))
131 toIndex++;
134 fromIndex = m_YourBaseBoth.FindLineNumber(from);
135 if (fromIndex < 0)
136 return;
137 while((fromIndex < m_YourBaseBoth.GetCount())&&
138 ((m_YourBaseBoth.GetState(fromIndex) != DiffState::Removed)&&
139 (!m_YourBaseBoth.IsMoved(fromIndex))||(!m_YourBaseBoth.IsMovedFrom(fromIndex))||
140 (m_YourBaseBoth.GetLineNumber(fromIndex) != from)))
142 fromIndex++;
144 if ((fromIndex < m_YourBaseBoth.GetCount())&&(toIndex < m_YourBaseBoth.GetCount()))
146 m_YourBaseBoth.SetMovedIndex(fromIndex, toIndex, true);
147 m_YourBaseBoth.SetMovedIndex(toIndex, fromIndex, false);
152 bool CDiffData::CompareWithIgnoreWS(CString s1, CString s2, IgnoreWS ignoreWs) const
154 if (ignoreWs == IgnoreWS::WhiteSpaces)
156 s1 = s1.TrimLeft(L" \t");
157 s2 = s2.TrimLeft(L" \t");
159 else
161 s1 = s1.TrimRight(L" \t");
162 s2 = s2.TrimRight(L" \t");
165 return s1 != s2;
168 void CDiffData::StickAndSkip(svn_diff_t * &tempdiff, apr_off_t &original_length_sticked, apr_off_t &modified_length_sticked) const
170 if((m_bViewMovedBlocks)&&(tempdiff->type == svn_diff__type_diff_modified))
172 svn_diff_t * nextdiff = tempdiff->next;
173 while((nextdiff)&&(nextdiff->type == svn_diff__type_diff_modified))
175 original_length_sticked += nextdiff->original_length;
176 modified_length_sticked += nextdiff->modified_length;
177 tempdiff = nextdiff;
178 nextdiff = tempdiff->next;
183 BOOL CDiffData::Load()
185 m_arBaseFile.RemoveAll();
186 m_arYourFile.RemoveAll();
187 m_arTheirFile.RemoveAll();
189 m_YourBaseBoth.Clear();
190 m_YourBaseLeft.Clear();
191 m_YourBaseRight.Clear();
193 m_TheirBaseBoth.Clear();
194 m_TheirBaseLeft.Clear();
195 m_TheirBaseRight.Clear();
197 m_Diff3.Clear();
199 CRegDWORD regIgnoreWS = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreWS");
200 CRegDWORD regIgnoreEOL = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreEOL", TRUE);
201 CRegDWORD regIgnoreCase = CRegDWORD(L"Software\\TortoiseGitMerge\\CaseInsensitive", FALSE);
202 CRegDWORD regIgnoreComments = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreComments", FALSE);
203 IgnoreWS ignoreWs = static_cast<IgnoreWS>(static_cast<DWORD>(regIgnoreWS));
204 bool bIgnoreEOL = static_cast<DWORD>(regIgnoreEOL) != 0;
205 BOOL bIgnoreCase = static_cast<DWORD>(regIgnoreCase) != 0;
206 bool bIgnoreComments = static_cast<DWORD>(regIgnoreComments) != 0;
208 // The Subversion diff API only can ignore whitespaces and eol styles.
209 // It also can only handle one-byte charsets.
210 // To ignore case changes or to handle UTF-16 files, we have to
211 // save the original file in UTF-8 and/or the letters changed to lowercase
212 // so the Subversion diff can handle those.
213 CString sConvertedBaseFilename = m_baseFile.GetFilename();
214 CString sConvertedYourFilename = m_yourFile.GetFilename();
215 CString sConvertedTheirFilename = m_theirFile.GetFilename();
217 m_baseFile.StoreFileAttributes();
218 m_theirFile.StoreFileAttributes();
219 m_yourFile.StoreFileAttributes();
220 //m_mergedFile.StoreFileAttributes();
222 bool bBaseNeedConvert = false;
223 bool bTheirNeedConvert = false;
224 bool bYourNeedConvert = false;
225 bool bBaseIsUtf8 = false;
226 bool bTheirIsUtf8 = false;
227 bool bYourIsUtf8 = false;
229 if (IsBaseFileInUse())
231 if (!m_arBaseFile.Load(m_baseFile.GetFilename()))
233 m_sError = m_arBaseFile.GetErrorString();
234 return FALSE;
236 bBaseNeedConvert = bIgnoreCase || bIgnoreComments || (m_arBaseFile.NeedsConversion()) || !m_rx._Empty();
237 bBaseIsUtf8 = (m_arBaseFile.GetUnicodeType() != CFileTextLines::UnicodeType::ASCII) || bBaseNeedConvert;
240 if (IsTheirFileInUse())
242 // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file
243 // It's a fair guess that the files will be roughly the same size
244 if (!m_arTheirFile.Load(m_theirFile.GetFilename(), m_arBaseFile.GetCount()))
246 m_sError = m_arTheirFile.GetErrorString();
247 return FALSE;
249 bTheirNeedConvert = bIgnoreCase || bIgnoreComments || (m_arTheirFile.NeedsConversion()) || !m_rx._Empty();
250 bTheirIsUtf8 = (m_arTheirFile.GetUnicodeType() != CFileTextLines::UnicodeType::ASCII) || bTheirNeedConvert;
253 if (IsYourFileInUse())
255 // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file
256 // It's a fair guess that the files will be roughly the same size
257 if (!m_arYourFile.Load(m_yourFile.GetFilename(), m_arBaseFile.GetCount()))
259 m_sError = m_arYourFile.GetErrorString();
260 return FALSE;
262 bYourNeedConvert = bIgnoreCase || bIgnoreComments || (m_arYourFile.NeedsConversion()) || !m_rx._Empty();
263 bYourIsUtf8 = (m_arYourFile.GetUnicodeType() != CFileTextLines::UnicodeType::ASCII) || bYourNeedConvert;
266 // in case at least one of the files got converted or is UTF8
267 // we have to convert all non UTF8 (ASCII) files
268 // otherwise one file might be in ANSI and the other in UTF8 and we'll end up
269 // with lines marked as different throughout the files even though the lines
270 // would show no change at all in the viewer.
272 // convert all files we need to
273 bool bIsUtf8 = bBaseIsUtf8 || bTheirIsUtf8 || bYourIsUtf8; // any file end as UTF8
274 bBaseNeedConvert |= (IsBaseFileInUse() && !bBaseIsUtf8 && bIsUtf8);
275 if (bBaseNeedConvert)
277 sConvertedBaseFilename = CTempFiles::Instance().GetTempFilePathString();
278 m_baseFile.SetConvertedFileName(sConvertedBaseFilename);
279 m_arBaseFile.Save(sConvertedBaseFilename, true, true, 0, bIgnoreCase, m_bBlame
280 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
281 , m_rx, m_replacement);
283 bYourNeedConvert |= (IsYourFileInUse() && !bYourIsUtf8 && bIsUtf8);
284 if (bYourNeedConvert)
286 sConvertedYourFilename = CTempFiles::Instance().GetTempFilePathString();
287 m_yourFile.SetConvertedFileName(sConvertedYourFilename);
288 m_arYourFile.Save(sConvertedYourFilename, true, true, 0, bIgnoreCase, m_bBlame
289 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
290 , m_rx, m_replacement);
292 bTheirNeedConvert |= (IsTheirFileInUse() && !bTheirIsUtf8 && bIsUtf8);
293 if (bTheirNeedConvert)
295 sConvertedTheirFilename = CTempFiles::Instance().GetTempFilePathString();
296 m_theirFile.SetConvertedFileName(sConvertedTheirFilename);
297 m_arTheirFile.Save(sConvertedTheirFilename, true, true, 0, bIgnoreCase, m_bBlame
298 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
299 , m_rx, m_replacement);
302 // Calculate the number of lines in the largest of the three files
303 int lengthHint = GetLineCount();
307 m_YourBaseBoth.Reserve(lengthHint);
308 m_YourBaseLeft.Reserve(lengthHint);
309 m_YourBaseRight.Reserve(lengthHint);
311 m_TheirBaseBoth.Reserve(lengthHint);
312 m_TheirBaseLeft.Reserve(lengthHint);
313 m_TheirBaseRight.Reserve(lengthHint);
315 catch (CMemoryException* e)
317 e->GetErrorMessage(CStrBuf(m_sError, 255), 255);
318 e->Delete();
319 return FALSE;
322 apr_pool_t* pool = nullptr;
323 apr_pool_create_ex(&pool, nullptr, abort_on_pool_failure, nullptr);
325 // Is this a two-way diff?
326 if (IsBaseFileInUse() && IsYourFileInUse() && !IsTheirFileInUse())
328 if (!DoTwoWayDiff(sConvertedBaseFilename, sConvertedYourFilename, ignoreWs, bIgnoreEOL, !!bIgnoreCase, bIgnoreComments, pool))
330 apr_pool_destroy (pool); // free the allocated memory
331 return FALSE;
335 if (IsBaseFileInUse() && IsTheirFileInUse() && !IsYourFileInUse())
337 ASSERT(FALSE);
340 // Is this a three-way diff?
341 if (IsBaseFileInUse() && IsTheirFileInUse() && IsYourFileInUse())
343 m_Diff3.Reserve(lengthHint);
345 if (!DoThreeWayDiff(sConvertedBaseFilename, sConvertedYourFilename, sConvertedTheirFilename, ignoreWs, bIgnoreEOL, !!bIgnoreCase, bIgnoreComments, pool))
347 apr_pool_destroy (pool); // free the allocated memory
348 return FALSE;
352 // free the allocated memory
353 apr_pool_destroy (pool);
355 m_arBaseFile.RemoveAll();
356 m_arYourFile.RemoveAll();
357 m_arTheirFile.RemoveAll();
359 return TRUE;
362 bool CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilename, IgnoreWS ignoreWs, bool bIgnoreEOL, bool bIgnoreCase, bool bIgnoreComments, apr_pool_t* pool)
364 svn_diff_file_options_t* options = CreateDiffFileOptions(ignoreWs, bIgnoreEOL, pool);
365 // convert CString filenames (UTF-16 or ANSI) to UTF-8
366 CStringA sBaseFilenameUtf8 = CUnicodeUtils::GetUTF8(sBaseFilename);
367 CStringA sYourFilenameUtf8 = CUnicodeUtils::GetUTF8(sYourFilename);
369 svn_diff_t* diffYourBase = nullptr;
370 svn_error_t * svnerr = svn_diff_file_diff_2(&diffYourBase, sBaseFilenameUtf8, sYourFilenameUtf8, options, pool);
372 if (svnerr)
373 return HandleSvnError(svnerr);
375 tsvn_svn_diff_t_extension* movedBlocks = nullptr;
376 if(m_bViewMovedBlocks)
377 movedBlocks = MovedBlocksDetect(diffYourBase, ignoreWs, pool); // Side effect is that diffs are now splitted
379 svn_diff_t * tempdiff = diffYourBase;
380 LONG baseline = 0;
381 LONG yourline = 0;
382 while (tempdiff)
384 svn_diff__type_e diffType = tempdiff->type;
385 // Side effect described above overcoming - sticking together
386 apr_off_t original_length_sticked = tempdiff->original_length;
387 apr_off_t modified_length_sticked = tempdiff->modified_length;
388 StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked);
390 for (int i=0; i<original_length_sticked; i++)
392 if (baseline >= m_arBaseFile.GetCount())
394 m_sError.LoadString(IDS_ERR_DIFF_NEWLINES);
395 return false;
397 const CString& sCurrentBaseLine = m_arBaseFile.GetAt(baseline);
398 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
399 if (diffType == svn_diff__type_common)
401 if (yourline >= m_arYourFile.GetCount())
403 m_sError.LoadString(IDS_ERR_DIFF_NEWLINES);
404 return false;
406 const CString& sCurrentYourLine = m_arYourFile.GetAt(yourline);
407 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
408 if (sCurrentBaseLine != sCurrentYourLine)
410 bool changedWS = false;
411 if (ignoreWs == IgnoreWS::WhiteSpaces)
412 changedWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, ignoreWs);
413 if (changedWS || ignoreWs == IgnoreWS::None)
415 // one-pane view: two lines, one 'removed' and one 'added'
416 m_YourBaseBoth.AddData(sCurrentBaseLine, DiffState::RemovedWhitespace, yourline, endingBase, changedWS && ignoreWs != IgnoreWS::None ? HideState::Hidden : HideState::Shown, -1);
417 m_YourBaseBoth.AddData(sCurrentYourLine, DiffState::AddedWhitespace, yourline, endingYours, changedWS && ignoreWs != IgnoreWS::None ? HideState::Hidden : HideState::Shown, -1);
419 else
421 m_YourBaseBoth.AddData(sCurrentYourLine, DiffState::Normal, yourline, endingBase, HideState::Hidden, -1);
424 else
426 m_YourBaseBoth.AddData(sCurrentYourLine, DiffState::Normal, yourline, endingBase, HideState::Hidden, -1);
428 yourline++; //in both files
430 else
431 { // small trick - we need here a baseline, but we fix it back to yourline at the end of routine
432 m_YourBaseBoth.AddData(sCurrentBaseLine, DiffState::Removed, -1, endingBase, HideState::Shown, -1);
434 baseline++;
436 if (diffType == svn_diff__type_diff_modified)
438 for (int i=0; i<modified_length_sticked; i++)
440 if (m_arYourFile.GetCount() > yourline)
442 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::Added, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
444 yourline++;
447 tempdiff = tempdiff->next;
450 HideUnchangedSections(&m_YourBaseBoth, nullptr, nullptr);
452 tempdiff = diffYourBase;
453 baseline = 0;
454 yourline = 0;
456 // arbitrary length of 500
457 static const int maxstringlengthforwhitespacecheck = 500;
458 auto s1 = std::make_unique<wchar_t[]>(maxstringlengthforwhitespacecheck);
459 auto s2 = std::make_unique<wchar_t[]>(maxstringlengthforwhitespacecheck);
460 while (tempdiff)
462 if (tempdiff->type == svn_diff__type_common)
464 for (int i = 0; i<tempdiff->original_length; i++)
466 const CString& sCurrentYourLine = m_arYourFile.GetAt(yourline);
467 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
468 const CString& sCurrentBaseLine = m_arBaseFile.GetAt(baseline);
469 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
470 if (sCurrentBaseLine != sCurrentYourLine)
472 bool changedNonWS = false;
473 auto ds = DiffState::Normal;
475 if (ignoreWs == IgnoreWS::AllWhiteSpaces)
477 // the strings could be identical in relation to a filter.
478 // so to find out if there are whitespace changes, we have to strip the strings
479 // of all non-whitespace chars and then compare them.
480 // Note: this is not really fast! So we only do that if the lines are not too long (arbitrary value)
481 if ((sCurrentBaseLine.GetLength() < maxstringlengthforwhitespacecheck) &&
482 (sCurrentYourLine.GetLength() < maxstringlengthforwhitespacecheck))
484 auto pLine1 = static_cast<LPCWSTR>(sCurrentBaseLine);
485 auto pLine2 = static_cast<LPCWSTR>(sCurrentYourLine);
486 auto pS1 = s1.get();
487 while (*pLine1)
489 if ((*pLine1 == L' ') || (*pLine1 == L'\t'))
491 *pS1 = *pLine1;
492 ++pS1;
494 ++pLine1;
496 *pS1 = L'\0';
498 pS1 = s1.get();
499 auto pS2 = s2.get();
500 while (*pLine2)
502 if ((*pLine2 == L' ') || (*pLine2 == L'\t'))
504 *pS2 = *pLine2;
505 ++pS2;
507 ++pLine2;
509 *pS2 = '\0';
510 auto hasWhitespaceChanges = wcscmp(s1.get(), s2.get()) != 0;
511 if (hasWhitespaceChanges)
512 ds = DiffState::Whitespace;
515 if (ignoreWs == IgnoreWS::WhiteSpaces)
516 changedNonWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, ignoreWs);
517 if (!changedNonWS)
519 ds = DiffState::Normal;
521 if ((ds == DiffState::Normal) && (!m_rx._Empty() || bIgnoreCase || bIgnoreComments))
523 ds = DiffState::FilteredDiff;
526 m_YourBaseLeft.AddData(sCurrentBaseLine, ds, baseline, endingBase, (ds == DiffState::Normal) && (ignoreWs != IgnoreWS::None) ? HideState::Hidden : HideState::Shown, -1);
527 m_YourBaseRight.AddData(sCurrentYourLine, ds, yourline, endingYours, (ds == DiffState::Normal) && (ignoreWs != IgnoreWS::None) ? HideState::Hidden : HideState::Shown, -1);
529 else
531 m_YourBaseLeft.AddData(sCurrentBaseLine, DiffState::Normal, baseline, endingBase, HideState::Hidden, -1);
532 m_YourBaseRight.AddData(sCurrentYourLine, DiffState::Normal, yourline, endingYours, HideState::Hidden, -1);
534 baseline++;
535 yourline++;
538 if (tempdiff->type == svn_diff__type_diff_modified)
540 // now we trying to stick together parts, that were splitted by MovedBlocks
541 apr_off_t original_length_sticked = tempdiff->original_length;
542 apr_off_t modified_length_sticked = tempdiff->modified_length;
543 StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked);
545 apr_off_t original_length = original_length_sticked;
546 for (int i=0; i<modified_length_sticked; i++)
548 if (m_arYourFile.GetCount() > yourline)
550 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
551 m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DiffState::Added, yourline, endingYours, HideState::Shown, -1);
552 if (original_length-- <= 0)
554 m_YourBaseLeft.AddEmpty();
556 else
558 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
559 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DiffState::Removed, baseline, endingBase, HideState::Shown, -1);
560 baseline++;
562 yourline++;
565 apr_off_t modified_length = modified_length_sticked;
566 for (int i=0; i<original_length_sticked; i++)
568 if ((modified_length-- <= 0)&&(m_arBaseFile.GetCount() > baseline))
570 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
571 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DiffState::Removed, baseline, endingBase, HideState::Shown, -1);
572 m_YourBaseRight.AddEmpty();
573 baseline++;
577 tempdiff = tempdiff->next;
579 // add last (empty) lines if needed - diff don't report those
580 if (m_arBaseFile.GetCount() > baseline)
582 if (m_arYourFile.GetCount() > yourline)
584 // last line is missing in both files add them to end and mark as no diff
585 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DiffState::Normal, baseline, m_arBaseFile.GetLineEnding(baseline), HideState::Shown, -1);
586 m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DiffState::Normal, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
587 yourline++;
588 baseline++;
590 else
592 viewdata oViewData(m_arBaseFile.GetAt(baseline), DiffState::Removed, baseline, m_arBaseFile.GetLineEnding(baseline), HideState::Shown);
593 baseline++;
595 // find first EMPTY line in last blok
596 int nPos = m_YourBaseLeft.GetCount();
597 while (--nPos>=0 && m_YourBaseLeft.GetState(nPos)==DiffState::Empty) ;
598 if (++nPos<m_YourBaseLeft.GetCount())
600 m_YourBaseLeft.SetData(nPos, oViewData);
602 else
604 m_YourBaseLeft.AddData(oViewData);
605 m_YourBaseRight.AddEmpty();
609 else if (m_arYourFile.GetCount() > yourline)
611 viewdata oViewData(m_arYourFile.GetAt(yourline), DiffState::Added, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown);
612 yourline++;
614 // try to move last line higher
615 int nPos = m_YourBaseRight.GetCount();
616 while (--nPos>=0 && m_YourBaseRight.GetState(nPos)==DiffState::Empty) ;
617 if (++nPos<m_YourBaseRight.GetCount())
619 m_YourBaseRight.SetData(nPos, oViewData);
621 else
623 m_YourBaseLeft.AddEmpty();
624 m_YourBaseRight.AddData(oViewData);
629 // Fixing results for conforming moved blocks
631 while(movedBlocks)
633 tempdiff = movedBlocks->base;
634 if(movedBlocks->moved_to != -1)
636 // set states in a block original:length -> moved_to:length
637 TieMovedBlocks(static_cast<int>(tempdiff->original_start), movedBlocks->moved_to, tempdiff->original_length);
639 if(movedBlocks->moved_from != -1)
641 // set states in a block modified:length -> moved_from:length
642 TieMovedBlocks(movedBlocks->moved_from, static_cast<int>(tempdiff->modified_start), tempdiff->modified_length);
644 movedBlocks = movedBlocks->next;
647 // replace baseline with the yourline in m_YourBaseBoth
648 /* yourline = 0;
649 for(int i=0; i<m_YourBaseBoth.GetCount(); i++)
651 DiffStates state = m_YourBaseBoth.GetState(i);
652 if((state == DiffState::Removed)||(state == DIFFSTATE_MOVED_FROM))
654 m_YourBaseBoth.SetLineNumber(i, -1);
656 else
658 yourline++;
660 }//*/
662 TRACE(L"done with 2-way diff\n");
664 HideUnchangedSections(&m_YourBaseLeft, &m_YourBaseRight, nullptr);
666 return true;
669 bool CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFilename, const CString& sTheirFilename, IgnoreWS ignoreWs, bool bIgnoreEOL, bool bIgnoreCase, bool bIgnoreComments, apr_pool_t* pool)
671 // the following three arrays are used to check for conflicts even in case the
672 // user has ignored spaces/eols.
673 CStdDWORDArray m_arDiff3LinesBase;
674 CStdDWORDArray m_arDiff3LinesYour;
675 CStdDWORDArray m_arDiff3LinesTheir;
676 #define AddLines(baseline, yourline, theirline) m_arDiff3LinesBase.Add(baseline), m_arDiff3LinesYour.Add(yourline), m_arDiff3LinesTheir.Add(theirline)
677 int lengthHint = GetLineCount();
679 m_arDiff3LinesBase.Reserve(lengthHint);
680 m_arDiff3LinesYour.Reserve(lengthHint);
681 m_arDiff3LinesTheir.Reserve(lengthHint);
683 svn_diff_file_options_t* options = CreateDiffFileOptions(ignoreWs, bIgnoreEOL, pool);
685 // convert CString filenames (UTF-16 or ANSI) to UTF-8
686 CStringA sBaseFilenameUtf8 = CUnicodeUtils::GetUTF8(sBaseFilename);
687 CStringA sYourFilenameUtf8 = CUnicodeUtils::GetUTF8(sYourFilename);
688 CStringA sTheirFilenameUtf8 = CUnicodeUtils::GetUTF8(sTheirFilename);
690 svn_diff_t* diffTheirYourBase = nullptr;
691 svn_error_t * svnerr = svn_diff_file_diff3_2(&diffTheirYourBase, sBaseFilenameUtf8, sTheirFilenameUtf8, sYourFilenameUtf8, options, pool);
692 if (svnerr)
693 return HandleSvnError(svnerr);
695 svn_diff_t * tempdiff = diffTheirYourBase;
696 LONG baseline = 0;
697 LONG yourline = 0;
698 LONG theirline = 0;
699 LONG resline = 0;
700 // common viewdata
701 const viewdata emptyConflictEmpty(L"", DiffState::ConflictEmpty, DIFF_EMPTYLINENUMBER, EOL::NoEnding, HideState::Shown);
702 const viewdata emptyIdenticalRemoved(L"", DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, EOL::NoEnding, HideState::Shown);
703 while (tempdiff)
705 if (tempdiff->type == svn_diff__type_common)
707 ASSERT((tempdiff->latest_length == tempdiff->modified_length) && (tempdiff->modified_length == tempdiff->original_length));
708 for (int i=0; i<tempdiff->original_length; i++)
710 if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline))
712 AddLines(baseline, yourline, theirline);
714 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DiffState::Normal, resline, m_arYourFile.GetLineEnding(yourline), HideState::Hidden, -1);
715 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::Normal, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Hidden, -1);
716 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DiffState::Normal, theirline, m_arTheirFile.GetLineEnding(theirline), HideState::Hidden, -1);
718 baseline++;
719 yourline++;
720 theirline++;
721 resline++;
725 else if (tempdiff->type == svn_diff__type_diff_common)
727 ASSERT(tempdiff->latest_length == tempdiff->modified_length);
728 //both theirs and yours have lines replaced
729 for (int i=0; i<tempdiff->original_length; i++)
731 if (m_arBaseFile.GetCount() > baseline)
733 AddLines(baseline, yourline, theirline);
735 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HideState::Shown, -1);
736 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, EOL::NoEnding, HideState::Shown, -1);
737 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, EOL::NoEnding, HideState::Shown, -1);
739 baseline++;
742 for (int i=0; i<tempdiff->modified_length; i++)
744 if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline))
746 AddLines(baseline, yourline, theirline);
748 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DiffState::IdenticalAdded, resline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
749 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::IdenticalAdded, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
750 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DiffState::IdenticalAdded, resline, m_arTheirFile.GetLineEnding(theirline), HideState::Shown, -1);
752 yourline++;
753 theirline++;
754 resline++;
758 else if (tempdiff->type == svn_diff__type_conflict)
760 apr_off_t length = max(tempdiff->original_length, tempdiff->modified_length);
761 length = max(tempdiff->latest_length, length);
762 apr_off_t original = tempdiff->original_length;
763 apr_off_t modified = tempdiff->modified_length;
764 apr_off_t latest = tempdiff->latest_length;
766 apr_off_t originalresolved = 0;
767 apr_off_t modifiedresolved = 0;
768 apr_off_t latestresolved = 0;
770 LONG base = baseline;
771 LONG your = yourline;
772 LONG their = theirline;
773 if (tempdiff->resolved_diff)
775 originalresolved = tempdiff->resolved_diff->original_length;
776 modifiedresolved = tempdiff->resolved_diff->modified_length;
777 latestresolved = tempdiff->resolved_diff->latest_length;
779 for (int i=0; i<length; i++)
781 EOL endingBase = m_arBaseFile.GetCount() > baseline ? m_arBaseFile.GetLineEnding(baseline) : EOL::AutoLine;
782 if (original)
784 if (m_arBaseFile.GetCount() > baseline)
786 AddLines(baseline, yourline, theirline);
788 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, endingBase , HideState::Shown, -1);
789 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, endingBase , HideState::Shown, -1);
790 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::IdenticalRemoved, DIFF_EMPTYLINENUMBER, endingBase , HideState::Shown, -1);
793 else if ((originalresolved)||((modifiedresolved)&&(latestresolved)))
795 AddLines(baseline, yourline, theirline);
797 m_Diff3.AddData(emptyIdenticalRemoved);
798 if ((latestresolved)&&(modifiedresolved))
800 m_YourBaseBoth.AddData(emptyIdenticalRemoved);
801 m_TheirBaseBoth.AddData(emptyIdenticalRemoved);
804 if (original)
806 original--;
807 baseline++;
809 if (originalresolved)
810 originalresolved--;
812 if (modified)
814 modified--;
815 theirline++;
817 if (modifiedresolved)
818 modifiedresolved--;
819 if (latest)
821 latest--;
822 yourline++;
824 if (latestresolved)
825 latestresolved--;
827 original = tempdiff->original_length;
828 modified = tempdiff->modified_length;
829 latest = tempdiff->latest_length;
830 baseline = base;
831 yourline = your;
832 theirline = their;
833 if (tempdiff->resolved_diff)
835 originalresolved = tempdiff->resolved_diff->original_length;
836 modifiedresolved = tempdiff->resolved_diff->modified_length;
837 latestresolved = tempdiff->resolved_diff->latest_length;
839 for (int i=0; i<length; i++)
841 if ((latest)||(modified))
843 AddLines(baseline, yourline, theirline);
845 m_Diff3.AddData(L"", DiffState::Conflicted, resline, EOL::NoEnding, HideState::Shown, -1);
847 resline++;
850 if (latest)
852 if (m_arYourFile.GetCount() > yourline)
854 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::ConflictAdded, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
857 else if ((latestresolved)||(modified)||(modifiedresolved))
859 m_YourBaseBoth.AddData(emptyConflictEmpty);
861 if (modified)
863 if (m_arTheirFile.GetCount() > theirline)
865 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DiffState::ConflictAdded, theirline, m_arTheirFile.GetLineEnding(theirline), HideState::Shown, -1);
868 else if ((modifiedresolved)||(latest)||(latestresolved))
870 m_TheirBaseBoth.AddData(emptyConflictEmpty);
872 if (original)
874 original--;
875 baseline++;
877 if (originalresolved)
878 originalresolved--;
879 if (modified)
881 modified--;
882 theirline++;
884 if (modifiedresolved)
885 modifiedresolved--;
886 if (latest)
888 latest--;
889 yourline++;
891 if (latestresolved)
892 latestresolved--;
895 else if (tempdiff->type == svn_diff__type_diff_modified)
897 //deleted!
898 for (int i=0; i<tempdiff->original_length; i++)
900 if ((m_arBaseFile.GetCount() > baseline)&&(m_arYourFile.GetCount() > yourline))
902 AddLines(baseline, yourline, theirline);
904 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DiffState::TheirsRemoved, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HideState::Shown, -1);
905 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::Normal, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
906 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::TheirsRemoved, DIFF_EMPTYLINENUMBER, EOL::NoEnding, HideState::Shown, -1);
908 baseline++;
909 yourline++;
912 //added
913 for (int i=0; i<tempdiff->modified_length; i++)
915 if (m_arTheirFile.GetCount() > theirline)
917 AddLines(baseline, yourline, theirline);
919 m_Diff3.AddData(m_arTheirFile.GetAt(theirline), DiffState::TheirsAdded, resline, m_arTheirFile.GetLineEnding(theirline), HideState::Shown, -1);
920 m_YourBaseBoth.AddEmpty();
921 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DiffState::TheirsAdded, theirline, m_arTheirFile.GetLineEnding(theirline), HideState::Shown, -1);
923 theirline++;
924 resline++;
928 else if (tempdiff->type == svn_diff__type_diff_latest)
930 //YOURS differs from base
932 for (int i=0; i<tempdiff->original_length; i++)
934 if ((m_arBaseFile.GetCount() > baseline)&&(m_arTheirFile.GetCount() > theirline))
936 AddLines(baseline, yourline, theirline);
938 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DiffState::YoursRemoved, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HideState::Shown, -1);
939 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DiffState::YoursRemoved, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HideState::Shown, -1);
940 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DiffState::Normal, theirline, m_arTheirFile.GetLineEnding(theirline), HideState::Hidden, -1);
942 baseline++;
943 theirline++;
946 for (int i=0; i<tempdiff->latest_length; i++)
948 if (m_arYourFile.GetCount() > yourline)
950 AddLines(baseline, yourline, theirline);
952 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DiffState::YoursAdded, resline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
953 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DiffState::IdenticalAdded, yourline, m_arYourFile.GetLineEnding(yourline), HideState::Shown, -1);
954 m_TheirBaseBoth.AddEmpty();
956 yourline++;
957 resline++;
961 else
963 TRACE(L"something bad happened during diff!\n");
965 tempdiff = tempdiff->next;
967 } // while (tempdiff)
969 if ((options->ignore_space != svn_diff_file_ignore_space_none) || (bIgnoreCase || bIgnoreEOL || bIgnoreComments || !m_rx._Empty()))
971 // If whitespaces are ignored, a conflict could have been missed
972 // We now go through all lines again and check if they're identical.
973 // If they're not, then that means it is a conflict, and we
974 // mark the conflict with the proper colors.
975 for (long i=0; i<m_Diff3.GetCount(); ++i)
977 DiffState state1 = m_YourBaseBoth.GetState(i);
978 DiffState state2 = m_TheirBaseBoth.GetState(i);
980 if (((state1 == DiffState::IdenticalAdded)||(state1 == DiffState::Normal))&&
981 ((state2 == DiffState::IdenticalAdded)||(state2 == DiffState::Normal)))
983 LONG lineyour = m_arDiff3LinesYour.GetAt(i);
984 LONG linetheir = m_arDiff3LinesTheir.GetAt(i);
985 LONG linebase = m_arDiff3LinesBase.GetAt(i);
986 if ((lineyour < m_arYourFile.GetCount()) &&
987 (linetheir < m_arTheirFile.GetCount()) &&
988 (linebase < m_arBaseFile.GetCount()))
990 if (((m_arYourFile.GetLineEnding(lineyour)!=m_arBaseFile.GetLineEnding(linebase))&&
991 (m_arTheirFile.GetLineEnding(linetheir)!=m_arBaseFile.GetLineEnding(linebase))&&
992 (m_arYourFile.GetLineEnding(lineyour)!=m_arTheirFile.GetLineEnding(linetheir))) ||
993 ((m_arYourFile.GetAt(lineyour).Compare(m_arBaseFile.GetAt(linebase))!=0)&&
994 (m_arTheirFile.GetAt(linetheir).Compare(m_arBaseFile.GetAt(linebase))!=0)&&
995 (m_arYourFile.GetAt(lineyour).Compare(m_arTheirFile.GetAt(linetheir))!=0)))
997 m_Diff3.SetState(i, DiffState::Conflicted_Ignored);
998 m_YourBaseBoth.SetState(i, DiffState::ConflictAdded);
999 m_TheirBaseBoth.SetState(i, DiffState::ConflictAdded);
1005 ASSERT(m_Diff3.GetCount() == m_YourBaseBoth.GetCount());
1006 ASSERT(m_TheirBaseBoth.GetCount() == m_YourBaseBoth.GetCount());
1008 TRACE(L"done with 3-way diff\n");
1010 HideUnchangedSections(&m_Diff3, &m_YourBaseBoth, &m_TheirBaseBoth);
1012 return true;
1015 void CDiffData::HideUnchangedSections(CViewData * data1, CViewData * data2, CViewData * data3) const
1017 if (!data1)
1018 return;
1020 CRegDWORD contextLines = CRegDWORD(L"Software\\TortoiseGitMerge\\ContextLines", 1);
1022 if (data1->GetCount() > 1)
1024 HideState lastHideState = data1->GetHideState(0);
1025 if (lastHideState == HideState::Hidden)
1027 data1->SetLineHideState(0, HideState::Marker);
1028 if (data2)
1029 data2->SetLineHideState(0, HideState::Marker);
1030 if (data3)
1031 data3->SetLineHideState(0, HideState::Marker);
1033 for (int i = 1; i < data1->GetCount(); ++i)
1035 HideState hideState = data1->GetHideState(i);
1036 if (hideState != lastHideState)
1038 if (hideState == HideState::Shown)
1040 // go back and show the last 'contextLines' lines to "SHOWN"
1041 int lineback = i - 1;
1042 int stopline = lineback - static_cast<int>(contextLines);
1043 while ((lineback >= 0)&&(lineback > stopline))
1045 data1->SetLineHideState(lineback, HideState::Shown);
1046 if (data2)
1047 data2->SetLineHideState(lineback, HideState::Shown);
1048 if (data3)
1049 data3->SetLineHideState(lineback, HideState::Shown);
1050 lineback--;
1053 else if ((hideState == HideState::Hidden)&&(lastHideState != HideState::Marker))
1055 // go forward and show the next 'contextLines' lines to "SHOWN"
1056 int lineforward = i + 1;
1057 int stopline = lineforward + static_cast<int>(contextLines);
1058 while ((lineforward < data1->GetCount())&&(lineforward < stopline))
1060 data1->SetLineHideState(lineforward, HideState::Shown);
1061 if (data2)
1062 data2->SetLineHideState(lineforward, HideState::Shown);
1063 if (data3)
1064 data3->SetLineHideState(lineforward, HideState::Shown);
1065 lineforward++;
1067 if ((lineforward < data1->GetCount())&&(data1->GetHideState(lineforward) == HideState::Hidden))
1069 data1->SetLineHideState(lineforward, HideState::Marker);
1070 if (data2)
1071 data2->SetLineHideState(lineforward, HideState::Marker);
1072 if (data3)
1073 data3->SetLineHideState(lineforward, HideState::Marker);
1077 lastHideState = hideState;
1082 void CDiffData::SetCommentTokens( const CString& sLineStart, const CString& sBlockStart, const CString& sBlockEnd )
1084 m_CommentLineStart = sLineStart;
1085 m_CommentBlockStart = sBlockStart;
1086 m_CommentBlockEnd = sBlockEnd;
1089 void CDiffData::SetRegexTokens( const std::wregex& rx, const std::wstring& replacement )
1091 m_rx = rx;
1092 m_replacement = replacement;