How to update code and resolve resulting conflicts in a branch

Sometimes you'll need to make a change in the codebase that can't automatically be propagated to dependent functions, for example: if you alter a data constructor for a type or change the arguments to a function. Unison can programmatically guide you through the edits that you need to make in these cases.

Note, your code which calls the old versions of a function will not be broken (it'll still run as originally implemented) but it might refer to the old version by hash reference, which is how you end up with cryptic terms that look like this:

exclaim : Text
exclaim =
    use Text ++
    #7i0ceb20mm 4 ++ "!!"

This document walks through a workflow for making changes to your codebase and resolving the resulting conflicts in a way that helps to preserve the human readable names for your functions. 🤖

Suggested workflow summary:

  1. branch off of the project branch you'd like to edit before making the change.
  2. make your changes in the branch
  3. update the codebase
  4. run todo to see dependencies to update
  5. edit and update the functions that the UCM suggests
  6. merge the forked branch back

Walk through an example

Let's say we have a simple type and a function which makes use of that type in our codebase:

unique type Box = Box Nat

Box.toText : Box -> Text
Box.toText box = match box with
  Box.Box nat -> Nat.toText nat

Later, we may realize its data constructor should be changed. The first thing we should do is branch from our working branch so we have a nice environment to make our changes in.

myProject/main> branch updateMyType

In our new branch, let's edit the type:

myProject/updateMyType>
myProject/updateMyType> edit Box

We've changed the data constructor from taking a value of type Nat to Int.

unique type Box = Box Int

After making the change to the type we'll update the codebase, and see if we have any functions or types to adjust given our change with the todo command. Remember, we're making these updates in a branch.

myProject/updateMyType> update
myProject/updateMyType> todo

🚧

The namespace has 1 transitive dependent(s) left to upgrade.
Your edit frontier is the dependents of these definitions:

  unique type Box

I recommend working on them in the following order:

1. toText : Box -> Text

We only have one todo item here, but in a more complicated codebase the suggested edit order from the UCM can be extremely helpful in prompting you to work from the "edges" of your codebase, working up the function call chain.

💡

You can tackle your "todo's" one by one, but did you know, you can also update all of your dependencies at once with the edit 1-n syntax.

Maybe you want to edit a grouping of connected functions, but they aren't contiguous in the numbered list? You can combine the edit 1-n syntax with a space separated list of terms you'd like to edit, for example edit 2-4 6 8 or edit 1 4 7-9.

In this case, it looks like the toText function will need to be changed to point to the updated type in this branch and a few names and functions should be changed to handle the Int. Because we've kept around a copy of the old data type the old branch, the UCM renders it with the hash of its project prefixed before the name of the type (this is a temporary stop-gap) instead of an entirely cryptic hash.

-- old code
Box.toText : <projectsHash>.Box -> Text
Box.toText = cases <projectsHash>.Box.Box nat -> Nat.toText nat

-- new version ✨
Box.toText : Box -> Text
Box.toText = cases Box.Box int -> Int.toText int

When you're satisfied with the changes in your forked namespace and there are no more todo items, you can merge the new branch into the old branch to fully replace the old implementation. We can clean up our branch structure with the delete.branch command

myProject/updateType> merge /updateType /main
myProject/updateType> switch /main
myProject/main> delete.branch /updateType

There you have it! You've updated your codebase with the help of the UCM! 🌻