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.
27 #include "UnicodeUtils.h"
29 #include "MovedBlocks.h"
32 #pragma warning(disable: 4702) // unreachable code
33 int CDiffData::abort_on_pool_failure (int /*retcode*/)
40 CDiffData::CDiffData(void)
41 : m_bViewMovedBlocks(false)
42 , m_bPatchRequired(false)
45 svn_dso_initialize2();
49 m_sPatchOriginal
= L
": original";
50 m_sPatchPatched
= L
": patched";
53 CDiffData::~CDiffData(void)
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();
73 svn_diff_file_ignore_space_t
CDiffData::GetIgnoreSpaceMode(DWORD dwIgnoreWS
) const
78 return svn_diff_file_ignore_space_none
;
80 return svn_diff_file_ignore_space_all
;
82 return svn_diff_file_ignore_space_change
;
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
);
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
;
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
);
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
);
120 int toIndex
= m_YourBaseRight
.FindLineNumber(to
);
123 m_YourBaseLeft
.SetMovedIndex(fromIndex
, toIndex
, true);
124 m_YourBaseRight
.SetMovedIndex(toIndex
, fromIndex
, false);
126 toIndex
= m_YourBaseBoth
.FindLineNumber(to
);
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
)))
137 fromIndex
= m_YourBaseBoth
.FindLineNumber(from
);
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
)))
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
159 s1
= s1
.TrimLeft(L
" \t");
160 s2
= s2
.TrimLeft(L
" \t");
164 s1
= s1
.TrimRight(L
" \t");
165 s2
= s2
.TrimRight(L
" \t");
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
;
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();
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();
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();
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();
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();
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
339 if (IsBaseFileInUse() && IsTheirFileInUse() && !IsYourFileInUse())
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
356 // free the allocated memory
357 apr_pool_destroy (pool
);
359 m_arBaseFile
.RemoveAll();
360 m_arYourFile
.RemoveAll();
361 m_arTheirFile
.RemoveAll();
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
);
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
;
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
);
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
);
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);
426 m_YourBaseBoth
.AddData(sCurrentYourLine
, DIFFSTATE_NORMAL
, yourline
, endingBase
, HIDESTATE_HIDDEN
, -1);
431 m_YourBaseBoth
.AddData(sCurrentYourLine
, DIFFSTATE_NORMAL
, yourline
, endingBase
, HIDESTATE_HIDDEN
, -1);
433 yourline
++; //in both files
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);
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);
452 tempdiff
= tempdiff
->next
;
455 HideUnchangedSections(&m_YourBaseBoth
, nullptr, nullptr);
457 tempdiff
= diffYourBase
;
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
);
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
;
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
;
494 if ((*pLine1
== L
' ') || (*pLine1
== L
'\t'))
507 if ((*pLine2
== L
' ') || (*pLine2
== L
'\t'))
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
);
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);
536 m_YourBaseLeft
.AddData(sCurrentBaseLine
, DIFFSTATE_NORMAL
, baseline
, endingBase
, HIDESTATE_HIDDEN
, -1);
537 m_YourBaseRight
.AddData(sCurrentYourLine
, DIFFSTATE_NORMAL
, yourline
, endingYours
, HIDESTATE_HIDDEN
, -1);
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();
563 EOL endingBase
= m_arBaseFile
.GetLineEnding(baseline
);
564 m_YourBaseLeft
.AddData(m_arBaseFile
.GetAt(baseline
), DIFFSTATE_REMOVED
, baseline
, endingBase
, HIDESTATE_SHOWN
, -1);
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();
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);
597 viewdata
oViewData(m_arBaseFile
.GetAt(baseline
), DIFFSTATE_REMOVED
, baseline
, m_arBaseFile
.GetLineEnding(baseline
), HIDESTATE_SHOWN
);
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
);
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
);
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
);
628 m_YourBaseLeft
.AddEmpty();
629 m_YourBaseRight
.AddData(oViewData
);
634 // Fixing results for conforming moved blocks
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
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);
667 TRACE(L
"done with 2-way diff\n");
669 HideUnchangedSections(&m_YourBaseLeft
, &m_YourBaseRight
, nullptr);
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
);
700 return HandleSvnError(svnerr
);
702 svn_diff_t
* tempdiff
= diffTheirYourBase
;
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
);
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);
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);
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);
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
;
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
);
816 if (originalresolved
)
824 if (modifiedresolved
)
834 original
= tempdiff
->original_length
;
835 modified
= tempdiff
->modified_length
;
836 latest
= tempdiff
->latest_length
;
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);
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
);
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
);
884 if (originalresolved
)
891 if (modifiedresolved
)
902 else if (tempdiff
->type
== svn_diff__type_diff_modified
)
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);
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);
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);
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();
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
);
1022 void CDiffData::HideUnchangedSections(CViewData
* data1
, CViewData
* data2
, CViewData
* data3
) const
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
);
1036 data2
->SetLineHideState(0, HIDESTATE_MARKER
);
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
);
1054 data2
->SetLineHideState(lineback
, HIDESTATE_SHOWN
);
1056 data3
->SetLineHideState(lineback
, HIDESTATE_SHOWN
);
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
);
1069 data2
->SetLineHideState(lineforward
, HIDESTATE_SHOWN
);
1071 data3
->SetLineHideState(lineforward
, HIDESTATE_SHOWN
);
1074 if ((lineforward
< data1
->GetCount())&&(data1
->GetHideState(lineforward
) == HIDESTATE_HIDDEN
))
1076 data1
->SetLineHideState(lineforward
, HIDESTATE_MARKER
);
1078 data2
->SetLineHideState(lineforward
, HIDESTATE_MARKER
);
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
)
1099 m_replacement
= replacement
;