unison/unison-src/transcripts/merge.output.md
2024-08-13 12:46:42 -04:00

42 KiB
Raw Permalink Blame History

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:

scratch/main> help merge

  merge
  `merge /branch` merges `branch` into the current branch

scratch/main> help merge.commit

  merge.commit (or commit.merge)
  `merge.commit` merges a temporary branch created by the
  `merge` command back into its parent branch, and removes the
  temporary branch.
  
  For example, if you've done `merge topic` from main, then
  `merge.commit` is equivalent to doing
  
    * switch /main
    * merge /merge-topic-into-main
    * delete.branch /merge-topic-into-main

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

Alice's adds:

foo : Text
foo = "alices foo"

Bob's adds:

bar : Text
bar = "bobs bar"

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo bar

  bar : Text
  bar = "bobs bar"
  
  foo : Text
  foo = "alices foo"

Basic merge: two identical adds

If Alice and Bob also happen to add the same definition, that's not a conflict.

Alice's adds:

foo : Text
foo = "alice and bobs foo"

Bob's adds:

foo : Text
foo = "alice and bobs foo"

bar : Text
bar = "bobs bar"

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo bar

  bar : Text
  bar = "bobs bar"
  
  foo : Text
  foo = "alice and bobs foo"

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.

Original branch:

foo : Text
foo = "old foo"

Alice's updates:

foo : Text
foo = "new foo"

Bob's adds:

bar : Text
bar = foo ++ " - " ++ foo
project/bob> display bar

  "old foo - old foo"

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo bar

  bar : Text
  bar =
    use Text ++
    foo ++ " - " ++ foo
  
  foo : Text
  foo = "new foo"

project/alice> display bar

  "old foo - old foo"

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.

Original branch:

foo : Text
foo = "foo" ++ " - " ++ bar ++ " - " ++ baz

bar : Text
bar = "old bar"

baz : Text
baz = "old baz"

Alice's updates:

bar : Text
bar = "alices bar"
project/alice> display foo

  "foo - alices bar - old baz"

Bob's updates:

baz : Text
baz = "bobs baz"
project/bob> display foo

  "foo - old bar - bobs baz"

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo bar baz

  bar : Text
  bar = "alices bar"
  
  baz : Text
  baz = "bobs baz"
  
  foo : Text
  foo =
    use Text ++
    "foo" ++ " - " ++ bar ++ " - " ++ baz

project/alice> display foo

  "foo - alices bar - bobs baz"

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.

Original branch:

foo : Text
foo = "old foo" ++ " - " ++ bar

bar : Text
bar = "old bar" ++ " - " ++ baz

baz : Text
baz = "old baz"
project/main> display foo

  "old foo - old bar - old baz"

Alice's updates:

baz : Text
baz = "alices baz"
project/alice> display foo

  "old foo - old bar - alices baz"

Bob's updates:

bar : Text
bar = "bobs bar" ++ " - " ++ baz
project/bob> display foo

  "old foo - bobs bar - old baz"

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo bar baz

  bar : Text
  bar =
    use Text ++
    "bobs bar" ++ " - " ++ baz
  
  baz : Text
  baz = "alices baz"
  
  foo : Text
  foo =
    use Text ++
    "old foo" ++ " - " ++ bar

project/alice> display foo

  "old foo - bobs bar - alices baz"

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.

Original branch:

foo : Text
foo = "old foo"

Alice's updates:

foo : Text
foo = "alices foo"

Bob's changes:

project/bob> delete.term foo

  Done.

Merge result:

project/alice> merge /bob

  I merged project/bob into project/alice.

project/alice> view foo

  foo : Text
  foo = "alices foo"

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.

Alice's adds:

lib.alice.foo : Nat
lib.alice.foo = 17

lib.bothSame.bar : Nat
lib.bothSame.bar = 18

lib.bothDifferent.baz : Nat
lib.bothDifferent.baz = 19

Bob's adds:

lib.bob.foo : Nat
lib.bob.foo = 20

lib.bothSame.bar : Nat
lib.bothSame.bar = 18

lib.bothDifferent.baz : Nat
lib.bothDifferent.baz = 21

Merge result:

project/alice> merge bob

  I merged project/bob into project/alice.

project/alice> view foo bar baz

  lib.alice.foo : Nat
  lib.alice.foo = 17
  
  lib.bob.foo : Nat
  lib.bob.foo = 20
  
  lib.bothDifferent__0.baz : Nat
  lib.bothDifferent__0.baz = 19
  
  lib.bothDifferent__1.baz : Nat
  lib.bothDifferent__1.baz = 21
  
  lib.bothSame.bar : Nat
  lib.bothSame.bar = 18

No-op merge (Bob = Alice)

If Bob is equals Alice, then merging Bob into Alice looks like this.

project/main> branch alice

  Done. I've created the alice branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /alice`.

project/main> branch bob

  Done. I've created the bob branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /bob`.

project/alice> merge /bob

  😶
  
  project/alice was already up-to-date with project/bob.

No-op merge (Bob < Alice)

If Bob is behind Alice, then merging Bob into Alice looks like this.

project/main> branch alice

  Done. I've created the alice branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /alice`.

project/main> branch bob

  Done. I've created the bob branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /bob`.

Alice's addition:

foo : Text
foo = "foo"
project/alice> add

  ⍟ I've added these definitions:
  
    foo : Text

project/alice> merge /bob

  😶
  
  project/alice was already up-to-date with project/bob.

Fast-forward merge (Bob > Alice)

If Bob is ahead of Alice, then merging Bob into Alice looks like this.

project/main> branch alice

  Done. I've created the alice branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /alice`.

project/main> branch bob

  Done. I've created the bob branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /bob`.

Bob's addition:

foo : Text
foo = "foo"
project/bob> add

  ⍟ I've added these definitions:
  
    foo : Text

project/alice> merge /bob

  I fast-forward merged project/bob into project/alice.

No-op merge: merge empty namespace into empty namespace

project/main> branch topic

  Done. I've created the topic branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /topic`.

project/main> merge /topic

  😶
  
  project/main was already up-to-date with project/topic.

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.

Original branch:

foo : Text
foo = "foo"

Alice's delete:

project/alice> delete.term foo

  Done.

Bob's new code that depends on foo:

bar : Text
bar = foo ++ " - " ++ foo
project/bob> add

  ⍟ I've added these definitions:
  
    bar : Text

project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

bar : Text
bar =
  use Text ++
  foo ++ " - " ++ foo

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.

Original branch:

foo : Text
foo = "foo"

Alice's update:

foo : Nat
foo = 100

Bob's new definition:

bar : Text
bar = foo ++ " - " ++ foo
project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

bar : Text
bar =
  use Text ++
  foo ++ " - " ++ foo

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.

Original branch:

foo : Text
foo = "old foo"

bar : Text
bar = "old bar"

Alice's changes:

foo : Text
foo = "alices foo"

bar : Text
bar = "alices bar"

qux : Text
qux = "alices qux depends on alices foo" ++ foo

Bob's changes:

foo : Text
foo = "bobs foo"

baz : Text
baz = "bobs baz"
project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
foo : Text
foo = "alices foo"

-- project/bob
foo : Text
foo = "bobs foo"

-- The definitions below are not conflicted, but they each depend on one or more
-- conflicted definitions above.

qux : Text
qux =
  use Text ++
  "alices qux depends on alices foo" ++ foo

project/merge-bob-into-alice> view bar baz

  bar : Text
  bar = "alices bar"
  
  baz : Text
  baz = "bobs baz"

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).

Original branch:

unique type Foo = MkFoo Nat

Alice's changes:

unique type Foo = MkFoo Nat Nat

Bob's changes:

unique type Foo = MkFoo Nat Text
project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
type Foo = MkFoo Nat Nat

-- project/bob
type Foo = MkFoo Nat Text

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.

Original branch:

unique type Foo = Baz Nat | Qux Text

Alice's changes Baz Nat to Baz Nat Nat

unique type Foo = Baz Nat Nat | Qux Text

Bob's renames Qux to BobQux:

project/bob> move.term Foo.Qux Foo.BobQux

  Done.

project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
type Foo = Baz Nat Nat | Qux Text

-- project/bob
type Foo = BobQux Text | Baz Nat

Merge failure: constructor-rename conflict

Here is another example demonstrating that constructor renames are modeled as updates.

Original branch:

unique type Foo = Baz Nat | Qux Text

Alice's rename:

project/alice> move.term Foo.Baz Foo.Alice

  Done.

Bob's rename:

project/bob> move.term Foo.Qux Foo.Bob

  Done.

project/alice> merge bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
type Foo = Qux Text | Alice Nat

-- project/bob
type Foo = Bob Text | Baz Nat

Merge failure: non-constructor/constructor conflict

A constructor on one side can conflict with a regular term definition on the other.

Alice's additions:

my.cool.thing : Nat
my.cool.thing = 17

Bob's additions:

unique ability my.cool where
  thing : Nat -> Nat
project/alice> merge bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
my.cool.thing : Nat
my.cool.thing = 17

-- project/bob
ability my.cool where thing : Nat ->{cool} Nat

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.

Original branch:

Foo.Bar : Nat
Foo.Bar = 17

Alice adds this type Foo with constructor Foo.Alice:

unique type Foo = Alice Nat

Bob adds the type Foo with constructor Foo.Bar, replacing the original Foo.Bar term:

project/bob> delete.term Foo.Bar

  Done.

unique type Foo = Bar Nat Nat

These won't cleanly merge.

project/alice> merge bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
Foo.Bar : Nat
Foo.Bar = 17

-- project/alice
type Foo = Alice Nat

-- project/bob
type Foo = Bar Nat Nat

Here's a more involved example that demonstrates the same idea.

In the LCA, we have a type with two constructors, and some term.

unique type Foo
  = Bar.Baz Nat
  | Bar.Qux Nat Nat

Foo.Bar.Hello : Nat
Foo.Bar.Hello = 17

Alice deletes this type entirely, and repurposes its constructor names for other terms. She also updates the term.

project/alice> view Foo.Bar.Baz Foo.Bar.Qux Foo.Bar.Hello

  Foo.Bar.Baz : Nat
  Foo.Bar.Baz = 100
  
  Foo.Bar.Hello : Nat
  Foo.Bar.Hello = 18
  
  Foo.Bar.Qux : Nat
  Foo.Bar.Qux = 200

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.

project/bob> view Foo.Bar

  type Foo.Bar = Baz Nat | Hello Nat Nat

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".

project/alice> merge bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
Foo.Bar.Baz : Nat
Foo.Bar.Baz = 100

-- project/alice
Foo.Bar.Hello : Nat
Foo.Bar.Hello = 18

-- project/bob
type Foo.Bar = Baz Nat | Hello Nat Nat

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.

Alice's additions:

unique type Foo = Bar

alice : Foo -> Nat
alice _ = 18

Bob's additions:

unique type Foo = Bar

bob : Foo -> Nat
bob _ = 19
project/alice> merge bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
type Foo
  = Bar

-- project/bob
type Foo
  = Bar

-- The definitions below are not conflicted, but they each depend on one or more
-- conflicted definitions above.

alice : Foo -> Nat
alice _ = 18

bob : Foo -> Nat
bob _ = 19

merge.commit example (success)

After merge conflicts are resolved, you can use merge.commit rather than switch + merge + branch.delete to "commit" your changes.

Original branch:

foo : Text
foo = "old foo"

Alice's changes:

foo : Text
foo = "alices foo"

Bob's changes:

foo : Text
foo = "bobs foo"

Attempt to merge:

project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
foo : Text
foo = "alices foo"

-- project/bob
foo : Text
foo = "bobs foo"

Resolve conflicts and commit:

foo : Text
foo = "alice and bobs foo"

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      foo : Text

project/merge-bob-into-alice> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  Done.

project/merge-bob-into-alice> merge.commit

  I fast-forward merged project/merge-bob-into-alice into
  project/alice.

project/alice> view foo

  foo : Text
  foo = "alice and bobs foo"

project/alice> branches

       Branch   Remote branch
  1.   alice    
  2.   bob      
  3.   main     

merge.commit example (failure)

merge.commit can only be run on a "merge branch".

project/main> branch topic

  Done. I've created the topic branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /topic`.

project/topic> merge.commit

  It doesn't look like there's a merge in progress.

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).

Original branch:

foo : Nat
foo = 100

bar : Nat
bar = 100

Alice's updates:

foo : Nat
foo = 200

bar : Nat
bar = 300

Bob's addition:

baz : Text
baz = "baz"
project/alice> merge /bob

  Sorry, I wasn't able to perform the merge:
  
  On the merge ancestor, bar and foo were aliases for the same
  term, but on project/alice the names have different
  definitions currently. I'd need just a single new definition
  to use in their dependents when I merge.
  
  Please fix up project/alice to resolve this. For example,
  
    * `update` the definitions to be the same again, so that
      there's nothing for me to decide.
    * `move` or `delete` all but one of the definitions; I'll
      use the remaining name when propagating updates. (You can
      `move` it back after the merge.)
  
  and then try merging again.

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.

Alice's branch:

project/alice> alias.type lib.builtins.Nat MyNat

  Done.

Bob's branch:

unique type MyNat = MyNat Nat
project/alice> merge /bob

  Sorry, I wasn't able to perform the merge:
  
  There's a merge conflict on type MyNat, but it's a builtin on
  one or both branches. I can't yet handle merge conflicts
  involving builtins.
  
  Please eliminate this conflict by updating one branch or the
  other, making MyNat the same on both branches, or making
  neither of them a builtin, and then try the merge again.

Constructor alias

Each naming of a decl may not have more than one name for each constructor, within the decl's namespace.

Alice's branch:

unique type Foo = Bar
project/alice> alias.term Foo.Bar Foo.some.other.Alias

  Done.

Bob's branch:

bob : Nat
bob = 100
project/alice> merge /bob

  Sorry, I wasn't able to perform the merge:
  
  On project/alice, the type Foo has a constructor with multiple
  names, and I can't perform a merge in this situation:
  
    * Foo.Bar
    * Foo.some.other.Alias
  
  Please delete all but one name for each constructor, and then
  try merging again.

Missing constructor name

Each naming of a decl must have a name for each constructor, within the decl's namespace.

Alice's branch:

unique type Foo = Bar
project/alice> delete.term Foo.Bar

  Done.

Bob's branch:

bob : Nat
bob = 100
project/alice> merge /bob

  Sorry, I wasn't able to perform the merge:
  
  On project/alice, the type Foo has some constructors with
  missing names, and I can't perform a merge in this situation.
  
  You can use `view Foo` and
  `alias.term <hash> Foo.<ConstructorName>` to give names to
  each unnamed constructor, and then try the merge again.

Nested decl alias

A decl cannot be aliased within the namespace of another of its aliased.

Alice's branch:

structural type A = B Nat | C Nat Nat
structural type A.inner.X = Y Nat | Z Nat Nat
project/alice> names A

  Type
  Hash:  #65mdg7015r
  Names: A A.inner.X

Bob's branch:

bob : Nat
bob = 100
project/alice> merge /bob

  On project/alice, the type A.inner.X is an alias of A. I'm not
  able to perform a merge when a type exists nested under an
  alias of itself. Please separate them or delete one copy, and
  then try merging again.

Stray constructor alias

Constructors may only exist within the corresponding decl's namespace.

Alice's branch:

project/alice> add

  ⍟ I've added these definitions:
  
    type Foo

project/alice> alias.term Foo.Bar AliasOutsideFooNamespace

  Done.

Bob's branch:

project/bob> add

  ⍟ I've added these definitions:
  
    bob : Nat

project/alice> merge bob

  Sorry, I wasn't able to perform the merge, because I need all
  constructor names to be nested somewhere beneath the
  corresponding type name.
  
  On project/alice, the constructor AliasOutsideFooNamespace is
  not nested beneath the corresponding type name. Please either
  use `move` to move it, or if it's an extra copy, you can
  simply `delete` it. Then try the merge again.

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.

Alice's branch:

lib.foo : Nat
lib.foo = 1

Bob's branch:

bob : Nat
bob = 100
project/alice> merge /bob

  Sorry, I wasn't able to perform the merge:
  
  On project/alice, there's a type or term at the top level of
  the `lib` namespace, where I only expect to find subnamespaces
  representing library dependencies.
  
  Please move or remove it and then try merging again.

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.

LCA:

structural type Foo = Bar Nat | Baz Nat Nat

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      structural type Foo

project/main> add

  ⍟ I've added these definitions:
  
    structural type Foo

project/main> delete.term Foo.Baz

  Done.

Alice's branch:

project/main> branch alice

  Done. I've created the alice branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /alice`.

project/alice> delete.type Foo

  Done.

project/alice> delete.term Foo.Bar

  Done.

alice : Nat
alice = 100

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      alice : Nat

project/alice> add

  ⍟ I've added these definitions:
  
    alice : Nat

Bob's branch:

project/main> branch bob

  Done. I've created the bob branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /bob`.

project/bob> delete.type Foo

  Done.

project/bob> delete.term Foo.Bar

  Done.

bob : Nat
bob = 101

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      bob : Nat

project/bob> add

  ⍟ I've added these definitions:
  
    bob : Nat

Now we merge:

project/alice> merge /bob

  I merged project/bob into project/alice.

Regression tests

Delete one alias and update the other

foo = 17
bar = 17

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      bar : Nat
      foo : Nat

project/main> add

  ⍟ I've added these definitions:
  
    bar : Nat
    foo : Nat

project/main> branch alice

  Done. I've created the alice branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /alice`.

project/alice> delete.term bar

  Done.

foo = 18

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      foo : Nat

project/alice> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  Done.

project/main> branch bob

  Done. I've created the bob branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /bob`.

bob = 101

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      bob : Nat

project/bob> add

  ⍟ I've added these definitions:
  
    bob : Nat

project/alice> merge /bob

  I merged project/bob into project/alice.

Delete a constructor

type Foo = Bar | Baz

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      type Foo

project/main> add

  ⍟ I've added these definitions:
  
    type Foo

project/main> branch topic

  Done. I've created the topic branch based off of main.
  
  Tip: To merge your work back into the main branch, first
       `switch /main` then `merge /topic`.

boop = "boop"

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      boop : Text

project/topic> add

  ⍟ I've added these definitions:
  
    boop : Text

type Foo = Bar

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      type Foo

project/main> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  Done.

project/main> merge topic

  I merged project/topic into project/main.

project/main> view Foo

  type Foo = Bar

Dependent that doesn't need to be in the file

This test demonstrates a bug.

In the LCA, we have foo with dependent bar, and baz.

foo : Nat
foo = 17

bar : Nat
bar = foo + foo

baz : Text
baz = "lca"

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      bar : Nat
      baz : Text
      foo : Nat

project/alice> add

  ⍟ I've added these definitions:
  
    bar : Nat
    baz : Text
    foo : Nat

project/alice> branch bob

  Done. I've created the bob branch based off of alice.
  
  Tip: To merge your work back into the alice branch, first
       `switch /alice` then `merge /bob`.

On Bob, we update baz to "bob".

baz : Text
baz = "bob"

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      baz : Text

project/bob> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  Done.

On Alice, we update baz to "alice" (conflict), but also update foo (unconflicted), which propagates to bar.

foo : Nat
foo = 18

baz : Text
baz = "alice"

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      baz : Text
      foo : Nat

project/alice> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  That's done. Now I'm making sure everything typechecks...

  Everything typechecks, so I'm saving the results...

  Done.

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.

project/alice> merge /bob

  I couldn't automatically merge project/bob into project/alice.
  However, I've added the definitions that need attention to the
  top of scratch.u.
  
  When you're done, you can run
  
    merge.commit
  
  to merge your changes back into alice and delete the temporary
  branch. Or, if you decide to cancel the merge instead, you can
  run
  
    delete.branch /merge-bob-into-alice
  
  to delete the temporary branch and switch back to alice.

-- project/alice
baz : Text
baz = "alice"

-- project/bob
baz : Text
baz = "bob"

-- The definitions below are not conflicted, but they each depend on one or more
-- conflicted definitions above.

bar : Nat
bar =
  use Nat +
  foo + foo

But bar was put into the scratch file instead.

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:

a = 1

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      a : ##Nat

project/alice> add

  ⍟ I've added these definitions:
  
    a : ##Nat

b = 2

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      b : ##Nat

project/alice> add

  ⍟ I've added these definitions:
  
    b : ##Nat

b = 2

  Loading changes detected in scratch.u.

  I found and typechecked the definitions in scratch.u. This
  file has been previously added to the codebase.

project/bob> add

  ⍟ I've added these definitions:
  
    b : ##Nat

a = 1

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      a : ##Nat

project/bob> add

  ⍟ I've added these definitions:
  
    a : ##Nat

a = 1
b = 2

  Loading changes detected in scratch.u.

  I found and typechecked the definitions in scratch.u. This
  file has been previously added to the codebase.

project/carol> add

  ⍟ I've added these definitions:
  
    a : ##Nat
    b : ##Nat

project/bob> merge /alice

  I merged project/alice into project/bob.

project/carol> merge /bob

  I merged project/bob into project/carol.

project/carol> history

  Note: The most recent namespace hash is immediately below this
        message.
  
  
  
  This segment of history starts with a merge. Use
  `history #som3n4m3space` to view history starting from a given
  namespace hash.
  
  ⊙ 1. #b7fr6ifj87
  ⑃
  2. #9npggauqo9
  3. #dm4u1eokg1

Variables named _

This test demonstrates a change in syntactic hashing that fixed a bug due to auto-generated variable names for ignored results.

ignore : a -> ()
ignore _ = ()

foo : Nat
foo = 18

bar : Nat
bar =
  ignore "hi"
  foo + foo

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These new definitions are ok to `add`:
    
      bar    : Nat
      foo    : Nat
      ignore : a -> ()

scratch/alice> add

  ⍟ I've added these definitions:
  
    bar    : Nat
    foo    : Nat
    ignore : a -> ()

scratch/alice> branch bob

  Done. I've created the bob branch based off of alice.
  
  Tip: To merge your work back into the alice branch, first
       `switch /alice` then `merge /bob`.

bar : Nat
bar =
  ignore "hi"
  foo + foo + foo

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      bar : Nat

scratch/bob> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  Done.

Previously, this update to foo would also cause a "real update" on bar, its dependent. Now it doesn't, so the merge will succeed.

foo : Nat
foo = 19

  Loading changes detected in scratch.u.

  I found and typechecked these definitions in scratch.u. If you
  do an `add` or `update`, here's how your codebase would
  change:
  
    ⍟ These names already exist. You can `update` them to your
      new definition:
    
      foo : Nat

scratch/alice> update

  Okay, I'm searching the branch for code that needs to be
  updated...

  That's done. Now I'm making sure everything typechecks...

  Everything typechecks, so I'm saving the results...

  Done.

scratch/alice> merge /bob

  I merged scratch/bob into scratch/alice.