How GitButler's Oplog Tracks Operations: A Deep Dive into Snapshot-Based History

GitButler records every significant repository change in a Git-based operation log (oplog) that stores immutable snapshot commits capturing the complete working tree state, virtual-branch metadata, and structured operation details.

The GitButler oplog is a lightweight, Git-native history system implemented in the gitbutlerapp/gitbutler repository. Unlike traditional Git reflogs, the oplog creates full snapshot commits that enable time-travel restores and provide a complete audit trail of user actions like commits, branch creations, and file discards.

What Is the GitButler Oplog?

The oplog (operation log) is a specialized persistence layer that treats repository state as a series of immutable snapshots. Each entry in the log is a standard Git commit stored in a dedicated chain, separate from the user's main branch history. This design leverages Git's content-addressed storage for efficient deduplication while maintaining a separate timeline of operational metadata.

The system serves two primary purposes: it provides undo/redo functionality by allowing users to restore previous states, and it creates a structured audit trail where every action is classified by type and annotated with human-readable descriptions.

Core Components of the Oplog System

Oplog Head and State Management

The oplog maintains a single source of truth for the latest snapshot through the oplog head, stored in a TOML file named operations-log.toml. The OplogHandle struct in crates/gitbutler-oplog/src/state.rs provides thread-safe read and write access to this file, ensuring that concurrent operations cannot corrupt the log pointer.

When a new snapshot is created, the system updates this TOML file to point to the new commit SHA, effectively moving the oplog head forward while preserving the full ancestry chain for history traversal.

Snapshot Commits and Metadata

Each oplog entry is a snapshot commit created by the logic in crates/gitbutler-oplog/src/oplog.rs. These commits are unique in that their tree objects capture the complete repository state—including the working directory, virtual branch metadata, conflict markers, and index state—while their commit messages encode structured metadata.

The commit message format follows a specific schema defined by the SnapshotDetails struct in crates/gitbutler-oplog/src/entry.rs. This payload includes:

  • A version number (currently 3)
  • The operation kind (e.g., CreateCommit, MergeUpstream)
  • A human-readable title and optional body
  • Arbitrary key-value trailers for extensible metadata

Operation Kinds and Classification

The OperationKind enum, also defined in crates/gitbutler-oplog/src/entry.rs, provides an exhaustive taxonomy of user actions. This classification system enables precise filtering and semantic understanding of the oplog history.

Common operation kinds include:

  • CreateCommit – Recording a new commit
  • CreateBranch – Creating a virtual branch
  • MergeUpstream – Merging upstream changes
  • DiscardFile – Discarding file changes
  • RestoreFromSnapshot – Restoring to a previous oplog state

How the GitButler Oplog Records Operations

The process of recording an operation follows a strict four-phase protocol implemented in the OplogExt trait:

  1. Prepare the snapshot – The prepare_snapshot method walks the repository to collect the current worktree, virtual branch trees, index state, and conflict files. It constructs a Git tree object representing the complete state.

  2. Create snapshot details – The caller constructs a SnapshotDetails instance, specifying the OperationKind (e.g., OperationKind::CreateCommit) and optional metadata trailers. The SnapshotDetails::new method automatically sets the version to 3.

  3. Commit the snapshot – The commit_snapshot method creates a Git commit using the prepared tree. The commit message is the string serialization of SnapshotDetails, and the commit parent is the current oplog head (if one exists).

  4. Update the oplog head – Finally, OplogHandle::set_oplog_head atomically writes the new commit SHA to operations-log.toml, ensuring that subsequent operations build upon this new baseline.

When retrieving history, the list_snapshots method walks the commit ancestry from the current oplog head, parsing each commit message back into SnapshotDetails using SnapshotDetails::from_str, and optionally filtering by OperationKind.

Working with the Oplog: Code Examples

Creating a Snapshot After User Actions

To record an operation in the oplog, use the create_snapshot method provided by the OplogExt trait. This example records a commit creation:

use gitbutler_oplog::OplogExt;
use gitbutler_oplog::entry::OperationKind;
use but_ctx::Context;

fn on_user_created_commit(ctx: &Context) -> anyhow::Result<()> {
    // Define the operation type
    let details = OperationKind::CreateCommit.into();

    // Acquire exclusive access for the write operation
    let mut exclusive = ctx.exclusive_worktree_access();

    // Create and commit the snapshot
    let snapshot_commit = ctx.create_snapshot(details, &mut exclusive)?;

    println!("Snapshot created: {}", snapshot_commit);
    Ok(())
}

The Context::create_snapshot method internally calls prepare_snapshot, constructs the SnapshotDetails, commits the snapshot, and updates the oplog head in operations-log.toml.

Listing and Filtering Snapshots

To retrieve the operation history, use list_snapshots with optional filtering by OperationKind:

use gitbutler_oplog::OplogExt;
use gitbutler_oplog::entry::OperationKind;
use but_ctx::Context;

fn recent_commits(ctx: &Context) -> anyhow::Result<()> {
    // Retrieve the 10 most recent branch creation operations
    let snapshots = ctx.list_snapshots(
        10,
        None,
        vec![],                                     // exclude none
        Some(vec![OperationKind::CreateBranch]),    // include only CreateBranch
    )?;

    for snap in snapshots {
        println!(
            "- {} ({}) – {}",
            snap.commit_id,
            snap.created_at.seconds(),
            snap.details
                .as_ref()
                .map(|d| d.title.clone())
                .unwrap_or_else(|| "<no title>".into())
        );
    }
    Ok(())
}

This method walks the commit ancestry from the oplog head, parsing each commit message via SnapshotDetails::from_str to reconstruct the operation metadata.

Restoring from a Previous State

The oplog enables time-travel by restoring the repository to any previous snapshot:

use gitbutler_oplog::OplogExt;
use but_ctx::Context;

fn restore_to(commit_id: git2::Oid, ctx: &Context) -> anyhow::Result<()> {
    // Obtain exclusive lock for the destructive operation
    let mut exclusive = ctx.exclusive_worktree_access();

    // Restore the snapshot and record the restore operation
    let new_snapshot = ctx.restore_snapshot(commit_id, &mut exclusive)?;

    println!("Repository restored. New snapshot: {}", new_snapshot);
    Ok(())
}

The restore_snapshot method reads the target snapshot's tree, applies it to the working directory, and creates a new oplog entry with OperationKind::RestoreFromSnapshot to record the undo action itself.

Key Source Files in gitbutlerapp/gitbutler

File Purpose
crates/gitbutler-oplog/src/oplog.rs Implements the OplogExt trait with core logic for create_snapshot, list_snapshots, and restore_snapshot.
crates/gitbutler-oplog/src/state.rs Manages the operations-log.toml file and the OplogHandle struct for atomic head updates.
crates/gitbutler-oplog/src/entry.rs Defines SnapshotDetails, OperationKind, and Trailer structs; handles commit message serialization.
crates/gitbutler-oplog/src/reflog.rs Maintains a Git reflog reference to prevent snapshot chain garbage collection.

Summary

  • The GitButler oplog is a Git-based operation log that stores complete repository snapshots as regular commits, enabling robust undo/redo functionality.
  • The oplog head is tracked in operations-log.toml via the OplogHandle in state.rs, providing a single source of truth for the latest snapshot.
  • Each snapshot embeds structured metadata via SnapshotDetails in the commit message, including the OperationKind enum that classifies user actions like CreateCommit or RestoreFromSnapshot.
  • The OplogExt trait in oplog.rs provides the public API for creating, listing, and restoring snapshots, handling the full lifecycle from tree preparation to head updates.
  • Because snapshots are standard Git commits, the system inherits Git's integrity, deduplication, and traversal capabilities while maintaining a separate operational history.

Frequently Asked Questions

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

While Git's reflog only tracks updates to branch references, the GitButler oplog creates full snapshot commits that capture the entire working tree, virtual branch metadata, and conflict states. The oplog also embeds structured operation metadata via SnapshotDetails in commit messages, enabling semantic filtering by OperationKind—functionality that standard reflogs cannot provide.

What information is stored in an oplog snapshot commit?

Each snapshot commit stores three distinct layers of information: the tree object contains the complete repository state including the working directory, index, and virtual branch trees; the commit message encodes a SnapshotDetails struct with the operation type (OperationKind), version number, human-readable title, and optional trailers; and the parent pointer links to the previous oplog entry, forming an immutable history chain.

Can I filter the oplog to find specific types of operations?

Yes, the list_snapshots method in the OplogExt trait accepts optional OperationKind filters. You can specify inclusion lists (e.g., only CreateBranch and MergeUpstream operations) or exclusion lists to narrow down the history. The system parses each snapshot's commit message back into SnapshotDetails and filters based on the deserialized OperationKind enum variant.

How does restoring from an oplog snapshot affect the current repository state?

When you call restore_snapshot, GitButler reads the target snapshot's tree and applies it to the working directory, effectively rewriting the current state to match the historical snapshot. This operation itself is recorded in the oplog with OperationKind::RestoreFromSnapshot, creating a new entry that points to the restore action. The method requires an exclusive worktree lock to prevent race conditions during the destructive state change.

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 →