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

> Discover how GitButler implements virtual branches using a stack-based architecture and lightweight TOML configuration to manage parallel workstreams efficiently without ref namespace clutter.

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

---

**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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs), where the `VirtualBranchesHandle` struct manages the [`virtual_branches.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs) provides atomic read/write operations for the [`virtual_branches.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/stack.rs)) contains:

```rust
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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs) | Handles persistence (`VirtualBranchesHandle`) and ordering logic. | [state.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/state.rs) |
| [`crates/gitbutler-stack/src/stack.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/stack.rs) | Defines the `Stack` struct (virtual branch) and its helper constructors. | [stack.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/stack.rs) |
| [`crates/gitbutler-stack/src/stack_branch.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/stack_branch.rs) | Defines `StackBranch` (a mutable head) and reference‑management helpers. | [stack_branch.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/stack_branch.rs) |
| [`crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs) |
| [`crates/but-api/src/legacy/virtual_branches.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but-api/src/legacy/virtual_branches.rs) | Public RPC façade exposing virtual‑branch operations to the UI/CLI. | [virtual_branches.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-api/src/legacy/virtual_branches.rs) |
| [`crates/gitbutler-branch-actions/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-branch-actions/src/lib.rs) | Re‑exports all branch‑action functions (create, amend, move, delete, etc.). | [lib.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/lib.rs) |
| [`crates/gitbutler-workspace/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/lib.rs) | Workspace glue that applies/unapplies virtual branches to the worktree. | [workspace lib.rs](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-workspace/src/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/virtual_branches.toml). This three-layer approach ensures that amending commits updates both the metadata and the underlying Git objects atomically.