/// <summary> /// Generates the diff, one line of output at a time. /// </summary> /// <returns> /// A collection of <see cref="AlignedDiffChange"/> objects, one for /// each line in the first or second collection (sometimes one instance for a line /// from both, when lines are equal or similar.) /// </returns> public IEnumerable <AlignedDiffChange> Generate() { int i1 = 0; int i2 = 0; foreach (Diff2Change section in Diff2.Compare <T>(_Collection1, _Collection2, _EqualityComparer)) { if (section.Equal) { for (int index = 0; index < section.Length1; index++) { yield return(new AlignedDiffChange(ChangeType.Same, i1, i2)); i1++; i2++; } } else { bool deletePlusAdd = true; if (section.Length1 > 0 && section.Length2 > 0) { AlignedDiffChange[] alignedChanges = TryAlignChanges(section, i1, i2); if (alignedChanges.Length > 0) { deletePlusAdd = false; foreach (var change in alignedChanges) { yield return(change); } i1 += section.Length1; i2 += section.Length2; } } if (deletePlusAdd) { for (int index = 0; index < section.Length1; index++) { yield return(new AlignedDiffChange(ChangeType.Deleted, i1, -1)); i1++; } for (int index = 0; index < section.Length2; index++) { yield return(new AlignedDiffChange(ChangeType.Added, -1, i2)); i2++; } } } } }
/// <summary> /// Compares the two collections and generate a diff. /// </summary> /// <typeparam name="T">Type of the element in the list</typeparam> /// <param name="left">The first collection.</param> /// <param name="right">The second collection.</param> /// <param name="comparer">The comparer.</param> /// <returns>An enumeration of <see cref="Diff2Change"/>.</returns> public static IEnumerable <Diff2Change> Compare <T>(IList <T> left, IList <T> right, IEqualityComparer <T> comparer) { if (left == null) { throw new ArgumentNullException("left"); } if (right == null) { throw new ArgumentNullException("right"); } if (comparer == null) { throw new ArgumentNullException("comparer"); } var diff = new Diff2 <T>(left, right, comparer); return(diff.Generate()); }
/// <summary> /// Does a similarity comparison between the two values and returns their /// similarity, a value ranging from 0.0 to 1.0, where 0.0 means they're /// completely different and 1.0 means they have the same value. /// </summary> /// <param name="value1"> /// The first value to compare. /// </param> /// <param name="value2"> /// The second value to compare. /// </param> /// <returns> /// A value ranging from 0.0 to 1.0, where 0.0 means they're /// completely different and 1.0 means they have the same value. /// </returns> public double Compare(string value1, string value2) { if (ReferenceEquals(value1, value2)) { return(1.0); } value1 = (value1 ?? String.Empty); value2 = (value2 ?? String.Empty); if (value1.Length == 0 && value2.Length == 0) { return(1.0); } if (value1.Length == 0 || value2.Length == 0) { return(0.0); } int same = Diff2.Compare(value1.ToCharArray(), value2.ToCharArray()).Where(s => s.Equal).Sum(s => s.Length1); return((same * 2.0) / (value1.Length + value2.Length + 0.0)); }
/// <summary> /// Determines if the two values are similar enough to align them /// as a change, instead of not aligning them but reporting them /// as a delete plus an add instead. /// </summary> /// <param name="value1"> /// The first value to compare against <paramref name="value2"/>. /// </param> /// <param name="value2"> /// The second value to compare against <paramref name="value1"/>. /// </param> /// <returns> /// <c>true</c> if the two values are similar enough to report /// them as a change; false if the two values aren't similar enough /// but needs to be reported as a delete plus an add. /// </returns> public bool CanAlign(string value1, string value2) { if (ReferenceEquals(value1, value2)) { return(true); } value1 = (value1 ?? String.Empty); value2 = (value2 ?? String.Empty); if (value1.Length == 0 && value2.Length == 0) { return(true); } if (value1.Length == 0 || value2.Length == 0) { return(false); } var diff = Diff2.Compare(value1.ToCharArray(), value2.ToCharArray()); return(_DiffPredicate(value1, value2, diff)); }
private List <Diff3Change> FixPreviousConflictAsMerge(List <Diff3Change> changes) { List <Diff3Change> toChanges = null; for (int index = 0; index < changes.Count; index++) { var diff3 = changes[index]; if (diff3.ChangeType != Diff3ChangeType.Conflict) { if (toChanges != null) { AddChangeType(toChanges, diff3); } continue; } if (!diff3.Base.IsValid && diff3.From1.IsValid && !diff3.From2.IsValid) { diff3.ChangeType = Diff3ChangeType.MergeFrom1; if (toChanges != null) { AddChangeType(toChanges, diff3); } } else if (!diff3.Base.IsValid && !diff3.From1.IsValid && diff3.From2.IsValid) { diff3.ChangeType = Diff3ChangeType.MergeFrom2; if (toChanges != null) { AddChangeType(toChanges, diff3); } } else if (diff3.Base.IsValid && diff3.From1.IsValid && !diff3.From2.IsValid) { if (IsRangeEqual(baseList, diff3.Base, modified1List, diff3.From1)) { diff3.ChangeType = Diff3ChangeType.MergeFrom2; } if (toChanges != null) { AddChangeType(toChanges, diff3); } } else if (diff3.Base.IsValid && diff3.From2.IsValid && !diff3.From1.IsValid) { if (IsRangeEqual(baseList, diff3.Base, modified2List, diff3.From2)) { diff3.ChangeType = Diff3ChangeType.MergeFrom1; } if (toChanges != null) { AddChangeType(toChanges, diff3); } } else if (diff3.From1.IsValid && diff3.From2.IsValid) { // If there is no base, we will try to merge 1 and 2 together directly if (!diff3.Base.IsValid) { if (toChanges == null) { toChanges = new List <Diff3Change>(changes.Count); for (int i = 0; i < index; i++) { toChanges.Add(changes[i]); } } var from1Range = diff3.From1; var from2Range = diff3.From2; if (subList1 == null) { subList1 = new List <T>(Math.Max(10, from1Range.Length)); } if (subList2 == null) { subList2 = new List <T>(Math.Max(10, from2Range.Length)); } subList1.Clear(); subList2.Clear(); for (int i = from1Range.From; i <= from1Range.To; i++) { subList1.Add(modified1List[i]); } for (int i = from2Range.From; i <= from2Range.To; i++) { subList2.Add(modified2List[i]); } var diffModified1Modified2 = Diff2.Compare(subList1, subList2, comparer).ToList(); int diff1Index = from1Range.From; int diff2Index = from2Range.From; // Try to evaluate the base index to give an indication where the merge would be inserted in base var baseIndex = index > 0 ? changes[index - 1].Base.IsValid ? changes[index - 1].Base.To + 1 : 0 : 0; var spanBase = new Span(baseIndex, baseIndex); foreach (var diff2Change in diffModified1Modified2) { if (diff2Change.Equal) { AddChangeType(toChanges, Diff3ChangeType.MergeFrom1And2, spanBase, new Span(diff1Index, diff1Index + diff2Change.Length1 - 1), new Span(diff2Index, diff2Index + diff2Change.Length2 - 1), null, null); } else if (diff2Change.Length1 == 0) { AddChangeType(toChanges, Diff3ChangeType.MergeFrom2, spanBase, Span.Invalid, new Span(diff2Index, diff2Index + diff2Change.Length2 - 1), null, null); } else if (diff2Change.Length2 == 0) { AddChangeType(toChanges, Diff3ChangeType.Conflict, spanBase, new Span(diff1Index, diff1Index + diff2Change.Length1 - 1), Span.Invalid, null, null); } else { AddChangeType(toChanges, Diff3ChangeType.Conflict, spanBase, new Span(diff1Index, diff1Index + diff2Change.Length1 - 1), new Span(diff2Index, diff2Index + diff2Change.Length2 - 1), null, null); } diff1Index += diff2Change.Length1; diff2Index += diff2Change.Length2; } } else { // if base = v1 then MergeFrom2 if (diff3.IsBaseEqualFrom1.HasValue && diff3.IsBaseEqualFrom1.Value) { diff3.ChangeType = Diff3ChangeType.MergeFrom2; } // if base = v2 then MergeFrom1 else if (diff3.IsBaseEqualFrom2.HasValue && diff3.IsBaseEqualFrom2.Value) { diff3.ChangeType = Diff3ChangeType.MergeFrom1; } else if (diff3.Base.Length == diff3.From1.Length && diff3.Base.Length == diff3.From2.Length) { // Else if have a base != v1 and base != v2 // Check that if v1 = v2 and range is same for (base,v1,v2) then // this can be considered as a MergeFrom1And2 if (diff3.IsBaseEqualFrom1.HasValue && diff3.IsBaseEqualFrom1.Value && (!diff3.IsBaseEqualFrom2.HasValue || !diff3.IsBaseEqualFrom2.Value)) { diff3.ChangeType = Diff3ChangeType.MergeFrom2; } else if (diff3.IsBaseEqualFrom2.HasValue && diff3.IsBaseEqualFrom2.Value && (!diff3.IsBaseEqualFrom1.HasValue || !diff3.IsBaseEqualFrom1.Value)) { diff3.ChangeType = Diff3ChangeType.MergeFrom1; } else { bool isFrom1AndFrom2Equal = true; for (int i = 0; i < diff3.Base.Length; i++) { if (!comparer.Equals(modified1List[diff3.From1.From + i], modified2List[diff3.From2.From + i])) { isFrom1AndFrom2Equal = false; break; } } if (isFrom1AndFrom2Equal) { diff3.ChangeType = Diff3ChangeType.MergeFrom1And2; } } } if (toChanges != null) { AddChangeType(toChanges, diff3); } } } else { if (toChanges != null) { AddChangeType(toChanges, diff3); } } } return(toChanges ?? changes); }
public List <Diff3Change> Generate() { var diffBaseModified1 = ConvertDiffChangeWithNoRange(Diff2.Compare(baseList, modified1List, comparer)).ToList(); var diffBaseModified2 = ConvertDiffChangeWithNoRange(Diff2.Compare(baseList, modified2List, comparer)).ToList(); var changes = new List <Diff3Change>(); int index1 = 0; int index2 = 0; int baseIndex1 = 0; int baseIndex2 = 0; int diffIndex1 = 0; int diffIndex2 = 0; while (diffIndex1 < diffBaseModified1.Count && diffIndex2 < diffBaseModified2.Count) { var diff1 = diffBaseModified1[diffIndex1]; var diff2 = diffBaseModified2[diffIndex2]; // If element1 and element2 are both equals to the base element if (diff1.Equal && diff2.Equal && baseIndex1 == baseIndex2) { AddChangeType(changes, Diff3ChangeType.Equal, new Span(baseIndex1, baseIndex2), new Span(index1, index1 + diff1.Length2 - 1), new Span(index2, index2 + diff2.Length2 - 1), true, true); baseIndex1 += diff1.Length1; index1 += diff1.Length2; baseIndex2 += diff2.Length1; index2 += diff2.Length2; diffIndex1++; diffIndex2++; } else { var baseRange = Span.Invalid; var from1Range = Span.Invalid; var from2Range = Span.Invalid; bool change1 = false; bool change2 = false; var diff1Equal = true; var diff2Equal = true; // Try to advance as much as possible on both sides until we reach a common point (baseIndex1 == baseIndex2) // This will form a single conflict node while (true) { if (diffIndex1 < diffBaseModified1.Count && !diff1.Equal || baseIndex2 > baseIndex1) { // Advance in list1 change1 = true; diff1Equal &= diff1.Equal; if (diff1.Length2 > 0) { from1Range = new Span(from1Range != Span.Invalid ? from1Range.From : index1, index1 + diff1.Length2 - 1); } if (diff1.Length1 > 0) { baseRange = new Span(baseRange != Span.Invalid ? baseRange.From : baseIndex1, baseIndex1 + diff1.Length1 - 1); } baseIndex1 += diff1.Length1; index1 += diff1.Length2; if (++diffIndex1 < diffBaseModified1.Count) { diff1 = diffBaseModified1[diffIndex1]; } } else if (diffIndex2 < diffBaseModified2.Count && !diff2.Equal || baseIndex1 > baseIndex2) { // Advance in list2 change2 = true; diff2Equal &= diff2.Equal; if (diff2.Length2 > 0) { from2Range = new Span(from2Range != Span.Invalid ? from2Range.From : index2, index2 + diff2.Length2 - 1); } if (diff2.Length1 > 0) { baseRange = new Span(baseRange != Span.Invalid ? baseRange.From : baseIndex2, baseIndex2 + diff2.Length1 - 1); } baseIndex2 += diff2.Length1; index2 += diff2.Length2; if (++diffIndex2 < diffBaseModified2.Count) { diff2 = diffBaseModified2[diffIndex2]; } } else { break; } } AddChangeType(changes, Diff3ChangeType.Conflict, baseRange, from1Range, from2Range, change1 ? (bool?)diff1Equal : null, change2 ? (bool?)diff2Equal : null); } } // Not sure this could happen, but in case, output conflicts instead of merge if original base-v1 and base-v2 diffs were not entirely processed bool isInConflict = diffIndex1 < diffBaseModified1.Count && diffIndex2 < diffBaseModified2.Count; // Process remaining diffs from1 while (diffIndex1 < diffBaseModified1.Count) { var diff1 = diffBaseModified1[diffIndex1]; AddChangeType(changes, isInConflict ? Diff3ChangeType.Conflict : Diff3ChangeType.MergeFrom1, new Span(baseIndex1, baseIndex1), new Span(index1, index1 + diff1.Length2 - 1), Span.Invalid, null, null); baseIndex1 += diff1.Length1; index1 += diff1.Length2; diffIndex1++; } // Process remaining diffs from2 while (diffIndex2 < diffBaseModified2.Count) { var diff2 = diffBaseModified2[diffIndex2]; AddChangeType(changes, isInConflict ? Diff3ChangeType.Conflict : Diff3ChangeType.MergeFrom2, new Span(baseIndex2, baseIndex2), Span.Invalid, new Span(index2, index2 + diff2.Length2 - 1), null, null); baseIndex2 += diff2.Length1; index2 += diff2.Length2; diffIndex2++; } return(FixPreviousConflictAsMerge(changes)); }