3.6 KiB
.> builtins.merge
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:
- Compute
LCA(P1,P2)
and do a three way merge of that level of the tree, using the algorithm below. What about the children ofP1
andP2
? Let's just consider a child namespacefoo
. There are a few cases:P1
andP2
both have foo as a child namespace. Thenmerge(P1,P2).foo == merge(P1.foo, P2.foo)
P1
hasfoo
as a child namespace, butP2
does not (or vice versa). Then we have two subcases:LCA(P1,P2)
has nofoo
. This means thatfoo
child namespace was added byP1
. The merged result for thefoo
subnamespace is justP1.foo
.LCA(P1,P2)
does havefoo
. This means thatP2
deleted thefoo
subnamespace. The merged result for thefoo
subnamespace is thenmerge(P1.foo, cons empty LCA(P1,P2).foo)
. This does a history-preserving delete of all the definitions that existed at theLCA
point in history.- Example is like if
P1
added a new definitionfoo.bar = 23
after theLCA
, thenfoo.bar
will exist in the merged result, but all the definitions that existed infoo
at the time of theLCA
will be deleted in the result.
- Example is like if
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 <>
.
foo.w = 2
foo.x = 1
baz.x = 3
quux.x = 4
.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
.P0> fork .P0 .P1
.P0> fork .P0 .P2
foo.y = 2483908
bar.y = 383
quux.y = 333
.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.
foo.z = +28348
.P2> add
.P2> delete.namespace baz
.P2> delete.namespace quux
.P2> find
We added foo.z
, deleted whole namespaces baz
and quux
which should stay
deleted in the merge.
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.
.P0> merge .P1
.P0> merge .P2
.P0> find
.P0> view foo.x foo.y foo.z bar.y quux.y
These test that things we expect to be deleted are still deleted.
.> view P0.foo.w
.> view P0.baz.x
.> view P0.quux.x
Corner cases
We're going to now do two concurrent edits with an update on one side to make sure 3-way merge behaves as expected.
Here's the starting namespace, which will be the LCA.
a = 1
f = (x y -> y) a "woot!"
.c1> add
.> fork c1 c1a
.> fork c1 c1b
oog.b = 230948
oog.c = 339249
In c1a
, we add new definitions, b
and c
.
.c1a> add
In c1b
, we update the definition a
, which is used by f
.
a = "hello world!"
.c1b> update
Now merging c1b
into c1a
should result in the updated version of a
and f
, and the new definitions b
and c
:
.> merge c1b c1a
.c1a> todo .c1b.patch
.c1a> find
.c1a> view 1-4