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:
- Stores the conflict metadata in a dedicated
conflicts/subtree within Git's object store - Marks the commit with a conflicted flag accessible via
commit.is_conflicted() - 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:
- Collecting all conflicted commits into a vector during breadth-first traversal
- Reversing the collection using
into_iter().rev()to achieve reverse-topological order - 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:
- Verifies the target commit is conflicted via
commit.is_conflicted() - Stores the current conflict map using
find_conflicted_commits - 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:
- Commits the resolved changes on a temporary branch
- Identifies all later commits in the same stack
- Rebases those commits onto the new, resolved commit using
rebase::rebasefromcrates/gitbutler-repo/src/rebase.rs - 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::rebaseto maintain stack integrity. - Snapshot support integrates conflict states into the oplog system via
write_conflicts_treeandrestore_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →