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

> GitButler revolutionizes merge conflict resolution by treating conflicts as persistent objects. Resolve them in any order while maintaining stack integrity with automatic rebasing. Learn the technical architecture and CLI workf...

- Repository: [GitButler/gitbutler](https://github.com/gitbutlerapp/gitbutler)
- Tags: architecture
- Published: 2026-02-16

---

**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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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:

```bash

# 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:

```bash

# 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:

```bash

# Commit changes and automatically rebase dependent commits

but resolve finish

```

Or to abort:

```bash

# Discard edits and restore pre-resolution workspace

but resolve cancel

```

The `finish` command triggers `finalize_resolution` (lines 170-190 in [`resolve.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/crates/but/src/args/resolve.rs) | CLI sub-commands (`status`, `finish`, `cancel`) | Argument parsing for resolution commands |
| [`crates/gitbutler-operating-modes/src/lib.rs`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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`](https://github.com/gitbutlerapp/gitbutler/blob/main/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.