Git Notes: Attaching Extra Information to Commits Without Rewriting History
AI generated
git
HEAD
Git
Git Notes
Attaching extra information to commits without rewriting history

A commit is content immutable once created, any later change to the commit message produces a new hash. Git notes solve this problem by storing additional information in a separate, referenced namespace that is loosely linked to the commit without touching its identity.

9 min read Git Workflow Metadata

1. Why adding comments to commits after the fact is a problem

A commit's hash is derived from a hash over its tree, parent references, author, and commit message. Any change to the commit message, even a single character, inevitably produces a new hash and therefore a new commit. Anyone wanting to add information to an already pushed commit later on, say the result of a subsequent code review or a CI pipeline's build status, cannot simply edit the commit message without rewriting the entire history that follows it.

This is exactly the case git notes was built for: a mechanism that stores extra information in its own object and links it to the original, unchanged commit through a dedicated reference. The commit itself remains completely untouched, its identity and its hash do not change.

2. How Git notes are structured under the hood

Notes are stored by default under the reference refs/notes/commits, a dedicated namespace that is internally organized like a small repository of its own: every commit with a note has a blob object holding the note text, referenced through a tree whose path is derived from the commit hash. This lets Git manage an arbitrary number of notes efficiently, without having to scan a complete linear list for every note.

The command git notes add creates a new note for the current or a specified commit, git notes show displays it, and git log shows existing notes right below the commit message by default, with no extra command required.


# Add a note to the current HEAD commit
git notes add -m "Code review: security relevant change, reviewed by the security team"

# Show the note for an arbitrary commit
git notes show <commit-hash>

# Notes appear automatically in git log
git log --oneline -1

3. Editing, appending to, and removing notes

An existing note can be opened and overwritten in the configured editor with git notes edit, while git notes append adds further text to an already existing note without overwriting the previous content. That is particularly handy for iterative processes such as several consecutive CI runs, whose results should all be documented on the same commit.

git notes remove removes a note, and git notes list shows every commit with a note in the current namespace. Since notes are themselves versioned Git objects, every earlier version of a note remains theoretically traceable through the notes reference history, even though Git does not ship a convenient built in diff view for that.


# Edit an existing note in the editor
git notes edit <commit-hash>

# Append additional text to an existing note without overwriting it
git notes append -m "CI run #482: all tests passed" <commit-hash>

# Remove a note entirely
git notes remove <commit-hash>

4. Synchronizing notes between repositories

Notes are not transferred automatically on either git push or git fetch by default, because refs/notes/* is not part of the standard refspec. Anyone wanting to share notes across a team has to specify the reference explicitly, either once per command or permanently through the remote configuration.

An important difference from regular branches is that several people editing the same note in parallel easily leads to merge conflicts in the notes namespace, since Git does not provide a specialized merge driver beyond the standard text merge logic for this. In practice this works most reliably when notes are mostly maintained automatically by CI systems rather than manually by several developers in parallel.


# Fetch notes from the remote once
git fetch origin refs/notes/commits:refs/notes/commits

# Permanently add notes to the fetch refspec
git config --add remote.origin.fetch "+refs/notes/*:refs/notes/*"

# Push notes to the remote
git push origin refs/notes/commits

5. Multiple notes namespaces for different purposes

Git does not restrict notes to the default namespace refs/notes/commits. Through the environment variable GIT_NOTES_REF or the --ref flag, an arbitrary number of parallel namespaces can be created, for example a dedicated namespace for build status, another for review comments, and a third for deployment timestamps, none of which interfere with each other.

This separation is especially valuable in automated pipelines, since every tool can maintain its own namespace without accidentally overwriting another tool's notes. The notes.displayRef configuration additionally determines which namespaces get shown in git log by default, so the output can be scoped to the notes that actually matter.


# Create a note in a dedicated namespace for build status
git notes --ref=build-status add -m "Build #1183: succeeded" HEAD

# Show all configured namespaces together in git log
git config --add notes.displayRef refs/notes/build-status
git config --add notes.displayRef refs/notes/commits

6. Practical use: setting notes from CI pipelines

A common use case is automatically attaching build and test results directly to the corresponding commit instead of keeping that information only in an external CI dashboard that might eventually get archived or deleted. That way the information stays traceable inside the repository itself permanently, even years after the actual CI run.

For this use case, a dedicated namespace per information type is worth setting up, along with a CI job that attaches a note with structured, machine readable data, for example in JSON format, after a successful build and pushes it deliberately to that one namespace.


#!/usr/bin/env bash
# CI script: attach the build result as a structured note
set -euo pipefail

STATUS='{"job": "build", "result": "success", "duration_seconds": 214}'
git notes --ref=ci-status add -f -m "$STATUS" "$CI_COMMIT_SHA"
git push origin refs/notes/ci-status

7. Git notes compared to alternative approaches

An obvious alternative to notes is using trailers inside the commit message itself, for example lines like Reviewed-by: Name at the end of the message, as used by many open source projects. Trailers have the advantage of automatically traveling along with every git log, git push, and git fetch, with no separate configuration needed. The decisive downside is that a trailer can only be set at commit time without changing the hash, while later additions such as a subsequent review result rule this approach out from the start.

A second alternative is offloading such metadata entirely into an external system, such as a CI dashboard or a separate database. That works well for transient, high frequency data, but decouples the information from the repository itself: if the external system ever gets shut down or migrated, the link to the relevant commit is usually lost. Notes, by contrast, remain permanently inside the repository and travel along with every full clone, provided the namespace reference gets synchronized explicitly.

8. Best practices for using Git notes

Notes are particularly well suited for automatically generated, structured extra information that should not be part of the actual commit message, such as build status, test coverage, or deployment timestamps. For human review comments, pull request discussions on the hosting platform are usually the better choice, since they offer a more convenient interface and notifications.

A dedicated namespace per purpose keeps the structure manageable and prevents different tools from overwriting each other. Anyone using notes across a team should document the fetch and push configuration explicitly, since notes otherwise easily stay local unnoticed and drift apart across the team.

9. Common pitfalls when working with notes

The most common pitfall is simply that notes are neither pushed nor fetched without explicit configuration, leading a team to wrongly assume notes are automatically part of the normal workflow. Another problem is calling git notes add without the --force flag on a commit that already has a note: the command then fails with an error instead of silently overwriting the existing note.

During a rebase or squash of commits, the associated notes are lost by default, because the commit hash changes and the notes reference still points at the old hash. Git offers the configuration notes.rewrite.rebase and notes.rewriteMode for this, which can carry notes over to the new commit automatically, but this has to be enabled explicitly.

Aspect Commit message Git notes
Mutability Editing produces a new commit hash Editable without touching the commit hash
Visible by default Always shown in git log Only if the namespace is configured
Synchronization Part of every normal push and fetch Must be configured explicitly
Typical content Description of the change itself After the fact metadata such as CI status

Mironsoft

Git workflows, branching strategies, and CI hooks

Chaotic Git history and unclear branching rules across the team?

We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.

Workflow Audit

Review the existing branching strategy and merge practice for weak spots.

Hook Automation

Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.

Team Training

Teach rebase, cherry-pick, and conflict resolution hands-on across the team.

10. Summary

Git Notes

Default namespace

refs/notes/commits

Core command

git notes add -m "text"

Synchronization

Must be configured explicitly via refspec

Typical use

CI build status, structured metadata

11. FAQ: Git Notes

1Does adding a note change the commit's hash?
No, notes are stored entirely outside the commit object and only reference the commit through its hash. The commit itself and its hash remain unchanged.
2Are notes automatically transferred with git push?
No, refs/notes/* is not part of the standard refspec. Notes have to be pushed and fetched explicitly, either once per command or permanently through an extended remote configuration.
3What happens to notes during a rebase?
By default they are lost, because the commit hash changes and the notes reference still points at the old hash. With notes.rewrite.rebase enabled, Git carries notes over to the new commit automatically.
4Can I attach multiple notes to a single commit?
Only one note exists per commit within a given namespace, but it can be extended with additional text using git notes append. For topically separate notes, a dedicated namespace per purpose is the better approach.
5Where are Git notes physically stored?
Like all Git objects, in the local object database under .git/objects, referenced through refs/notes/commits or another configured namespace. They are regular Git objects, not a separate database.
6Are notes suitable for manual review comments?
Technically yes, but in practice pull request discussions on the hosting platform are usually better suited, since they offer notifications, threading, and a more convenient interface than pure command line access to notes.
7Can I use several independent notes namespaces at once?
Yes, through the --ref flag or the GIT_NOTES_REF environment variable, an arbitrary number of parallel namespaces can be created that do not interfere with each other and can be maintained separately.
8Does git log show notes by default?
Yes, for the default namespace refs/notes/commits, notes are shown automatically below the commit message. For additional namespaces, notes.displayRef has to be configured accordingly.
9What happens if two people edit the same note at the same time?
A merge conflict can occur in the notes namespace, which Git handles with standard text merge logic. Since there is no specialized merge driver, the conflict has to be resolved manually.
10Are notes suitable for structured data such as JSON?
Yes, notes store arbitrary text with no format requirement, so structured formats such as JSON can be stored without issues, as long as the consuming tools parse the content accordingly.