How GitButler Oplog Revert Operations Restore Repository State

GitButler oplog revert operations work by loading a previously saved snapshot—a Git tree containing the worktree, index, conflicts, and virtual branch metadata—and reconstructing the entire repository state from that snapshot, then recording the revert itself as a new snapshot.

The gitbutlerapp/gitbutler codebase implements a robust undo system through its operations log (oplog). Unlike traditional Git resets, GitButler oplog revert operations provide a declarative, auditable mechanism for restoring complex repository states including virtual branches and conflict markers.

What Is the GitButler Oplog?

The oplog is an append-only log where every user action—commits, branch-stack changes, conflict resolutions, and reverts—is stored as an immutable snapshot. Each snapshot is a Git tree object that encapsulates the complete state of the repository at that moment.

According to the source code in crates/gitbutler-oplog/src/oplog.rs, a snapshot captures:

  • worktree/ – Current worktree files
  • index/ – Staging area state
  • target_tree/ – The target commit tree
  • conflicts/ – Conflict state metadata
  • Virtual branch metadata (virtual_branches.toml and per-branch sub-trees)

How Snapshots Capture Repository State

Before understanding GitButler oplog revert operations, it is essential to understand how snapshots serialize repository state. The prepare_snapshot function in crates/gitbutler-oplog/src/oplog.rs traverses the working directory, index, and virtual branch state to construct a Git tree object.

Virtual branches—GitButler's core abstraction for stacked changes—are serialized into the snapshot via virtual_branches.toml and individual branch trees. This ensures that complex branch-stack configurations are preserved atomically.

The GitButler Oplog Revert Process Step-by-Step

When a user triggers a revert, the restore_snapshot function (lines 64-71 in crates/gitbutler-oplog/src/oplog.rs) executes an eight-step restoration process:

1. Load the Snapshot Tree

The revert operation begins by loading the Git tree associated with the target snapshot SHA. This tree contains all serialized state components needed for reconstruction.

2. Restore Conflict State

The restore_conflicts_tree function (lines 73-80) extracts the conflicts/ subtree and writes conflict markers back to .git/base_merge_parent and .git/conflicts. This resurrects the exact conflict resolution state present at snapshot creation.

3. Recreate Virtual Branch Commits

Snapshots may contain virtual branch commits that do not exist in the main object database. The system deserializes stored commit data using deserialize_commit (lines 45-57) and writes commit objects into the repository if they are absent. This ensures branch stacks remain intact even if commits were garbage collected.

4. Update the Workspace Branch

If the snapshot contains a workspace branch, the system resets HEAD, deletes the old gitbutler/workspace reference, and recreates it at the restored commit (lines 60-78). This synchronizes GitButler's internal workspace pointer with the historical state.

5. Checkout the Worktree

The worktree restoration checks the cv3 feature flag to determine the checkout method:

  • With cv3 enabled: Uses but_core::worktree::safe_checkout_from_head (lines 94-100) for fast, atomic worktree updates
  • Legacy mode: Falls back to the git2 checkout builder (lines 102-107)

Both methods restore working directory files to their snapshot state.

6. Restore Virtual Branch Metadata

The system writes the saved virtual_branches.toml blob back to the project data directory and re-syncs reference pointers (lines 109-122). This resurrects the exact virtual branch configuration, including branch names, dependencies, and stack ordering.

7. Restore the Index

The staging area is reconstructed from the index/ tree (lines 124-132), ensuring that staged changes present in the snapshot are accurately reflected in the restored state.

8. Record the Revert Operation

Finally, commit_snapshot (lines 162-169) creates a new snapshot documenting the revert itself. This new snapshot becomes the head of the oplog, maintaining an auditable chain of all operations including undos.

Code Example: Reverting to a Snapshot

The following Rust example demonstrates how to programmatically execute GitButler oplog revert operations using the OplogExt trait:

use but_ctx::{Context, access::RepoExclusive};
use gitbutler_oplog::OplogExt;
use git2::Oid;

/// Reverts the repository to a specific snapshot SHA.
fn revert_to_snapshot(ctx: &Context, snapshot: Oid) -> anyhow::Result<Oid> {
    // Acquire exclusive access to prevent concurrent modifications
    let mut guard = ctx.exclusive_worktree_access();

    // Execute the revert operation
    // This internally calls restore_snapshot and creates a new revert snapshot
    let new_snapshot_sha = ctx.restore_snapshot(snapshot, &mut guard)?;

    println!("Reverted to {} – new snapshot SHA: {}", snapshot, new_snapshot_sha);
    Ok(new_snapshot_sha)
}

To create a snapshot that can be reverted to later:

use but_ctx::{Context, access::RepoExclusive};
use gitbutler_oplog::{OplogExt, SnapshotDetails, OperationKind};

fn create_revertible_snapshot(ctx: &Context) -> anyhow::Result<Oid> {
    let mut guard = ctx.exclusive_worktree_access();

    let details = SnapshotDetails {
        version: Default::default(),
        operation: OperationKind::UserAction,
        title: "Critical changes before refactor".into(),
        body: None,
        trailers: vec![],
    };

    ctx.create_snapshot(details, &mut guard)
}

Key Source Files for Oplog Operations

Understanding GitButler oplog revert operations requires familiarity with these specific files in the gitbutlerapp/gitbutler repository:

File Role
crates/gitbutler-oplog/src/oplog.rs Core implementation containing restore_snapshot, prepare_snapshot, commit_snapshot, and conflict restoration logic.
crates/gitbutler-oplog/src/entry.rs Defines SnapshotDetails, OperationKind, and metadata structures stored with each snapshot.
crates/gitbutler-oplog/src/state.rs Manages the oplog head pointer in operations-log.toml and persistent state storage.
apps/desktop/src/lib/history/oplogService.svelte.ts Frontend service that invokes Rust Oplog APIs from the Svelte-based desktop UI.
crates/but/src/command/legacy/oplog.rs CLI command wrappers exposing oplog functionality to the legacy command interface.

Summary

GitButler oplog revert operations provide a deterministic mechanism for restoring complex repository states through the following key mechanisms:

  • Snapshot-based architecture: Every operation is stored as an immutable Git tree containing worktree, index, conflicts, and virtual branch metadata
  • Declarative restoration: The restore_snapshot function in crates/gitbutler-oplog/src/oplog.rs reconstructs state by replaying stored snapshots rather than performing Git resets
  • Virtual branch preservation: The system recreates missing commits and restores virtual_branches.toml to maintain branch stack integrity
  • Audit trail maintenance: Every revert creates a new snapshot, ensuring the oplog remains a complete, reversible history of all user actions

Frequently Asked Questions

How does GitButler's oplog differ from Git's reflog?

GitButler's oplog stores complete state snapshots as Git trees, while Git's reflog only records reference updates. The oplog captures virtual branch metadata, conflict states, and worktree contents, enabling full repository restoration rather than just reference movement. Additionally, oplog entries are immutable snapshots identified by SHA, whereas reflog entries expire and are not portable.

Can I revert to a snapshot if the original commits were garbage collected?

Yes. During restore_snapshot, GitButler executes deserialize_commit (lines 45-57 in crates/gitbutler-oplog/src/oplog.rs) to recreate any virtual branch commits that exist only in the snapshot but are missing from the object database. This ensures branch stacks remain intact even after aggressive garbage collection.

What happens to uncommitted worktree changes during a revert?

The revert process checks out the saved worktree/ tree from the snapshot. If the cv3 feature flag is enabled, GitButler uses but_core::worktree::safe_checkout_from_head for atomic worktree updates; otherwise, it falls back to the git2 checkout builder (lines 94-107 in crates/gitbutler-oplog/src/oplog.rs). Any current uncommitted changes are replaced by the snapshot's worktree state.

Is the revert operation itself recorded in the oplog?

Yes. After restoring state, commit_snapshot (lines 162-169 in crates/gitbutler-oplog/src/oplog.rs) creates a new snapshot documenting the revert operation. This new snapshot becomes the head of the oplog, maintaining a complete audit trail that includes the revert itself, allowing users to undo the undo if necessary.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →