Build a Merge Message with git fmt-merge-msg

git fmt-merge-msg turns Git's raw fetch metadata into a proper merge message you can actually read. Every automated merge commit with a message nobody can parse started life as unformatted fetch data, which is exactly what this plumbing command fixes. You will finish with a repeatable way to generate a merge message, optionally with a bounded list of commit subjects, and hand the result to a later merge or script. The examples were checked with Git 2.43.0 from the installed git-man package, version 1:2.43.0-1ubuntu7.3.

This command is plumbing: it reads a list of merged objects and writes a message to standard output. It does not perform the merge, create a commit, fetch anything or touch your working tree.

1. Check the installed command

Confirm which Git version and executable your shell will use. These are ordinary, read-only commands and need no sudo:

$ git --version
git version 2.43.0
$ command -v git
/usr/bin/git

The subcommand is written git fmt-merge-msg, though its manpage is named git-fmt-merge-msg. Its normal input is the list of objects a fetch recorded, commonly $GIT_DIR/FETCH_HEAD.

Checkpoint: stay in the repository whose fetch you want to describe.

$ git rev-parse --show-toplevel
/path/to/project
$ git rev-parse --git-dir
.git

2. Generate the default message from FETCH_HEAD

Fetch the branch or branches you want to review, then pass the resulting metadata to the command:

$ git fetch origin main
$ git fmt-merge-msg <"$(git rev-parse --git-path FETCH_HEAD)"
Merge branch 'main' of https://example.invalid/project into main

The exact first line depends on the remote, the branch names and the destination. The useful result is the message on standard output: the command never applies it to a commit, so there is nothing here to undo. To inspect the raw fetch record instead:

$ sed -n '1,5p' "$(git rev-parse --git-path FETCH_HEAD)"
<object-id>    branch 'main' of https://example.invalid/project

Do not swap in an arbitrary file from another repository in place of FETCH_HEAD. Every object name in the list must actually be available in the repository where you run the command.

3. Add commit subjects when the branch name alone will not do

Use --log when a reviewer needs more than just the branch name:

$ git fmt-merge-msg --log <"$(git rev-parse --git-path FETCH_HEAD)"
Merge branch 'main' of https://example.invalid/project into main

* https://example.invalid/project:
  Fix timeout when the worker is idle
  Document the retry limit
$ git fmt-merge-msg --log=5 <"$(git rev-parse --git-path FETCH_HEAD)"

--log overrides the merge.log setting for this one invocation. --no-log explicitly suppresses the subject list, useful when a repository's configuration normally turns it on:

$ git fmt-merge-msg --no-log <"$(git rev-parse --git-path FETCH_HEAD)"

Checkpoint: decide whether the generated text is for a human review or a machine pipeline. A long subject list can bury the merge's actual purpose; omitting it entirely can make an integration commit harder to audit later.

4. Supply a deliberate first line

Pass --message when the branch-derived title is misleading, or your automation already has a known reason for the merge:

$ git fmt-merge-msg --message 'Merge release fixes' --log=5 <"$(git rev-parse --git-path FETCH_HEAD)"
Merge release fixes

* https://example.invalid/project:
  Fix timeout when the worker is idle

This changes only the first line, not the source objects or the commit history. Quote the value as one shell argument, and validate any user-supplied text before it lands in an automated commit workflow.

To use a saved input list instead of standard input, use --file or its short form -F:

$ git fmt-merge-msg --no-log --file /path/to/FETCH_HEAD.copy

That option is only an input redirection alternative; it does not write to that file.

5. Understand what your configuration is already doing

Check relevant settings before blaming the command for an unexpected title:

$ git config --show-origin --get-regexp '^merge\.(branchdesc|log|summary|suppressDest)$'
file:.git/config    merge.log true

No output is also a perfectly valid result; these keys may simply be unset. merge.log controls whether commit subjects appear by default, and true means a limit of 20. --log and --no-log override that choice for a single run.

merge.branchdesc can add branch description text. merge.suppressDest takes globs naming integration branches whose generated title should drop the destination phrase. Leave it entirely unset and Git 2.43.0 falls back to master for backward compatibility. The older merge.summary name is a deprecated synonym for merge.log; do not add it to new configuration.

These settings change the generated text, but they never replace the need to actually read it. Configuration changes persist, so do not run git config --global or edit repository configuration merely to test a message. If you did change a setting to test this, restore the previous value with the matching git config --unset, or use your normal configuration backup to recover it.

6. Pass the result to a merge only after review

Scripts commonly feed this command's output straight into git merge. If you want to review the generated text first, save it to a new temporary file rather than overwriting an existing message:

$ message_file=$(mktemp /tmp/merge-message.XXXXXX)
$ git fmt-merge-msg --log=5 <"$(git rev-parse --git-path FETCH_HEAD)" >"$message_file"
$ sed -n '1,20p' "$message_file"
Merge branch 'main' of https://example.invalid/project into main

* https://example.invalid/project:
  Fix timeout when the worker is idle

Read the file before using it as a commit message. The merge itself is a state-changing operation that can create a merge commit or expose conflicts, so it sits outside this guide's read-only scope. Once the text is no longer needed, remove only the exact temporary file you created.

Common failure traps

Done means