diff --git a/unison-src/transcripts/fix1250.md b/unison-src/transcripts/fix1250.md index 9b014bd9f..5f648976f 100644 --- a/unison-src/transcripts/fix1250.md +++ b/unison-src/transcripts/fix1250.md @@ -3,48 +3,97 @@ .> builtins.merge ``` -Start by creating a new namespace with some definitions in it: +# How merging works + +Suppose we have two branches, `P1` and `P2`, and a subnamespace, `foo`, which we'll refer to with `P1.foo` , `P2.foo`. This doc explains how `merge(P1,P2)` is computed, including the `merge(P1,P2).foo` subnamespace. + +`LCA(P1,P2)` is the lowest common ancestor of `P1` and `P2`. To compute `merge(P1,P2)`, we: + +1. Compute `LCA(P1,P2)` and do a three way merge of that level of the tree, using the algorithm below. What about the children of `P1` and `P2`? Let's just consider a child namespace `foo`. There are a few cases: + 1. `P1` and `P2` both have foo as a child namespace. Then `merge(P1,P2).foo == merge(P1.foo, P2.foo)` + 2. `P1` has `foo` as a child namespace, but `P2` does not (or vice versa). Then we have two subcases: + 1. `LCA(P1,P2)` has no `foo`. This means that `foo` child namespace was added by `P1`. The merged result for the `foo` subnamespace is just `P1.foo`. + 2. `LCA(P1,P2)` does have `foo`. This means that `P2` _deleted_ the `foo` subnamespace. The merged result for the `foo` subnamespace is then `merge(P1.foo, cons empty LCA(P1,P2).foo)`. This does a history-preserving delete of all the definitions that existed at the `LCA` point in history. + 1. Example is like if `P1` added a new definition `foo.bar = 23` after the `LCA`, then `foo.bar` will exist in the merged result, but all the definitions that existed in `foo` at the time of the `LCA` will be deleted in the result. + +### Diff-based 3-way merge algorithm + +Standard 3 way merge algorithm to merge `a` and `b`: + +* Let `lca = LCA(a,b)` +* merged result is: `apply(diff(lca,a) <> diff(lca,b), lca)` + +Relies on some diff combining operation `<>`. ```unison:hide -x.foo.bar = 1 -y.foo.bar = 2 +foo.w = 2 +foo.x = 1 +baz.x = 3 +quux.x = 4 ``` -```ucm:hide -.base> add -``` - -We're going to create two copies of `base`, and add some definitions to each, then merge both back into `base`: - ```ucm -.base> fork .base .base1 -.base> fork .base .base2 +.P0> add +``` + +Now P0 has 3 sub-namespaces. +* foo will be modified definition-wise in each branch +* baz will be deleted in the P2 branch and left alone in P1 +* quux will be deleted in the P2 branch and added to in P1 +* P1 will add a bar sub-namespace + +```ucm +.P0> fork .P0 .P1 +.P0> fork .P0 .P2 ``` ```unison:hide -z.foo.bar = 2483908 +foo.y = 2483908 +bar.y = 383 +quux.y = 333 ``` ```ucm -.base1> add +.P1> add +.P1> delete.term foo.w ``` +We added to `foo`, `bar` and `baz`, and deleted `foo.w`, which should stay deleted in the merge. + ```unison:hide -p.foo.bar = +28348 +foo.z = +28348 ``` ```ucm -.base2> add +.P2> add +.P2> delete.namespace baz +.P2> delete.namespace quux +.P2> find ``` -Now we'll try merging `base1` and `base2` back into `base`. We should see the union of all their definitions in the merged version of `base`. +We added `foo.z`, deleted whole namespaces `baz` and `quux` which should stay +deleted in the merge. -This should succeed and the resulting namespace should have `x`, `y`, `z`, and `p` in it: +Now we'll try merging `P1` and `P2` back into `P0`. We should see the union of all their definitions in the merged version of `P0`. + +This should succeed and the resulting P0 namespace should have `foo`, `bar` +and `quux` namespaces. ```ucm -.base> merge .base1 -.base> ls -.base> merge .base2 -.base> ls -.base> view z.foo.bar p.foo.bar +.P0> merge .P1 +.P0> merge .P2 +.P0> find +.P0> view foo.x foo.y foo.z bar.y quux.y +``` + +```ucm:error +.P0> view foo.w +``` + +```ucm:error +.P0> view baz.x +``` + +```ucm:error +.P0> view quux.x ```