After-the-fact sign-offs
Add Tested-by or Reviewed-by lines to commits that are already published, without rewriting history or changing hashes.
Git · Command
git notes lets you attach, read, change and remove extra text (notes) on Git objects, usually commits, without touching the objects. The commit hash stays the same, so you can annotate history that has already been published.
Last updated
Each note is a blob. Notes live in a separate notes ref, which is refs/notes/commits by default. That ref works like a branch whose tree holds files named after the object IDs they annotate, split into two-hex-digit directories for speed. Subcommands such as add, append, edit, show, copy, remove and prune change this tree. git log and git show then print matching notes under a "Notes:" heading after the commit message. You can pick a different notes ref with --ref, GIT_NOTES_REF or core.notesRef, which lets you keep separate groups of notes, such as review notes or build notes.
git notes [list [<object>]] | add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [-e] [<object>] | append ... | edit [<object>] | show [<object>] | copy [-f] (--stdin | <from-object> [<to-object>]) | merge [-s <strategy>] <notes-ref> | remove [--ignore-missing] [--stdin] [<object>...] | prune [-n] [-v] | get-ref| Name | Type | Description |
|---|---|---|
<object> | object name | The object the note is attached to. Most subcommands default to HEAD. |
-m <msg> / --message=<msg> | string | Sets the note text. If you give it more than once, each value becomes its own paragraph. |
-F <file> / --file=<file> | path | Reads the note text from a file. Use '-' to read from standard input. |
-C / -c <object> | blob | Uses an existing blob as the note text. -C copies it word for word; -c opens it in an editor first. |
-f / --force | flag | Replaces an existing note instead of stopping with an error. |
--allow-empty | flag | Keeps an empty note. Without it, empty notes are removed automatically. |
--separator[=<paragraph-break>] / --no-separator | string | Sets the text placed between paragraphs. The default is a blank line. |
--[no-]stripspace | flag | Turns whitespace cleanup on or off. It is on by default, except with -C. |
--ref=<ref> | ref | Works on this notes ref instead of the default one. Overrides GIT_NOTES_REF and core.notesRef. |
-s <strategy> / --strategy=<strategy> | manual|ours|theirs|union|cat_sort_uniq | Sets how merge resolves conflicting notes. |
--stdin | flag | Reads object names (for remove) or object pairs (for copy) from standard input. |
-n / --dry-run | flag | For prune: lists the notes that would be removed without removing them. |
#!/usr/bin/env bash
set -euo pipefail
# Attach a note to HEAD without changing its hash
git notes add -m 'Tested-by: CI pipeline #482'
# Add another paragraph to the same note
git notes append -m 'Perf: p95 latency 120ms'
# Print the note, and see it in the log
git notes show HEAD
git log -1
# Keep review notes in their own ref (refs/notes/review)
git notes --ref=review add -m 'LGTM - alice' HEAD
git log -1 --notes=review
# Show which notes ref is currently in use
git notes get-ref
# Keep notes when amending/rebasing
git config notes.rewriteRef refs/notes/commits
# Preview removal of notes on objects that no longer exist
git notes prune -n -vThis script adds a note to a commit and appends to it, keeps review notes in a separate ref, turns on note copying for amend and rebase, and previews a prune.
Add Tested-by or Reviewed-by lines to commits that are already published, without rewriting history or changing hashes.
Store build results, test reports or artifact blobs in a dedicated ref such as refs/notes/built, so they never clutter commit messages.
Keep per-commit explanations as notes, then include them below the '---' line of emailed patches with git format-patch --notes.
Different teams keep separate notes refs and combine them with git notes merge, using a strategy such as union or cat_sort_uniq.
Amending creates a new commit with a new hash, which rewrites history. A note is stored as a separate blob in a notes ref, so the original commit stays exactly the same. git log still shows the note under a 'Notes:' heading, which is why notes work for annotating commits that are already published.
Follow-up: How are notes stored internally? The notes ref points to a commit whose tree has files named after the annotated object IDs, split into two-hex-digit directories for speed; each file is the note blob.
With -m or -F it stops with an error, so nothing is overwritten by accident. Use -f to replace the note, or use append to add text to it. If you run add with no message, it opens the existing note in your editor, the same way edit does.
Follow-up: How do you delete a note, and what happens with an empty message? Use git notes remove (add --ignore-missing to skip objects that have no note). Saving an empty message has the same effect, because empty notes are removed unless you pass --allow-empty.
git notes merge <notes-ref> uses the manual strategy by default. It checks out the conflicting notes into .git/NOTES_MERGE_WORKTREE so you can fix them by hand. You then run git notes merge --commit to finish or --abort to cancel. You can also pick ours, theirs, union or cat_sort_uniq with -s or the notes.mergeStrategy setting.
Follow-up: Can you set a different strategy for each notes ref? Yes: notes.<name>.mergeStrategy applies to merges into refs/notes/<name> and takes priority over the general notes.mergeStrategy setting.
git loggit showgit format-patchgit configgit hash-objectgit stripspacegit commit