Ken Muse

Storing Data in Git Objects With Notes


Sometimes you need to attach additional information to a commit – test results, code review notes, build metadata, or deployment information – without modifying the commit itself. You may not realize it, but Git provides a built-in feature for this called notes. They allow you to annotate any Git object (commits, trees, blobs, or tags) with arbitrary data, while keeping the original object’s SHA intact.

In this post, I’ll show you how to use git notes effectively. You’ll learn how to create and manage notes, organize them with namespaces, share them with your team, and even store structured data like YAML or JSON for programmatic processing. I’ll also cover some important limitations to be aware of before adopting notes in your workflow.

Why use Git notes?

Before diving into the mechanics, let’s talk about when notes make sense. Notes are particularly useful when:

  • You want to annotate objects without modifying history. Notes don’t change the object’s SHA, so they won’t trigger rebases or break references. They provide additional context or data without needing to alter the object itself. This can be especially important if the commit is signed.
  • You need to attach data or metadata after the fact. Testing results, build numbers, code review comments, or deployment timestamps can be added long after the original commit.
  • You want to store machine-readable data. Relationships, CI/CD status, or custom workflow metadata can be attached programmatically and parsed by scripts.

Of course, there are some trade-offs and limitations – but I’ll get to those in a moment.

Creating and managing notes

The git notes command provides subcommands for adding, viewing, editing, and removing notes. Let’s walk through the basics.

Adding notes

The simplest way to add a note is with a message directly on the command line:

 # Add a note to the current HEAD commit
 git notes add -m "Tested by QA on 2026-01-15"
 
 # Add a note to a specific commit or object using its SHA
 git notes add -m "Reviewed by Alice" abc1234

If you omit the -m flag, then Git will open your default editor to let you edit the note content. You can also add notes from a file using -F, which is useful for multi-line content or attaching structured data.

Viewing notes

Notes aren’t helpful unless you can see them! You can use git notes show to display notes for the current commit. You can also specify a specific object SHA to view any attached notes. For example, git notes show abc1234.

Notes are also displayed in git log by default. They appear indented below the commit message:

 $ git log -1
 commit abc1234...
 Author: Ken Muse <ken@example.com>
 Date:   Wed Jan 15 09:00:00 2026 -0500
 
     Add feature X
     
 Notes:
     Tested by QA on 2026-01-15

Updating and removing notes

Each object can only have one note per namespace (you’ll see why when we look at how notes are stored). If you try to add a note to an object that already has one, git notes add will fail. You have a few options:

  • Use -f to overwrite the existing note entirely
  • Use git notes append to add content to an existing note (or create one if it doesn’t exist)
  • Use git notes edit to open the note in your editor
  • Use git notes remove to delete the note

Git automatically cleans up empty notes by default – if you edit a note and save it with no content, Git treats this the same as removing it.

Namespaces – organizing notes by purpose

By default, notes are stored as refs – like branches and tags – in the file refs/notes/commits. But what if you want to track different types of information separately? That’s where namespaces come in.

Namespaces let you create distinct collections of notes. Each namespace is stored as a separate ref, allowing you to keep different types of metadata isolated or group together related notes such as deployment records or test results.

Use the --ref option to work with a specific namespace. For example, git notes --ref=reviews add -m "Approved" abc1234 stores the note under refs/notes/reviews. You can use any name you like, and even nest them (like ci/builds for refs/notes/ci/builds).

By default, git log only shows notes from the default namespace. To see notes from other namespaces, use git log --notes=reviews or git log --notes='*' to show all. You can also configure Git to always display certain namespaces:

 git config --add notes.displayRef 'refs/notes/reviews'

Sharing notes with push and fetch

Here’s an important detail that often surprises developers: notes are not shared automatically. Like tags, they exist only in your local repository until you explicitly push them.

You can push a specific namespace with git push origin refs/notes/commits or all notes with git push origin 'refs/notes/*'. Similarly, fetch with git fetch origin 'refs/notes/*:refs/notes/*'.

To avoid doing this manually every time, configure your repository to include notes automatically:

 # Always fetch all notes
 git config --add remote.origin.fetch '+refs/notes/*:refs/notes/*'
 
 # Push all notes by default
 git config --add remote.origin.push 'refs/notes/*'

Storing structured data

Notes aren’t limited to plain text. Since notes are stored as blobs (just like file content), the content can be JSON, YAML, or any format that suits your workflow. The -F flag attaches file content directly as a note.

Here’s how you might attach CI/CD build metadata:

 # Generate build metadata from your CI pipeline
 cat > /tmp/build-info.json << EOF
 {
   "build_number": 5678,
   "status": "success",
   "coverage": 87.5,
   "timestamp": "2026-01-15T14:30:00Z"
 }
 EOF
 
 # Attach to the commit
 git notes --ref=ci/builds add -F /tmp/build-info.json abc1234
 
 # Later, retrieve and parse with jq
 git notes --ref=ci/builds show abc1234 | jq '.coverage'

You can also pipe content directly using -F - to read from stdin:

 ./run-tests.sh --json | git notes --ref=tests add -F - abc1234

This approach works well for deployment tracking, quality gates, or linking commits to external systems.

How notes are stored internally

If you’re curious about how Git stores notes under the hood (especially after reading about how Git stores data), here’s a quick look.

Notes are stored using the same object model as everything else in Git. The notes ref (by default refs/notes/commits) is a file that points to a commit object, just like a branch. That note commit points to its parent commit (the last note committed in that namespace) and to a tree, such as:

 100644 blob 8e674b74f04e0e...    145ef0a7a4e0a379b7d8...
 100644 blob f336a85ea6b41f...    d99cd6837d46bc424bc...

The filenames in this tree are the object IDs (SHAs) of the commits being annotated, and the file (blob) content is your note text. This means if you check out the notes ref, the working directory would contain files named using the related commit SHAs, and the note would be in the file content.

Limitations to consider

While notes are powerful, they have some significant limitations you should understand before adopting them:

  • While notes can attach to any Git object (commits, trees, blobs, or tags), notes on non-commit objects won’t appear in git log. You’ll need to use git notes show <object> to view them directly.
  • Notes are often not migrated automatically between Git hosting systems. If you move a repository from GitHub to GitLab (or vice versa), notes may not transfer. Similar to Git LFS (Large File Storage), you may need to manually push notes to the new remote after migration.
  • Most IDEs don’t support notes. Visual Studio Code, JetBrains IDEs, and other popular editors don’t provide built-in ways to view, create, or edit notes. You’ll need to use the command line.
  • Web interfaces for Git repositories typically don’t display notes. GitHub, GitLab, Bitbucket, and Azure DevOps don’t show notes in their commit views or pull request interfaces. Your team can only see notes by cloning the repository and using git notes show or git log --notes.
  • Notes require explicit synchronization. As covered earlier, notes don’t push or fetch automatically. Team members who aren’t aware of this may never see the notes you’ve added. Or they may forget to push their own notes back to the shared repository.

Best practices

With those limitations in mind, here are some tips for using notes effectively:

  • Choose clear namespace conventions. Use descriptive names like reviews, builds, or deployments so the purpose is obvious.
  • Document your note schemas. If you’re storing structured data, document the expected format so automation and team members know what to expect. Consider including versioning with structured data to allow schemas to evolve.
  • Not all notes need to be shared. Local notes can be useful for personal workflows.

Wrapping up

Git notes are a powerful but often overlooked feature. They let you attach arbitrary metadata to any Git object without modifying it – perfect for CI/CD tracking, deployment records, and automated workflows. There’s more to explore, but hopefully this post has given you a solid foundation to start using notes in your projects. If you’re interested in learning more, see the official git-notes reference. Make sure to explore this feature to understand how it can help your workflows!