Get rid of magic numbers
[TortoiseGit.git] / src / TortoiseMerge / DiffData.cpp
blob813ef728d69841b9ac769b9367dd7cafcecaab0d
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2006-2017 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #pragma warning(push)
21 #include "diff.h"
22 #pragma warning(pop)
23 #include "TempFile.h"
24 #include "registry.h"
25 #include "resource.h"
26 #include "DiffData.h"
27 #include "UnicodeUtils.h"
28 #include "svn_dso.h"
29 #include "MovedBlocks.h"
31 #pragma warning(push)
32 #pragma warning(disable: 4702) // unreachable code
33 int CDiffData::abort_on_pool_failure (int /*retcode*/)
35 abort ();
36 return -1;
38 #pragma warning(pop)
40 CDiffData::CDiffData(void)
41 : m_bViewMovedBlocks(false)
42 , m_bPatchRequired(false)
44 apr_initialize();
45 svn_dso_initialize2();
47 m_bBlame = false;
49 m_sPatchOriginal = L": original";
50 m_sPatchPatched = L": patched";
53 CDiffData::~CDiffData(void)
55 apr_terminate();
58 void CDiffData::SetMovedBlocks(bool bViewMovedBlocks/* = true*/)
60 m_bViewMovedBlocks = bViewMovedBlocks;
63 int CDiffData::GetLineCount() const
65 int count = (int)m_arBaseFile.GetCount();
66 if (count < m_arTheirFile.GetCount())
67 count = m_arTheirFile.GetCount();
68 if (count < m_arYourFile.GetCount())
69 count = m_arYourFile.GetCount();
70 return count;
73 svn_diff_file_ignore_space_t CDiffData::GetIgnoreSpaceMode(DWORD dwIgnoreWS) const
75 switch (dwIgnoreWS)
77 case 0:
78 return svn_diff_file_ignore_space_none;
79 case 1:
80 return svn_diff_file_ignore_space_all;
81 case 2:
82 return svn_diff_file_ignore_space_change;
83 default:
84 return svn_diff_file_ignore_space_none;
88 svn_diff_file_options_t * CDiffData::CreateDiffFileOptions(DWORD dwIgnoreWS, bool bIgnoreEOL, apr_pool_t * pool)
90 svn_diff_file_options_t * options = svn_diff_file_options_create(pool);
91 options->ignore_eol_style = bIgnoreEOL;
92 options->ignore_space = GetIgnoreSpaceMode(dwIgnoreWS);
93 return options;
96 bool CDiffData::HandleSvnError(svn_error_t * svnerr)
98 TRACE(L"diff-error in CDiffData::Load()\n");
99 TRACE(L"diff-error in CDiffData::Load()\n");
100 CStringA sMsg = CStringA(svnerr->message);
101 while (svnerr->child)
103 svnerr = svnerr->child;
104 sMsg += L'\n';
105 sMsg += CStringA(svnerr->message);
107 CString readableMsg = CUnicodeUtils::GetUnicode(sMsg);
108 m_sError.Format(IDS_ERR_DIFF_DIFF, (LPCTSTR)readableMsg);
109 svn_error_clear(svnerr);
110 return false;
113 void CDiffData::TieMovedBlocks(int from, int to, apr_off_t length)
115 for(int i=0; i<length; i++, to++, from++)
117 int fromIndex = m_YourBaseLeft.FindLineNumber(from);
118 if (fromIndex < 0)
119 return;
120 int toIndex = m_YourBaseRight.FindLineNumber(to);
121 if (toIndex < 0)
122 return;
123 m_YourBaseLeft.SetMovedIndex(fromIndex, toIndex, true);
124 m_YourBaseRight.SetMovedIndex(toIndex, fromIndex, false);
126 toIndex = m_YourBaseBoth.FindLineNumber(to);
127 if (toIndex < 0)
128 return;
129 while((toIndex < m_YourBaseBoth.GetCount())&&
130 ((m_YourBaseBoth.GetState(toIndex) != DIFFSTATE_ADDED)&&
131 (!m_YourBaseBoth.IsMoved(toIndex))||(m_YourBaseBoth.IsMovedFrom(toIndex))||
132 (m_YourBaseBoth.GetLineNumber(toIndex) != to)))
134 toIndex++;
137 fromIndex = m_YourBaseBoth.FindLineNumber(from);
138 if (fromIndex < 0)
139 return;
140 while((fromIndex < m_YourBaseBoth.GetCount())&&
141 ((m_YourBaseBoth.GetState(fromIndex) != DIFFSTATE_REMOVED)&&
142 (!m_YourBaseBoth.IsMoved(fromIndex))||(!m_YourBaseBoth.IsMovedFrom(fromIndex))||
143 (m_YourBaseBoth.GetLineNumber(fromIndex) != from)))
145 fromIndex++;
147 if ((fromIndex < m_YourBaseBoth.GetCount())&&(toIndex < m_YourBaseBoth.GetCount()))
149 m_YourBaseBoth.SetMovedIndex(fromIndex, toIndex, true);
150 m_YourBaseBoth.SetMovedIndex(toIndex, fromIndex, false);
155 bool CDiffData::CompareWithIgnoreWS(CString s1, CString s2, DWORD dwIgnoreWS) const
157 if (dwIgnoreWS == 2)
159 s1 = s1.TrimLeft(L" \t");
160 s2 = s2.TrimLeft(L" \t");
162 else
164 s1 = s1.TrimRight(L" \t");
165 s2 = s2.TrimRight(L" \t");
168 return s1 != s2;
171 void CDiffData::StickAndSkip(svn_diff_t * &tempdiff, apr_off_t &original_length_sticked, apr_off_t &modified_length_sticked) const
173 if((m_bViewMovedBlocks)&&(tempdiff->type == svn_diff__type_diff_modified))
175 svn_diff_t * nextdiff = tempdiff->next;
176 while((nextdiff)&&(nextdiff->type == svn_diff__type_diff_modified))
178 original_length_sticked += nextdiff->original_length;
179 modified_length_sticked += nextdiff->modified_length;
180 tempdiff = nextdiff;
181 nextdiff = tempdiff->next;
186 BOOL CDiffData::Load()
188 m_arBaseFile.RemoveAll();
189 m_arYourFile.RemoveAll();
190 m_arTheirFile.RemoveAll();
192 m_YourBaseBoth.Clear();
193 m_YourBaseLeft.Clear();
194 m_YourBaseRight.Clear();
196 m_TheirBaseBoth.Clear();
197 m_TheirBaseLeft.Clear();
198 m_TheirBaseRight.Clear();
200 m_Diff3.Clear();
202 CRegDWORD regIgnoreWS = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreWS");
203 CRegDWORD regIgnoreEOL = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreEOL", TRUE);
204 CRegDWORD regIgnoreCase = CRegDWORD(L"Software\\TortoiseGitMerge\\CaseInsensitive", FALSE);
205 CRegDWORD regIgnoreComments = CRegDWORD(L"Software\\TortoiseGitMerge\\IgnoreComments", FALSE);
206 DWORD dwIgnoreWS = regIgnoreWS;
207 bool bIgnoreEOL = ((DWORD)regIgnoreEOL)!=0;
208 BOOL bIgnoreCase = ((DWORD)regIgnoreCase)!=0;
209 bool bIgnoreComments = ((DWORD)regIgnoreComments)!=0;
211 // The Subversion diff API only can ignore whitespaces and eol styles.
212 // It also can only handle one-byte charsets.
213 // To ignore case changes or to handle UTF-16 files, we have to
214 // save the original file in UTF-8 and/or the letters changed to lowercase
215 // so the Subversion diff can handle those.
216 CString sConvertedBaseFilename = m_baseFile.GetFilename();
217 CString sConvertedYourFilename = m_yourFile.GetFilename();
218 CString sConvertedTheirFilename = m_theirFile.GetFilename();
220 m_baseFile.StoreFileAttributes();
221 m_theirFile.StoreFileAttributes();
222 m_yourFile.StoreFileAttributes();
223 //m_mergedFile.StoreFileAttributes();
225 bool bBaseNeedConvert = false;
226 bool bTheirNeedConvert = false;
227 bool bYourNeedConvert = false;
228 bool bBaseIsUtf8 = false;
229 bool bTheirIsUtf8 = false;
230 bool bYourIsUtf8 = false;
232 if (IsBaseFileInUse())
234 if (!m_arBaseFile.Load(m_baseFile.GetFilename()))
236 m_sError = m_arBaseFile.GetErrorString();
237 return FALSE;
239 bBaseNeedConvert = bIgnoreCase || bIgnoreComments || (m_arBaseFile.NeedsConversion()) || !m_rx._Empty();
240 bBaseIsUtf8 = (m_arBaseFile.GetUnicodeType()!=CFileTextLines::ASCII) || bBaseNeedConvert;
243 if (IsTheirFileInUse())
245 // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file
246 // It's a fair guess that the files will be roughly the same size
247 if (!m_arTheirFile.Load(m_theirFile.GetFilename(), m_arBaseFile.GetCount()))
249 m_sError = m_arTheirFile.GetErrorString();
250 return FALSE;
252 bTheirNeedConvert = bIgnoreCase || bIgnoreComments || (m_arTheirFile.NeedsConversion()) || !m_rx._Empty();
253 bTheirIsUtf8 = (m_arTheirFile.GetUnicodeType()!=CFileTextLines::ASCII) || bTheirNeedConvert;
256 if (IsYourFileInUse())
258 // m_arBaseFile.GetCount() is passed as a hint for the number of lines in this file
259 // It's a fair guess that the files will be roughly the same size
260 if (!m_arYourFile.Load(m_yourFile.GetFilename(), m_arBaseFile.GetCount()))
262 m_sError = m_arYourFile.GetErrorString();
263 return FALSE;
265 bYourNeedConvert = bIgnoreCase || bIgnoreComments || (m_arYourFile.NeedsConversion()) || !m_rx._Empty();
266 bYourIsUtf8 = (m_arYourFile.GetUnicodeType()!=CFileTextLines::ASCII) || bYourNeedConvert;
269 // in case at least one of the files got converted or is UTF8
270 // we have to convert all non UTF8 (ASCII) files
271 // otherwise one file might be in ANSI and the other in UTF8 and we'll end up
272 // with lines marked as different throughout the files even though the lines
273 // would show no change at all in the viewer.
275 // convert all files we need to
276 bool bIsUtf8 = bBaseIsUtf8 || bTheirIsUtf8 || bYourIsUtf8; // any file end as UTF8
277 bBaseNeedConvert |= (IsBaseFileInUse() && !bBaseIsUtf8 && bIsUtf8);
278 if (bBaseNeedConvert)
280 sConvertedBaseFilename = CTempFiles::Instance().GetTempFilePathString();
281 m_baseFile.SetConvertedFileName(sConvertedBaseFilename);
282 m_arBaseFile.Save(sConvertedBaseFilename, true, true, 0, bIgnoreCase, m_bBlame
283 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
284 , m_rx, m_replacement);
286 bYourNeedConvert |= (IsYourFileInUse() && !bYourIsUtf8 && bIsUtf8);
287 if (bYourNeedConvert)
289 sConvertedYourFilename = CTempFiles::Instance().GetTempFilePathString();
290 m_yourFile.SetConvertedFileName(sConvertedYourFilename);
291 m_arYourFile.Save(sConvertedYourFilename, true, true, 0, bIgnoreCase, m_bBlame
292 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
293 , m_rx, m_replacement);
295 bTheirNeedConvert |= (IsTheirFileInUse() && !bTheirIsUtf8 && bIsUtf8);
296 if (bTheirNeedConvert)
298 sConvertedTheirFilename = CTempFiles::Instance().GetTempFilePathString();
299 m_theirFile.SetConvertedFileName(sConvertedTheirFilename);
300 m_arTheirFile.Save(sConvertedTheirFilename, true, true, 0, bIgnoreCase, m_bBlame
301 , bIgnoreComments, m_CommentLineStart, m_CommentBlockStart, m_CommentBlockEnd
302 , m_rx, m_replacement);
305 // Calculate the number of lines in the largest of the three files
306 int lengthHint = GetLineCount();
310 m_YourBaseBoth.Reserve(lengthHint);
311 m_YourBaseLeft.Reserve(lengthHint);
312 m_YourBaseRight.Reserve(lengthHint);
314 m_TheirBaseBoth.Reserve(lengthHint);
315 m_TheirBaseLeft.Reserve(lengthHint);
316 m_TheirBaseRight.Reserve(lengthHint);
318 catch (CMemoryException* e)
320 e->GetErrorMessage(m_sError.GetBuffer(255), 255);
321 m_sError.ReleaseBuffer();
322 e->Delete();
323 return FALSE;
326 apr_pool_t* pool = nullptr;
327 apr_pool_create_ex(&pool, nullptr, abort_on_pool_failure, nullptr);
329 // Is this a two-way diff?
330 if (IsBaseFileInUse() && IsYourFileInUse() && !IsTheirFileInUse())
332 if (!DoTwoWayDiff(sConvertedBaseFilename, sConvertedYourFilename, dwIgnoreWS, bIgnoreEOL, !!bIgnoreCase, bIgnoreComments, pool))
334 apr_pool_destroy (pool); // free the allocated memory
335 return FALSE;
339 if (IsBaseFileInUse() && IsTheirFileInUse() && !IsYourFileInUse())
341 ASSERT(FALSE);
344 // Is this a three-way diff?
345 if (IsBaseFileInUse() && IsTheirFileInUse() && IsYourFileInUse())
347 m_Diff3.Reserve(lengthHint);
349 if (!DoThreeWayDiff(sConvertedBaseFilename, sConvertedYourFilename, sConvertedTheirFilename, dwIgnoreWS, bIgnoreEOL, !!bIgnoreCase, bIgnoreComments, pool))
351 apr_pool_destroy (pool); // free the allocated memory
352 return FALSE;
356 // free the allocated memory
357 apr_pool_destroy (pool);
359 m_arBaseFile.RemoveAll();
360 m_arYourFile.RemoveAll();
361 m_arTheirFile.RemoveAll();
363 return TRUE;
366 bool
367 CDiffData::DoTwoWayDiff(const CString& sBaseFilename, const CString& sYourFilename, DWORD dwIgnoreWS, bool bIgnoreEOL, bool bIgnoreCase, bool bIgnoreComments, apr_pool_t* pool)
369 svn_diff_file_options_t * options = CreateDiffFileOptions(dwIgnoreWS, bIgnoreEOL, pool);
370 // convert CString filenames (UTF-16 or ANSI) to UTF-8
371 CStringA sBaseFilenameUtf8 = CUnicodeUtils::GetUTF8(sBaseFilename);
372 CStringA sYourFilenameUtf8 = CUnicodeUtils::GetUTF8(sYourFilename);
374 svn_diff_t* diffYourBase = nullptr;
375 svn_error_t * svnerr = svn_diff_file_diff_2(&diffYourBase, sBaseFilenameUtf8, sYourFilenameUtf8, options, pool);
377 if (svnerr)
378 return HandleSvnError(svnerr);
380 tsvn_svn_diff_t_extension* movedBlocks = nullptr;
381 if(m_bViewMovedBlocks)
382 movedBlocks = MovedBlocksDetect(diffYourBase, dwIgnoreWS, pool); // Side effect is that diffs are now splitted
384 svn_diff_t * tempdiff = diffYourBase;
385 LONG baseline = 0;
386 LONG yourline = 0;
387 while (tempdiff)
389 svn_diff__type_e diffType = tempdiff->type;
390 // Side effect described above overcoming - sticking together
391 apr_off_t original_length_sticked = tempdiff->original_length;
392 apr_off_t modified_length_sticked = tempdiff->modified_length;
393 StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked);
395 for (int i=0; i<original_length_sticked; i++)
397 if (baseline >= m_arBaseFile.GetCount())
399 m_sError.LoadString(IDS_ERR_DIFF_NEWLINES);
400 return false;
402 const CString& sCurrentBaseLine = m_arBaseFile.GetAt(baseline);
403 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
404 if (diffType == svn_diff__type_common)
406 if (yourline >= m_arYourFile.GetCount())
408 m_sError.LoadString(IDS_ERR_DIFF_NEWLINES);
409 return false;
411 const CString& sCurrentYourLine = m_arYourFile.GetAt(yourline);
412 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
413 if (sCurrentBaseLine != sCurrentYourLine)
415 bool changedWS = false;
416 if (dwIgnoreWS == 2 || dwIgnoreWS == 3)
417 changedWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, dwIgnoreWS);
418 if (changedWS || dwIgnoreWS == 0)
420 // one-pane view: two lines, one 'removed' and one 'added'
421 m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVEDWHITESPACE, yourline, endingBase, HIDESTATE_SHOWN, -1);
422 m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_ADDEDWHITESPACE, yourline, endingYours, HIDESTATE_SHOWN, -1);
424 else
426 m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingBase, HIDESTATE_HIDDEN, -1);
429 else
431 m_YourBaseBoth.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingBase, HIDESTATE_HIDDEN, -1);
433 yourline++; //in both files
435 else
436 { // small trick - we need here a baseline, but we fix it back to yourline at the end of routine
437 m_YourBaseBoth.AddData(sCurrentBaseLine, DIFFSTATE_REMOVED, -1, endingBase, HIDESTATE_SHOWN, -1);
439 baseline++;
441 if (diffType == svn_diff__type_diff_modified)
443 for (int i=0; i<modified_length_sticked; i++)
445 if (m_arYourFile.GetCount() > yourline)
447 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
449 yourline++;
452 tempdiff = tempdiff->next;
455 HideUnchangedSections(&m_YourBaseBoth, nullptr, nullptr);
457 tempdiff = diffYourBase;
458 baseline = 0;
459 yourline = 0;
461 // arbitrary length of 500
462 static const int maxstringlengthforwhitespacecheck = 500;
463 auto s1 = std::make_unique<wchar_t[]>(maxstringlengthforwhitespacecheck);
464 auto s2 = std::make_unique<wchar_t[]>(maxstringlengthforwhitespacecheck);
465 while (tempdiff)
467 if (tempdiff->type == svn_diff__type_common)
469 for (int i = 0; i<tempdiff->original_length; i++)
471 const CString& sCurrentYourLine = m_arYourFile.GetAt(yourline);
472 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
473 const CString& sCurrentBaseLine = m_arBaseFile.GetAt(baseline);
474 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
475 if (sCurrentBaseLine != sCurrentYourLine)
477 bool changedNonWS = false;
478 auto ds = DIFFSTATE_NORMAL;
480 if (dwIgnoreWS == 1)
482 // the strings could be identical in relation to a filter.
483 // so to find out if there are whitespace changes, we have to strip the strings
484 // of all non-whitespace chars and then compare them.
485 // Note: this is not really fast! So we only do that if the lines are not too long (arbitrary value)
486 if ((sCurrentBaseLine.GetLength() < maxstringlengthforwhitespacecheck) &&
487 (sCurrentYourLine.GetLength() < maxstringlengthforwhitespacecheck))
489 auto pLine1 = (LPCWSTR)sCurrentBaseLine;
490 auto pLine2 = (LPCWSTR)sCurrentYourLine;
491 auto pS1 = s1.get();
492 while (*pLine1)
494 if ((*pLine1 == L' ') || (*pLine1 == L'\t'))
496 *pS1 = *pLine1;
497 ++pS1;
499 ++pLine1;
501 *pS1 = L'\0';
503 pS1 = s1.get();
504 auto pS2 = s2.get();
505 while (*pLine2)
507 if ((*pLine2 == L' ') || (*pLine2 == L'\t'))
509 *pS2 = *pLine2;
510 ++pS2;
512 ++pLine2;
514 *pS2 = '\0';
515 auto hasWhitespaceChanges = wcscmp(s1.get(), s2.get()) != 0;
516 if (hasWhitespaceChanges)
517 ds = DIFFSTATE_WHITESPACE;
520 if (dwIgnoreWS == 2 || dwIgnoreWS == 3)
521 changedNonWS = CompareWithIgnoreWS(sCurrentBaseLine, sCurrentYourLine, dwIgnoreWS);
522 if (!changedNonWS)
524 ds = DIFFSTATE_NORMAL;
526 if ((ds == DIFFSTATE_NORMAL) && (!m_rx._Empty() || bIgnoreCase || bIgnoreComments))
528 ds = DIFFSTATE_FILTEREDDIFF;
531 m_YourBaseLeft.AddData(sCurrentBaseLine, ds, baseline, endingBase, HIDESTATE_SHOWN, -1);
532 m_YourBaseRight.AddData(sCurrentYourLine, ds, yourline, endingYours, HIDESTATE_SHOWN, -1);
534 else
536 m_YourBaseLeft.AddData(sCurrentBaseLine, DIFFSTATE_NORMAL, baseline, endingBase, HIDESTATE_HIDDEN, -1);
537 m_YourBaseRight.AddData(sCurrentYourLine, DIFFSTATE_NORMAL, yourline, endingYours, HIDESTATE_HIDDEN, -1);
539 baseline++;
540 yourline++;
543 if (tempdiff->type == svn_diff__type_diff_modified)
545 // now we trying to stick together parts, that were splitted by MovedBlocks
546 apr_off_t original_length_sticked = tempdiff->original_length;
547 apr_off_t modified_length_sticked = tempdiff->modified_length;
548 StickAndSkip(tempdiff, original_length_sticked, modified_length_sticked);
550 apr_off_t original_length = original_length_sticked;
551 for (int i=0; i<modified_length_sticked; i++)
553 if (m_arYourFile.GetCount() > yourline)
555 EOL endingYours = m_arYourFile.GetLineEnding(yourline);
556 m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, endingYours, HIDESTATE_SHOWN, -1);
557 if (original_length-- <= 0)
559 m_YourBaseLeft.AddEmpty();
561 else
563 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
564 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase, HIDESTATE_SHOWN, -1);
565 baseline++;
567 yourline++;
570 apr_off_t modified_length = modified_length_sticked;
571 for (int i=0; i<original_length_sticked; i++)
573 if ((modified_length-- <= 0)&&(m_arBaseFile.GetCount() > baseline))
575 EOL endingBase = m_arBaseFile.GetLineEnding(baseline);
576 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, endingBase, HIDESTATE_SHOWN, -1);
577 m_YourBaseRight.AddEmpty();
578 baseline++;
582 tempdiff = tempdiff->next;
584 // add last (empty) lines if needed - diff don't report those
585 if (m_arBaseFile.GetCount() > baseline)
587 if (m_arYourFile.GetCount() > yourline)
589 // last line is missing in both files add them to end and mark as no diff
590 m_YourBaseLeft.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_NORMAL, baseline, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1);
591 m_YourBaseRight.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
592 yourline++;
593 baseline++;
595 else
597 viewdata oViewData(m_arBaseFile.GetAt(baseline), DIFFSTATE_REMOVED, baseline, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN);
598 baseline++;
600 // find first EMPTY line in last blok
601 int nPos = m_YourBaseLeft.GetCount();
602 while (--nPos>=0 && m_YourBaseLeft.GetState(nPos)==DIFFSTATE_EMPTY) ;
603 if (++nPos<m_YourBaseLeft.GetCount())
605 m_YourBaseLeft.SetData(nPos, oViewData);
607 else
609 m_YourBaseLeft.AddData(oViewData);
610 m_YourBaseRight.AddEmpty();
614 else if (m_arYourFile.GetCount() > yourline)
616 viewdata oViewData(m_arYourFile.GetAt(yourline), DIFFSTATE_ADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN);
617 yourline++;
619 // try to move last line higher
620 int nPos = m_YourBaseRight.GetCount();
621 while (--nPos>=0 && m_YourBaseRight.GetState(nPos)==DIFFSTATE_EMPTY) ;
622 if (++nPos<m_YourBaseRight.GetCount())
624 m_YourBaseRight.SetData(nPos, oViewData);
626 else
628 m_YourBaseLeft.AddEmpty();
629 m_YourBaseRight.AddData(oViewData);
634 // Fixing results for conforming moved blocks
636 while(movedBlocks)
638 tempdiff = movedBlocks->base;
639 if(movedBlocks->moved_to != -1)
641 // set states in a block original:length -> moved_to:length
642 TieMovedBlocks((int)tempdiff->original_start, movedBlocks->moved_to, tempdiff->original_length);
644 if(movedBlocks->moved_from != -1)
646 // set states in a block modified:length -> moved_from:length
647 TieMovedBlocks(movedBlocks->moved_from, (int)tempdiff->modified_start, tempdiff->modified_length);
649 movedBlocks = movedBlocks->next;
652 // replace baseline with the yourline in m_YourBaseBoth
653 /* yourline = 0;
654 for(int i=0; i<m_YourBaseBoth.GetCount(); i++)
656 DiffStates state = m_YourBaseBoth.GetState(i);
657 if((state == DIFFSTATE_REMOVED)||(state == DIFFSTATE_MOVED_FROM))
659 m_YourBaseBoth.SetLineNumber(i, -1);
661 else
663 yourline++;
665 }//*/
667 TRACE(L"done with 2-way diff\n");
669 HideUnchangedSections(&m_YourBaseLeft, &m_YourBaseRight, nullptr);
671 return true;
674 bool
675 CDiffData::DoThreeWayDiff(const CString& sBaseFilename, const CString& sYourFilename, const CString& sTheirFilename, DWORD dwIgnoreWS, bool bIgnoreEOL, bool bIgnoreCase, bool bIgnoreComments, apr_pool_t* pool)
677 // the following three arrays are used to check for conflicts even in case the
678 // user has ignored spaces/eols.
679 CStdDWORDArray m_arDiff3LinesBase;
680 CStdDWORDArray m_arDiff3LinesYour;
681 CStdDWORDArray m_arDiff3LinesTheir;
682 #define AddLines(baseline, yourline, theirline) m_arDiff3LinesBase.Add(baseline), m_arDiff3LinesYour.Add(yourline), m_arDiff3LinesTheir.Add(theirline)
683 int lengthHint = GetLineCount();
685 m_arDiff3LinesBase.Reserve(lengthHint);
686 m_arDiff3LinesYour.Reserve(lengthHint);
687 m_arDiff3LinesTheir.Reserve(lengthHint);
689 CRegDWORD contextLines = CRegDWORD(L"Software\\TortoiseGitMerge\\ContextLines", 3);
690 svn_diff_file_options_t * options = CreateDiffFileOptions(dwIgnoreWS, bIgnoreEOL, pool);
692 // convert CString filenames (UTF-16 or ANSI) to UTF-8
693 CStringA sBaseFilenameUtf8 = CUnicodeUtils::GetUTF8(sBaseFilename);
694 CStringA sYourFilenameUtf8 = CUnicodeUtils::GetUTF8(sYourFilename);
695 CStringA sTheirFilenameUtf8 = CUnicodeUtils::GetUTF8(sTheirFilename);
697 svn_diff_t* diffTheirYourBase = nullptr;
698 svn_error_t * svnerr = svn_diff_file_diff3_2(&diffTheirYourBase, sBaseFilenameUtf8, sTheirFilenameUtf8, sYourFilenameUtf8, options, pool);
699 if (svnerr)
700 return HandleSvnError(svnerr);
702 svn_diff_t * tempdiff = diffTheirYourBase;
703 LONG baseline = 0;
704 LONG yourline = 0;
705 LONG theirline = 0;
706 LONG resline = 0;
707 // common viewdata
708 const viewdata emptyConflictEmpty(L"", DIFFSTATE_CONFLICTEMPTY, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN);
709 const viewdata emptyIdenticalRemoved(L"", DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN);
710 while (tempdiff)
712 if (tempdiff->type == svn_diff__type_common)
714 ASSERT((tempdiff->latest_length == tempdiff->modified_length) && (tempdiff->modified_length == tempdiff->original_length));
715 for (int i=0; i<tempdiff->original_length; i++)
717 if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline))
719 AddLines(baseline, yourline, theirline);
721 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_HIDDEN, -1);
722 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_HIDDEN, -1);
723 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_HIDDEN, -1);
725 baseline++;
726 yourline++;
727 theirline++;
728 resline++;
732 else if (tempdiff->type == svn_diff__type_diff_common)
734 ASSERT(tempdiff->latest_length == tempdiff->modified_length);
735 //both theirs and yours have lines replaced
736 for (int i=0; i<tempdiff->original_length; i++)
738 if (m_arBaseFile.GetCount() > baseline)
740 AddLines(baseline, yourline, theirline);
742 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1);
743 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1);
744 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1);
746 baseline++;
749 for (int i=0; i<tempdiff->modified_length; i++)
751 if ((m_arYourFile.GetCount() > yourline)&&(m_arTheirFile.GetCount() > theirline))
753 AddLines(baseline, yourline, theirline);
755 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
756 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
757 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_IDENTICALADDED, resline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1);
759 yourline++;
760 theirline++;
761 resline++;
765 else if (tempdiff->type == svn_diff__type_conflict)
767 apr_off_t length = max(tempdiff->original_length, tempdiff->modified_length);
768 length = max(tempdiff->latest_length, length);
769 apr_off_t original = tempdiff->original_length;
770 apr_off_t modified = tempdiff->modified_length;
771 apr_off_t latest = tempdiff->latest_length;
773 apr_off_t originalresolved = 0;
774 apr_off_t modifiedresolved = 0;
775 apr_off_t latestresolved = 0;
777 LONG base = baseline;
778 LONG your = yourline;
779 LONG their = theirline;
780 if (tempdiff->resolved_diff)
782 originalresolved = tempdiff->resolved_diff->original_length;
783 modifiedresolved = tempdiff->resolved_diff->modified_length;
784 latestresolved = tempdiff->resolved_diff->latest_length;
786 for (int i=0; i<length; i++)
788 EOL endingBase = m_arBaseFile.GetCount() > baseline ? m_arBaseFile.GetLineEnding(baseline) : EOL_AUTOLINE;
789 if (original)
791 if (m_arBaseFile.GetCount() > baseline)
793 AddLines(baseline, yourline, theirline);
795 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1);
796 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1);
797 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_IDENTICALREMOVED, DIFF_EMPTYLINENUMBER, endingBase , HIDESTATE_SHOWN, -1);
800 else if ((originalresolved)||((modifiedresolved)&&(latestresolved)))
802 AddLines(baseline, yourline, theirline);
804 m_Diff3.AddData(emptyIdenticalRemoved);
805 if ((latestresolved)&&(modifiedresolved))
807 m_YourBaseBoth.AddData(emptyIdenticalRemoved);
808 m_TheirBaseBoth.AddData(emptyIdenticalRemoved);
811 if (original)
813 original--;
814 baseline++;
816 if (originalresolved)
817 originalresolved--;
819 if (modified)
821 modified--;
822 theirline++;
824 if (modifiedresolved)
825 modifiedresolved--;
826 if (latest)
828 latest--;
829 yourline++;
831 if (latestresolved)
832 latestresolved--;
834 original = tempdiff->original_length;
835 modified = tempdiff->modified_length;
836 latest = tempdiff->latest_length;
837 baseline = base;
838 yourline = your;
839 theirline = their;
840 if (tempdiff->resolved_diff)
842 originalresolved = tempdiff->resolved_diff->original_length;
843 modifiedresolved = tempdiff->resolved_diff->modified_length;
844 latestresolved = tempdiff->resolved_diff->latest_length;
846 for (int i=0; i<length; i++)
848 if ((latest)||(modified))
850 AddLines(baseline, yourline, theirline);
852 m_Diff3.AddData(L"", DIFFSTATE_CONFLICTED, resline, EOL_NOENDING, HIDESTATE_SHOWN, -1);
854 resline++;
857 if (latest)
859 if (m_arYourFile.GetCount() > yourline)
861 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_CONFLICTADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
864 else if ((latestresolved)||(modified)||(modifiedresolved))
866 m_YourBaseBoth.AddData(emptyConflictEmpty);
868 if (modified)
870 if (m_arTheirFile.GetCount() > theirline)
872 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_CONFLICTADDED, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1);
875 else if ((modifiedresolved)||(latest)||(latestresolved))
877 m_TheirBaseBoth.AddData(emptyConflictEmpty);
879 if (original)
881 original--;
882 baseline++;
884 if (originalresolved)
885 originalresolved--;
886 if (modified)
888 modified--;
889 theirline++;
891 if (modifiedresolved)
892 modifiedresolved--;
893 if (latest)
895 latest--;
896 yourline++;
898 if (latestresolved)
899 latestresolved--;
902 else if (tempdiff->type == svn_diff__type_diff_modified)
904 //deleted!
905 for (int i=0; i<tempdiff->original_length; i++)
907 if ((m_arBaseFile.GetCount() > baseline)&&(m_arYourFile.GetCount() > yourline))
909 AddLines(baseline, yourline, theirline);
911 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1);
912 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_NORMAL, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
913 m_TheirBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_THEIRSREMOVED, DIFF_EMPTYLINENUMBER, EOL_NOENDING, HIDESTATE_SHOWN, -1);
915 baseline++;
916 yourline++;
919 //added
920 for (int i=0; i<tempdiff->modified_length; i++)
922 if (m_arTheirFile.GetCount() > theirline)
924 AddLines(baseline, yourline, theirline);
926 m_Diff3.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, resline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1);
927 m_YourBaseBoth.AddEmpty();
928 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_THEIRSADDED, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_SHOWN, -1);
930 theirline++;
931 resline++;
935 else if (tempdiff->type == svn_diff__type_diff_latest)
937 //YOURS differs from base
939 for (int i=0; i<tempdiff->original_length; i++)
941 if ((m_arBaseFile.GetCount() > baseline)&&(m_arTheirFile.GetCount() > theirline))
943 AddLines(baseline, yourline, theirline);
945 m_Diff3.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1);
946 m_YourBaseBoth.AddData(m_arBaseFile.GetAt(baseline), DIFFSTATE_YOURSREMOVED, DIFF_EMPTYLINENUMBER, m_arBaseFile.GetLineEnding(baseline), HIDESTATE_SHOWN, -1);
947 m_TheirBaseBoth.AddData(m_arTheirFile.GetAt(theirline), DIFFSTATE_NORMAL, theirline, m_arTheirFile.GetLineEnding(theirline), HIDESTATE_HIDDEN, -1);
949 baseline++;
950 theirline++;
953 for (int i=0; i<tempdiff->latest_length; i++)
955 if (m_arYourFile.GetCount() > yourline)
957 AddLines(baseline, yourline, theirline);
959 m_Diff3.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_YOURSADDED, resline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
960 m_YourBaseBoth.AddData(m_arYourFile.GetAt(yourline), DIFFSTATE_IDENTICALADDED, yourline, m_arYourFile.GetLineEnding(yourline), HIDESTATE_SHOWN, -1);
961 m_TheirBaseBoth.AddEmpty();
963 yourline++;
964 resline++;
968 else
970 TRACE(L"something bad happened during diff!\n");
972 tempdiff = tempdiff->next;
974 } // while (tempdiff)
976 if ((options->ignore_space != svn_diff_file_ignore_space_none) || (bIgnoreCase || bIgnoreEOL || bIgnoreComments || !m_rx._Empty()))
978 // If whitespaces are ignored, a conflict could have been missed
979 // We now go through all lines again and check if they're identical.
980 // If they're not, then that means it is a conflict, and we
981 // mark the conflict with the proper colors.
982 for (long i=0; i<m_Diff3.GetCount(); ++i)
984 DiffStates state1 = m_YourBaseBoth.GetState(i);
985 DiffStates state2 = m_TheirBaseBoth.GetState(i);
987 if (((state1 == DIFFSTATE_IDENTICALADDED)||(state1 == DIFFSTATE_NORMAL))&&
988 ((state2 == DIFFSTATE_IDENTICALADDED)||(state2 == DIFFSTATE_NORMAL)))
990 LONG lineyour = m_arDiff3LinesYour.GetAt(i);
991 LONG linetheir = m_arDiff3LinesTheir.GetAt(i);
992 LONG linebase = m_arDiff3LinesBase.GetAt(i);
993 if ((lineyour < m_arYourFile.GetCount()) &&
994 (linetheir < m_arTheirFile.GetCount()) &&
995 (linebase < m_arBaseFile.GetCount()))
997 if (((m_arYourFile.GetLineEnding(lineyour)!=m_arBaseFile.GetLineEnding(linebase))&&
998 (m_arTheirFile.GetLineEnding(linetheir)!=m_arBaseFile.GetLineEnding(linebase))&&
999 (m_arYourFile.GetLineEnding(lineyour)!=m_arTheirFile.GetLineEnding(linetheir))) ||
1000 ((m_arYourFile.GetAt(lineyour).Compare(m_arBaseFile.GetAt(linebase))!=0)&&
1001 (m_arTheirFile.GetAt(linetheir).Compare(m_arBaseFile.GetAt(linebase))!=0)&&
1002 (m_arYourFile.GetAt(lineyour).Compare(m_arTheirFile.GetAt(linetheir))!=0)))
1004 m_Diff3.SetState(i, DIFFSTATE_CONFLICTED_IGNORED);
1005 m_YourBaseBoth.SetState(i, DIFFSTATE_CONFLICTADDED);
1006 m_TheirBaseBoth.SetState(i, DIFFSTATE_CONFLICTADDED);
1012 ASSERT(m_Diff3.GetCount() == m_YourBaseBoth.GetCount());
1013 ASSERT(m_TheirBaseBoth.GetCount() == m_YourBaseBoth.GetCount());
1015 TRACE(L"done with 3-way diff\n");
1017 HideUnchangedSections(&m_Diff3, &m_YourBaseBoth, &m_TheirBaseBoth);
1019 return true;
1022 void CDiffData::HideUnchangedSections(CViewData * data1, CViewData * data2, CViewData * data3) const
1024 if (!data1)
1025 return;
1027 CRegDWORD contextLines = CRegDWORD(L"Software\\TortoiseGitMerge\\ContextLines", 1);
1029 if (data1->GetCount() > 1)
1031 HIDESTATE lastHideState = data1->GetHideState(0);
1032 if (lastHideState == HIDESTATE_HIDDEN)
1034 data1->SetLineHideState(0, HIDESTATE_MARKER);
1035 if (data2)
1036 data2->SetLineHideState(0, HIDESTATE_MARKER);
1037 if (data3)
1038 data3->SetLineHideState(0, HIDESTATE_MARKER);
1040 for (int i = 1; i < data1->GetCount(); ++i)
1042 HIDESTATE hideState = data1->GetHideState(i);
1043 if (hideState != lastHideState)
1045 if (hideState == HIDESTATE_SHOWN)
1047 // go back and show the last 'contextLines' lines to "SHOWN"
1048 int lineback = i - 1;
1049 int stopline = lineback - (int)(DWORD)contextLines;
1050 while ((lineback >= 0)&&(lineback > stopline))
1052 data1->SetLineHideState(lineback, HIDESTATE_SHOWN);
1053 if (data2)
1054 data2->SetLineHideState(lineback, HIDESTATE_SHOWN);
1055 if (data3)
1056 data3->SetLineHideState(lineback, HIDESTATE_SHOWN);
1057 lineback--;
1060 else if ((hideState == HIDESTATE_HIDDEN)&&(lastHideState != HIDESTATE_MARKER))
1062 // go forward and show the next 'contextLines' lines to "SHOWN"
1063 int lineforward = i + 1;
1064 int stopline = lineforward + (int)(DWORD)contextLines;
1065 while ((lineforward < data1->GetCount())&&(lineforward < stopline))
1067 data1->SetLineHideState(lineforward, HIDESTATE_SHOWN);
1068 if (data2)
1069 data2->SetLineHideState(lineforward, HIDESTATE_SHOWN);
1070 if (data3)
1071 data3->SetLineHideState(lineforward, HIDESTATE_SHOWN);
1072 lineforward++;
1074 if ((lineforward < data1->GetCount())&&(data1->GetHideState(lineforward) == HIDESTATE_HIDDEN))
1076 data1->SetLineHideState(lineforward, HIDESTATE_MARKER);
1077 if (data2)
1078 data2->SetLineHideState(lineforward, HIDESTATE_MARKER);
1079 if (data3)
1080 data3->SetLineHideState(lineforward, HIDESTATE_MARKER);
1084 lastHideState = hideState;
1089 void CDiffData::SetCommentTokens( const CString& sLineStart, const CString& sBlockStart, const CString& sBlockEnd )
1091 m_CommentLineStart = sLineStart;
1092 m_CommentBlockStart = sBlockStart;
1093 m_CommentBlockEnd = sBlockEnd;
1096 void CDiffData::SetRegexTokens( const std::wregex& rx, const std::wstring& replacement )
1098 m_rx = rx;
1099 m_replacement = replacement;