# How GitButler's Parallel Branch System Works: A Deep Dive into Virtual Stacking

> Explore GitButler's innovative parallel branch system. Learn how virtual stacks enable simultaneous development and dynamic merging into a single workspace branch for efficient workflows.

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

---

**GitButler's parallel branch system allows developers to work on multiple independent feature branches simultaneously by maintaining isolated virtual stacks that are dynamically merged into a single `gitbutler/workspace` branch using an octopus merge strategy.**

The `gitbutlerapp/gitbutler` repository implements this system through a sophisticated Rust-based architecture that separates branch metadata from Git's native refs. This approach enables developers to apply, unapply, and reorder feature work without rewriting commit history or managing complex rebase operations.

## Core Concepts of the Parallel Branch System

The parallel branch system rests on three foundational abstractions that separate user-visible branches from Git's internal representation.

### Virtual Branches (Stacks)

A **virtual branch** (internally called a `Stack`) is a lightweight metadata structure describing a series of commits belonging to a user-visible branch. Unlike traditional Git branches, these exist outside the `.git/refs` namespace.

In [`crates/gitbutler-stack/src/stack.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/stack.rs), the `Stack` struct defines this abstraction:

```rust
pub struct Stack {
    pub id: StackId,
    pub heads: Vec<StackBranch>,
    pub in_workspace: bool,  // Key flag for parallel operation
    // ... additional fields
}

```

The `in_workspace` boolean determines whether the stack participates in the parallel merge. Stacks persist in a TOML file ([`virtual_branches.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/virtual_branches.toml)) managed by the `VirtualBranchesHandle` API in [`crates/gitbutler-stack/src/state.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs).

### Workspace State

The **WorkspaceState** represents a snapshot of all currently applied parallel branches. It captures the base commit and the tree objects of every active stack.

Defined in [`crates/gitbutler-workspace/src/branch_trees.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/branch_trees.rs):

```rust
pub struct WorkspaceState {
    heads: Vec<git2::Oid>,   // Tree IDs of each applied stack
    base:  git2::Oid,        // Common ancestor commit tree
}

```

This state is ephemeral—recomputed whenever branches are applied or unapplied—and serves as the input for the merge algorithm that produces the working directory view.

### The gitbutler/workspace Branch

The `gitbutler/workspace` branch is a real Git ref that contains a single synthetic commit representing the union of all applied parallel branches. Created during setup in [`crates/but/src/setup.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but/src/setup.rs), this branch never holds user changes directly.

Instead, its commit is the result of an **octopus merge** combining every stack's tree. This allows Git's standard tools to view the parallel work as a single coherent state while preserving the isolation of individual feature branches in the virtual layer.

## How Virtual Branches Are Stored

When users create a branch without supplying a name, GitButler generates a parallel branch automatically. As documented in [`crates/but/src/args/branch.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but/src/args/branch.rs):

```rust
/// If no branch name is provided, a new parallel branch with a generated
/// name will be created.

```

The persistence layer uses the `VirtualBranchesHandle` to serialize stack metadata:

```rust
// crates/gitbutler-stack/src/state.rs
pub fn set_stack(&self, stack: Stack) -> Result<()> {
    let mut vb = self.read_file()?;
    vb.branches.insert(stack.id, stack);
    self.write_file(&vb)
}

```

Each `Stack` contains:
- `heads` – A list of `StackBranch` objects pointing to specific commits
- `in_workspace` – **true** when the stack is actively applied to the workspace

## The Workspace Merge Algorithm

When at least one stack is marked `in_workspace = true`, GitButler rebuilds the workspace view through a multi-step merge process.

### Building the Workspace State

The system first collects the tree objects of all applied stacks via `list_stacks_in_workspace` in [`crates/gitbutler-stack/src/state.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs):

```rust
pub fn list_stacks_in_workspace(&self) -> Result<Vec<Stack>> {
    self.list_all_stacks()
        .map(|stacks| stacks.into_iter().filter(|s| s.in_workspace).collect())
}

```

For each stack, GitButler reads the tree associated with its tip commit. These trees, combined with the workspace base commit, form the `WorkspaceState`.

### Octopus Merge Implementation

The actual merge occurs in [`crates/gitbutler-workspace/src/branch_trees.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/branch_trees.rs) through iterative pairwise merging:

```rust
pub fn merge_workspace(repo: &git2::Repository, workspace: WorkspaceState) -> Result<git2::Oid> {
    let mut output = workspace.base;
    for head in workspace.heads {
        let mut merge_options = git2::MergeOptions::new();
        merge_options.fail_on_conflict(true);
        let mut merge = repo.merge_trees(
            &repo.find_tree(workspace.base)?,
            &repo.find_tree(output)?,
            &repo.find_tree(head)?,
            Some(&merge_options),
        )?;
        output = merge.write_tree_to(repo)?;
    }
    Ok(output)
}

```

This **octopus merge** strategy combines all branch trees into a single tree object. Git's native merge algorithm handles file-level conflicts, ensuring the workspace presents a coherent filesystem state even when parallel branches touch the same files.

## Applying and Unapplying Parallel Branches

The `but apply` and `but unapply` commands toggle a stack's participation in the workspace.

### Applying a Branch

When running `but apply <branch>`, the system executes the logic in [`crates/but-workspace/src/branch/apply.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but-workspace/src/branch/apply.rs):

1. **Validate** the target reference (local or remote tracking)
2. **Mark** the stack `in_workspace = true` via `VirtualBranchesHandle::set_stack`
3. **Re-create** the workspace state with `WorkspaceState::create`, now including the new stack's tree
4. **Octopus-merge** all heads via `merge_workspace`
5. **Update** the `gitbutler/workspace` reference to the new merge commit

Key implementation snippet:

```rust
// crates/but-workspace/src/branch/apply.rs -> function::apply
let ws = workspace;                     // current Workspace projection
let new_state = WorkspaceState::create(ctx, perm)?;  // includes all in‑workspace stacks
let merged_oid = merge_workspace(repo, new_state)?;
repo.reference(
    "refs/heads/gitbutler/workspace", merged_oid, true, "apply parallel branch"
)?;

```

### Unapplying a Branch

`but unapply <branch>` performs the inverse operation, as implemented in [`crates/but-workspace/src/branch/unapply.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but-workspace/src/branch/unapply.rs):

1. Find the stack ID
2. Set `in_workspace = false` via `VirtualBranchesHandle::mark_as_not_in_workspace`
3. Re-compute the workspace state **without** that stack
4. Rewrite the `gitbutler/workspace` branch with the reduced merge

If the unapplied branch was the **only** applied stack, the workspace branch is **deleted**, returning the repository to a standard Git state.

## Garbage Collection and Cleanup

The system automatically removes stale virtual branches through garbage collection logic in [`crates/gitbutler-stack/src/state.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-stack/src/state.rs):

```rust
// crates/gitbutler-stack/src/state.rs -> fn garbage_collect
if !stack.in_workspace && branch_head == repo.merge_base(branch_head, target.sha)? {
    to_remove.push(stack.id);
}

```

Stacks are eligible for removal when they are **not in the workspace** and their head commit has been fully merged into the target branch (indicated by the merge base check).

## Parallel vs. Stacked Branches

Understanding the distinction between parallel and stacked workflows clarifies GitButler's design philosophy:

* **Stacked branches** form a linear series where each new branch builds directly on top of the previous one. This creates a dependency chain that requires careful rebasing when reordering.
* **Parallel branches** remain independent stacks that are **merged together** only within the workspace. They never rewrite each other's history, making it safe to apply many features simultaneously and switch contexts without destructive rebase operations.

This architecture allows developers to treat feature branches as truly independent workstreams while presenting a unified working directory view.

## Summary

GitButler's parallel branch system reimagines branch management through virtual abstraction:

- **Virtual branches** (`Stack` objects) store branch metadata in [`virtual_branches.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/virtual_branches.toml) with an `in_workspace` flag controlling visibility
- **Workspace state** captures the base commit and tree objects of all applied stacks via `WorkspaceState` in [`crates/gitbutler-workspace/src/branch_trees.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/gitbutler-workspace/src/branch_trees.rs)
- **Octopus merging** combines parallel branch trees into a single coherent commit on the `gitbutler/workspace` branch
- **Apply/unapply operations** toggle the `in_workspace` flag and rebuild the merged workspace view without rewriting Git history
- **Garbage collection** automatically removes virtual branches that have been fully merged and are no longer in the workspace

## Frequently Asked Questions

### How does GitButler differ from traditional Git worktrees?

GitButler uses a single working directory managed through virtual branch metadata and octopus merges, whereas Git worktrees create separate directory checkouts for each branch. The parallel branch system allows instantaneous context switching by toggling the `in_workspace` flag and rebuilding the merged tree, without requiring disk operations or directory changes.

### What happens when two parallel branches modify the same file?

When conflicts occur during the octopus merge in `merge_workspace`, Git's standard merge algorithm attempts automatic resolution using the three-way merge strategy between the workspace base, current output tree, and the incoming branch tree. If automatic resolution fails, the `fail_on_conflict(true)` option ensures the operation reports the conflict rather than producing a corrupted state, allowing the user to resolve it through GitButler's interface.

### Can I use GitButler's parallel branches with a standard Git workflow?

Yes, the `gitbutler/workspace` branch is a standard Git ref that can be pushed, pulled, and inspected with normal Git commands. However, the virtual branch metadata stored in [`virtual_branches.toml`](https://github.com/gitbutlerapp/gitbutler/blob/main/virtual_branches.toml) is specific to GitButler. Without GitButler, collaborators see only the merged workspace commit, not the individual parallel branches, making this workflow most effective when team members also use GitButler or when parallel branches are used for personal organization before squashing to traditional branches.