GitoxideLabs/gitoxide · error

the copy source and target must differ

Error message

the copy source and target must differ

What it means

A copy operation requires the source commit and the target insertion point to be different commits. Inserting a commit 'after itself' is a no-op that would corrupt the plan's parent bookkeeping, so the plan builder rejects `source == target` up front.

Solutions

  1. Pass a different target commit than the source.
  2. If the intent is a no-op, skip calling the copy operation entirely when the IDs match.
  3. Fix argument ordering in scripts so target is not accidentally bound to the source value.

Example fix

// before
copy_insert_plan(repo, graph, id, id)

// after
if source != target {
    copy_insert_plan(repo, graph, source, target)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if source == target {
    eprintln!("copy source and target must differ");
    return Ok(());
}

Type guard

fn copy_args_valid(source: ObjectId, target: ObjectId) -> bool {
    source != target
}

Try / catch

match copy_insert_plan(&repo, &graph, source, target) {
    Err(e) if e.to_string().contains("must differ") => eprintln!("nothing to do: source equals target"),
    other => other?,
}

Prevention

When it happens

Trigger: Calling `copy_insert_plan(repo, graph, source, target)` with the same `ObjectId` for both `source` and `target`, e.g. a UI or script that passes the selected commit for both fields.

Common situations: Interactive tooling where the user selects one commit and both 'copy this' and 'paste here' resolve to it; scripts with a defaulted/empty target argument falling back to the source.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/rebase.rs:565

) -> Result<Plan> {
    let source_parents = graph
        .parents_of(source)
        .context("the copy source is not in the loaded history")?;
    let [_source_parent] = source_parents.as_slice() else {
        anyhow::bail!("copying a commit requires it to have exactly one parent");
    };
    let source_commit = repo
        .find_commit(source)
        .context("could not find the copy source")?
        .decode()
        .context("could not decode the copy source")?
        .into_owned()
        .context("could not own the copy source")?;
    if super::review::reference(&source_commit)?.is_some() {
        anyhow::bail!("review commits cannot be copied");
    }
    if source == target {
        anyhow::bail!("the copy source and target must differ");
    }

    let mut scope = graph
        .descendants_in_parent_order(target)
        .context("the copy target is not in the loaded history")?;
    scope.retain(|id| *id != target);
    let mut steps = vec![PlanStep {
        parent: PlanParent::Existing(target),
        commit: PlanCommit::Copy(source),
        squash: Vec::new(),
    }];
    let mut step_by_id = HashMap::with_capacity(scope.len());
    for id in &scope {
        let parents = graph.parents_of(*id).context("an affected copy commit is incomplete")?;
        let [parent] = parents.as_slice() else {
            anyhow::bail!("copying a commit cannot rewrite root or merge commits");
        };
        let parent = if *parent == target {

View on GitHub (pinned to e73179060b)