GitButler

repository·master·Indexed 10 days ago

https://github.com/gitbutlerapp/gitbutler

A modern Git-based version control system featuring a GUI and a CLI (`but`). It is designed to support advanced workflows including stacked branches, parallel development, and AI-powered agentic automation.

Tokens
122.5K
Snippets
419
Records
625
Agent score
97%

What's inside GitButler

  1. What is GitButler?

    master

    GitButler is a desktop Git client designed for managing Git branches with advanced workflows. Key features include:

    • Virtual Branches: Manage independent features simultaneously.
    • Stacked Branches: Work with dependent features.
    • Commit Management: Drag and drop interface for reordering, amending, and squashing commits.
    • AI Integration: Automated generation of commit messages and Pull Request (PR) descriptions.
  2. Overview of GitButler features

    master

    GitButler is a modern Git-based version control interface designed for both humans and AI agents. It provides a GUI (built with Tauri, Svelte, and TypeScript) and a CLI named but (built with Rust). It is designed to work as a drop-in replacement for standard Git interfaces in any existing Git repository.

    Key capabilities include:

    • Stacked Branches: Create branches on top of other branches with automatic restacking when commits are amended.
    • Parallel Branches: Work on multiple branches simultaneously without constant context switching.
    • Easy Commit Management: Perform operations like uncommitting, rewording, amending, moving, splitting, and squashing via drag-and-drop (GUI) or simple commands (CLI), replacing the need for rebase -i.
    • Undo Timeline: A log of all operations that allows for easy undoing or reverting of changes.
    • First Class Conflicts: Commits can be marked as conflicted and resolved at any time and in any order; rebases are designed to always succeed.
    • Forge Integration: Direct authentication with GitHub, GitLab, or Bitbucket to manage Pull Requests, list branches, and view CI statuses.
    • AI Tooling: Built-in handlers for generating commit messages, branch names, and PR descriptions, along with support for agentic hooks and skills.
  3. Understand @gitbutler/but-sdk

    master
    @gitbutler/but-sdk is a local npm package that exposes GitButler Rust APIs to JavaScript/TypeScript environments via a native Node add-on. It provides two distinct 'flavors' of the SDK to support different workspace projection models, ensuring that TypeScript types and the native runtime remain in sync.
  4. Overview of @gitbutler/lite architecture

    master

    The @gitbutler/lite package is an Electron application scaffold using React (Vite) and TanStack Router. The application is split into two distinct process boundaries to maintain security and runtime separation:

    1. ui/ (Renderer Process): Contains the frontend React code. It communicates with the main process via a secure IPC bridge and never accesses native bindings or Node.js APIs directly.
    2. electron/ (Main/Preload Process): Contains the privileged Electron code, including the main process and the preload script. This layer handles Node.js operations, native Rust bindings via @gitbutler/but-sdk, and manages the IPC contract.

    Security is enforced using contextIsolation: true and nodeIntegration: false, with all privileged capabilities exposed to the UI through a contextBridge in the preload script.

  5. Understand Hunk Dependency and the Dependency Graph

    master

    The but-hunk-dependency crate calculates the dependency graph between commits and uncommitted changes.

    Core Concepts

    • Patch Dependency: A patch is considered 'dependent' on another if it cannot be applied on top of it without causing a merge conflict in the stacks. Stacks (formerly virtual branches) must be independently mergeable to main and to each other.
    • Integrity: Dependency ensures stack integrity. For example, it must be impossible to move a commit that introduces file A above a commit that modifies file A.
    • Dependency Goals:
      1. Map uncommitted changes to specific stacks.
      2. Identify which commits depend on preceding commits.
      3. Identify which commits are depended upon by following commits or uncommitted changes (the inverse map).

    The Hunk Range Approach

    To achieve high performance, the crate maintains an ordered list of line ranges (hunk ranges) that track which commit 'owns' specific lines in a file. This data structure acts similarly to a git-blame for a file, representing the file as a one-dimensional vector of line changes.

  6. Handle Single Branch / Normal Git Mode

    master
    When GitButler is not managing a workspace (e.g., in 'normal Git mode' or single-branch mode), the RefInfo behavior changes. In these modes, the system provides unmanaged RefInfo that may behave differently than the standard workspace-managed projection, specifically regarding how segments and legacy stacks are presented.
  7. Prevent whole-file commits when hunks are stale

    master

    To avoid the high-severity risk of accidentally committing an entire file because selected hunks were stale, implement the following logic in uncommittedService.svelte.ts:

    Instead of pushing hunkHeaders: [] (which the backend interprets as a command to commit the whole file), track the stale-skip count per path. If every selected hunk in a file is stale, drop that file from the commit process and notify the user via an info toast.

  8. How IPC type safety works in @gitbutler/lite

    master

    The application uses a contract-first IPC model to ensure end-to-end type safety between the Electron main process and the React renderer:

    1. Contract Definition: The IPC channels and API interface are defined in electron/src/ipc.ts.
    2. Preload Implementation: The preload script implements these interfaces and exposes them to the renderer via window.lite using Electron's contextBridge.
    3. Renderer Consumption: The UI consumes these types through ambient declarations (defined in ui/src/electron.d.ts) and uses type-only imports from the Electron subtree (often via the #electron/* path alias).

    This pattern ensures that the renderer can call window.lite.* methods with full TypeScript support without having runtime access to the underlying IPC or native modules.

  9. Choose between SDK flavors: linear vs graph

    master

    The SDK provides two flavors depending on how you want WorkspaceState to project data. You must import the correct entry point to ensure your TypeScript types match the underlying native binary:

    1. @gitbutler/but-sdk (linear flavor): Built without the graph-workspace feature. WorkspaceState uses the legacy RefInfo-based headInfo projection.
    2. @gitbutler/but-sdk/graph (graph flavor): Built with the --features graph-workspace feature. WorkspaceState uses the graphWorkspace (DetailedGraphWorkspace) projection.

    Note: If you are using the desktop app, you must keep your import flavor in lockstep with the gitbutler-tauri backend's graph-workspace feature setting.

    import { ... } from '@gitbutler/but-sdk'; // Linear flavor
    import { ... } from '@gitbutler/but-sdk/graph'; // Graph flavor
  10. Watcher deduplication and subscription behavior

    master

    The architecture distinguishes between deduplicating the resource (the Rust watcher) and the listeners (the subscriptions):

    Project-level Deduplication

    WatcherManager in Node ensures there is at most one active Rust watcher per project ID. If multiple requests to subscribe to the same project occur concurrently, the system reuses the existing watcher state or the pending promise from pendingProjectWatchers to avoid spawning duplicate Rust processes.

    Subscription Non-deduplication

    While the Rust watcher is shared, subscriptions are not deduplicated. Every call to subscribeToProject(...) creates:

    • A unique subscriptionId.
    • A unique eventChannel.
    • A separate entry in watcherSubscriptions.

    This allows multiple consumers (e.g., different windows or independent listeners in one window) to coexist for the same project, each with its own channel lifecycle. Note: The UI is expected to create only one subscription per project.

  11. Understand but-server localhost-only security enforcement

    master

    The but-server uses a security middleware (localhost_only_middleware) to prevent unauthorized remote access.

    Connection Validation

    The middleware performs the following checks on every connection:

    1. Extracts the client's socket address.
    2. Verifies if the IP address is a loopback address (127.0.0.1 for IPv4 or ::1 for IPv6).
    3. Rejects any connection from a non-localhost address with an HTTP 403 Forbidden error.

    CORS Validation

    In addition to IP checks, the server validates the Origin header. It only allows origins that use the http scheme and have localhost as the host (e.g., http://localhost or http://localhost:3000).

    Troubleshooting

    If a connection is rejected by the security middleware, it is logged with the following warning format: Rejected non-localhost connection from: <ip_address>

  12. Understand the GitButler Workspace Model

    master

    Unlike traditional Git which uses serial branching (one branch at a time via git checkout), GitButler uses a Parallel Stacks model.

    In a GitButler workspace, you work on multiple branches simultaneously. All applied branches are merged into your working directory at once, meaning you don't need to switch contexts to work on different tasks.

    Key concepts:

    • The gitbutler/workspace branch: A merge commit containing all applied stacks. Do not interact with this branch directly; use but commands instead.
    • Applied vs Unapplied:
      • Applied branches: Active in your working directory; you can make changes and commit to them.
      • Unapplied branches: Exist in the system but are not active in your working directory. Use but apply or but unapply to toggle their state.