GitoxideLabs/gitoxide · error

the reword target or one of its descendants must be pinned

Error message

the reword target or one of its descendants must be pinned

What it means

Rewording a commit that is not reachable from HEAD and not covered by any pin would leave the old commit garbage-collectable with no way back. ensure_retained_target requires that the reword target (or a pinned descendant of it) is retained — pinned — unless HEAD itself is attached to the history containing it. This protects against rewriting commits that nothing references.

Solutions

  1. Pin the target (or a descendant) with `tix pin` before rewording
  2. Re-attach HEAD to the branch containing the target, then reword
  3. Verify pins with `tix history` / pin listing to see why retention check fails

Example fix

// before
tix reword <oid> -m "new message"   # detached HEAD, no pins
// after
tix pin <oid>
tix reword <oid> -m "new message"
Defensive patterns

Strategy: validation

Validate before calling

// Before rewording, ensure the target is retained
let pins = crate::history::all_pins(&repo)?;
let attached = repo.head_detached() == Ok(false);
if !attached && !pins.iter().any(|p| graph.is_ancestor(target, p.id)) {
    // create a pin first
}

Try / catch

if let Err(e) = tix_reword(target, msg) {
    if e.to_string().contains("must be pinned") {
        tix_pin(target)?;
        tix_reword(target, msg)?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling `tix reword` for a target OID while in detached HEAD state and no pin in `tix history` pins covers the target via is_ancestor.

Common situations: Rewording an old commit on a detached HEAD; pins expired or never created; editing a commit from a branch that was since deleted or rewritten.

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/2b6b6a7f15487743. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/command/reword.rs:114

    let head = repository.head().context("could not read HEAD after editing commit")?;
    let attached_head = !head.is_detached() && head.id().map(gix::Id::detach) == Some(target);
    drop(head);
    ensure_retained_target(&graph, target, &pins, attached_head)?;
    let output_repository = repository.clone();
    finish_editor(
        &output_repository,
        crate::edit::reword::apply(repository, &graph, target, &edited)?,
    )
}

fn ensure_retained_target(
    graph: &crate::history::HistoryGraph,
    target: gix::ObjectId,
    pins: &[crate::history::Pin],
    attached_head: bool,
) -> Result<()> {
    if !attached_head && !pins.iter().any(|pin| graph.is_ancestor(target, pin.id)) {
        anyhow::bail!("the reword target or one of its descendants must be pinned");
    }
    Ok(())
}

pub(super) fn explicit_message(args: &MessageArgs, mut stdin: impl Read) -> Result<Option<Vec<u8>>> {
    if !args.message.is_empty() {
        let mut out = Vec::new();
        for (index, message) in args.message.iter().enumerate() {
            if index > 0 {
                out.extend_from_slice(b"\n\n");
            }
            out.extend_from_slice(
                gix::path::os_str_into_bstr(message)
                    .with_context(|| format!("message {} is not valid UTF-8", index + 1))?,
            );
        }
        return Ok(Some(out));
    }

View on GitHub (pinned to e73179060b)