public virtual void TestAddOne() { Edit e = new Edit(1, 2, 1, 1); EditList l = new EditList(); l.AddItem(e); NUnit.Framework.Assert.AreEqual(1, l.Count); NUnit.Framework.Assert.IsFalse(l.IsEmpty()); NUnit.Framework.Assert.AreSame(e, l[0]); NUnit.Framework.Assert.AreSame(e, l.Iterator().Next()); NUnit.Framework.Assert.AreEqual(l, l); NUnit.Framework.Assert.IsFalse(l.Equals(new EditList())); EditList l2 = new EditList(); l2.AddItem(e); NUnit.Framework.Assert.AreEqual(l2, l); NUnit.Framework.Assert.AreEqual(l, l2); NUnit.Framework.Assert.AreEqual(l.GetHashCode(), l2.GetHashCode()); }
// An special edit which acts as a sentinel value by marking the end the // list of edits /// <summary>Does the three way merge between a common base and two sequences.</summary> /// <remarks>Does the three way merge between a common base and two sequences.</remarks> /// <?></?> /// <param name="cmp">comparison method for this execution.</param> /// <param name="base">the common base sequence</param> /// <param name="ours">the first sequence to be merged</param> /// <param name="theirs">the second sequence to be merged</param> /// <returns>the resulting content</returns> public MergeResult <S> Merge <S>(SequenceComparator <S> cmp, S @base, S ours, S theirs ) where S : Sequence { IList <S> sequences = new AList <S>(3); sequences.AddItem(@base); sequences.AddItem(ours); sequences.AddItem(theirs); MergeResult <S> result = new MergeResult <S>(sequences); if (ours.Size() == 0) { if (theirs.Size() != 0) { EditList theirsEdits = diffAlg.Diff(cmp, @base, theirs); if (!theirsEdits.IsEmpty()) { // we deleted, they modified -> Let their complete content // conflict with empty text result.Add(1, 0, 0, MergeChunk.ConflictState.FIRST_CONFLICTING_RANGE); result.Add(2, 0, theirs.Size(), MergeChunk.ConflictState.NEXT_CONFLICTING_RANGE); } else { // we deleted, they didn't modify -> Let our deletion win result.Add(1, 0, 0, MergeChunk.ConflictState.NO_CONFLICT); } } else { // we and they deleted -> return a single chunk of nothing result.Add(1, 0, 0, MergeChunk.ConflictState.NO_CONFLICT); } return(result); } else { if (theirs.Size() == 0) { EditList oursEdits = diffAlg.Diff(cmp, @base, ours); if (!oursEdits.IsEmpty()) { // we modified, they deleted -> Let our complete content // conflict with empty text result.Add(1, 0, ours.Size(), MergeChunk.ConflictState.FIRST_CONFLICTING_RANGE); result.Add(2, 0, 0, MergeChunk.ConflictState.NEXT_CONFLICTING_RANGE); } else { // they deleted, we didn't modify -> Let their deletion win result.Add(2, 0, 0, MergeChunk.ConflictState.NO_CONFLICT); } return(result); } } EditList oursEdits_1 = diffAlg.Diff(cmp, @base, ours); Iterator <Edit> baseToOurs = oursEdits_1.Iterator(); EditList theirsEdits_1 = diffAlg.Diff(cmp, @base, theirs); Iterator <Edit> baseToTheirs = theirsEdits_1.Iterator(); int current = 0; // points to the next line (first line is 0) of base // which was not handled yet Edit oursEdit = NextEdit(baseToOurs); Edit theirsEdit = NextEdit(baseToTheirs); // iterate over all edits from base to ours and from base to theirs // leave the loop when there are no edits more for ours or for theirs // (or both) while (theirsEdit != END_EDIT || oursEdit != END_EDIT) { if (oursEdit.GetEndA() < theirsEdit.GetBeginA()) { // something was changed in ours not overlapping with any change // from theirs. First add the common part in front of the edit // then the edit. if (current != oursEdit.GetBeginA()) { result.Add(0, current, oursEdit.GetBeginA(), MergeChunk.ConflictState.NO_CONFLICT ); } result.Add(1, oursEdit.GetBeginB(), oursEdit.GetEndB(), MergeChunk.ConflictState. NO_CONFLICT); current = oursEdit.GetEndA(); oursEdit = NextEdit(baseToOurs); } else { if (theirsEdit.GetEndA() < oursEdit.GetBeginA()) { // something was changed in theirs not overlapping with any // from ours. First add the common part in front of the edit // then the edit. if (current != theirsEdit.GetBeginA()) { result.Add(0, current, theirsEdit.GetBeginA(), MergeChunk.ConflictState.NO_CONFLICT ); } result.Add(2, theirsEdit.GetBeginB(), theirsEdit.GetEndB(), MergeChunk.ConflictState .NO_CONFLICT); current = theirsEdit.GetEndA(); theirsEdit = NextEdit(baseToTheirs); } else { // here we found a real overlapping modification // if there is a common part in front of the conflict add it if (oursEdit.GetBeginA() != current && theirsEdit.GetBeginA() != current) { result.Add(0, current, Math.Min(oursEdit.GetBeginA(), theirsEdit.GetBeginA()), MergeChunk.ConflictState .NO_CONFLICT); } // set some initial values for the ranges in A and B which we // want to handle int oursBeginB = oursEdit.GetBeginB(); int theirsBeginB = theirsEdit.GetBeginB(); // harmonize the start of the ranges in A and B if (oursEdit.GetBeginA() < theirsEdit.GetBeginA()) { theirsBeginB -= theirsEdit.GetBeginA() - oursEdit.GetBeginA(); } else { oursBeginB -= oursEdit.GetBeginA() - theirsEdit.GetBeginA(); } // combine edits: // Maybe an Edit on one side corresponds to multiple Edits on // the other side. Then we have to combine the Edits of the // other side - so in the end we can merge together two single // edits. // // It is important to notice that this combining will extend the // ranges of our conflict always downwards (towards the end of // the content). The starts of the conflicting ranges in ours // and theirs are not touched here. // // This combining is an iterative process: after we have // combined some edits we have to do the check again. The // combined edits could now correspond to multiple edits on the // other side. // // Example: when this combining algorithm works on the following // edits // oursEdits=((0-5,0-5),(6-8,6-8),(10-11,10-11)) and // theirsEdits=((0-1,0-1),(2-3,2-3),(5-7,5-7)) // it will merge them into // oursEdits=((0-8,0-8),(10-11,10-11)) and // theirsEdits=((0-7,0-7)) // // Since the only interesting thing to us is how in ours and // theirs the end of the conflicting range is changing we let // oursEdit and theirsEdit point to the last conflicting edit Edit nextOursEdit = NextEdit(baseToOurs); Edit nextTheirsEdit = NextEdit(baseToTheirs); for (; ;) { if (oursEdit.GetEndA() >= nextTheirsEdit.GetBeginA()) { theirsEdit = nextTheirsEdit; nextTheirsEdit = NextEdit(baseToTheirs); } else { if (theirsEdit.GetEndA() >= nextOursEdit.GetBeginA()) { oursEdit = nextOursEdit; nextOursEdit = NextEdit(baseToOurs); } else { break; } } } // harmonize the end of the ranges in A and B int oursEndB = oursEdit.GetEndB(); int theirsEndB = theirsEdit.GetEndB(); if (oursEdit.GetEndA() < theirsEdit.GetEndA()) { oursEndB += theirsEdit.GetEndA() - oursEdit.GetEndA(); } else { theirsEndB += oursEdit.GetEndA() - theirsEdit.GetEndA(); } // A conflicting region is found. Strip off common lines in // in the beginning and the end of the conflicting region // Determine the minimum length of the conflicting areas in OURS // and THEIRS. Also determine how much bigger the conflicting // area in THEIRS is compared to OURS. All that is needed to // limit the search for common areas at the beginning or end // (the common areas cannot be bigger then the smaller // conflicting area. The delta is needed to know whether the // complete conflicting area is common in OURS and THEIRS. int minBSize = oursEndB - oursBeginB; int BSizeDelta = minBSize - (theirsEndB - theirsBeginB); if (BSizeDelta > 0) { minBSize -= BSizeDelta; } int commonPrefix = 0; while (commonPrefix < minBSize && cmp.Equals(ours, oursBeginB + commonPrefix, theirs , theirsBeginB + commonPrefix)) { commonPrefix++; } minBSize -= commonPrefix; int commonSuffix = 0; while (commonSuffix < minBSize && cmp.Equals(ours, oursEndB - commonSuffix - 1, theirs , theirsEndB - commonSuffix - 1)) { commonSuffix++; } minBSize -= commonSuffix; // Add the common lines at start of conflict if (commonPrefix > 0) { result.Add(1, oursBeginB, oursBeginB + commonPrefix, MergeChunk.ConflictState.NO_CONFLICT ); } // Add the conflict (Only if there is a conflict left to report) if (minBSize > 0 || BSizeDelta != 0) { result.Add(1, oursBeginB + commonPrefix, oursEndB - commonSuffix, MergeChunk.ConflictState .FIRST_CONFLICTING_RANGE); result.Add(2, theirsBeginB + commonPrefix, theirsEndB - commonSuffix, MergeChunk.ConflictState .NEXT_CONFLICTING_RANGE); } // Add the common lines at end of conflict if (commonSuffix > 0) { result.Add(1, oursEndB - commonSuffix, oursEndB, MergeChunk.ConflictState.NO_CONFLICT ); } current = Math.Max(oursEdit.GetEndA(), theirsEdit.GetEndA()); oursEdit = nextOursEdit; theirsEdit = nextTheirsEdit; } } } // maybe we have a common part behind the last edit: copy it to the // result if (current < @base.Size()) { result.Add(0, current, @base.Size(), MergeChunk.ConflictState.NO_CONFLICT); } return(result); }
public virtual void TestAddTwo() { Edit e1 = new Edit(1, 2, 1, 1); Edit e2 = new Edit(8, 8, 8, 12); EditList l = new EditList(); l.AddItem(e1); l.AddItem(e2); NUnit.Framework.Assert.AreEqual(2, l.Count); NUnit.Framework.Assert.AreSame(e1, l[0]); NUnit.Framework.Assert.AreSame(e2, l[1]); Iterator<Edit> i = l.Iterator(); NUnit.Framework.Assert.AreSame(e1, i.Next()); NUnit.Framework.Assert.AreSame(e2, i.Next()); NUnit.Framework.Assert.AreEqual(l, l); NUnit.Framework.Assert.IsFalse(l.Equals(new EditList())); EditList l2 = new EditList(); l2.AddItem(e1); l2.AddItem(e2); NUnit.Framework.Assert.AreEqual(l2, l); NUnit.Framework.Assert.AreEqual(l, l2); NUnit.Framework.Assert.AreEqual(l.GetHashCode(), l2.GetHashCode()); }