unison/unison-src/transcripts/merge.md
2024-09-05 10:44:48 -04:00

1750 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`:
```ucm
scratch/main> help merge
scratch/main> help merge.commit
```
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
scratch/main> builtins.mergeio lib.builtins
```
```ucm:hide
scratch/main> branch alice
```
Alice's adds:
```unison:hide
foo : Text
foo = "alices foo"
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's adds:
```unison:hide
bar : Text
bar = "bobs bar"
```
```ucm:hide
scratch/bob> add
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo bar
```
```ucm:hide
scratch/main> project.delete scratch
```
## Basic merge: two identical adds
If Alice and Bob also happen to add the same definition, that's not a conflict.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
scratch/main> branch alice
```
Alice's adds:
```unison:hide
foo : Text
foo = "alice and bobs foo"
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's adds:
```unison:hide
foo : Text
foo = "alice and bobs foo"
bar : Text
bar = "bobs bar"
```
```ucm:hide
scratch/bob> add
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo bar
```
```ucm:hide
scratch/main> project.delete scratch
```
## 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 original `foo`. When Bob's branch is merged into Alice's, her update to `foo` is propagated to his `bar`.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "old foo"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's updates:
```unison:hide
foo : Text
foo = "new foo"
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's adds:
```unison:hide
bar : Text
bar = foo ++ " - " ++ foo
```
```ucm
scratch/bob> display bar
```
```ucm:hide
scratch/bob> add
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo bar
scratch/alice> display bar
```
```ucm:hide
scratch/main> project.delete scratch
```
## Update propagation with common dependent
We classify something as an update if its "syntactic hash"—not its normal Unison hash—differs from the original definition. 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
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "foo" ++ " - " ++ bar ++ " - " ++ baz
bar : Text
bar = "old bar"
baz : Text
baz = "old baz"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's updates:
```unison:hide
bar : Text
bar = "alices bar"
```
```ucm:hide
scratch/alice> update
```
```ucm
scratch/alice> display foo
```
```ucm:hide
scratch/main> branch bob
```
Bob's updates:
```unison:hide
baz : Text
baz = "bobs baz"
```
```ucm:hide
scratch/bob> update
```
```ucm
scratch/bob> display foo
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo bar baz
scratch/alice> display foo
```
```ucm:hide
scratch/main> project.delete scratch
```
## Propagating an update to an update
Of course, it's also 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
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "old foo" ++ " - " ++ bar
bar : Text
bar = "old bar" ++ " - " ++ baz
baz : Text
baz = "old baz"
```
```ucm:hide
scratch/main> add
```
```ucm
scratch/main> display foo
```
```ucm:hide
scratch/main> branch alice
```
Alice's updates:
```unison:hide
baz : Text
baz = "alices baz"
```
```ucm:hide
scratch/alice> update
```
```ucm
scratch/alice> display foo
```
```ucm:hide
scratch/main> branch bob
```
Bob's updates:
```unison:hide
bar : Text
bar = "bobs bar" ++ " - " ++ baz
```
```ucm:hide
scratch/bob> update
```
```ucm
scratch/bob> display foo
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo bar baz
scratch/alice> display foo
```
```ucm:hide
scratch/main> project.delete scratch
```
## Update + delete isn't (currently) a conflict
We don't currently consider "update + delete" a conflict like Git does. In this situation, the delete is just ignored, allowing the update to proceed.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "old foo"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's updates:
```unison:hide
foo : Text
foo = "alices foo"
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's changes:
```ucm
scratch/bob> delete.term foo
```
Merge result:
```ucm
scratch/alice> merge /bob
scratch/alice> view foo
```
```ucm:hide
scratch/main> project.delete scratch
```
In a future version, we'd like to give the user a warning at least.
## Library dependencies don't create merge conflicts
Library dependencies don't cause merge conflicts, the library dependencies are just unioned together. If two library dependencies have the same name but different namespace hashes, then the merge algorithm makes up two fresh names.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Alice's adds:
```ucm:hide
scratch/main> branch alice
```
```unison:hide
lib.alice.foo : Nat
lib.alice.foo = 17
lib.bothSame.bar : Nat
lib.bothSame.bar = 18
lib.bothDifferent.baz : Nat
lib.bothDifferent.baz = 19
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's adds:
```unison:hide
lib.bob.foo : Nat
lib.bob.foo = 20
lib.bothSame.bar : Nat
lib.bothSame.bar = 18
lib.bothDifferent.baz : Nat
lib.bothDifferent.baz = 21
```
```ucm:hide
scratch/bob> add
```
Merge result:
```ucm
scratch/alice> merge bob
scratch/alice> view foo bar baz
```
```ucm:hide
scratch/main> project.delete scratch
```
## No-op merge (Bob = Alice)
If Bob is equals Alice, then merging Bob into Alice looks like this.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm
scratch/main> branch alice
scratch/main> branch bob
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## No-op merge (Bob < Alice)
If Bob is behind Alice, then merging Bob into Alice looks like this.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm
scratch/main> branch alice
scratch/main> branch bob
```
Alice's addition:
```unison:hide
foo : Text
foo = "foo"
```
```ucm
scratch/alice> add
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Fast-forward merge (Bob > Alice)
If Bob is ahead of Alice, then merging Bob into Alice looks like this.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm
scratch/main> branch alice
scratch/main> branch bob
```
Bob's addition:
```unison:hide
foo : Text
foo = "foo"
```
```ucm
scratch/bob> add
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## No-op merge: merge empty namespace into empty namespace
```ucm
scratch/main> branch topic
scratch/main> merge /topic
```
```ucm:hide
scratch/main> project.delete scratch
```
## 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, and the user may have to do some digging around to find what the deleted name used to refer to. In a future version, we would emit a \[better\] warning at least.
In this example, Alice deletes `foo`, while Bob adds a new dependent of `foo`.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "foo"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's delete:
```ucm
scratch/alice> delete.term foo
```
```ucm:hide
scratch/main> branch bob
```
Bob's new code that depends on `foo`:
```unison:hide
bar : Text
bar = foo ++ " - " ++ foo
```
```ucm:error
scratch/bob> add
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge failure: type error
It may be Alice's and Bob's changes merge together cleanly in the sense that there's no textual conflicts, 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 causes a typechecking failure.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "foo"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's update:
```unison:hide
foo : Nat
foo = 100
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's new definition:
```unison:hide
bar : Text
bar = foo ++ " - " ++ foo
```
```ucm:hide
scratch/bob> update
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## 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
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "old foo"
bar : Text
bar = "old bar"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's changes:
```unison:hide
foo : Text
foo = "alices foo"
bar : Text
bar = "alices bar"
qux : Text
qux = "alices qux depends on alices foo" ++ foo
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's changes:
```unison:hide
foo : Text
foo = "bobs foo"
baz : Text
baz = "bobs baz"
```
```ucm:hide
scratch/bob> update
```
```ucm:error
scratch/alice> merge /bob
```
```ucm
scratch/merge-bob-into-alice> view bar baz
```
```ucm:hide
scratch/main> project.delete scratch
```
## 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
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
unique type Foo = MkFoo Nat
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's changes:
```unison:hide
unique type Foo = MkFoo Nat Nat
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's changes:
```unison:hide
unique type Foo = MkFoo Nat Text
```
```ucm:hide
scratch/bob> update
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge failure: type-update + constructor-rename conflict
We model the renaming of a type's constructor as an update, so if Alice updates a type and Bob renames one of its constructors (even without changing its structure), we consider it a conflict.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
unique type Foo = Baz Nat | Qux Text
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's changes `Baz Nat` to `Baz Nat Nat`
```unison:hide
unique type Foo = Baz Nat Nat | Qux Text
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's renames `Qux` to `BobQux`:
```ucm
scratch/bob> move.term Foo.Qux Foo.BobQux
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge failure: constructor-rename conflict
Here is another example demonstrating that constructor renames are modeled as updates.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
unique type Foo = Baz Nat | Qux Text
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's rename:
```ucm
scratch/alice> move.term Foo.Baz Foo.Alice
```
```ucm:hide
scratch/main> branch bob
```
Bob's rename:
```ucm
scratch/bob> move.term Foo.Qux Foo.Bob
```
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge failure: non-constructor/constructor conflict
A constructor on one side can conflict with a regular term definition on the other.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm:hide
scratch/main> branch alice
```
Alice's additions:
```unison:hide
my.cool.thing : Nat
my.cool.thing = 17
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's additions:
```unison:hide
unique ability my.cool where
thing : Nat -> Nat
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge failure: type/type conflict with term/constructor conflict
Here's a subtle situation where a new type is added on each side of the merge, and an existing term is replaced with a constructor of one of the types.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
Foo.Bar : Nat
Foo.Bar = 17
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice adds this type `Foo` with constructor `Foo.Alice`:
```unison:hide
unique type Foo = Alice Nat
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob adds the type `Foo` with constructor `Foo.Bar`, replacing the original `Foo.Bar` term:
```ucm
scratch/bob> delete.term Foo.Bar
```
```unison:hide
unique type Foo = Bar Nat Nat
```
```ucm:hide
scratch/bob> add
```
These won't cleanly merge.
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
Here's a more involved example that demonstrates the same idea.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
In the LCA, we have a type with two constructors, and some term.
```unison:hide
unique type Foo
= Bar.Baz Nat
| Bar.Qux Nat Nat
Foo.Bar.Hello : Nat
Foo.Bar.Hello = 17
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice deletes this type entirely, and repurposes its constructor names for other terms. She also updates the term.
```ucm:hide
scratch/alice> delete.type Foo
scratch/alice> delete.term Foo.Bar.Baz
scratch/alice> delete.term Foo.Bar.Qux
```
```unison:hide:all
Foo.Bar.Baz : Nat
Foo.Bar.Baz = 100
Foo.Bar.Qux : Nat
Foo.Bar.Qux = 200
Foo.Bar.Hello : Nat
Foo.Bar.Hello = 18
```
```ucm:hide
scratch/alice> update
```
```ucm
scratch/alice> view Foo.Bar.Baz Foo.Bar.Qux Foo.Bar.Hello
```
Bob, meanwhile, first deletes the term, then sort of deletes the type and re-adds it under another name, but one constructor's fully qualified names doesn't actually change. The other constructor reuses the name of the deleted term.
```ucm:hide
scratch/main> branch bob
scratch/bob> delete.term Foo.Bar.Hello
scratch/bob> move.type Foo Foo.Bar
scratch/bob> move.term Foo.Bar.Qux Foo.Bar.Hello
```
```ucm
scratch/bob> view Foo.Bar
```
At this point, Bob and alice have both updated the name `Foo.Bar.Hello` in different ways, so that's a conflict. Therefore, Bob's entire type (`Foo.Bar` with constructors `Foo.Bar.Baz` and `Foo.Bar.Hello`) gets rendered into the scratch file.
Notably, Alice's "unconflicted" update on the name "Foo.Bar.Baz" (because she changed its hash and Bob didn't touch it) is nonetheless considered conflicted with Bob's "Foo.Bar.Baz".
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Merge algorithm quirk: add/add unique types
Currently, two unique types created by Alice and Bob will be considered in conflict, even if they "look the same".
The result may be confusing to a user a file containing two identical-looking copies of a unique type is rendered,
which is a parse error.
We will resolve this situation automatically in a future version.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm:hide
scratch/main> branch alice
```
Alice's additions:
```unison:hide
unique type Foo = Bar
alice : Foo -> Nat
alice _ = 18
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's additions:
```unison:hide
unique type Foo = Bar
bob : Foo -> Nat
bob _ = 19
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## `merge.commit` example (success)
After merge conflicts are resolved, you can use `merge.commit` rather than `switch` + `merge` + `branch.delete` to
"commit" your changes.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Text
foo = "old foo"
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's changes:
```unison:hide
foo : Text
foo = "alices foo"
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's changes:
```unison:hide
foo : Text
foo = "bobs foo"
```
Attempt to merge:
```ucm:hide
scratch/bob> update
```
```ucm:error
scratch/alice> merge /bob
```
Resolve conflicts and commit:
```unison
foo : Text
foo = "alice and bobs foo"
```
```ucm
scratch/merge-bob-into-alice> update
scratch/merge-bob-into-alice> merge.commit
scratch/alice> view foo
scratch/alice> branches
```
```ucm:hide
scratch/main> project.delete scratch
```
## `merge.commit` example (failure)
`merge.commit` can only be run on a "merge branch".
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm
scratch/main> branch topic
```
```ucm:error
scratch/topic> merge.commit
```
```ucm:hide
scratch/main> project.delete scratch
```
## Precondition violations
There are a number of 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
scratch/main> builtins.mergeio lib.builtins
```
Original branch:
```unison:hide
foo : Nat
foo = 100
bar : Nat
bar = 100
```
```ucm:hide
scratch/main> add
scratch/main> branch alice
```
Alice's updates:
```unison:hide
foo : Nat
foo = 200
bar : Nat
bar = 300
```
```ucm:hide
scratch/alice> update
scratch/main> branch bob
```
Bob's addition:
```unison:hide
baz : Text
baz = "baz"
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### 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.
One way to fix this in the future would be to introduce a syntax for defining aliases in the scratch file.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm:hide
scratch/main> branch alice
```
Alice's branch:
```ucm
scratch/alice> alias.type lib.builtins.Nat MyNat
```
Bob's branch:
```ucm:hide
scratch/main> branch bob
```
```unison:hide
unique type MyNat = MyNat Nat
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Constructor alias
Each naming of a decl may not have more than one name for each constructor, within the decl's namespace.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```ucm:hide
scratch/main> branch alice
```
Alice's branch:
```unison:hide
unique type Foo = Bar
```
```ucm:hide
scratch/alice> add
```
```ucm
scratch/alice> alias.term Foo.Bar Foo.some.other.Alias
```
Bob's branch:
```ucm:hide
scratch/main> branch bob
```
```unison:hide
bob : Nat
bob = 100
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Missing constructor name
Each naming of a decl must have a name for each constructor, within the decl's namespace.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Alice's branch:
```ucm:hide
scratch/main> branch alice
```
```unison:hide
unique type Foo = Bar
```
```ucm:hide
scratch/alice> add
```
```ucm
scratch/alice> delete.term Foo.Bar
```
Bob's branch:
```ucm:hide
scratch/main> branch /bob
```
```unison:hide
bob : Nat
bob = 100
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Nested decl alias
A decl cannot be aliased within the namespace of another of its aliased.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Alice's branch:
```ucm:hide
scratch/main> branch alice
```
```unison:hide
structural type A = B Nat | C Nat Nat
structural type A.inner.X = Y Nat | Z Nat Nat
```
```ucm:hide
scratch/alice> add
```
```ucm
scratch/alice> names A
```
Bob's branch:
```ucm:hide
scratch/main> branch bob
```
```unison:hide
bob : Nat
bob = 100
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Stray constructor alias
Constructors may only exist within the corresponding decl's namespace.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Alice's branch:
```ucm:hide
scratch/main> branch alice
```
```unison:hide:all
unique type Foo = Bar
```
```ucm
scratch/alice> add
scratch/alice> alias.term Foo.Bar AliasOutsideFooNamespace
```
Bob's branch:
```ucm:hide
scratch/main> branch bob
```
```unison:hide:all
bob : Nat
bob = 101
```
```ucm
scratch/bob> add
```
```ucm:error
scratch/alice> merge bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Term or type in `lib`
By convention, `lib` can only namespaces; each of these represents a library dependencies. Individual terms and types are not allowed at the top level of `lib`.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
Alice's branch:
```ucm:hide
scratch/main> branch alice
```
```unison:hide
lib.foo : Nat
lib.foo = 1
```
```ucm:hide
scratch/alice> add
scratch/main> branch bob
```
Bob's branch:
```unison:hide
bob : Nat
bob = 100
```
```ucm:hide
scratch/bob> add
```
```ucm:error
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## LCA precondition violations
The LCA is not subject to most precondition violations, which is good, because the user can't easily manipulate it!
Here's an example. We'll delete a constructor name from the LCA and still be able to merge Alice and Bob's stuff
together.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
LCA:
```unison
structural type Foo = Bar Nat | Baz Nat Nat
```
```ucm
scratch/main> add
scratch/main> delete.term Foo.Baz
```
Alice's branch:
```ucm
scratch/main> branch alice
scratch/alice> delete.type Foo
scratch/alice> delete.term Foo.Bar
```
```unison
alice : Nat
alice = 100
```
```ucm
scratch/alice> add
```
Bob's branch:
```ucm
scratch/main> branch bob
scratch/bob> delete.type Foo
scratch/bob> delete.term Foo.Bar
```
```unison
bob : Nat
bob = 101
```
```ucm
scratch/bob> add
```
Now we merge:
```ucm
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
## Regression tests
### Delete one alias and update the other
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```unison
foo = 17
bar = 17
```
```ucm
scratch/main> add
scratch/main> branch alice
scratch/alice> delete.term bar
```
```unison
foo = 18
```
```ucm
scratch/alice> update
scratch/main> branch bob
```
```unison
bob = 101
```
```ucm
scratch/bob> add
```
```ucm
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Delete a constructor
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```unison
type Foo = Bar | Baz
```
```ucm
scratch/main> add
scratch/main> branch topic
```
```unison
boop = "boop"
```
```ucm
scratch/topic> add
```
```unison
type Foo = Bar
```
```ucm
scratch/main> update
```
```ucm
scratch/main> merge topic
scratch/main> view Foo
```
```ucm:hide
scratch/main> project.delete scratch
```
### Dependent that doesn't need to be in the file
This test demonstrates a bug.
```ucm:hide
scratch/alice> builtins.mergeio lib.builtins
```
In the LCA, we have `foo` with dependent `bar`, and `baz`.
```unison
foo : Nat
foo = 17
bar : Nat
bar = foo + foo
baz : Text
baz = "lca"
```
```ucm
scratch/alice> add
scratch/alice> branch bob
```
On Bob, we update `baz` to "bob".
```unison
baz : Text
baz = "bob"
```
```ucm
scratch/bob> update
```
On Alice, we update `baz` to "alice" (conflict), but also update `foo` (unconflicted), which propagates to `bar`.
```unison
foo : Nat
foo = 18
baz : Text
baz = "alice"
```
```ucm
scratch/alice> update
```
When we try to merge Bob into Alice, we should see both versions of `baz`, with Alice's unconflicted `foo` and `bar` in
the underlying namespace.
```ucm:error
scratch/alice> merge /bob
```
But `bar` was put into the scratch file instead.
```ucm:hide
scratch/main> project.delete scratch
```
### Merge loop test
This tests for regressions of https://github.com/unisonweb/unison/issues/1276 where trivial merges cause loops in the
history.
Let's make three identical namespaces with different histories:
```unison
a = 1
```
```ucm
scratch/alice> add
```
```unison
b = 2
```
```ucm
scratch/alice> add
```
```unison
b = 2
```
```ucm
scratch/bob> add
```
```unison
a = 1
```
```ucm
scratch/bob> add
```
```unison
a = 1
b = 2
```
```ucm
scratch/carol> add
scratch/bob> merge /alice
scratch/carol> merge /bob
scratch/carol> history
```
```ucm:hide
scratch/main> project.delete scratch
```
### Variables named `_`
This test demonstrates a change in syntactic hashing that fixed a bug due to auto-generated variable names for ignored
results.
```ucm:hide
scratch/alice> builtins.mergeio lib.builtins
```
```unison
ignore : a -> ()
ignore _ = ()
foo : Nat
foo = 18
bar : Nat
bar =
ignore "hi"
foo + foo
```
```ucm
scratch/alice> add
scratch/alice> branch bob
```
```unison
bar : Nat
bar =
ignore "hi"
foo + foo + foo
```
```ucm
scratch/bob> update
```
Previously, this update to `foo` would also cause a "real update" on `bar`, its dependent. Now it doesn't, so the merge
will succeed.
```unison
foo : Nat
foo = 19
```
```ucm
scratch/alice> update
```
```ucm
scratch/alice> merge /bob
```
```ucm:hide
scratch/main> project.delete scratch
```
### Unique type GUID reuse
Previously, a merge branch would not include any dependents in the namespace, but that resulted in dependent unique
types' GUIDs being regenerated.
```ucm:hide
scratch/main> builtins.mergeio lib.builtins
```
```unison
type Foo = Lca
type Bar = MkBar Foo
```
```ucm
scratch/main> add
scratch/main> branch alice
scratch/alice> move.term Foo.Lca Foo.Alice
scratch/main> branch bob
scratch/bob> move.term Foo.Lca Foo.Bob
```
```ucm:error
scratch/alice> merge /bob
```
```ucm
scratch/merge-bob-into-alice>
```
```unison
type Foo = Merged
type Bar = MkBar Foo
```
```ucm
scratch/merge-bob-into-alice> update
scratch/merge-bob-into-alice> names Bar
scratch/alice> names Bar
```
```ucm:hide
scratch/main> project.delete scratch
```