delete old merge transcript

This commit is contained in:
Mitchell Rosen 2024-04-24 11:49:03 -04:00
parent 5e563ebf93
commit 240f00dd64
4 changed files with 2483 additions and 2979 deletions

View File

@ -1,147 +1,797 @@
# The `merge` command
# How merging works
The `merge` command merges together two branches in the same project: the current branch (unspecificed), and the target
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`.
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.
Let's see a simple unconflicted merge in action: Alice (us) and Bob (them) add different terms. The merged result
contains both additions.
`LCA(P1,P2)` is the lowest common ancestor of `P1` and `P2`. To compute `merge(P1,P2)`, we:
## Basic merge: two unconflicted adds
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
foo.w = 2
foo.x = 1
baz.x = 3
quux.x = 4
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
.P0.lib> builtins.merge
.P0> add
project/main> branch alice
```
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
foo.y = 2483908
bar.y = 383
quux.y = 333
```unison
foo : Text
foo = "alices foo"
```
```ucm
.P1> add
.> delete.term.verbose P1.foo.w
project/alice> add
project/main> branch bob
```
We added to `foo`, `bar` and `baz`, and deleted `foo.w`, which should stay deleted in the merge.
```unison:hide
foo.z = +28348
```unison
bar : Text
bar = "bobs bar"
```
```ucm
.P2> add
.P2> delete.namespace baz
.P2> delete.namespace quux
.P2> find
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
We added `foo.z`, deleted whole namespaces `baz` and `quux` which should stay
deleted in the merge.
```ucm:hide
.> project.delete project
```
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`.
## Basic merge: two equal adds
This should succeed and the resulting P0 namespace should have `foo`, `bar`
and `quux` namespaces.
If Alice and Bob also happen to add the same thing, that's not a conflict.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
.P0> merge .P1
.P0> merge .P2
.P0> find
.P0> view foo.x foo.y foo.z bar.y quux.y
project/main> branch alice
```
These test that things we expect to be deleted are still deleted.
```unison
foo : Text
foo = "alice and bobs foo"
```
```ucm:error
.> view P0.foo.w
```ucm
project/alice> add
project/main> branch bob
```
```unison
foo : Text
foo = "alice and bobs foo"
bar : Text
bar = "bobs bar"
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Simple update propagation
Updates that occur in one branch are propagated to the other. In this example, Alice updates `foo`, while Bob adds a new
dependent `bar` of (the old) `foo`. When Bob's branch is merged into Alice's, her update to `foo` is propagated to his
`bar`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "new foo"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Update propagation with common dependent
We classify something as an update if its "syntactic hash" - not its normal Unison hash - differs. This allows us to
cleanly merge unconflicted updates that were individually propagated to a common dependent.
Let's see an example. We have `foo`, which depends on `bar` and `baz`. Alice updates `bar` (propagating to `foo`),
and Bob updates `baz` (propagating to `foo`). When we merge their updates, both updates will be reflected in the final
`foo`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo" ++ bar ++ baz
bar : Text
bar = "old bar"
baz : Text
baz = "old baz"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
bar : Text
bar = "alices bar"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "bobs baz"
```
```ucm
project/bob> update
project/alice> merge2 /bob
project/alice> view foo bar baz
```
```ucm:hide
.> project.delete project
```
## Propagating an update to an update
It's also (of course) possible for Alice's update to propagate to one of Bob's updates. In this example, `foo` depends
on `bar` which depends on `baz`. Alice updates `baz`, propagating to `bar` and `foo`, while Bob updates `bar` (to
something that still depends on `foo`), propagating to `baz`. The merged result will have Alice's update to `foo`
incorporated into Bob's updated `bar`, and both updates will propagate to `baz`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo" ++ bar
bar : Text
bar = "old bar" ++ baz
baz : Text
baz = "old baz"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
baz : Text
baz = "alices baz"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = "bobs bar" ++ baz
```
```ucm
project/bob> update
project/alice> merge2 /bob
project/alice> view foo bar baz
```
```ucm:hide
.> project.delete project
```
## Update + delete isn't (currently) a conflict
We don't (yet?) consider update+delete a conflict; in this case, the delete is just ignored.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
```
```ucm
project/alice> update
project/main> branch bob
project/bob> delete.term foo
project/alice> merge2 /bob
project/alice> view foo
```
```ucm:hide
.> project.delete project
```
## No-op merge (Bob = Alice)
If Bob is equals Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## No-op merge (Bob < Alice)
If Bob is behind Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/alice> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Fast-forward merge (Bob > Alice)
If Bob is ahead of Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: someone deleted something
If either Alice or Bob delete something, so long as the other person didn't update it (in which case we ignore the
delete, as explained above), then the delete goes through. This can cause merge failures due to out-of-scope
identifiers. The user may have to do some digging around to find what the deleted name used to refer to.
In this example, Alice deletes `foo`, while Bob adds a new dependent of `foo`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/main> add
project/main> branch alice
project/alice> delete.term foo
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm:error
.> view P0.baz.x
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: type error
It may be possible to cleanly merge Alice's and Bob's changes together, yet the resulting namespace doesn't typecheck.
In this example, Alice updates a `Text` to a `Nat`, while Bob adds a new dependent of the `Text`. Upon merging,
propagating Alice's update to Bob's dependent fails to typecheck.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 100
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm:error
.> view P0.quux.x
project/bob> update
project/alice> merge2 /bob
```
### Corner cases
```ucm:hide
.> project.delete project
```
We're going to now do two concurrent edits with an update on one side to make sure 3-way merge behaves as expected.
## Merge failure: simple term conflict
Here's the starting namespace, which will be the LCA.
Alice and Bob may disagree about the definition of a term. In this case, the conflicted term and all of its dependents
are presented to the user to resolve.
```unison:hide
a = 1
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
f = (x y -> y) a "woot!"
```unison
foo : Text
foo = "old foo"
bar : Text
bar = "old bar"
```
```ucm
.c1.lib> builtins.merge
.c1> add
.> fork c1 c1a
.> fork c1 c1b
project/main> add
project/main> branch alice
```
```unison:hide
oog.b = 230948
oog.c = 339249
```
```unison
foo : Text
foo = "alices foo"
In `c1a`, we add new definitions, `b` and `c`.
bar : Text
bar = "alices bar"
```ucm
.c1a> add
```
In `c1b`, we update the definition `a`, which is used by `f`.
```unison:hide
a = "hello world!"
qux : Text
qux = "alices qux depends on alices foo" ++ foo
```
```ucm
.c1b> update.old
project/alice> update
project/main> branch bob
```
Now merging `c1b` into `c1a` should result in the updated version of `a` and `f`, and the new definitions `b` and `c`:
```unison
foo : Text
foo = "bobs foo"
baz : Text
baz = "bobs baz"
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm
.> merge c1b c1a
.c1a> todo .c1b.patch
.c1a> find
.c1a> view 1-4
project/merge-bob-into-alice> view bar baz
```
```ucm:hide
.> project.delete project
```
## Merge failure: simple type conflict
Ditto for types; if the hashes don't match, it's a conflict. In this example, Alice and Bob do different things to the
same constructor. However, any explicit changes to the same type will result in a conflict, including changes that could
concievably be merged (e.g. Alice and Bob both add a new constructor, or edit different constructors).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo = MkFoo Nat
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo = MkFoo Nat Nat
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo = MkFoo Nat Text
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: type-update + constructor-rename conflict
Renaming a constructor is modeled as an update, so if Alice updates a type and Bob renames one of its constructors but
doesn't change its hash, that's still a conflict.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo = Baz Nat | Qux Text
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo = Baz Nat Nat | Qux Text
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo = Baz Nat | BobQux Text
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Precondition violations
Let's see a number of merge precondition violations. These are conditions under which we can't perform a merge, and the
user will have to fix up the namespace(s) manually before attempting to merge again.
### Conflicted aliases
If `foo` and `bar` are aliases in the nearest common ancestor, but not in Alice's branch, then we don't know whether to
update Bob's dependents to Alice's `foo` or Alice's `bar` (and vice-versa).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Nat
foo = 100
bar : Nat
bar = 100
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 200
bar : Nat
bar = 300
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "baz"
```
```ucm:error
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
### Conflict involving builtin
We don't have a way of rendering a builtin in a scratch file, where users resolve merge conflicts. Thus, if there is a
conflict involving a builtin, we can't perform a merge.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
project/main> alias.type builtin.Nat MyNat
project/topic>
```
```unison
unique type MyNat = MyNat Nat
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Constructor alias
Each naming of a decl may not have more than one name for each constructor underneath the decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> alias.term Foo.Bar Foo.some.other.Alias
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Missing constructor name
Each naming of a decl may not have zero names for a constructor underneath the decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> delete.term Foo.Bar
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Nested decl alias
Decl aliases must be disjoint in a namespace: one cannot contain another.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
structural type A = B Nat | C Nat Nat
structural type A.inner.X = Y Nat | Z Nat Nat
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Stray constructor alias
Each naming of a constructor must be underneath its decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> alias.term Foo.Bar AliasOutsideFooNamespace
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Term or type in `lib`
A bit of an odd one, but we have a convention that `lib` contains only namespaces, which are dependencies. Thus, the
`lib` namespace can always merge cleanly, so long as there aren't stray terms or types in it.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
lib.foo : Nat
lib.foo = 1
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```

File diff suppressed because it is too large Load Diff

View File

@ -1,797 +0,0 @@
# The `merge` command
The `merge` command merges together two branches in the same project: the current branch (unspecificed), and the target
branch. For example, to merge `topic` into `main`, switch to `main` and run `merge topic`.
Let's see a simple unconflicted merge in action: Alice (us) and Bob (them) add different terms. The merged result
contains both additions.
## Basic merge: two unconflicted adds
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
```
```ucm
project/alice> add
project/main> branch bob
```
```unison
bar : Text
bar = "bobs bar"
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Basic merge: two equal adds
If Alice and Bob also happen to add the same thing, that's not a conflict.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
```
```unison
foo : Text
foo = "alice and bobs foo"
```
```ucm
project/alice> add
project/main> branch bob
```
```unison
foo : Text
foo = "alice and bobs foo"
bar : Text
bar = "bobs bar"
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Simple update propagation
Updates that occur in one branch are propagated to the other. In this example, Alice updates `foo`, while Bob adds a new
dependent `bar` of (the old) `foo`. When Bob's branch is merged into Alice's, her update to `foo` is propagated to his
`bar`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "new foo"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm
project/bob> add
project/alice> merge2 /bob
project/alice> view foo bar
```
```ucm:hide
.> project.delete project
```
## Update propagation with common dependent
We classify something as an update if its "syntactic hash" - not its normal Unison hash - differs. This allows us to
cleanly merge unconflicted updates that were individually propagated to a common dependent.
Let's see an example. We have `foo`, which depends on `bar` and `baz`. Alice updates `bar` (propagating to `foo`),
and Bob updates `baz` (propagating to `foo`). When we merge their updates, both updates will be reflected in the final
`foo`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo" ++ bar ++ baz
bar : Text
bar = "old bar"
baz : Text
baz = "old baz"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
bar : Text
bar = "alices bar"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "bobs baz"
```
```ucm
project/bob> update
project/alice> merge2 /bob
project/alice> view foo bar baz
```
```ucm:hide
.> project.delete project
```
## Propagating an update to an update
It's also (of course) possible for Alice's update to propagate to one of Bob's updates. In this example, `foo` depends
on `bar` which depends on `baz`. Alice updates `baz`, propagating to `bar` and `foo`, while Bob updates `bar` (to
something that still depends on `foo`), propagating to `baz`. The merged result will have Alice's update to `foo`
incorporated into Bob's updated `bar`, and both updates will propagate to `baz`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo" ++ bar
bar : Text
bar = "old bar" ++ baz
baz : Text
baz = "old baz"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
baz : Text
baz = "alices baz"
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = "bobs bar" ++ baz
```
```ucm
project/bob> update
project/alice> merge2 /bob
project/alice> view foo bar baz
```
```ucm:hide
.> project.delete project
```
## Update + delete isn't (currently) a conflict
We don't (yet?) consider update+delete a conflict; in this case, the delete is just ignored.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
```
```ucm
project/alice> update
project/main> branch bob
project/bob> delete.term foo
project/alice> merge2 /bob
project/alice> view foo
```
```ucm:hide
.> project.delete project
```
## No-op merge (Bob = Alice)
If Bob is equals Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## No-op merge (Bob < Alice)
If Bob is behind Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/alice> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Fast-forward merge (Bob > Alice)
If Bob is ahead of Alice, then merging Bob into Alice looks like this.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch alice
project/main> branch bob
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: someone deleted something
If either Alice or Bob delete something, so long as the other person didn't update it (in which case we ignore the
delete, as explained above), then the delete goes through. This can cause merge failures due to out-of-scope
identifiers. The user may have to do some digging around to find what the deleted name used to refer to.
In this example, Alice deletes `foo`, while Bob adds a new dependent of `foo`.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/main> add
project/main> branch alice
project/alice> delete.term foo
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm:error
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: type error
It may be possible to cleanly merge Alice's and Bob's changes together, yet the resulting namespace doesn't typecheck.
In this example, Alice updates a `Text` to a `Nat`, while Bob adds a new dependent of the `Text`. Upon merging,
propagating Alice's update to Bob's dependent fails to typecheck.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "foo"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 100
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
bar : Text
bar = foo ++ foo
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: simple term conflict
Alice and Bob may disagree about the definition of a term. In this case, the conflicted term and all of its dependents
are presented to the user to resolve.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Text
foo = "old foo"
bar : Text
bar = "old bar"
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Text
foo = "alices foo"
bar : Text
bar = "alices bar"
qux : Text
qux = "alices qux depends on alices foo" ++ foo
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
foo : Text
foo = "bobs foo"
baz : Text
baz = "bobs baz"
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm
project/merge-bob-into-alice> view bar baz
```
```ucm:hide
.> project.delete project
```
## Merge failure: simple type conflict
Ditto for types; if the hashes don't match, it's a conflict. In this example, Alice and Bob do different things to the
same constructor. However, any explicit changes to the same type will result in a conflict, including changes that could
concievably be merged (e.g. Alice and Bob both add a new constructor, or edit different constructors).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo = MkFoo Nat
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo = MkFoo Nat Nat
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo = MkFoo Nat Text
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Merge failure: type-update + constructor-rename conflict
Renaming a constructor is modeled as an update, so if Alice updates a type and Bob renames one of its constructors but
doesn't change its hash, that's still a conflict.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
unique type Foo = Baz Nat | Qux Text
```
```ucm
project/main> add
project/main> branch alice
```
```unison
unique type Foo = Baz Nat Nat | Qux Text
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
unique type Foo = Baz Nat | BobQux Text
```
```ucm:error
project/bob> update
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
## Precondition violations
Let's see a number of merge precondition violations. These are conditions under which we can't perform a merge, and the
user will have to fix up the namespace(s) manually before attempting to merge again.
### Conflicted aliases
If `foo` and `bar` are aliases in the nearest common ancestor, but not in Alice's branch, then we don't know whether to
update Bob's dependents to Alice's `foo` or Alice's `bar` (and vice-versa).
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```unison
foo : Nat
foo = 100
bar : Nat
bar = 100
```
```ucm
project/main> add
project/main> branch alice
```
```unison
foo : Nat
foo = 200
bar : Nat
bar = 300
```
```ucm
project/alice> update
project/main> branch bob
```
```unison
baz : Text
baz = "baz"
```
```ucm:error
project/bob> add
project/alice> merge2 /bob
```
```ucm:hide
.> project.delete project
```
### Conflict involving builtin
We don't have a way of rendering a builtin in a scratch file, where users resolve merge conflicts. Thus, if there is a
conflict involving a builtin, we can't perform a merge.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
project/main> alias.type builtin.Nat MyNat
project/topic>
```
```unison
unique type MyNat = MyNat Nat
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Constructor alias
Each naming of a decl may not have more than one name for each constructor underneath the decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> alias.term Foo.Bar Foo.some.other.Alias
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Missing constructor name
Each naming of a decl may not have zero names for a constructor underneath the decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> delete.term Foo.Bar
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Nested decl alias
Decl aliases must be disjoint in a namespace: one cannot contain another.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
structural type A = B Nat | C Nat Nat
structural type A.inner.X = Y Nat | Z Nat Nat
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Stray constructor alias
Each naming of a constructor must be underneath its decl's namespace.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
unique type Foo = Bar
```
```ucm:error
project/topic> add
project/topic> alias.term Foo.Bar AliasOutsideFooNamespace
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```
### Term or type in `lib`
A bit of an odd one, but we have a convention that `lib` contains only namespaces, which are dependencies. Thus, the
`lib` namespace can always merge cleanly, so long as there aren't stray terms or types in it.
```ucm:hide
.> project.create-empty project
project/main> builtins.mergeio
```
```ucm
project/main> branch topic
```
```unison
lib.foo : Nat
lib.foo = 1
```
```ucm:error
project/topic> add
project/main> merge2 /topic
```
```ucm:hide
.> project.delete project
```

File diff suppressed because it is too large Load Diff