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
- Pass a different target commit than the source.
- If the intent is a no-op, skip calling the copy operation entirely when the IDs match.
- 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
- Guard at the command/UI layer before constructing a plan
- Disable the 'copy here' action when the selected target equals the source
- Validate distinctness of all id arguments in a single preflight function
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
- the squash target must be a strict ancestor of the source
- the stack base must be an ancestor of HEAD
- the move target must not be part of the moved stack
- Cannot derive commit or tree from blob at
- rebase todo requires at least one -x/--hide revision when…
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)