GitButler Rust Backend Architecture: A Deep Dive into the Modular Design
GitButler's Rust backend employs a layered crate architecture centered on a global Context, virtual branch state management, and octopus-merge workspace calculations to enable advanced stacked development workflows.
GitButler is an open-source Git client that reimagines version control through virtual branches and stacked changes. The GitButler Rust backend architecture provides the foundation for these capabilities through a carefully modularized system of crates that handle repository context, persistent state, and high-level branch operations.
Application Context and Global State
The Context (but_ctx::Context) serves as the central entry point for every backend operation. Defined in crates/but-ctx/src/lib.rs, this struct provides lazy-loaded repository access, global settings, and thread-safe permission management.
The Context maintains:
- Global settings (
AppSettings) loaded from the user configuration directory. - Lazy-loaded repositories for both
gix(pure-Rust) andgit2(libgit2) viaOnDemandwrappers. - Cached workspace projection (
but_graph::projection::Workspace) for fast access to the current working state. - Permission tokens to prevent deadlocks during nested operations.
All mutable operations require explicit permissions obtained through ctx.exclusive_worktree_access() (for writes) or ctx.shared_worktree_access() (for reads). This design prevents deadlocks when nested calls also need repository locks.
use but_ctx::Context;
let ctx = Context::open("/path/to/repo")?;
let repo = ctx.repo.get()?; // gix repository (fast diffing)
let git2 = ctx.git2_repo.get()?; // libgit2 for legacy ops
let (repo, ws, db) = ctx.workspace_and_db()?; // Cached workspace + DB
Virtual Branch Management
Virtual branches in GitButler are implemented as Stacks—user-visible work streams stored separately from Git's native ref namespace. The gitbutler_stack crate manages this state through three core types.
Stack (crates/gitbutler-stack/src/stack.rs) represents a high-level virtual branch with an ID, display order, workspace inclusion flag, and branch heads.
StackBranch (crates/gitbutler-stack/src/stack_branch.rs) wraps individual Git references within a stack, storing the head OID, display name, and associated PR numbers.
VirtualBranchesHandle (crates/gitbutler-stack/src/state.rs) provides thread-safe CRUD operations on the virtual_branches.toml file that persists stack metadata.
use gitbutler_stack::VirtualBranchesHandle;
use gitbutler_branch_actions::BranchCreateRequest;
// Acquire exclusive permission
let mut perm = ctx.exclusive_worktree_access().write_permission()?;
// Create a new virtual branch (stack)
let create_req = BranchCreateRequest {
name: Some("feature/login".to_string()),
order: None,
..Default::default()
};
let stack = gitbutler_branch_actions::create_virtual_branch(&ctx, &create_req, &mut perm)?;
println!("Created stack {} with id {}", stack.name, stack.id);
Workspace Calculation and Octopus Merging
The Workspace represents a merged view of all applied stacks and the base branch, enabling fast diffing and safe checkouts. Located in crates/gitbutler-workspace/src/lib.rs, the workspace is computed as an octopus merge of the default target commit and all applied stack heads.
Key functions include:
workspace_base– Computes the merge-base of all stack heads and the default target.WorkspaceState::create– Builds the merged tree IDs for both heads and base.update_uncommitted_changes– Reapplies uncommitted changes on top of a new workspace snapshot.
The octopus merge is performed in merge_workspace, which iteratively merges each head into the running result with fail_on_conflict(true) to abort on conflicts.
use gitbutler_workspace::{workspace_base, WorkspaceState, update_uncommitted_changes};
// Computing the workspace base
let base_oid = gitbutler_workspace::workspace_base(&ctx, &perm)?;
println!("Workspace base OID: {}", base_oid);
// Capturing workspace state for change management
let old_ws = WorkspaceState::create(ctx, &perm)?;
// ... perform branch operations ...
let new_ws = WorkspaceState::create(ctx, &perm)?;
// Reapply uncommitted changes to new workspace state
let mut exclusive = ctx.exclusive_worktree_access().write_permission()?;
update_uncommitted_changes(ctx, old_ws, new_ws, &mut exclusive)?;
High-Level Branch Actions API
All user-facing operations delegate to the actions module in crates/gitbutler-branch-actions/src/actions.rs. This API orchestrates permission checks, workspace mode validation, and state changes while providing a stable interface for both CLI and GUI front-ends.
Notable actions include:
create_virtual_branch– Creates new stacks with proper ordering and persistence.amend– Rewrites the tip of a stack by creating a new commit and updating the reference.squash_commits– Combines a range of commits into a single commit.move_branch– Reorders stacks by updating theorderfield and persisting changes.integrate_upstream_commits– Pulls remote changes into a stack while respecting upstream tracking.unapply_stack– Removes a stack from the workspace while preserving metadata.
Source: [actions.rs (crates/gitbutler-branch-actions/src/actions.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/actions.rs)
Git Integration Layer
GitButler abstracts over two Git libraries to balance performance and compatibility. The dual library strategy allows different operations to use the most efficient underlying implementation.
gix (pure-Rust) handles performance-critical operations like fast diffing, merge-base calculation, and safe merging. The Context provides clone_repo_for_merging optimized for gix operations.
git2 (libgit2 bindings) handles legacy operations requiring working-tree checkout, detailed reflog access, and commit creation.
The Context lazily initializes both repositories via OnDemand wrappers, allowing crates to select the appropriate API. For example, workspace_base uses gix for merge-base calculation, while unapply_stack uses git2 for checkout operations.
CLI and GUI Front-Ends
Both the command-line interface and the desktop application share identical backend logic through the Context API, ensuring consistent behavior across interfaces.
CLI Implementation (crates/but/src/main.rs) parses command-line arguments and forwards them to the actions API. Commands like but create or but stack squash eventually call gitbutler_branch_actions::create_virtual_branch or related functions.
Tauri GUI (crates/gitbutler-tauri) provides the desktop interface built with Svelte and TypeScript. The Rust side exposes Tauri commands that invoke the same Context-based APIs used by the CLI, ensuring the GUI and CLI remain functionally equivalent.
Source: [but CLI entry (crates/but/src/main.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but/src/main.rs)
Key Source Files
| File | Purpose |
|---|---|
[Context (crates/but-ctx/src/lib.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but-ctx/src/lib.rs) |
Global application context, lazy repo loading, permission handling. |
[VirtualBranchesHandle (crates/gitbutler-stack/src/state.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/state.rs) |
Reads/writes virtual_branches.toml; CRUD for stacks. |
[Stack (crates/gitbutler-stack/src/stack.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/stack.rs) |
Core data model for a virtual branch (stack). |
[StackBranch (crates/gitbutler-stack/src/stack_branch.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-stack/src/stack_branch.rs) |
Representation of individual heads within a stack. |
[workspace_base & WorkspaceState (crates/gitbutler-workspace/src/lib.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-workspace/src/lib.rs) |
Octopus merge of applied stacks; provides base OID. |
[Branch Creation (crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs) |
Implements create_virtual_branch and related logic. |
[High‑level actions (crates/gitbutler-branch-actions/src/actions.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-branch-actions/src/actions.rs) |
Public API used by CLI/GUI for all branch operations. |
[Watcher Handler (crates/gitbutler-watcher/src/handler.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/gitbutler-watcher/src/handler.rs) |
Filesystem event handling (auto‑refresh of workspace). |
[CLI entry point (crates/but/src/main.rs)](https://github.com/gitbutlerapp/gitbutler/blob/master/crates/but/src/main.rs) |
Parses command‑line arguments and forwards to the action layer. |
[Tauri GUI (crates/gitbutler-tauri)](https://github.com/gitbutlerapp/gitbutler/tree/master/crates/gitbutler-tauri) |
Front‑end UI that calls the same Rust backend via Tauri commands. |
Summary
- The Context (
but_ctx::Context) serves as the central entry point, managing lazy-loaded repositories and permission tokens for thread-safe operations. - Virtual branches are modeled as Stacks stored in
virtual_branches.toml, managed byVirtualBranchesHandlefor persistence and CRUD operations. - The Workspace computes an octopus merge of all applied stacks and the base branch, providing a unified working directory view via
WorkspaceState. - High-level actions in
gitbutler_branch_actionsorchestrate user operations like creating, squashing, and reordering branches while enforcing permission safety. - Dual Git libraries (
gixfor performance,git2for compatibility) allow the backend to optimize operations while maintaining broad Git compatibility. - Shared backend between CLI (
but) and Tauri GUI ensures identical behavior across interfaces through the sameContext-based APIs.
Frequently Asked Questions
What is the purpose of the Context in GitButler's backend?
The Context (but_ctx::Context) acts as the central facade for all backend operations. It provides lazy-loaded access to both gix and git2 repositories, manages global application settings, and controls access permissions through RepoExclusive and RepoShared tokens to prevent deadlocks during nested operations.
How does GitButler store virtual branch metadata?
Virtual branch metadata is persisted in a TOML file (virtual_branches.toml) within the repository. The VirtualBranchesHandle type in crates/gitbutler-stack/src/state.rs provides thread-safe CRUD operations on this file, while the Stack and StackBranch types model the in-memory representation of virtual branches and their constituent heads.
What is an octopus merge in GitButler's workspace calculation?
The octopus merge is a Git operation that combines multiple branch heads into a single tree. In crates/gitbutler-workspace/src/lib.rs, the merge_workspace function iteratively merges the default target commit with all applied stack heads to create the WorkspaceState. This produces a unified working directory view that incorporates changes from all active virtual branches simultaneously.
Why does GitButler use both gix and git2 libraries?
GitButler employs a dual library strategy to balance performance and compatibility. The gix crate (pure-Rust) handles performance-critical operations like fast diffing, merge-base calculation, and safe merging. The git2 crate (libgit2 bindings) handles legacy operations requiring working-tree checkout, detailed reflog access, and commit creation. The Context lazily initializes both repositories, allowing each crate to select the most efficient API for its specific task.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →