jdx/mise · error

git merge-file failed

Error message

git merge-file failed: {}

What it means

`git merge-file` reports conflicts with a positive exit code and success with 0; a negative code (or no code) means git itself errored — e.g. the binary is missing/broken or arguments are invalid. mise surfaces that condition with the captured stderr. A conflict-free or conflicted-but-valid merge is not an error here.

Solutions

  1. Read the stderr in the message; if empty, verify `git merge-file --help` works at all.
  2. Reinstall or repair your git installation.
  3. Check free disk space and permissions on the temp directory used for merges.
  4. Retry the dotfile sync after the environment issue is fixed.
Defensive patterns

Strategy: fallback

Validate before calling

if std::process::Command::new("git").arg("merge-file").arg("--help").status().is_err() {
  eprintln!("git is not usable; repair installation before syncing dotfiles");
}

Try / catch

match result {
  Err(e) if e.to_string().contains("git merge-file failed") => {
    // fall back to leaving the file untouched and surface the git install problem
  }
  other => other?,
}

Prevention

When it happens

Trigger: Three-way merging an individual dotfile via `git merge-file` when the git invocation crashes (negative exit status): broken git installation, unreadable temp file for the file content, or invalid merge arguments.

Common situations: Corrupt or half-installed git (e.g. partial upgrade); filesystem errors preventing temp-file creation during merge; SELinux/AV blocking git from writing temp merge files.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/0c0468adb51da116. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/shadow.rs:1621

        std::fs::write(&t, theirs)?;
        let output = self.git.output_unchecked(PlumbingCall::new([
            "merge-file",
            "-p",
            "-L",
            "local",
            "-L",
            "base",
            "-L",
            "remote",
            &o.to_string_lossy(),
            &b.to_string_lossy(),
            &t.to_string_lossy(),
        ]))?;
        // exit status is the number of conflicts; negative on error
        match output.status.code() {
            Some(0) => Ok(Some(output.stdout)),
            Some(code) if code > 0 => Ok(None),
            _ => bail!(
                "git merge-file failed: {}",
                String::from_utf8_lossy(&output.stderr).trim()
            ),
        }
    }

    /// Runs a network command with the user's git configuration.
    pub(crate) fn network<'a>(
        &self,
        args: impl IntoIterator<Item = &'a str>,
    ) -> Result<std::process::Output> {
        self.git.network_output(PlumbingCall::new(args))
    }

    const ANNOTATION_TRAILER: &'static str = "Mise-Annotation: ";

    /// An annotation is an empty ordinary child commit, so labels and
    /// descriptions travel with the same ancestry without rewriting it.

View on GitHub (pinned to 533346cc37)