How GitButler's Virtual Branch System Works: Architecture and Implementation

GitButler's virtual branch system implements lightweight, stackable branches as an overlay on standard Git repositories, storing metadata in TOML files while maintaining hidden Git references for each branch tip.

GitButler's virtual branch system (also referred to as stacks) provides a way to manage multiple parallel lines of work without polluting the standard Git reference namespace. Unlike traditional Git branches that exist as refs in .git/refs/heads/, virtual branches persist their state in a dedicated TOML configuration file while using hidden references only for the current commit tips. This architecture allows developers to maintain numerous work-in-progress branches while keeping the underlying Git repository clean.

Architecture Overview

The virtual branch system is organized into three distinct layers that separate persistence concerns from operational logic.

State Persistence Layer

Located in crates/gitbutler-stack/src/state.rs, this layer handles the virtual_branches.toml file that stores all virtual branch metadata. The VirtualBranchesHandle struct provides the interface for reading and writing this state, including methods like read_file and write_file that deserialize and serialize the VirtualBranches struct.

In-Memory Model

Defined in crates/gitbutler-stack/src/stack.rs, the Stack struct represents a virtual branch containing one or more StackBranch heads. Each StackBranch (defined in crates/gitbutler-stack/src/stack_branch.rs) acts as a mutable pointer to a commit or patch and manages the underlying Git reference through methods like set_real_reference and rename_real_reference.

Operations and API

High-level functions for creating, amending, applying, and deleting virtual branches reside in crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs and crates/gitbutler-branch-actions/src/lib.rs. The RPC facade in crates/but-api/src/legacy/virtual_branches.rs exposes these operations to the UI and CLI.

Persistent Storage with VirtualBranchesHandle

The VirtualBranchesHandle struct in crates/gitbutler-stack/src/state.rs manages the virtual_branches.toml file located in the project directory. This file contains the default target branch configuration and an array of branch entries.

use gitbutler_stack::VirtualBranchesHandle;

let handle = VirtualBranchesHandle::new(project_path);
let state = handle.read_file()?;

The TOML structure includes a default_target specifying the base branch and commit SHA, plus a branches array containing UUIDs, ordering indices, workspace status flags, and head configurations for each virtual branch.

The Stack and StackBranch Data Model

A virtual branch is represented by the Stack struct in crates/gitbutler-stack/src/stack.rs. Each stack maintains an ordered list of heads through the heads: Vec<StackBranch> field.

pub struct Stack {
    pub id: StackId,                     // Unique UUID
    pub source_refname: Option<Refname>, // Optional upstream branch
    pub upstream: Option<RemoteRefname>, // Remote tracking
    pub order: usize,                    // UI ordering
    pub in_workspace: bool,              // Applied status
    pub heads: Vec<StackBranch>,         // Series of commits/patches
}

Each StackBranch in crates/gitbutler-stack/src/stack_branch.rs manages the actual Git reference through helper methods like set_real_reference and rename_real_reference, ensuring the hidden ref stays synchronized with the stored OID.

Creating Virtual Branches

The creation flow in crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs follows a specific sequence when create_virtual_branch is called:

  1. Load the default target from VirtualBranchesHandle
  2. Generate a unique branch name and compute the next order index
  3. Shift existing stacks to accommodate the new order
  4. Initialize an empty Stack with a single StackBranch pointing to the target SHA
  5. Persist via VirtualBranchesHandle::set_stack
  6. Create the hidden Git reference via add_branch_reference
  7. Update the workspace commit via update_workspace_commit
use gitbutler_branch_actions::create_virtual_branch;
use gitbutler_branch::BranchCreateRequest;

let request = BranchCreateRequest {
    name: Some("feature-x".into()),
    order: None,
    ..Default::default()
};

let stack_entry = create_virtual_branch(&mut ctx, &request)?;
println!("New virtual branch ID: {}", stack_entry.id);

Applying and Unapplying Branches

Virtual branches can be toggled between active (in workspace) and inactive states through the workspace integration layer in crates/gitbutler-workspace/src/lib.rs.

Applying a branch via but_workspace::branch::apply:

  • Checks out the stack's tip commit into the worktree
  • Sets in_workspace = true for the stack
  • Persists the updated state via VirtualBranchesHandle::set_stack

Unapplying via gitbutler_branch_actions::unapply_stack:

  • Removes working tree changes associated with the branch
  • Sets in_workspace = false
  • Triggers garbage collection for unreachable stacks

Amending and Modifying Commits

The amend operation in crates/but-api/src/legacy/virtual_branches.rs allows rewriting the tip of a virtual branch without affecting the underlying TOML metadata structure.

use gitbutler_branch_actions::amend;
use gix::ObjectId;

let new_head_oid: ObjectId = /* new commit SHA */;
let diff_specs = vec![/* file changes as DiffSpec */];

let new_oid = amend(&mut ctx, stack_id, new_head_oid, diff_specs)?;

This delegates to the branch actions layer, which updates the StackBranch head and synchronizes the hidden Git reference through the set_real_reference helper.

Garbage Collection

Inactive virtual branches that have no reachable commits are automatically cleaned up to prevent metadata bloat. The VirtualBranchesHandle::garbage_collect method in crates/gitbutler-stack/src/state.rs scans all stacks listed in virtual_branches.toml, verifies whether their head commits are reachable from the default target branch, and deletes orphaned entries that are no longer accessible.

Summary

  • GitButler's virtual branch system uses a TOML-based state file (virtual_branches.toml) to store branch metadata separately from standard Git refs
  • The architecture separates concerns into persistence (VirtualBranchesHandle), in-memory models (Stack, StackBranch), and high-level operations
  • Virtual branches exist as hidden Git references only when active, keeping the .git/refs/heads/ namespace clean
  • Creation, application, and amendment operations maintain synchronization between the TOML state and underlying Git objects through the branch actions API
  • Garbage collection automatically removes unreachable virtual branches to prevent metadata accumulation

Frequently Asked Questions

What is the difference between a virtual branch and a regular Git branch?

Regular Git branches exist as references in .git/refs/heads/ and are part of the standard Git object model. Virtual branches in GitButler are lightweight overlays stored in a TOML configuration file that only create hidden Git references when actively applied to the workspace. This allows you to maintain many parallel workstreams without cluttering your Git ref namespace or affecting repository history until you choose to integrate changes.

Where does GitButler store virtual branch metadata?

GitButler stores all virtual branch metadata in a file named virtual_branches.toml located in the project directory. This file contains the default target branch configuration, branch UUIDs, ordering indices, workspace status flags, and head configurations. The VirtualBranchesHandle struct in crates/gitbutler-stack/src/state.rs manages all read and write operations to this file, ensuring atomic updates and consistency with the underlying Git repository.

How does GitButler handle virtual branch garbage collection?

The system automatically removes virtual branches that are no longer in the workspace and have unreachable commits. The VirtualBranchesHandle::garbage_collect method in crates/gitbutler-stack/src/state.rs scans all stacks listed in virtual_branches.toml, verifies whether their head commits are reachable from the default target branch, and deletes orphaned entries. This prevents metadata accumulation from abandoned workstreams while preserving active or reachable branch history.

Can virtual branches be converted to regular Git branches?

Yes, virtual branches can be converted to regular Git branches through GitButler's integration features. When you apply a virtual branch to the workspace, GitButler creates a hidden Git reference that points to the branch tip using add_branch_reference and set_real_reference methods. You can then push this reference to a remote or convert it to a standard branch reference. The StackBranch struct in stack_branch.rs provides the reference management helpers that ensure the virtual state remains synchronized with actual Git objects.

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 →