GitButler First-Class Conflict Resolution Order: Technical Architecture and CLI Workflow

GitButler treats merge conflicts as persistent repository objects, enabling developers to resolve them in any order while automatically rebasing dependent commits to maintain stack integrity.

GitButler's innovative approach to version control introduces first-class conflict resolution order, a system where merge conflicts become persistent objects stored within the repository itself. Unlike traditional Git workflows that treat conflicts as transient states, the gitbutlerapp/gitbutler codebase implements a deterministic resolution strategy that works across virtual branches. This architecture enables developers to address conflicts in any sequence while maintaining the integrity of their commit stacks.

What Are First-Class Conflicts?

In GitButler, a first-class conflict is a conflicted commit that persists as part of the repository's object store rather than a temporary working directory state. When GitButler detects a merge conflict, it:

  1. Stores the conflict metadata in a dedicated conflicts/ subtree within Git's object store
  2. Marks the commit with a conflicted flag accessible via commit.is_conflicted()
  3. Preserves the conflict state across sessions, allowing developers to revisit and resolve conflicts days later

This persistence is implemented in crates/gitbutler-repo/src/rebase.rs, where the extract_conflicted_files function builds a TOML representation of conflicts and writes it to a blob that becomes part of the commit's tree.

How GitButler Determines Conflict Resolution Order

Conflict Detection Across Virtual Branches

GitButler scans the entire workspace—including all virtual branches (stacks)—to identify conflicted commits. The detection logic lives in crates/but/src/command/legacy/resolve.rs within the find_conflicted_commits function.

This function performs a breadth-first traversal of every stack, collecting commits where commit.is_conflicted() returns true. The traversal captures conflicts across the entire workspace, not just the currently checked-out branch.

The Newest-First Grouping Algorithm

Once detected, GitButler presents conflicts in a deterministic newest-first order, grouped by branch. The find_conflicted_commits function (lines 30-84) implements this by:

  1. Collecting all conflicted commits into a vector during breadth-first traversal
  2. Reversing the collection using into_iter().rev() to achieve reverse-topological order
  3. Grouping results by virtual branch for UI presentation

This ordering ensures that the most recent conflicts appear first, allowing developers to address the latest changes before tackling older, potentially outdated conflicts.

The Resolution Workflow: Step by Step

GitButler provides a dedicated CLI workflow for resolving conflicts. The commands interact with the first-class conflict system to ensure order-independent resolution.

Checking Conflict Status

Before resolving, identify all conflicted commits:


# Human-readable status

but status

# Machine-readable JSON output for scripting

but status --json

# List all conflicted commits grouped by branch, newest first

but resolve status

Entering Resolution Mode

To resolve a specific conflict:


# Enter resolution mode for a specific commit

# <commit-id> is the CLI ID shown by `but status`

but resolve <commit-id>

This command:

  1. Verifies the target commit is conflicted via commit.is_conflicted()
  2. Stores the current conflict map using find_conflicted_commits
  3. Switches to resolution mode via enter_edit_and_return_to_workspace

Finalizing or Canceling Resolution

After editing conflicted files:


# Commit changes and automatically rebase dependent commits

but resolve finish

Or to abort:


# Discard edits and restore pre-resolution workspace

but resolve cancel

The finish command triggers finalize_resolution (lines 170-190 in resolve.rs), which runs rebase::rebase internally and then calls check_for_new_conflicts_after_rebase to handle any newly introduced conflicts.

Automatic Rebasing and Order Independence

The key innovation enabling order-independent resolution is GitButler's automatic rebasing system. When you resolve a conflicted commit, GitButler:

  1. Commits the resolved changes on a temporary branch
  2. Identifies all later commits in the same stack
  3. Rebases those commits onto the new, resolved commit using rebase::rebase from crates/gitbutler-repo/src/rebase.rs
  4. Recomputes the conflict map to detect any new conflicts introduced by the rebase

This mechanism ensures that resolving conflicts in any order produces the same final repository state. The system maintains stack linearity while allowing developers to prioritize which conflicts to tackle first based on context rather than dependency chains.

Key Source Files and Implementation Details

File Primary Responsibility Key Functions
crates/but/src/command/legacy/resolve.rs Whole conflict-resolution flow (detect, enter edit, finish, cancel) find_conflicted_commits, finalize_resolution, check_for_new_conflicts_after_rebase
crates/but/src/args/resolve.rs CLI sub-commands (status, finish, cancel) Argument parsing for resolution commands
crates/gitbutler-operating-modes/src/lib.rs Determines current mode and extracts worktree_conflicts operating_mode, worktree_conflicts extraction (lines 66-139)
crates/gitbutler-repo/src/rebase.rs Persists conflicted-file list into the conflicts/ tree, used for later replay extract_conflicted_files, rebase
crates/gitbutler-oplog/src/oplog.rs Snapshot/restore of the conflicts/ subtree for undo/redo write_conflicts_tree, restore_conflicts_tree
crates/gitbutler-workspace/src/branch_trees.rs Helper that builds a virtual merge and detects file-level conflicts has_conflicts()
crates/gitbutler-repo/src/repository_ext.rs create_wd_tree includes conflicted index entries for accurate conflict snapshots create_wd_tree

Summary

  • First-class conflicts are persistent repository objects stored in a conflicts/ subtree, surviving across sessions and enabling non-linear resolution workflows.
  • Deterministic ordering presents conflicts grouped by virtual branch in newest-first order via reverse-topological traversal in find_conflicted_commits.
  • Order independence allows developers to resolve conflicts in any sequence; GitButler automatically rebases dependent commits using rebase::rebase to maintain stack integrity.
  • Snapshot support integrates conflict states into the oplog system via write_conflicts_tree and restore_conflicts_tree, enabling undo/redo of resolution attempts.
  • CLI workflow provides dedicated commands (but resolve <commit>, but resolve finish, but resolve cancel) that interact with the first-class conflict system.

Frequently Asked Questions

How does GitButler store conflict information differently than standard Git?

Standard Git treats merge conflicts as temporary working directory states that disappear once resolved. GitButler persists conflict metadata in a dedicated conflicts/ subtree within Git's object store, making conflicts durable objects that can be revisited, listed, and resolved in any order across multiple sessions.

Can I resolve conflicts in any order without breaking my branch stack?

Yes. GitButler's architecture specifically enables order-independent resolution. When you resolve a conflicted commit using but resolve finish, the system automatically rebases all later commits in the stack onto your resolution via rebase::rebase. This maintains linear history regardless of which conflict you choose to tackle first.

What happens if I need to undo a conflict resolution?

GitButler includes conflict states in its operation log (oplog) system. When you enter resolution mode, the system calls write_conflicts_tree to snapshot the current state. If you run but resolve cancel or use the undo functionality, restore_conflicts_tree reverts the repository to its pre-resolution condition, including all conflict metadata.

How does GitButler determine which conflicts to show first?

The system uses a deterministic newest-first ordering algorithm implemented in find_conflicted_commits within crates/but/src/command/legacy/resolve.rs. It performs a breadth-first traversal of all virtual branches, collects conflicted commits, then reverses the collection using into_iter().rev() to produce a reverse-topological order. The UI presents these grouped by branch, ensuring the most recent conflicts appear first.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →