/// <summary> /// Gets th edit script of this line in comparison to its <see cref="Counterpart"/>. /// The change edit script can then be used to color each letter position in the line /// to indicate how one line can completely match the other using character based /// change operations (insert, delete, change, none). /// /// The method should only be invoked on demand (when a line is actually /// displayed) - we should wait with pulling it until we have to have it for rendering. /// /// This object will NOT cache the edit script so the caller should implement an external /// caching algorithm to avoid multiple computations of the same answer. /// /// Getting all intra-line diffs at once makes the whole process into an O(n^2) operation /// instead of just an O(n) operation for line-by-line diffs. So we try to defer the /// extra work until the user requests to see the changed line. It's still /// the same amount of work if the user views every line, but it makes the /// user interface more responsive to split it up like this. /// </summary> /// <param name="options"></param> /// <returns></returns> public EditScript GetChangeEditScript(ChangeDiffOptions options) { EditScript _changeEditScript = null; if ( //_changeEditScript == null && _editType == EditType.Change && this.Counterpart != null) { if (this.FromA) { int trimCountA, trimCountB; MyersDiff <char> diff = new MyersDiff <char>( GetCharactersToDiff(this.text, options, out trimCountA), GetCharactersToDiff(this.Counterpart.text, options, out trimCountB), false); // We don't want Change edits; just Deletes and Inserts. _changeEditScript = diff.Execute(null); // If we trimmed/ignored leading whitespace, we have to offset each Edit to account for that. foreach (Edit edit in _changeEditScript) { edit.Offset(trimCountA, trimCountB); } } else if (this.Counterpart.FromA && this.Counterpart.Counterpart == this) { // Defer to the A line because its edit script changes A into B. _changeEditScript = this.Counterpart.GetChangeEditScript(options); } } return(_changeEditScript); }
/** * Tests the performance of MyersDiff for texts which are similar (not * random data). The CPU time is measured and returned. Because of bad * accuracy of CPU time information the diffs are repeated. During each * repetition the interim CPU time is checked. The diff operation is * repeated until we have seen the CPU time clock changed its value at least * {@link #minCPUTimerTicks} times. * * @param characters * the size of the diffed character sequences. * @return performance data */ private PerfData test(int characters) { PerfData ret = new PerfData(); string a = DiffTestDataGenerator.generateSequence(characters, 971, 3); string b = DiffTestDataGenerator.generateSequence(characters, 1621, 5); CharArray ac = new CharArray(a); CharArray bc = new CharArray(b); MyersDiff myersDiff = null; int cpuTimeChanges = 0; long lastReadout = 0; long interimTime = 0; int repetitions = 0; stopwatch.start(); while (cpuTimeChanges < minCPUTimerTicks && interimTime < longTaskBoundary) { myersDiff = new MyersDiff(ac, bc); repetitions++; interimTime = stopwatch.readout(); if (interimTime != lastReadout) { cpuTimeChanges++; lastReadout = interimTime; } } ret.runningTime = stopwatch.stop() / repetitions; ret.N = (ac.size() + bc.size()); ret.D = myersDiff.getEdits().size(); return(ret); }
public LineAndCommit[] Blame(Commit commit, string path) { var leaf = commit.Tree[path] as Leaf; if (leaf == null) { throw new ArgumentException("The given path does not exist in this commit: " + path); } byte[] data = leaf.RawData; IntList lineMap = RawParseUtils.lineMap(data, 0, data.Length); var lines = new LineAndCommit[lineMap.size()]; var curText = new RawText(data); Commit prevAncestor = commit; Leaf prevLeaf = null; Commit prevCommit = null; int emptyLines = lineMap.size(); foreach (Commit ancestor in commit.Ancestors) { var cleaf = ancestor.Tree[path] as Leaf; if (prevCommit != null && (cleaf == null || cleaf.Hash != prevLeaf.Hash)) { byte[] prevData = prevLeaf.RawData; if (prevData == null) { break; } var prevText = new RawText(prevData); var differ = new MyersDiff(prevText, curText); foreach (Edit e in differ.getEdits()) { for (int n = e.BeginB; n < e.EndB; n++) { if (lines[n] == null) { lines[n] = CreateViewModel(GetLine(commit.Encoding, curText, n), prevCommit); emptyLines--; } } } if (cleaf == null || emptyLines <= 0) { break; } } prevCommit = ancestor; prevLeaf = cleaf; } for (int n = 0; n < lines.Length; n++) { if (lines[n] == null) { lines[n] = CreateViewModel(GetLine(commit.Encoding, curText, n), prevAncestor); } } return(lines); }
public Commit[] Blame(string path) { Leaf leaf = Tree [path] as Leaf; if (leaf == null) { throw new ArgumentException("The given path does not exist in this commit: " + path); } byte[] data = leaf.RawData; int lineCount = RawParseUtils.lineMap(data, 0, data.Length).size(); Commit[] lines = new Commit [lineCount]; var curText = new RawText(data); Commit prevAncestor = this; Leaf prevLeaf = null; Commit prevCommit = null; int emptyLines = lineCount; foreach (Commit ancestor in Ancestors) { Leaf cleaf = ancestor.Tree [path] as Leaf; if (prevCommit != null && (cleaf == null || cleaf.Hash != prevLeaf.Hash)) { byte[] prevData = prevLeaf.RawData; if (prevData == null) { break; } var prevText = new RawText(prevData); var differ = new MyersDiff(prevText, curText); foreach (Edit e in differ.getEdits()) { for (int n = e.BeginB; n < e.EndB; n++) { if (lines [n] == null) { lines [n] = prevCommit; emptyLines--; } } } if (cleaf == null || emptyLines <= 0) { break; } } prevCommit = ancestor; prevLeaf = cleaf; } for (int n = 0; n < lines.Length; n++) { if (lines [n] == null) { lines [n] = prevAncestor; } } return(lines); }
/// <summary> /// Creates a line-based diff from the the given byte arrays. /// </summary> /// <param name="a"></param> /// <param name="b"></param> public Diff(byte[] a, byte[] b) { m_sequence_a = new Text(a); m_sequence_b = new Text(b); var diff = new MyersDiff((RawText)m_sequence_a, (RawText)m_sequence_b); // <--- using the undocumented cast operator of Text m_edits = diff.getEdits(); }
/// <summary> /// Makes comparison of two collections of string lines /// </summary> /// <param name="listA">Lines sequence A</param> /// <param name="listB">Lines sequence B</param> /// <returns>Edit script to transform A to B</returns> public EditScript Execute(IList <string> listA, IList <string> listB) { int[] hashA = HashStringList(listA); int[] hashB = HashStringList(listB); MyersDiff <int> diff = new MyersDiff <int>(hashA, hashB, _supportChangeEditType); EditScript result = diff.Execute(); return(result); }
public static IEnumerable<DiffEntry> CompareCommits (NGit.Repository repo, AnyObjectId reference, ObjectId compared) { var diff = new MyersDiff (repo); var firstTree = new CanonicalTreeParser (); firstTree.Reset (repo.NewObjectReader (), new RevWalk (repo).ParseTree (reference)); diff.SetNewTree (firstTree); if (compared != ObjectId.ZeroId) { var secondTree = new CanonicalTreeParser (); secondTree.Reset (repo.NewObjectReader (), new RevWalk (repo).ParseTree (compared)); if (compared != ObjectId.ZeroId) diff.SetOldTree (secondTree); } return diff.Call (); }
/// <summary> /// Builds an EditScript which patches the original data to the desired data and uses this to create the Patchup patch. /// </summary> /// <param name="from">The original byte data which the patch will be applied to.</param> /// <param name="to">The desired byte data which will result after applying the patch to the original byte data.</param> public void Build(byte[] from, byte[] to) { this.EditScript = MyersDiff.SingleThreaded(from, to); }
/// <summary> /// Does the three way merge between a common base and two sequences. /// </summary> /// <param name="base">base the common base sequence</param> /// <param name="ours">ours the first sequence to be merged</param> /// <param name="theirs">theirs the second sequence to be merged</param> /// <returns>the resulting content</returns> public static MergeResult merge(Sequence @base, Sequence ours, Sequence theirs) { List <Sequence> sequences = new List <Sequence>(3); sequences.Add(@base); sequences.Add(ours); sequences.Add(theirs); MergeResult result = new MergeResult(sequences); EditList oursEdits = new MyersDiff(@base, ours).getEdits(); IteratorBase <Edit> baseToOurs = oursEdits.iterator(); EditList theirsEdits = new MyersDiff(@base, theirs).getEdits(); IteratorBase <Edit> baseToTheirs = theirsEdits.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.EndA <= theirsEdit.BeginA) { // 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.BeginA) { result.add(0, current, oursEdit.BeginA, MergeChunk.ConflictState.NO_CONFLICT); } result.add(1, oursEdit.BeginB, oursEdit.EndB, MergeChunk.ConflictState.NO_CONFLICT); current = oursEdit.EndA; oursEdit = nextEdit(baseToOurs); } else if (theirsEdit.EndA <= oursEdit.BeginA) { // 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.BeginA) { result.add(0, current, theirsEdit.BeginA, MergeChunk.ConflictState.NO_CONFLICT); } result.add(2, theirsEdit.BeginB, theirsEdit.EndB, MergeChunk.ConflictState.NO_CONFLICT); current = theirsEdit.EndA; 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.BeginA != current && theirsEdit.BeginA != current) { result.add(0, current, Math.Min(oursEdit.BeginA, theirsEdit.BeginA), MergeChunk.ConflictState.NO_CONFLICT); } // set some initial values for the ranges in A and B which we // want to handle int oursBeginB = oursEdit.BeginB; int theirsBeginB = theirsEdit.BeginB; // harmonize the start of the ranges in A and B if (oursEdit.BeginA < theirsEdit.BeginA) { theirsBeginB -= theirsEdit.BeginA - oursEdit.BeginA; } else { oursBeginB -= oursEdit.BeginA - theirsEdit.BeginA; } // 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.EndA > nextTheirsEdit.BeginA) { theirsEdit = nextTheirsEdit; nextTheirsEdit = nextEdit(baseToTheirs); } else if (theirsEdit.EndA > nextOursEdit.BeginA) { oursEdit = nextOursEdit; nextOursEdit = nextEdit(baseToOurs); } else { break; } } // harmonize the end of the ranges in A and B int oursEndB = oursEdit.EndB; int theirsEndB = theirsEdit.EndB; if (oursEdit.EndA < theirsEdit.EndA) { oursEndB += theirsEdit.EndA - oursEdit.EndA; } else { theirsEndB += oursEdit.EndA - theirsEdit.EndA; } // Add the conflict result.add(1, oursBeginB, oursEndB, MergeChunk.ConflictState.FIRST_CONFLICTING_RANGE); result.add(2, theirsBeginB, theirsEndB, MergeChunk.ConflictState.NEXT_CONFLICTING_RANGE); current = Math.Max(oursEdit.EndA, theirsEdit.EndA); 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 void assertDiff(string a, string b, string edits) { MyersDiff diff = new MyersDiff(toCharArray(a), toCharArray(b)); Assert.AreEqual(edits, toString(diff.getEdits())); }