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.
28 #include "UnicodeUtils.h"
30 #include "MovedBlocks.h"
33 #pragma warning(disable: 4702) // unreachable code
34 int CDiffData::abort_on_pool_failure (int /*retcode*/)
41 CDiffData::CDiffData()
44 svn_dso_initialize2();
46 m_sPatchOriginal
= L
": original";
47 m_sPatchPatched
= L
": patched";
50 CDiffData::~CDiffData()
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();
70 svn_diff_file_ignore_space_t
CDiffData::GetIgnoreSpaceMode(IgnoreWS ignoreWs
) const
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
;
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
);
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
);
100 svnerr
= svnerr
->child
;
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
);
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
);
117 int toIndex
= m_YourBaseRight
.FindLineNumber(to
);
120 m_YourBaseLeft
.SetMovedIndex(fromIndex
, toIndex
, true);
121 m_YourBaseRight
.SetMovedIndex(toIndex
, fromIndex
, false);
123 toIndex
= m_YourBaseBoth
.FindLineNumber(to
);
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
)))
134 fromIndex
= m_YourBaseBoth
.FindLineNumber(from
);
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
)))
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");
161 s1
= s1
.TrimRight(L
" \t");
162 s2
= s2
.TrimRight(L
" \t");
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
;
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();
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();
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();
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();
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);
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
335 if (IsBaseFileInUse() && IsTheirFileInUse() && !IsYourFileInUse())
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
352 // free the allocated memory
353 apr_pool_destroy (pool
);
355 m_arBaseFile
.RemoveAll();
356 m_arYourFile
.RemoveAll();
357 m_arTheirFile
.RemoveAll();
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
);
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
;
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
);
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
);
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);
421 m_YourBaseBoth
.AddData(sCurrentYourLine
, DiffState::Normal
, yourline
, endingBase
, HideState::Hidden
, -1);
426 m_YourBaseBoth
.AddData(sCurrentYourLine
, DiffState::Normal
, yourline
, endingBase
, HideState::Hidden
, -1);
428 yourline
++; //in both files
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);
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);
447 tempdiff
= tempdiff
->next
;
450 HideUnchangedSections(&m_YourBaseBoth
, nullptr, nullptr);
452 tempdiff
= diffYourBase
;
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
);
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
);
489 if ((*pLine1
== L
' ') || (*pLine1
== L
'\t'))
502 if ((*pLine2
== L
' ') || (*pLine2
== L
'\t'))
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
);
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);
531 m_YourBaseLeft
.AddData(sCurrentBaseLine
, DiffState::Normal
, baseline
, endingBase
, HideState::Hidden
, -1);
532 m_YourBaseRight
.AddData(sCurrentYourLine
, DiffState::Normal
, yourline
, endingYours
, HideState::Hidden
, -1);
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();
558 EOL endingBase
= m_arBaseFile
.GetLineEnding(baseline
);
559 m_YourBaseLeft
.AddData(m_arBaseFile
.GetAt(baseline
), DiffState::Removed
, baseline
, endingBase
, HideState::Shown
, -1);
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();
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);
592 viewdata
oViewData(m_arBaseFile
.GetAt(baseline
), DiffState::Removed
, baseline
, m_arBaseFile
.GetLineEnding(baseline
), HideState::Shown
);
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
);
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
);
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
);
623 m_YourBaseLeft
.AddEmpty();
624 m_YourBaseRight
.AddData(oViewData
);
629 // Fixing results for conforming moved blocks
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
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);
662 TRACE(L
"done with 2-way diff\n");
664 HideUnchangedSections(&m_YourBaseLeft
, &m_YourBaseRight
, nullptr);
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
);
693 return HandleSvnError(svnerr
);
695 svn_diff_t
* tempdiff
= diffTheirYourBase
;
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
);
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);
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);
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);
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
;
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
);
809 if (originalresolved
)
817 if (modifiedresolved
)
827 original
= tempdiff
->original_length
;
828 modified
= tempdiff
->modified_length
;
829 latest
= tempdiff
->latest_length
;
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);
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
);
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
);
877 if (originalresolved
)
884 if (modifiedresolved
)
895 else if (tempdiff
->type
== svn_diff__type_diff_modified
)
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);
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);
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);
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();
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
);
1015 void CDiffData::HideUnchangedSections(CViewData
* data1
, CViewData
* data2
, CViewData
* data3
) const
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
);
1029 data2
->SetLineHideState(0, HideState::Marker
);
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
);
1047 data2
->SetLineHideState(lineback
, HideState::Shown
);
1049 data3
->SetLineHideState(lineback
, HideState::Shown
);
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
);
1062 data2
->SetLineHideState(lineforward
, HideState::Shown
);
1064 data3
->SetLineHideState(lineforward
, HideState::Shown
);
1067 if ((lineforward
< data1
->GetCount())&&(data1
->GetHideState(lineforward
) == HideState::Hidden
))
1069 data1
->SetLineHideState(lineforward
, HideState::Marker
);
1071 data2
->SetLineHideState(lineforward
, HideState::Marker
);
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
)
1092 m_replacement
= replacement
;