Store Code Discussions in Git using Git Notes
Code discussions contain relevant information. Isn’t it a shame that we keep these in the centralized GitHub/GitLab servers, far away from our decentralized Git code? As soon as we move provider, we’ll lose all old discussions! And how do you ever find the pull requests back from 5 years ago? Symfony has implemented a lightweight solution to this problem years ago using a less-known feature of Git: Git Notes.
Git Notes?
Meet one of Git’s unknown features: Git notes. They are hard to discover… and hard to use. Yet, they provide an interesting feature: Adding extra information to a commit, after you’ve created it.
Git stores all data using immutable objects. This means that you cannot change the objects after creation. For instance, when you change a commit’s message, Git will create a new object and remove the previous one. Git notes are useful when you want to preserve the existing Git object, but want to add extra data. Git notes have a many-to-one relationship with objects: one Git object (commit) can have multiple Git notes references it.
Enough talking, let’s see it in practice!
First, create a commit:
1
2
3
$ git commit --allow-empty -m "Playing with Git notes"
[main a63c1ff] Playing with Git notes
Date: Mon Jul 8 21:18:00 2024 +0200
Now, we can add a note to this commit using the git notes
command:
1
2
3
# 'a63c1ff' is the object (commit) to attach this note, it defaults
# to HEAD (i.e. the latest commit on the current branch)
$ git notes add -m "Extra information of this commit" a63c1ff
Great, we’ve added extra information to that commit! Now, we can view it by
using the --notes
option:
1
2
3
4
5
6
7
8
9
$ git log --notes
commit a63c1ffcf90a6ee3188a086b14eebf33087aebaa (HEAD -> main)
Author: Wouter de Jong <wouter@example.com>
Date: Mon Jul 8 21:18:00 2024 +0200
Playing with Git notes
Notes:
Extra information of this commit
Cool! As you can see, the commit object didn’t change (the hash is the same as before), but we’ve attached the new note object.
Multiple Git Notes References
By default, notes are stored in the notes/commits
ref. But you can
categorize Git notes into multiple references, allowing you to add multiple
types of notes to a single commit.
You can use the --ref
option to specify the note tree:
1
2
3
4
5
6
7
8
9
10
$ git notes --ref=acceptance add -m "Tested-by: Jane doe <jane@example.com>"
$ git log --notes=acceptance
commit a63c1ffcf90a6ee3188a086b14eebf33087aebaa (HEAD -> main)
Author: Wouter de Jong <wouter@example.com>
Date: Mon Jul 8 21:18:00 2024 +0200
Playing with Git notes
Notes (acceptance):
Tested-by: Jane doe <jane@example.com>
The ref/notes/acceptance
now holds this new note. Using multiple
references allows us to add multiple notes to a single commit (one per
reference):
1
2
3
4
5
6
7
8
9
10
11
12
$ git log --notes=commits --notes=acceptance
commit a63c1ffcf90a6ee3188a086b14eebf33087aebaa (HEAD -> main)
Author: Wouter de Jong <wouter@example.com>
Date: Mon Jul 8 21:18:00 2024 +0200
Let's use Git notes
Notes:
Extra information for this commit
Notes (acceptance):
Tested-by: Jane doe <jane@example.com>
How Symfony uses Git Notes to store GitHub Discussions
Now we know a bit about Git notes, we can look at how we can use them to store code discussions and reviews.
Symfony maintainers use an internal CLI tool to merge contributions. Among other things it checks CI and core team approvals, and it helps us automatically re-target contributions to the correct version.
After merging a branch using Git, the tool also uses the GitHub API to fetch all pull request comments. The comments are stored in a Git Note and attached to the merge commit. This way, whenever you need to find the discussion for a particular feature, you can look up the merge commit and read it. Even if Symfony decides to switch to another platform one day.
Storing GitHub Comments in a Note
First, the tool fetches the pull request comments using the GitHub API via the KnpLabs GitHub API package:
1
2
3
4
5
6
7
8
9
10
use GitHub\Client;
$github = new Client();
$resultPager = new ResultPager($github);
$prComments = $resultPager->fetchAll(
$github->issues()->comments(),
'all',
['symfony', 'symfony', $prNumber]
);
Then, it loops over all comments and puts it in a temporary file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$notes = '';
foreach ($prComments as $comment) {
$notes .= <<<EOF
---------------------------------------------------------------------------
by {$comment['user']['login']} at {$comment['created_at']}
{$comment['body']}
EOF;
}
file_put_contents(getcwd().'/.notes', $notes);
Now, we can create a Git note from the file contents (Symfony puts them in a named “github-comments” reference):
1
2
3
4
5
# first, replace the local github-comments ref with the upstream one
$ git fetch origin refs/notes/github-comments:refs/notes/github-comments
# then, create the new note
$ git notes --ref=github-comments add --file=.notes
Finally, we can push the notes to GitHub. Git doesn’t push notes by default, we have to mention the reference explicitly:
1
$ git push --no-follow-tags origin refs/notes/github-comments
Reading the Notes back
GitHub no longer shows the refs/notes/...
objects anywhere. But as it is
just another set of objects (just like the commit tree), you can still
push and fetch it from GitHub if you reference it directly:
1
2
3
4
$ git fetch origin refs/notes/github-comments:refs/notes/github-comments
# or fetch all note references
$ git fetch origin "refs/notes/*:refs/notes/*"
You can also edit the .git/config
file of a project to always fetch
notes by default:
1
2
3
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
+ fetch = +refs/notes/*:refs/notes/*
Now the notes exist locally, you can view them in the Git logs for instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ git log --notes=github-comments
commit 1a16ebc32598faada074e0af12a6a698d2964a5e (HEAD -> 7.2, origin/7.2)
Merge: 09f9eb73b6 1303cd1a56
Author: Nicolas Grekas <nicolas.grekas@gmail.com>
Date: Wed Jul 10 17:23:17 2024 +0200
minor #57657 [Messenger] [SQS] Allow to pass `sslmode` as an option (spuf)
This PR was merged into the 7.2 branch.
Discussion
----------
[Messenger] [SQS] Allow to pass `sslmode` as an option
| Q | A
| ------------- | ---
| Branch? | 7.2
| Bug fix? | no
| New feature? | no
| Deprecations? | no
| Issues |
| License | MIT
This is a minor. There is an option "sslmode" that is documented as an option but can be passed only in dsn query string.
Commits
-------
1303cd1a56 [Messenger][SQS] Allow to pass sslmode as an option
Notes (github-comments):
---------------------------------------------------------------------------
by carsonbot at 2024-07-04T20:10:34Z
Hey!
I see that this is your first PR. That is great! Welcome!
...
Summary
- It’s important to distribute more than just the code
- Use Git notes to store all sorts of important information to commits
- You can view old Symfony discussions offline by using the
github-comments
notes reference