Skip to content

BranchBase Technical Architecture 🏗️

This document outlines the internal architecture, design principles, and lifecycle flow of BranchBase.


1. High-Level System Architecture

BranchBase sits between the developer's Git workflow, their application code, and the underlying database instance (typically running in Docker or natively).

graph TD
    subgraph Developer Workspace
        Git[Git CLI / IDE] -->|checkout / switch| GitHook[BranchBase Git Hook\n.git/hooks/post-checkout]
        App[Application Backend\ne.g., Next.js, Rails, FastAPI] -->|Queries to Port 5432| Proxy[BranchBase Transparent Proxy]
    end

    subgraph BranchBase Core
        GitHook -->|Trigger event| CoreDaemon[BranchBase Core Engine]
        Proxy -->|Query Router| CoreDaemon
        CoreDaemon -->|Branch Resolution| GitResolver[Git Head Resolver]
        CoreDaemon -->|Snapshot / Clone| DBDriver[Database Engine Driver]
    end

    subgraph Database Layer [Docker / Native]
        DBDriver -->|CREATE DATABASE TEMPLATE| PG[(PostgreSQL)]
        DBDriver -->|Reflink / CoW Copy| SQLite[(SQLite)]
        DBDriver -->|Volume / DB Clone| MySQL[(MySQL)]
    end

2. Core Components

2.1 The Git Context Resolver (internal/git)

  • Responsibility: Determines active, local, and merged branches in the repository without invoking uncontrolled subshells.
  • Mechanism:
  • ResolveCurrentBranch: Reads .git/HEAD (or worktree gitdir) directly to resolve active branch in sub-millisecond time.
  • ResolveLocalBranches: Queries git for-each-ref --format=%(refname:short) refs/heads with automatic fallback to filesystem inspection of .git/refs/heads/ and .git/packed-refs (retaining full branch awareness post-git gc).
  • ResolveMergedBranches: Queries git branch --merged <defaultBranch> using explicit argument vectors to identify safe deletion candidates.
  • Sanitizes branch names for database naming compatibility (feature/stripe-v2 $\rightarrow$ feature_stripe_v2).

2.2 The Database Driver Interface (internal/driver)

Every supported database implements a standard Go interface:

type Driver interface {
    // Name returns the driver identifier (e.g., "postgres", "sqlite")
    Name() string

    // Ping verifies connectivity to the underlying database engine
    Ping(ctx context.Context) error

    // BranchExists checks if a database for the given branch already exists
    BranchExists(ctx context.Context, branchName string) (bool, error)

    // CreateBranch clones sourceBranch into targetBranch
    CreateBranch(ctx context.Context, sourceBranch, targetBranch string) error

    // DeleteBranch tears down the ephemeral database
    DeleteBranch(ctx context.Context, branchName string) error

    // ListBranches returns all databases managed by BranchBase
    ListBranches(ctx context.Context) ([]BranchInfo, error)

    // Close releases database connections and engine pools
    Close() error
}

PostgreSQL Implementation:

Postgres natively supports instant database cloning via the TEMPLATE directive with lib/pq connection management:

-- Step 1: Disconnect any active connections to template (if needed)
SELECT pg_terminate_backend(pid) FROM pg_stat_activity 
WHERE datname = 'myapp_dev_main' AND pid <> pg_backend_pid();

-- Step 2: Instant copy-on-write clone
CREATE DATABASE "myapp_dev_feature_billing" TEMPLATE "myapp_dev_main";
Idempotency is preserved by gracefully handling PostgreSQL SQLSTATE 42P04 (duplicate_database).

  • Connection Pooling: Uses standard database/sql connection pooling (25 open / 5 idle connections by default). For short-lived operations such as branchbase hook-trigger, a lightweight single-connection profile (1 open / 1 idle connection) is used to minimize runtime allocation overhead while avoiding TCP re-handshakes across sequential queries.

SQLite Implementation:

For SQLite, BranchBase utilizes filesystem-level Copy-on-Write (CoW) snapshots with .db-wal and .db-shm replication: * macOS (APFS): Native clonefile(2) syscall for instant Copy-on-Write snapshots, with chunked copy fallback on unsupported filesystems. * Linux (Btrfs / XFS): ioctl(FICLONE) reflink copying. * Windows & Fallback: Buffered stream copy with full file sync.

MySQL / MariaDB Implementation:

MySQL does not support native TEMPLATE cloning. BranchBase implements schema-level replication: 1. Schema Creation: CREATE DATABASE <target> with backtick-quoted identifiers (QuoteIdentifier). 2. Table Cloning: Iterates information_schema.TABLES and executes CREATE TABLE <target>.<table> LIKE <source>.<table> for each table. 3. Data Replication: INSERT INTO <target>.<table> SELECT * FROM <source>.<table> to copy rows. 4. Storage Metrics: Queries information_schema.TABLES for DATA_LENGTH + INDEX_LENGTH to report branch database sizes.

  • Connection Pooling: Uses standard database/sql connection pooling (25 open / 5 idle connections by default), matching the PostgreSQL driver profile.

2.3 The Transparent TCP Proxy (internal/proxy)

To ensure developers never have to touch .env or restart their dev servers when switching branches:

  1. The proxy listens on the standard port (e.g., 5432 for Postgres).
  2. The real database container is remapped to an internal port (e.g., 5433 or a UNIX domain socket).
  3. On-Demand (JIT) Branch Provisioning:
  4. If incoming connection targets an unprovisioned branch, Server.ensureBranchExists() provisions the database on the fly from the default branch.
  5. Guarded by a refcounted keyedMutex using double-checked locking: fast-path check avoids locking for existing databases; slow-path lock serializes concurrent incoming connections for the same missing branch.
  6. Strictly validates and propagates branch existence errors on both fast and slow paths, preventing accidental database overwrites.
  7. Allocates dedicated 5-second timeout contexts once the lock is acquired, ensuring provisioning operations never time out prematurely under high lock contention.
  8. Wire-Protocol Rewriting & Error Reporting:
  9. Intercepts PostgreSQL StartupMessage, rewrites database parameter to branch database (myapp_dev_feature_billing), responds to SSL negotiation, and streams bidirectionally with zero overhead.
  10. Fail-Closed Invariant: If database rewriting fails (e.g. identifier exceeds 63 bytes or invalid characters), the connection is immediately aborted rather than forwarded to the default database.
  11. Wire Error Diagnostic: If JIT provisioning, backend connection, or database rewriting fails, the proxy builds and writes a standard PostgreSQL ErrorResponse ('E') packet (with severity FATAL, SQLSTATE code, and descriptive error message) before closing the client socket, ensuring developers receive clear diagnostics in CLI tools (psql) and ORMs (Prisma, dbt).
  12. TLS/SSL Client Negotiation:
  13. Supports clients connecting with sslmode=require by generating self-signed ECDSA development certificates at startup.
  14. Transparently upgrades client connections to TLS when the PostgreSQL SSLRequest packet is received.
  15. UNIX Domain Socket Support:
  16. Both the listener and backend target can be configured as UNIX domain sockets (e.g., /tmp/.s.PGSQL.5432), eliminating TCP overhead for local connections.
  17. Graceful Connection Draining:
  18. On Stop(), the proxy tracks all active connections (activeConns map[net.Conn]struct{}) and waits up to drainTimeout for in-flight queries to complete before force-closing remaining connections.

3. The Lifecycle of a Branch Switch

sequenceDiagram
    autonumber
    actor Dev as Developer
    participant Git as Git CLI
    participant Hook as post-checkout Hook
    participant BB as BranchBase Daemon
    participant DB as Postgres Instance
    participant Proxy as BranchBase Proxy

    Dev->>Git: git checkout -b feature/auth
    Git->>Hook: Trigger post-checkout(old_head, new_head, 1)
    Hook->>BB: Notify branch_switch("main", "feature/auth")
    BB->>DB: Check if db_feature_auth exists
    alt Database does not exist
        BB->>DB: CREATE DATABASE db_feature_auth TEMPLATE db_main;
        DB-->>BB: OK (Instant)
    end
    BB->>Proxy: Update active_branch_routing("feature/auth")
    Proxy-->>Dev: Ready!

    Note over Dev,Proxy: App sends query: "SELECT * FROM users;"
    Proxy->>DB: Forwarded to db_feature_auth transparently

4. Branch Cleanup & Pruning

Over time, working on dozens of feature branches can accumulate disk space.

  • Command: branchbase prune [--dry-run] [--force]
  • Algorithm:
  • Inspects active, local, and merged Git branches via git.ResolveCurrentBranch(), git.ResolveMergedBranches(), and git.ResolveLocalBranches(). Aborts immediately if active branch resolution fails to safeguard the active database.
  • Identifies all databases matching the pattern <base_db>_<branch>, excluding protected base databases and the currently active branch.
  • If --dry-run is provided, previews candidates and freed disk space without deleting.
  • Prompts interactive confirmation ([y/N]) before deletion unless --force / -f is specified.
  • Safely deletes ephemeral databases, freeing allocated storage.

5. Security & Isolation Guarantees

  • Zero Cloud Dependency: BranchBase runs strictly on localhost or via private UNIX domain sockets. No data or telemetry leaves the machine.
  • Non-Destructive: BranchBase never modifies or drops the base_database (e.g., main). Default branches are marked as protected by default.
  • Ephemeral Workspaces: Can be integrated into Docker Compose workflows via named volumes.