GitoxideLabs/gitoxide · error

the conflicted HEAD attachment does not directly reference

Error message

the conflicted HEAD attachment does not directly reference {commit}

What it means

After confirming HEAD's id matches the conflict commit, the code checks the undo-state of HEAD's referent: it must directly reference (State::Object) the conflict commit, not be unborn, symbolic to something else, or resolving indirectly. The ensure fires when `edit::undo::state` reports anything other than `State::Object(commit)`.

Solutions

  1. Re-attach HEAD to a reference that directly points at the conflict commit (e.g. `git checkout -B <branch> <commit>`).
  2. Clear/rebuild the stale undo state (`edit::undo::state`) so it reflects the actual HEAD referent.
  3. Repeat the conflict checkout so HEAD attachment and undo state are written together.

Example fix

// before
anyhow::ensure!(
    edit::undo::state(&repository, name.as_ref())? == edit::undo::State::Object(commit),
    "the conflicted HEAD attachment does not directly reference {commit}"
);
// after
if edit::undo::state(&repository, name.as_ref())? != edit::undo::State::Object(commit) {
    repository.reference(name.as_bstr(), commit, gix::refs::transaction::PreviousValue::Any)?;
}
Defensive patterns

Strategy: validation

Validate before calling

let state = edit::undo::state(&repo, referent.as_bstr())?;
assert!(matches!(state, edit::undo::State::Object(id) if id == commit),
    "HEAD attachment must directly reference the conflict commit");

Type guard

fn directly_references(state: &edit::undo::State, commit: gix::ObjectId) -> bool {
    matches!(state, edit::undo::State::Object(id) if *id == commit)
}

Try / catch

match edit::undo::state(&repo, name.as_bstr()) {
    Ok(s) if s != edit::undo::State::Object(commit) => rebuild_undo_state(),
    Ok(_) => proceed(),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: The HEAD referent's recorded undo state is not a direct object reference to `commit` — e.g. HEAD is unborn, attached to a branch pointing elsewhere, or the ref log/state was mutated after checkout.

Common situations: Undo bookkeeping interleaved with manual git commands that changed the branch HEAD points to; a detached-HEAD checkout where the expected attachment assumption breaks; stale undo state files.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/8d24f9c5ab10eb07. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/lib.rs:4380

    changes.clear();
    result
}

fn conflict_head(repository_path: &Path, bare: bool, commit: gix::ObjectId) -> Result<ConflictHead> {
    let repository = open_repository(repository_path, bare, false)
        .context("could not reopen the repository after checking out a conflict")?;
    let head = repository.head().context("could not inspect the conflicted HEAD")?;
    let id = head
        .id()
        .map(gix::Id::detach)
        .context("the conflicted HEAD is unborn")?;
    anyhow::ensure!(id == commit, "the conflict checkout did not leave HEAD at {commit}");
    let reference = head.referent_name().map(ToOwned::to_owned);
    drop(head);
    let name = reference
        .clone()
        .unwrap_or_else(|| "HEAD".try_into().expect("valid reference name"));
    anyhow::ensure!(
        edit::undo::state(&repository, name.as_ref())? == edit::undo::State::Object(commit),
        "the conflicted HEAD attachment does not directly reference {commit}"
    );
    let parents = repository
        .find_commit(commit)
        .context("could not find the checked-out conflict commit")?
        .parent_ids()
        .map(gix::Id::detach)
        .collect();
    Ok(ConflictHead { reference, parents })
}

fn reconcile_external_conflict(
    repository_path: &Path,
    bare: bool,
    pending: &mut Option<PendingConflictResolution>,
) -> Result<ExternalConflictResolution> {
    let state = pending

View on GitHub (pinned to e73179060b)