⏪ Resetting codebase state

The Unison Codebase Manager has a powerful set of commands for managing the state of your codebase. Other programming languages will make use of version control systems like Git (or spamming Ctrl-Z) to manage history, but in Unison changes to the codebase, such as updating terms or deleting them, are automatically preserved as a log of hashes in the UCM itself. One of the UCM's useful features is the ability to view and rewind the log of your codebase to a previous point in time. This can be helpful if you make a mistake and want to revert to a previous version of your code.

🗂️

Codebase state cheat sheet

  • reflog - Displays a log of changes to the current branch
  • reflog.project - Displays a log of changes to the current project
  • reflog.global - Displays a log of changes to the entire codebase
  • reset - Reverts a branch to a given hash
  • undo - Reverts the most recent change to the current branch
  • history - Displays the project history, including the names of updated terms

A walk-through

What does it actually look like when we say that the UCM creates a log of changes to the codebase? Let's walk through a simple example.

Viewing changes to a branch

Say we have the Unison term magicNumber = 40 that we add to a fresh project.

scratch/main> add

  ⍟ I've added these definitions:

  magicNumber : Nat

Realizing that our magic number is actually 42 and that we're missing another term, we can make those changes in our scratch.u file and then update the codebase.

magicNumber = 42
magicWord = "please"

Let's inspect the reflog to see what it looks like:

scratch/main> reflog

Below is a record of recent changes, you can use
`reset #abcdef` to reset the current branch to a previous
state.

Tip: Use `diff.namespace 1 7` to compare between points in
     history.

     Branch         When   Hash          Description
1.   scratch/main   now    #8lffaev34c   update
2.   scratch/main   now    #cr3um0rgg1   add
3.   scratch/main   now    #sg60bvjo91   Project Created

The reflog command displays a log of changes to the current branch along with the UCM command that produced that change.

Resetting the state

If we want to revert the latest update, we have two options, undo and reset.

undo rewinds the most recent change to the current branch:

scratch/main> undo

Here are the changes I undid

Updates:

  1. magicNumber : Nat
     ↓
  2. magicNumber : Nat

Added definitions:

  3. magicWord : Text

Because changes to a Unison codebase form an immutable log, this command also shows up in the reflog:

scratch/main> reflog

  Below is a record of recent changes, you can use
  `reset #abcdef` to reset the current branch to a previous
  state.

  Tip: Use `diff.namespace 1 7` to compare between points in
       history.

       Branch         When   Hash          Description
  1.   scratch/main   now    #cr3um0rgg1   undo
  2.   scratch/main   now    #8lffaev34c   update
  3.   scratch/main   now    #cr3um0rgg1   add
  4.   scratch/main   now    #sg60bvjo91   Project Created

If we need to move farther backward in the log, we can use the reset command with the hash of the state we want to return to:

scratch/main> reset #cr3um0rgg1
reset can also take a numbered argument instead of a hash, so it's common to issue reflog to see the list of changes and then enter something like reset 3 in the UCM without needing to copy and paste the hash.

Viewing changes across multiple branches in a project

Let's create a feature branch for this example and make some new changes.

scratch/main> branch addFeature
magicNumber = 1
magicWorld = "pretty please"

reflog.project will show the changes across all branches in the project:

scratch/addFeature> reflog.project

Below is a record of recent changes, you can use
`reset #abcdef` to reset the current branch to a previous
state.

Tip: Use `diff.namespace 1 7` to compare between points in
     history.

     Branch               When   Hash          Description
1.   scratch/addFeature   now    #4lotqj3cbo   update
2.   scratch/addFeature   now    #8lffaev34c   Branch created from scratch/main
3.   scratch/main         now    #8lffaev34c   update
4.   scratch/main         now    #cr3um0rgg1   add
5.   scratch/main         now    #sg60bvjo91   Project Created

Compare this to the history command output, which includes the name of the terms that were changed in the project across its branches:

scratch/addFeature> history

  Note: The most recent namespace hash is immediately below this
        message.

  ⊙ 1. #4lotqj3cbo

    + Adds / updates:

      magicNumber magicWorld

  ⊙ 2. #8lffaev34c

    + Adds / updates:

      magicNumber magicWord

  □ 3. #cr3um0rgg1 (start of history)
🧠

The history command shows a log of the project's states (across all its branches) and the names of the terms or types that changed. It does not include the UCM commands that produced those changes.

For folks who are used to Git, history is akin to git log and reflog.project is akin to git reflog. Here's a quick explainer, if you're curious.

Global codebase state

This is less common, but if you need to view changes across the entire codebase, you can use the reflog.global command.

The deprecated reset-root command had the ability to move backwards across the entire codebase state, but this is not recommended as it can lead to rewinding changes in unintended projects.