GitoxideLabs/gitoxide · error

BUG: tries to obtain object id from symbolic target

Error message

BUG: tries to obtain object id from symbolic target

What it means

`Reference::id()` on a `gix::Reference` panics with this message when the reference is symbolic (points at another ref, e.g. HEAD -> refs/heads/main) rather than directly at an object. The library deliberately panics because the caller asked for an object id that a symbolic target cannot provide. The non-panicking counterpart `try_id()` returns `None` instead.

Solutions

  1. Check the reference kind first and only call `id()` on direct refs: use `reference.try_id()` and handle `None`.
  2. If the ref is symbolic, call `.into_fully_peeled_id()` (or follow the symbolic chain) to resolve to the final object id.
  3. For HEAD specifically, use `repo.head()` / `repo.head_id()` / `repo.head_commit()` which handle symbolic and unborn states.

Example fix

// before
let id = reference.id();
// after
let id = match reference.try_id() {
    Some(id) => id,
    None => reference.into_fully_peeled_id()?, // symbolic: peel to object
};
Defensive patterns

Strategy: type-guard

Validate before calling

if reference.try_id().is_none() {
    // symbolic ref: resolve or handle
}

Type guard

fn is_direct(r: &gix::Reference<'_>) -> bool { r.try_id().is_some() }

Prevention

When it happens

Trigger: Calling `.id()` on a `Reference` obtained from e.g. `repo.head().into_reference()` or `repo.find_reference(...)` when that reference is symbolic, most commonly HEAD before any commit exists (unborn HEAD) or a detached-state check skipped.

Common situations: Reading HEAD on a freshly `git init`-ed repository where HEAD points to a yet-unborn branch; iterating refs and assuming all are direct; code ported from git2 that used `resolve()` implicitly.

Related errors


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

Appendix: source

Thrown at gix/src/reference/mod.rs:37

pub mod log;

mod edits;
pub use edits::{delete, set_target_id};

/// Access
impl<'repo> Reference<'repo> {
    /// Returns the attached id we point to, or `None` if this is a symbolic ref.
    pub fn try_id(&self) -> Option<Id<'repo>> {
        match self.inner.target {
            gix_ref::Target::Symbolic(_) => None,
            gix_ref::Target::Object(oid) => oid.to_owned().attach(self.repo).into(),
        }
    }

    /// Returns the attached id we point to, or panic if this is a symbolic ref.
    pub fn id(&self) -> Id<'repo> {
        self.try_id()
            .expect("BUG: tries to obtain object id from symbolic target")
    }

    /// Return the target to which this reference points to.
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    /// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }
    /// # let repo = doctest::open_repo(doctest::basic_repo_dir()?)?;
    /// let branch = repo.find_reference("main")?;
    ///
    /// assert_eq!(branch.target().try_id().expect("direct target"), repo.head_id()?.as_ref());
    /// # Ok(()) }
    /// ```
    pub fn target(&self) -> gix_ref::TargetRef<'_> {
        self.inner.target.to_ref()
    }

View on GitHub (pinned to e73179060b)