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.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Time: about fifteen minutes.
- You need: an existing Git repository and a completed fetch.
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
- No number given: Git includes at most 20 commit subjects from each merge parent.
- Smaller limit needed: set one explicitly to keep the message compact.
$ 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
- Empty or stale FETCH_HEAD. Usually means the fetch did not record the ref you expected. Check the fetch command and inspect the file before rerunning the formatter.
- Unexpected destination in the message. Can come from running in the wrong repository, or from
--into-name. Use that option only when you deliberately want Git to write the message as if merging into another branch. - Deprecated flag names still work.
--summaryand--no-summaryremain as synonyms for--logand--no-log; prefer the current names in scripts. - A zero exit status proves formatting, not correctness. It only says Git successfully formatted the metadata you supplied, nothing about whether the merge itself makes sense.
Done means
- Version confirmed: you checked Git 2.43.0 and the repository location.
- Message generated: you produced a message from the repository's current
FETCH_HEAD. - Log level chosen: you decided explicitly between no log, a bounded
--log=<n>list and the default limit. - Configuration checked: you reviewed settings that could affect branch descriptions or the destination title.
- Message reviewed: you read any saved message before a separate merge command touched history.