diff-match-patch icon indicating copy to clipboard operation
diff-match-patch copied to clipboard

Reverse/Unpatch capability

Open mgagliardo91 opened this issue 6 years ago • 4 comments

Is there any possibility of reversing a diff/patch, or unpatch, to go backwards?

For example:

LinkedList<DiffMatchPatch.Diff> diffA_B = diffMatchPatch.diffMain(a, b);
LinkedList<DiffMatchPatch.Patch> patchA_B = diffMatchPatch.patchMake(a, diffA_B);
String rebuiltA = (String) diffMatchPatch.patchApply(patchBToA, b)[0];

// unapply
String rebuiltB = (String) diffMatchPath.unpatch(patchBToA, rebuiltA)[0];

From what I can gather, there may not be enough information in the diff/patch to unapply, but I was hoping to see if this was suggested in the past or if there is an alternate approach to reversing a diff/patch without having to create a new diff/patch from B -> A.

Thanks!

mgagliardo91 avatar Aug 08 '18 17:08 mgagliardo91

@mgagliardo91 I was able to achieve this by simply changing DELETE operations in my diff to INSERT operations, and vice versa. In this (Java) example I am able to get back to the original string by applying the reversed patch.

public static void main(String[] args) {
        DiffMatchPatch diffMatchPatch = new DiffMatchPatch();
        String a = "some text";
        String b =  "this is some text that is different";
        LinkedList<DiffMatchPatch.Diff> diffAToB = diffMatchPatch.diffMain(a, b);
        LinkedList<DiffMatchPatch.Diff> diffBToA = reverseDiff(diffAToB);
        LinkedList<DiffMatchPatch.Patch> patchBToA  = diffMatchPatch.patchMake(diffBToA);
        String rebuiltB = (String) diffMatchPatch.patchApply(patchBToA, b)[0]; //unapply
        System.out.println(rebuiltB); //prints out "some text"
    }

    private static LinkedList<DiffMatchPatch.Diff> reverseDiff(LinkedList<DiffMatchPatch.Diff> diffs) {
        for(DiffMatchPatch.Diff diff : diffs) {
            if(diff.operation == DiffMatchPatch.Operation.DELETE) {
                diff.operation = DiffMatchPatch.Operation.INSERT;
            }
            else if(diff.operation == DiffMatchPatch.Operation.INSERT) {
                diff.operation = DiffMatchPatch.Operation.DELETE;
            }
        }
        return diffs;
    }

Hopefully that's helpful to you. It would be nice to have an easier way to do this.

davidhorton avatar Oct 10 '18 21:10 davidhorton

@davidhorton thanks 👍 From what I remember, an operation such as REPLACE lost information of what was being replaced, so I couldn't determine how to get that data back when attempting an unapply. It's been some time now, so that may have been with a different diff library, but I believe it was here.

mgagliardo91 avatar Oct 11 '18 13:10 mgagliardo91

@mgagliardo91 Maybe you can store reverse deltas instead? Instead of diffMain(a, b) you could do diffMain(b, a)

Miki-AG avatar Apr 16 '20 19:04 Miki-AG

I think I've found a more space efficient solution than storing 2 patches. You not only have to reverse all DELETE's to INSERT's and vice versa, but you also need to swap the length1 <-> length2 and start1 <-> start2 of each patch to make it work correctly. I haven't been able to find an example where the reverse patch fails to apply correctly to the second text to get the first one:

const reversePatch = patch => {
  return patch.map(patchObj => ({
    diffs: patchObj.diffs.map(([ op, val ]) => [
      op * -1, // The money maker
      val
    ]),
    start1: patchObj.start2,
    start2: patchObj.start1,
    length1: patchObj.length2,
    length2: patchObj.length1
  }));
};

PGilbertSchmitt avatar Jun 22 '20 16:06 PGilbertSchmitt