gitbutlerapp/gitbutler · error · anyhow::Error

line {line_number}: invalid merge commit: {err}

Error message

line {line_number}: invalid merge commit: {err}

What it means

Same validation as for pick, but for a `merge <commit>` line in the interactive integration script: the referenced commit is not resolvable within the editable divergence set (not a not-integrated local commit or upstream-only commit), or the short prefix is ambiguous across candidates.

Source

Thrown at crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs:73

                    commit_id: resolve_commit(commit, &allowed_commits).map_err(|err| {
                        anyhow::anyhow!("line {line_number}: invalid pick commit: {err}")
                    })?,
                }
            }
            "merge" => {
                if message_part.is_some() {
                    bail!("line {line_number}: merge does not accept a message clause");
                }
                if arguments.len() != 1 {
                    bail!("line {line_number}: merge requires exactly one commit");
                }
                let commit = arguments
                    .first()
                    .copied()
                    .expect("validated merge arity above");
                InteractiveIntegrationStep::Merge {
                    commit_id: resolve_commit(commit, &allowed_commits).map_err(|err| {
                        anyhow::anyhow!("line {line_number}: invalid merge commit: {err}")
                    })?,
                }
            }
            "squash" => {
                if arguments.len() < 2 {
                    bail!("line {line_number}: squash requires at least two commits");
                }
                let commits = arguments
                    .iter()
                    .map(|commit| {
                        resolve_commit(commit, &allowed_commits).map_err(|err| {
                            anyhow::anyhow!(
                                "line {line_number}: invalid squash commit '{commit}': {err}"
                            )
                        })
                    })
                    .collect::<Result<Vec<_>>>()?;
                InteractiveIntegrationStep::Squash {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Replace the hash with one listed in the generated merge lines of the current script
  2. Use a longer prefix if the inner error says 'ambiguous'
  3. Regenerate the plan after the branch/upstream state changed

Example fix

# before
merge 1111111         # integrated local commit → rejected
# after
merge 4444444         # an upstream-only editable commit
Defensive patterns

Strategy: validation

Validate before calling

// pre-check each merge token against editable candidates (see error 674 helper)
for line in script.lines().filter(|l| l.trim_start().starts_with("merge")) {
    let spec = line.split_whitespace().nth(1).expect("arity checked separately");
    ensure!(commit_resolves(&candidate_strings, spec), "merge token {spec} not resolvable");
}

Type guard

fn merge_line_valid(line: &str, candidates: &HashSet<String>) -> bool {
    let toks: Vec<_> = line.split_whitespace().collect();
    toks.len() == 2 && !line.contains('|') && commit_resolves(candidates, toks[1])
}

Try / catch

if let Err(e) = parse_integration_steps_script(&script, &divergence) {
    if e.to_string().contains("invalid merge commit") { /* surface line + hash to user */ }
}

Prevention

When it happens

Trigger: A script line `merge <hash>` where hash is absent from editable_candidate_commits(divergence) or its prefix matches several candidates; merge also requires exactly one argument and no message clause (separate errors).

Common situations: Hand-edited integration scripts with typo'd hashes; referencing integrated local commits which are deliberately excluded from the editable set.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/2c0ab4e88b8e0f1d. Report an issue: GitHub.