nikivdev/code · error

git merge --ff-only {} failed: {}

Error message

git merge --ff-only {} failed: {}

What it means

Thrown in src/sync.rs when `git merge --ff-only <remote_ref>` fails with an unexpected error. First the tool checks for an index-lock problem (separate error); if the failure is NOT one of the expected, tolerated ff-only merge outcomes (`is_expected_ff_only_merge_failure`), it bails including the remote ref and the full git output. Expected divergent-history outcomes are handled downstream instead of erroring.

Source

Thrown at src/sync.rs:4049

    recorder.record(
        stage,
        format!("merging {} commits from {}", behind, remote_ref),
    );

    let ff_only_output = git_run_output_in(repo_root, &["merge", "--ff-only", &remote_ref])?;
    if ff_only_output.status.success() {
        recorder.record(stage, format!("fast-forwarded to {}", remote_ref));
        return Ok(());
    }

    let ff_only_detail = git_output_text(&ff_only_output);
    if is_git_index_lock_error(&ff_only_detail) {
        bail!(
            "Git index lock detected during merge. Remove stale .git/index.lock (if no git process is running) and re-run."
        );
    }
    if !is_expected_ff_only_merge_failure(&ff_only_detail) {
        bail!(
            "git merge --ff-only {} failed: {}",
            remote_ref,
            ff_only_detail
        );
    }

    let merge_output = git_run_output_in(repo_root, &["merge", &remote_ref, "--no-edit"])?;
    if merge_output.status.success() {
        recorder.record(stage, format!("merged {} with commit", remote_ref));
        return Ok(());
    }
    let merge_detail = git_output_text(&merge_output);
    if is_git_index_lock_error(&merge_detail) {
        bail!(
            "Git index lock detected during merge. Remove stale .git/index.lock (if no git process is running) and re-run."
        );
    }

View on GitHub (pinned to a747e741ae)

Solutions

  1. Read the git output after the colon — it contains git's own merge failure reason.
  2. If histories diverged, decide: `git pull --rebase` or a real merge, then re-run sync.
  3. Check `git status` / `git rev-parse --abbrev-ref HEAD` — ensure you are on the expected branch, not detached HEAD.
  4. If upstream was force-pushed, fetch and hard-reset deliberately: `git fetch && git reset --hard origin/<branch>` (discards local commits).

Example fix

// before
f sync
// error: git merge --ff-only origin/main failed: fatal: Not possible to fast-forward, aborting.

// after (integrate divergence explicitly)
git pull --rebase origin main
f sync
Defensive patterns

Strategy: try-catch

Validate before calling

let local = git_capture(&["rev-parse", "HEAD"])?;
let remote = git_capture(&["rev-parse", "--verify", "origin/main"])?;
let base = git_capture(&["merge-base", "HEAD", "origin/main"])?;
if base.trim() != local.trim() {
    eprintln!("Branch diverged from origin/main; ff-only sync will fail or need integration.");
}

Try / catch

match sync::run(&repo_root, &cmd) {
    Err(e) if e.to_string().starts_with("git merge --ff-only") => {
        eprintln!("ff-only merge failed: {} — integrate with rebase/merge first", e);
    }
    other => other,
}

Prevention

When it happens

Trigger: During sync's fast-forward path, `git merge --ff-only <remote_ref>` exits non-zero with output that is neither an index-lock error nor a recognized expected failure — e.g., 'not possible to fast-forward', unborn branch, or detached HEAD oddities outside the expected set.

Common situations: Local branch diverged from the remote in a way the tool doesn't classify; the branch checked out is not what sync expects (detached HEAD); upstream history was force-pushed; shallow clone missing objects.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/50d9b1e6c78e8010. Report an issue: GitHub.