GitoxideLabs/gitoxide · error

cannot time-travel with unresolved index conflicts

Error message

cannot time-travel with unresolved index conflicts

What it means

`perform_reporting_rebased` refuses to start a time-travel operation while the repository index contains entries that are not unconflicted (i.e. an unresolved merge conflict is staged or present in the index). Before moving HEAD or materializing a different review state, the index must be clean of conflict stages, otherwise the conflict state could be silently lost or mangled. It is a guard ensuring time-travel only happens from a coherent index state.

Solutions

  1. Resolve the conflicted files and stage them (`git add <files>`) so the index only holds unconflicted stage-0 entries, then retry.
  2. Abort the in-progress merge/rebase with `git merge --abort` or `git rebase --abort` to clear conflict stages.
  3. Inspect the index with `git ls-files -u` to list unmerged entries before re-running the operation.
  4. Commit or stash the current conflict resolution before invoking time-travel.

Example fix

// before (conflicted index)
// conflict markers still in file.rs, not staged
if repo.time_travel(target).is_err() { /* cannot time-travel with unresolved index conflicts */ }

// after
// resolve file.rs, then:
repo.git(&["add", "file.rs"]).expect("stage resolution");
repo.time_travel(target).expect("index is conflict-free");
Defensive patterns

Strategy: validation

Validate before calling

let has_conflicts = repo.index_or_empty()?.entries().iter()
    .any(|e| e.stage() != gix::index::entry::Stage::Unconflicted);
if has_conflicts {
    anyhow::bail!("resolve index conflicts before time-travel");
}

Try / catch

match perform_reporting_rebased(&mut repo, target) {
    Ok(report) => /* proceed */,
    Err(e) if e.to_string().contains("unresolved index conflicts") => {
        // prompt user to resolve + `git add` before retrying
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling tix time-travel (via `perform` or related entry points) while the index has entries whose stage is not `gix::index::entry::Stage::Unconflicted`, i.e. after a merge/rebase/cherry-pick conflict was resolved in the worktree but never `git add`ed, or not resolved at all.

Common situations: A user starts a tix time-travel right after a conflicted merge; an automated tool aborts mid-resolution leaving stage entries; a CI environment inherits a dirty repo with leftover conflict stages.

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

Appendix: source

Thrown at gix-tix/src/edit/time_travel.rs:762

    mut report: impl FnMut(ObjectId),
) -> Result<Perform> {
    let mut repository =
        open_repository(repository_path, bare, false).context("could not open repository for time-travel")?;
    repository.workdir().context("time-travel requires a worktree")?;
    let head = repository.head().context("could not read HEAD before time-travel")?;
    let Some(mut head_id) = head.id().map(gix::Id::detach) else {
        anyhow::bail!("cannot time-travel from an unborn HEAD");
    };
    let head_was_detached = head.is_detached();
    drop(head);
    if repository
        .index_or_empty()
        .context("could not inspect the index before time-travel")?
        .entries()
        .iter()
        .any(|entry| entry.stage() != gix::index::entry::Stage::Unconflicted)
    {
        anyhow::bail!("cannot time-travel with unresolved index conflicts");
    }
    let source_review = review_tree(&repository, graph, review_roots, head_id)?;
    let destination_review = review_tree(&repository, graph, review_roots, selected)?;
    let crosses_review_boundary =
        source_review.as_ref().map(|review| review.root) != destination_review.as_ref().map(|review| review.root);
    let mut completed_graph = None;
    let mut original_ids = HashMap::new();
    let mut ref_rewrites = Vec::new();
    let mut ref_changes = Vec::new();
    let mut pending = pending_base(&repository, selected)?;
    while let Some(base) = pending {
        let graph = completed_graph.as_ref().unwrap_or(graph);
        let mut rebased = Vec::new();
        let outcome = super::rebase::perform_reporting_rebased(
            &repository,
            graph,
            super::rebase::Edit::Repeat {
                base,

View on GitHub (pinned to e73179060b)