jdx/mise · error

local and origin histories are unrelated; use a fresh setup

Error message

local and origin histories are unrelated; use a fresh setup store to adopt origin, or reconcile the repositories explicitly with Git. Neither history was replaced

What it means

When computing the sync base, read() finds both a local history ref and an origin ref but repo.merge_bases() returns no common ancestor — the two histories are unrelated. The sync deliberately aborts without replacing either history, because fast-forwarding or adopting origin would silently discard one lineage of saved files.

Source

Thrown at src/system/history/sync/graph.rs:31

    pub base: Option<String>,
}

#[derive(Debug)]
pub(crate) struct Candidate {
    pub commit: String,
    expected_local: Option<String>,
    expected_remote: Option<String>,
}

impl Heads {
    pub(crate) fn read(repo: &HistoryRepo) -> Result<Self> {
        let local = repo.ref_oid(HistoryRepo::HISTORY_REF)?;
        let remote = repo.ref_oid(UPSTREAM_REF)?;
        let base = match (&local, &remote) {
            (Some(local), Some(remote)) => {
                let bases = repo.merge_bases(local, remote)?;
                if bases.is_empty() {
                    bail!(
                        "local and origin histories are unrelated; use a fresh setup store to adopt origin, or reconcile the repositories explicitly with Git. Neither history was replaced"
                    );
                }
                // A criss-cross merge has shared ancestry but no unique base.
                // It needs a merge commit, never an arbitrary fast-forward.
                (bases.len() == 1).then(|| bases[0].clone())
            }
            _ => None,
        };
        Ok(Self {
            local,
            remote,
            base,
        })
    }

    /// Preparing a candidate does not advance a branch or write live files.
    /// The caller must finish its complete application before adopting it.

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Force-adopt origin by initializing a fresh setup store from origin (discarding unrelated local history) if origin is authoritative.
  2. Reconcile explicitly with git: graft or merge the two roots manually in the history repo, then push.
  3. Back up saved files, delete the local history ref, and re-pull to start from origin.

Example fix

# before: divergent unrelated histories
$ mise bootstrap dotfiles sync   # fails
# after: adopt origin with a fresh store
$ rm -rf ~/.local/share/mise/history && mise bootstrap dotfiles pull
Defensive patterns

Strategy: fallback

Validate before calling

let local = repo.ref_oid(HistoryRepo::HISTORY_REF)?;
let remote = repo.ref_oid(UPSTREAM_REF)?;
if let (Some(l), Some(r)) = (&local, &remote) {
    if repo.merge_bases(l, r)?.is_empty() {
        eprintln!("histories unrelated; decide adopt-origin vs manual reconcile");
    }
}

Try / catch

match read(repo) {
    Ok(base) => proceed(base),
    Err(e) if e.to_string().contains("histories are unrelated") => {
        eprintln!("prompt: adopt origin (fresh store) or reconcile with git");
        prompt_unrelated_history_action()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling read() on a HistoryRepo where HISTORY_REF and UPSTREAM_REF both exist but share no merge base (disjoint commit graphs).

Common situations: A fresh origin store was force-pushed/re-created while the local machine kept its old history; two machines independently initialized setup stores and both were pushed; restoring origin from a new init instead of cloning.

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 jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/05d79a73808c8870. Report an issue: GitHub.