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

> Explore how GitButler's oplog tracks operations using immutable snapshot commits. Understand its Git-based history and gain insights into virtual-branch metadata and operational details.

- Repository: [GitButler/gitbutler](https://github.com/gitbutlerapp/gitbutler)
- Tags: deep-dive
- Published: 2026-02-16

---

**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`](https://github.com/gitbutlerapp/gitbutler/blob/main/operations-log.toml). The `OplogHandle` struct in [`crates/gitbutler-oplog/src/state.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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:

```rust
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`](https://github.com/gitbutlerapp/gitbutler/blob/main/operations-log.toml).

### Listing and Filtering Snapshots

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

```rust
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:

```rust
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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-oplog/src/state.rs) | Manages the [`operations-log.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/operations-log.toml) file and the `OplogHandle` struct for atomic head updates. |
| [`crates/gitbutler-oplog/src/entry.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-oplog/src/entry.rs) | Defines `SnapshotDetails`, `OperationKind`, and `Trailer` structs; handles commit message serialization. |
| [`crates/gitbutler-oplog/src/reflog.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/operations-log.toml) via the `OplogHandle` in [`state.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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.