How GitButler Implements Virtual Branches: Inside the Stack-Based Architecture

GitButler implements virtual branches as lightweight stacks stored in a TOML configuration file and mirrored by hidden Git references, enabling multiple parallel workstreams without polluting the standard Git ref namespace.

GitButler's virtual branch system revolutionizes Git workflows by enabling developers to work on multiple features simultaneously without switching traditional Git branches. Unlike standard Git branches that rely on refs/heads/, GitButler's virtual branches—internally called stacks—persist state in a dedicated virtual_branches.toml file while maintaining hidden Git references for integrity. This article examines the complete implementation architecture, from the persistence layer in crates/gitbutler-stack to the high-level API exposed to the UI.

Architecture Overview: Three Layers of Virtual Branches

The virtual branch system consists of three distinct layers that separate persistence concerns from operational logic.

State Persistence Layer

The foundation resides in crates/gitbutler-stack/src/state.rs, where the VirtualBranchesHandle struct manages the virtual_branches.toml file. This TOML file stores metadata including default targets, branch order, and workspace inclusion status.

In-Memory Model Layer

The middle layer, defined in crates/gitbutler-stack/src/stack.rs, represents virtual branches as Stack structs containing ordered StackBranch heads. This model abstracts the underlying Git objects while maintaining reference integrity.

Operations and API Layer

The top layer in crates/gitbutler-branch-actions exposes high-level functions like create_virtual_branch and amend. This layer synchronizes the TOML state with hidden Git references under refs/gitbutler/.

Persistent State Management with VirtualBranchesHandle

GitButler persists virtual branch metadata outside the standard Git ref namespace to avoid cluttering git branch -a output. The VirtualBranchesHandle struct in crates/gitbutler-stack/src/state.rs provides atomic read/write operations for the virtual_branches.toml file.

When initializing a project, GitButler creates a handle pointing to <project_dir>/virtual_branches.toml. The handle's read_file method deserializes the TOML into a VirtualBranches struct containing:

  • default_target: The base branch and SHA for new virtual branches
  • branches: A vector of virtual branch metadata including UUIDs, order indices, and workspace status

The write_file method serializes changes back to disk, ensuring durability before Git references are updated. This two-phase commit pattern—persist TOML first, then update refs—prevents state corruption during crashes.

The Stack Model: Representing Virtual Branches In-Memory

While the TOML file provides persistence, the Stack struct in crates/gitbutler-stack/src/stack.rs serves as the runtime representation of a virtual branch. A Stack encapsulates everything needed to manipulate a workstream without touching the working directory directly.

Stack Structure

The Stack struct (lines 31-48 of stack.rs) contains:

pub struct Stack {
    pub id: StackId,                     // Unique UUID for the virtual branch
    pub source_refname: Option<Refname>, // Optional local branch association
    pub upstream: Option<RemoteRefname>, // Remote tracking branch
    pub order: usize,                    // UI display order
    pub in_workspace: bool,              // Whether applied to worktree
    pub heads: Vec<StackBranch>,         // Series of patches/commits
}

StackBranch: Mutable Heads

Each entry in the heads vector is a StackBranch defined in crates/gitbutler-stack/src/stack_branch.rs. Unlike traditional Git branches that point to a single commit, a StackBranch can reference patches, carry PR metadata, and maintain an archived flag.

The StackBranch struct manages the actual Git reference through helper methods set_real_reference and rename_real_reference (lines 94-124 and 137-181). These methods ensure that hidden refs under refs/gitbutler/ always point to the current OID stored in the head, maintaining synchronization between the TOML state and the Git object database.

Creating Virtual Branches: The Branch Creation Flow

When users create a new virtual branch through the UI or CLI, the request flows through gitbutler_branch_actions::create_virtual_branch in crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs. This function orchestrates the entire creation process across all three architectural layers.

Step-by-Step Creation Process

The implementation (lines 46-84 of branch_creation.rs) follows this sequence:

  1. Load Default Target: Retrieves the base branch configuration from VirtualBranchesHandle::get_default_target().

  2. Generate Unique Identity: Computes a unique name via dedup and determines the next order index using next_order_index.

  3. Reorder Existing Stacks: Shifts existing stacks to accommodate the new branch's position (lines 66-74).

  4. Initialize Empty Stack: Creates a Stack::new_empty containing a single StackBranch pointing to the default target's SHA.

  5. Persist State: Writes the new stack to virtual_branches.toml via vb_state.set_stack(branch.clone()).

  6. Create Git Reference: Adds the hidden reference via self.ctx.add_branch_reference(&branch).

  7. Update Workspace: Makes the branch visible through update_workspace_commit.

The function returns a StackEntryNoOpt containing the new stack's UUID, tip OID, and head list, which the UI renders immediately.

Applying and Unapplying Virtual Branches

Virtual branches exist in two states: applied (visible in the worktree) and unapplied (stored only in metadata). GitButler manages these transitions without traditional git checkout operations.

Applying a Branch

When users apply a virtual branch, but_workspace::branch::apply executes (referenced in branch_creation.rs lines 96-118). This operation:

  • Checks out the stack's tip commit into the worktree
  • Sets in_workspace = true on the Stack struct
  • Persists the change via VirtualBranchesHandle::set_stack

Unlike standard Git branches, multiple virtual branches can be applied simultaneously, with GitButler managing the merged working directory state.

Unapplying a Branch

The gitbutler_branch_actions::unapply_stack function (called from branch_actions.rs) reverses the process:

  • Removes working tree changes associated with the branch
  • Updates in_workspace = false in the TOML state
  • Triggers garbage collection for unreachable stacks

The garbage collection logic in VirtualBranchesHandle::garbage_collect (lines 96-124 of state.rs) scans all stacks and removes those not in the workspace with unreachable commits.

Amending, Rebasing, and Moving Commits

Virtual branches support advanced operations like amending commits and reordering patches through the same three-layer architecture.

Amend Operations

The amend_virtual_branch function in crates/but-api/src/legacy/virtual_branches.rs (lines 42-50) delegates to gitbutler_branch_actions::amend. This operation:

  1. Retrieves the target Stack via VirtualBranchesHandle::get_stack (lines 98-108 of state.rs)
  2. Updates the corresponding StackBranch head OID
  3. Synchronizes the hidden Git reference using StackBranch::set_real_reference
  4. Persists changes back to virtual_branches.toml

Move and Reorder Operations

Functions like move_commit and reorder_stack manipulate the ordered heads vector within the Stack struct. Because heads maintain their own Git references via set_real_reference and rename_real_reference (lines 94-124 and 137-181 of stack_branch.rs), reordering patches automatically updates the underlying ref topology.

This design allows GitButler to present a series of patches that behave like stacked commits while maintaining reference integrity through the hidden refs/gitbutler/ namespace.

Key Implementation Files

File Role Link
crates/gitbutler-stack/src/state.rs Handles persistence (VirtualBranchesHandle) and ordering logic. state.rs
crates/gitbutler-stack/src/stack.rs Defines the Stack struct (virtual branch) and its helper constructors. stack.rs
crates/gitbutler-stack/src/stack_branch.rs Defines StackBranch (a mutable head) and reference‑management helpers. stack_branch.rs
crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs Core creation logic for virtual branches (create_virtual_branch, create_virtual_branch_from_branch). branch_creation.rs
crates/but-api/src/legacy/virtual_branches.rs Public RPC façade exposing virtual‑branch operations to the UI/CLI. virtual_branches.rs
crates/gitbutler-branch-actions/src/lib.rs Re‑exports all branch‑action functions (create, amend, move, delete, etc.). lib.rs
crates/gitbutler-workspace/src/lib.rs Workspace glue that applies/unapplies virtual branches to the worktree. workspace lib.rs

These files together implement GitButler’s virtual branch system: a persistent, order‑aware collection of lightweight stacks that can be created, edited, applied, and garbage‑collected without polluting the underlying Git reference namespace.

Summary

  • GitButler stores virtual branch metadata in virtual_branches.toml, avoiding pollution of the standard Git ref namespace while maintaining durability through a dedicated VirtualBranchesHandle persistence layer.
  • The Stack struct represents virtual branches in memory, containing ordered StackBranch heads that map to hidden Git references under refs/gitbutler/.
  • Creation flows through create_virtual_branch, which initializes empty stacks, persists TOML state, creates hidden refs, and updates the workspace in a coordinated transaction.
  • Applied vs. unapplied states are managed through the in_workspace flag, allowing multiple virtual branches to coexist in the worktree simultaneously without traditional Git checkout operations.
  • Advanced operations like amend, move, and reorder manipulate the heads vector while StackBranch helpers synchronize hidden Git references automatically.

Frequently Asked Questions

How do virtual branches differ from standard Git branches?

Standard Git branches are references stored in refs/heads/ that point to commit objects. GitButler virtual branches are metadata entries in virtual_branches.toml that reference hidden Git refs under refs/gitbutler/. This allows GitButler to manage multiple applied branches simultaneously and maintain patch series without cluttering the standard branch namespace or requiring constant checkout operations.

Where does GitButler store virtual branch metadata?

GitButler persists virtual branch state in a TOML file located at <project_dir>/virtual_branches.toml. This file contains the default target branch, stack orderings, workspace inclusion flags, and head configurations. The VirtualBranchesHandle struct in crates/gitbutler-stack/src/state.rs manages atomic read and write operations for this file, ensuring consistency with the hidden Git references.

Can multiple virtual branches be active simultaneously?

Yes. Unlike standard Git which requires checking out a single branch, GitButler allows multiple virtual branches to be applied to the worktree at the same time. The in_workspace boolean flag on each Stack determines visibility, and the workspace layer merges changes from all applied branches. This enables workflows where developers work on several features concurrently without stashing or switching contexts.

How does GitButler handle commit operations like amend on virtual branches?

Amend operations flow through gitbutler_branch_actions::amend, which retrieves the target Stack via VirtualBranchesHandle::get_stack, updates the corresponding StackBranch head OID, and synchronizes the hidden Git reference using StackBranch::set_real_reference. The changes are then persisted back to virtual_branches.toml. This three-layer approach ensures that amending commits updates both the metadata and the underlying Git objects atomically.

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 →