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
- Replace the hash with one listed in the generated merge lines of the current script
- Use a longer prefix if the inner error says 'ambiguous'
- 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
- Keep merge lines to exactly one argument and no message clause
- Refresh candidate hashes after any upstream change before editing
- Provide UI validation of edited scripts before parse/execute round-trips
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
- line {line_number}: invalid pick commit: {err}
- line {line_number}: invalid squash commit '{commit}': {err}
- line {line_number}: invalid squash message: {err}
- Integration steps cannot be empty
- line {line_number}: pick does not accept a message clause
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/2c0ab4e88b8e0f1d.
Report an issue: GitHub.