To see posts by date, check out the archives

Per-project git commit templates
Tyler Cipriani Posted

People should try to compare the quality of the kernel git logs with some other projects, and cry themselves to sleep.

– Linus Torvalds

I’ll never remember your project’s commit guidelines.

Every project insists on something different:

But git commit templates help. Commit templates provide a scaffold for commit messages, offering documentation where you need it: inside the editor where you’re writing your commit message.

What is a git commit template?

Image for: What is a git commit template?

When you type git commit, git pops open your text editor1. Git can pre-fill your editor with a commit template—it’s like a form you fill out.

Creating a commit template is simple.

  • Create a plaintext file – mine lives at ~/.config/git/message.txt
  • Tell git to use it:
git config --global \
    commit.template '~/.config/git/message.txt'

My default template packs everything I know about writing a commit.

Project-specific templates with IncludeIf

Image for: Project-specific templates with IncludeIf

The real magic of commit templates is you can have different templates for each project.

Different projects can use different templates with git’s includeIf configuration setting.2

Large projects, such as the Linux kernel, git, and MediaWiki, have their own commit guidelines.

For Wikimedia work, I stow git repos in ~/Projects/Wikimedia and at the bottom of my global git config (~/.config/git/config) I have:

[includeIf "gitdir:~/Projects/Wikimedia/**"]
    path = ~/.config/git/config.wikimedia

In config.wikimedia, I point to my Wikimedia-specific commit template. I also override other git config settings like my user.email or core.hooksPath.

An example: my global template

Image for: An example: my global template

My default commit template contains three sections:

  1. Subject – 50 characters or less, capitalized, no end punctuation.
  2. Body – Wrap at 72 characters with a blank line separating it from the subject.
  3. Trailers – Standard formats with a blank line separating them from the body.

In each section, I added pointers for both format3 and content.

For the header, the guidance is quick:


# 50ch. wide ----------------------------- SUBJECT
#                                                |
#     "If applied, this commit will..."          |
#                                                |
#     Change / Add / Fix                         |
#     Remove / Update / Document                 |
#                                                |
# ------- ↓ LEAVE BLANK LINE ↓ ---------- /SUBJECT

For the body, I remind myself to answer basic questions:

# 72ch. wide ------------------------------------------------------ BODY
#                                                                      |
#     - Why should this change be made?                                |
#       - What problem are you solving?                                |
#       - Why this solution?                                           |
#     - What's wrong with the current code?                            |
#     - Are there other ways to do it?                                 |
#     - How can the reviewer confirm it works?                         |
#                                                                      |

And that’s it, except for git trailers.

The twisty maze of git trailers

Image for: The twisty maze of git trailers

My template has a section for trailers used by the projects I work on.

#     TRAILERS                                                         |
#     --------                                                         |
#     (optional) Uncomment as needed.                                  |
#     Leave a blank line before the trailers.                          |
#                                                                      |
# Bug: #xxxx
# Acked-by: Example User <user@example.com>
# Cc: Example User <user@example.com>
# Co-Authored-by: Example User <user@example.com>
# Requested-by: Example User <user@example.com>
# Reported-by: Example User <user@example.com>
# Reviewed-by: Example User <user@example.com>
# Suggested-by: Example User <user@example.com>
# Tested-by: Example User <user@example.com>
# Thanks: Example User <user@example.com>

These trailers serve as useful breadcrumbs of documentation. Git can parse them using standard commands.

For example, if I wanted a tab-separated list of commits and their related tasks, I could find Bug trailers using git log:

$ TAB=%x09
$ BUG_TRAILER='%(trailers:key=Bug,valueonly=true,separator=%x2C )'
$ SHORT_HASH=%h
$ SUBJ=%s
$ FORMAT="${SHORT_HASH}${TAB}${BUG_TRAILER}${TAB}${SUBJ}"
$ git log --topo-order --no-merges \
      --format="$FORMAT"
d2b09deb12f     T359762 Rewrite Kurdish (ku) Latin to Arabic converter
28123a6a262     T332865 tests: Remove non-static fallback in HookRunnerTestBase
4e919a307a4     T328919 tests: Remove unused argument from data provider in PageUpdaterTest
bedd0f685f9             objectcache: Improve `RESTBagOStuff::handleError()`
2182a0c4490     T393219 tests: Remove two data provider in RestStructureTest

Stop remembering commit message guidelines

Image for: Stop remembering commit message guidelines

Git commit templates free your brain from remembering what to write, allowing you to focus on the story you need to tell.

Save your brain for what it’s good at.


  1. Starting with core.editor in your git config, $VISUAL or $EDITOR in your shell, finally falling back to vi.↩︎

  2. You could also set it inside a repo’s .git/config, includeIf is useful if you have multiple repos with the same standards under one directory.↩︎

  3. All cribbed from Tim Pope↩︎

Boox Go 10.3, two months in
Tyler Cipriani Posted

[The] Linux kernel uses GPLv2, and if you distribute GPLv2 code, you have to provide a copy of the source (and modifications) once someone asks for it. And now I’m asking nicely for you to do so 🙂

– Joga, bbs.onyx-international.com

In January, I bought a Boox Go 10.3—a 10.3-inch, 300-ppi, e-ink Android tablet.

After two months, I use the Boox daily—it’s replaced my planner, notebook, countless PDF print-offs, and the good parts of my phone.

But Boox’s parent company, Onyx, is sketchy.

I’m conflicted. The Boox Go is a beautiful, capable tablet that I use every day, but I recommend avoiding as long as Onyx continues to disregard the rights of its users.

How I’m using my Boox

Image for: How I’m using my Boox

Each morning, I plop down in front of my MagicHold laptop stand and journal on my Boox with Obsidian.

I use Syncthing to back up my planner and sync my Zotero library between my Boox and laptop.

In the evening, I review my PDF planner and plot for tomorrow.

I use these apps:

  • Obsidian – a markdown editor that syncs between all my devices with no fuss for $8/mo.
  • Syncthing – I love Syncthing—it’s an encrypted, continuous file sync-er without a centralized server.
  • Meditation apps1 – Guided meditation away from the blue light glow of my phone or computer is better.

Before buying the Boox, I considered a reMarkable.

The reMarkable Paper Pro has a beautiful color screen with a frontlight, a nice pen, and a “type folio,” plus it’s certified by the Calm Tech Institute.

But the reMarkable is a distraction-free e-ink tablet. Meanwhile, I need distraction-lite.

What I like

Image for: What I like
  • Calm(ish) technology – The Boox is an intentional device. Browsing the internet, reading emails, and watching videos is hard, but that’s good.
  • Apps – Google Play works out of the box. I can install F-Droid and change my launcher without difficulty.
  • Split screen – The built-in launcher has a split screen feature. I use it to open a PDF side-by-side with a notes doc.
  • Reading – The screen is a 300ppi Carta 1200, making text crisp and clear.

What I dislike

Image for: What I dislike
  • Typing – Typing latency is noticeable.
    • At Boox’s highest refresh rate, after hitting a key, text takes between 150ms to 275ms to appear.
    • I can still type, though it’s distracting at times.
  • Accessories
    • Pen – The default pen looks like a child’s whiteboard marker and feels cheap. I replaced it with the Kindle Scribe Premium pen, and the writing experience is vastly improved.
    • Cover – It’s impossible to find a nice cover. I’m using a $15 cover that I’m encasing in stickers.
  • Tool switching – Swapping between apps is slow and clunky. I blame Android and the current limitations of e-ink more than Boox.
  • No frontlight – The Boox’s lack of frontlight prevents me from reading more with it. I knew this when I bought my Boox, but devices with frontlights seem to make other compromises.

Onyx

The Chinese company behind Boox, Onyx International, Inc., runs the servers where the Boox routes telemetry. I block this traffic with Pi-Hole2.

I inspected this traffic via Mitm proxy—most traffic was benign, though I never opted into sending any telemetry (nor am I logged in to a Boox account). But it’s also an Android device, so it’s feeding telemetry into Google’s gaping maw, too.

Worse, Onyx is flouting the terms of the GNU Public License, declining to release Linux kernel modifications to users. This is anathema to me—GPL violations are tantamount to theft.

Onyx’s disregard for user rights makes me regret buying the Boox.

Verdict

Image for: Verdict

I’ll continue to use the Boox and feel bad about it. I hope my digging in this post will help the next person.

Unfortunately, the e-ink tablet market is too niche to support the kind of solarpunk future I’d always imagined.

But there’s an opportunity for an open, Linux-based tablet to dominate e-ink. Linux is playing catch-up on phones with PostmarketOS. Meanwhile, the best e-ink tablets have to offer are old, unupdateable versions of Android, like the OS on the Boox.

In the future, I’d love to pay a license- and privacy-respecting company for beautiful, calm technology and recommend their product to everyone. But today is not the future.


  1. I go back and forth between “Waking Up” and “Calm”↩︎

  2. Using github.com/JordanEJ/Onyx-Boox-Blocklist↩︎

Eventually consistent plain text accounting
Tyler Cipriani Posted

Over the past six months, I’ve tracked my money with hledger—a plain text double-entry accounting system written in Haskell. It’s been surprisingly painless.

My previous attempts to pick up real accounting tools floundered. Hosted tools are privacy nightmares, and my stint with GnuCash didn’t last.

But after stumbling on Dmitry Astapov’s “Full-fledged hledger” wiki1, it clicked—eventually consistent accounting. Instead of modeling your money all at once, take it one hacking session at a time.

It should be easy to work towards eventual consistency. […] I should be able to [add financial records] bit by little bit, leaving things half-done, and picking them up later with little (mental) effort.

– Dmitry Astapov, Full-Fledged Hledger

Principles of my system

Image for: Principles of my system

I’ve cobbled together a system based on these principles:

  • Avoid manual entry – Avoid typing in each transaction. Instead, rely on CSVs from the bank.
  • CSVs as truth – CSVs are the only things that matter. Everything else can be blown away and rebuilt anytime.
  • Embrace version control – Keep everything under version control in Git for easy comparison and safe experimentation.

Learn hledger in five minutes

Image for: Learn hledger in five minutes

hledger concepts are heady, but its use is simple. I divide the core concepts into two categories:

  • Stuff hledger cares about:
    • Transactions – how hledger moves money between accounts.
    • Journal files – files full of transactions
  • Stuff I care about:
    • Rules files – how I set up accounts, import CSVs, and move money between accounts.
    • Reports – help me see where my money is going and if I messed up my rules.

Transactions move money between accounts:

2024-01-01 Payday
    income:work      $-100.00
    assets:checking   $100.00

This transaction shows that on Jan 1, 2024, money moved from income:work into assets:checking—Payday.

The sum of each transaction should be $0. Money comes from somewhere, and the same amount goes somewhere else—double-entry accounting. This is powerful technology—it makes mistakes impossible to ignore.

Journal files are text files containing one or more transactions:

2024-01-01 Payday
    income:work              $-100.00
    assets:checking           $100.00
2024-01-02 QUANSHENG UVK5
    assets:checking          $-29.34
    expenses:fun:radio        $29.34

Rules files transform CSVs into journal files via regex matching.

Here’s a CSV from my bank:

Transaction Date,Description,Category,Type,Amount,Memo
09/01/2024,DEPOSIT Paycheck,Payment,Payment,1000.00,
09/04/2024,PizzaPals Pizza,Food & Drink,Sale,-42.31,
09/03/2024,Amazon.com*XXXXXXXXY,Shopping,Sale,-35.56,
09/03/2024,OBSIDIAN.MD,Shopping,Sale,-10.00,
09/02/2024,Amazon web services,Personal,Sale,-17.89,

And here’s a checking.rules to transform that CSV into a journal file so I can use it with hledger:

# checking.rules
# --------------
# Map CSV fields → hledger fields[0]
fields date,description,category,type,amount,memo,_
# `account1`: the account for the whole CSV.[1]
account1    assets:checking
account2    expenses:unknown
skip 1

date-format %m/%d/%Y
currency $

if %type Payment
    account2 income:unknown
if %category Food & Drink
    account2 expenses:food:dining

# [0]: <https://hledger.org/hledger.html#field-names>
# [1]: <https://hledger.org/hledger.html#account-field>

With these two files (checking.rules and 2024-09_checking.csv), I can make the CSV into a journal:

$ > 2024-09_checking.journal \
    hledger print \
    --rules-file checking.rules \
    -f 2024-09_checking.csv
$ head 2024-09_checking.journal
2024-09-01 DEPOSIT Paycheck
    assets:checking        $1000.00
    income:unknown        $-1000.00

2024-09-02 Amazon web services
    assets:checking          $-17.89
    expenses:unknown          $17.89

Reports are interesting ways to view transactions between accounts.

There are registers, balance sheets, and income statements:

$ hledger incomestatement \
    --depth=2 \
    --file=2024-09_bank.journal

Revenues:
               $1000.00 income:unknown
-----------------------
               $1000.00


Expenses:
                 $42.31 expenses:food
                 $63.45 expenses:unknown
-----------------------
                $105.76
-----------------------
Net:            $894.24

At the beginning of September, I spent $105.76 and made $1000, leaving me with $894.24.

But a good chunk is going to the default expense account, expenses:unknown. I can use the hleger aregister to see what those transactions are:

$ hledger areg expenses:unknown \
    --file=2024-09_checking.journal \
    -O csv | \
  csvcut -c description,change | \
  csvlook
| description              | change |
| ------------------------ | ------ |
| OBSIDIAN.MD              |  10.00 |
| Amazon web services      |  17.89 |
| Amazon.com*XXXXXXXXY     |  35.56 |
l

Then, I can add some more rules to my checking.rules:

if OBSIDIAN.MD
    account2 expenses:personal:subscriptions
if Amazon web services
    account2 expenses:personal:web:hosting
if Amazon.com
    account2 expenses:personal:shopping:amazon

Now, I can reprocess my data to get a better picture of my spending:

$ > 2024-09_bank.journal \
    hledger print \
    --rules-file bank.rules \
    -f 2024-09_bank.csv
$ hledger bal expenses \
    --depth=3 \
    --percent \
    -f 2024-09_checking2.journal
              30.0 %  expenses:food:dining
              33.6 %  expenses:personal:shopping
               9.5 %  expenses:personal:subscriptions
              16.9 %  expenses:personal:web
--------------------
             100.0 %

For the Amazon.com purchase, I lumped it into the expenses:personal:shopping account. But I could dig deeper—download my order history from Amazon and categorize that spending.

This is the power of working bit-by-bit—the data guides you to the next, deeper rabbit hole.

Goals and non-goals

Image for: Goals and non-goals

Why am I doing this? For years, I maintained a monthly spreadsheet of account balances. I had a balance sheet. But I still had questions.

Before diving into accounting software, these were my goals:

  • Granular understanding of my spending – The big one. This is where my monthly spreadsheet fell short. I knew I had money in the bank—I kept my monthly balance sheet. I budgeted up-front the % of my income I was saving. But I had no idea where my other money was going.
  • Data privacy – I’m unwilling to hand the keys to my accounts to YNAB or Mint.
  • Increased value over time – The more time I put in, the more value I want to get out—this is what you get from professional tools built for nerds. While I wished for low-effort setup, I wanted the tool to be able to grow to more uses over time.

Non-goals—these are the parts I never cared about:

  • Investment tracking – For now, I left this out of scope. Between monthly balances in my spreadsheet and online investing tools’ ability to drill down, I was fine.2
  • Taxes – Folks smarter than me help me understand my yearly taxes.3
  • Shared system – I may want to share reports from this system, but no one will have to work in it except me.
  • Cash – Cash transactions are unimportant to me. I withdraw money from the ATM sometimes. It evaporates.

hledger can track all these things. My setup is flexible enough to support them someday. But that’s unimportant to me right now.

Monthly maintenance

Image for: Monthly maintenance

I spend about an hour a month checking in on my money Which frees me to spend time making fancy charts—an activity I perversely enjoy.

Here’s my setup:

$ tree ~/Documents/ledger
.
├── export
│   ├── 2024-balance-sheet.txt
│   └── 2024-income-statement.txt
├── import
│   ├── in
│   │   ├── amazon
│   │   │   └── order-history.csv
│   │   ├── credit
│   │   │   ├── 2024-01-01_2024-02-01.csv
│   │   │   ├── ...
│   │   │   └── 2024-10-01_2024-11-01.csv
│   │   └── debit
│   │       ├── 2024-01-01_2024-02-01.csv
│   │       ├── ...
│   │       └── 2024-10-01_2024-11-01.csv
│   └── journal
│       ├── amazon
│       │   └── order-history.journal
│       ├── credit
│       │   ├── 2024-01-01_2024-02-01.journal
│       │   ├── ...
│       │   └── 2024-10-01_2024-11-01.journal
│       └── debit
│           ├── 2024-01-01_2024-02-01.journal
│           ├── ...
│           └── 2024-10-01_2024-11-01.journal
├── rules
│   ├── amazon
│   │   └── journal.rules
│   ├── credit
│   │   └── journal.rules
│   ├── debit
│   │   └── journal.rules
│   └── common.rules
├── 2024.journal
├── Makefile
└── README

Process:

  1. Import – download a CSV for the month from each account and plop it into import/in/<account>/<dates>.csv
  2. Make – run make
  3. Squint – Look at git diff; if it looks good, git add . && git commit -m "💸" otherwise review hledger areg to see details.

The Makefile generates everything under import/journal:

  • journal files from my CSVs using their corresponding rules.
  • reports in the export folder

I include all the journal files in the 2024.journal with the line: include ./import/journal/*/*.journal

Here’s the Makefile:

SHELL := /bin/bash
RAW_CSV = $(wildcard import/in/**/*.csv)
JOURNALS = $(foreach file,$(RAW_CSV),$(subst /in/,/journal/,$(patsubst %.csv,%.journal,$(file))))

.PHONY: all
all: $(JOURNALS)
    hledger is -f 2024.journal > export/2024-income-statement.txt
    hledger bs -f 2024.journal > export/2024-balance-sheet.txt

.PHONY clean
clean:
        rm -rf import/journal/**/*.journal

import/journal/%.journal: import/in/%.csv
    @echo "Processing csv $< to $@"
    @echo "---"
    @mkdir -p $(shell dirname $@)
    @hledger print --rules-file rules/$(shell basename $$(dirname $<))/journal.rules -f "$<" > "$@"

If I find anything amiss (e.g., if my balances are different than what the bank tells me), I look at hleger areg. I may tweak my rules or my CSVs and then I run make clean && make and try again.

Simple, plain text accounting made simple.

And if I ever want to dig deeper, hledger’s docs have more to teach. But for now, the balance of effort vs. reward is perfect.


  1. while reading a blog post from Jonathan Dowland↩︎

  2. Note, this is covered by full-fledged hledger – Investements↩︎

  3. Also covered in full-fledged hledger – Tax returns↩︎

Mining for immature git hashes
Tyler Cipriani Posted

Luckily, I speak Leet.

Amita Ramanujan, Numb3rs, CBS’s IRC Drama

There’s an episode of the CBS prime-time drama Numb3rs that plumbs the depths of Dr. Joel Fleischman’s1 knowledge of IRC. In one scene, Fleischman wonders, “What’s ‘leet’”?

“Leet” is writing that replaces letters with numbers, e.g., “Numb3rs,” where 3 stands in for e.

In short, leet is like the heavy-metal “S” you drew in middle school: Sweeeeet.

 / \
/ | \
| | |
 \ \ 
| | |
\ | /
 \ /

ASCII art version of your misspent youth.

Following years of keen observation, I’ve noticed Git commit hashes are also letters and numbers.

Git commit hashes are, as Fleischman might say, prime targets for l33tification.

What can I spell with a git commit?

Image for: What can I spell with a git commit?

With hexidecimal we can spell any word containing the set of letters {A, B, C, D, E, F}DEADBEEF (a classic) or ABBABABE (for Mama Mia aficionados).

This is because hexidecimal is a base-16 numbering system—a single “digit” represents 16 numbers:

Base-10: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 16 15
Base-16: 0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F

Leet expands our palette of words—using 0, 1, and 5 to represent O, I, and S, respectively.

I created a script that scours a few word lists for valid words and phrases.

With it, I found masterpieces like DADB0D (dad bod), BADA55 (bad ass), and 5ADBAB1E5 (sad babies).

Manipulating commit hashes for fun and no profit

Image for: Manipulating commit hashes for fun and no profit

Git commit hashes are no mystery. A commit hash is the SHA-1 of a commit object.

And a commit object is the commit message with some metadata.

$ mkdir /tmp/BADA55-git && cd /tmp/BAD55-git
$ git init
Initialized empty Git repository in /tmp/BADA55-git/.git/
$ echo '# BADA55 git repo' > README.md && git add README.md && git commit -m 'Initial commit'
[main (root-commit) 68ec0dd] Initial commit
 1 file changed, 1 insertion(+)
  create mode 100644 README.md
$ git log --oneline
68ec0dd (HEAD -> main) Initial commit

Let’s confirm we can recreate the commit hash:

$ git cat-file -p 68ec0dd > commit-msg
$ sha1sum <(cat \
    <(printf "commit ") \
    <(wc -c < commit-msg | tr -d '\n') \
    <(printf '%b' '\0') commit-msg)
68ec0dd6dead532f18082b72beeb73bd828ee8fc  /dev/fd/63

Our repo’s first commit has the hash 68ec0dd. My goal is:

  1. Make 68ec0dd be BADA55.
  2. Keep the commit message the same, visibly at least.

But I’ll need to change the commit to change the hash. To keep those changes invisible in the output of git log, I’ll add a \t and see what happens to the hash.

$ truncate -s -1 commit-msg    # remove final newline
$ printf '\t\n' >> commit-msg  # Add a tab
$ # Check the new SHA to see if it's BADA55
$ sha1sum <(cat \
    <(printf "commit ") \
    <(wc -c < commit-msg | tr -d '\n') \
    <(printf '%b' '\0') commit-msg)
27b22ba5e1c837a34329891c15408208a944aa24  /dev/fd/63

Success! I changed the SHA-1. Now to do this over-and-over until we get to BADA55—mining for immature hashes.

Fortunately, user not-an-aardvark created a tool for that—lucky-commit that manipulates a commit message, adding a combination of \t and [:space:] characters until you hit a desired SHA-1.

Written in rust, lucky-commit computes all 256 unique 8-bit strings composed of only tabs and spaces. And then pads out commits up to 48-bits with those strings, using worker threads to quickly compute the SHA-12 of each commit.

It’s pretty fast:

$ time lucky_commit BADA555

real    0m0.091s
user    0m0.653s
sys     0m0.007s
$ git log --oneline
bada555 (HEAD -> main) Initial commit
$ xxd -c1 <(git cat-file -p 68ec0dd) | grep -cPo ': (20|09)'
12
$ xxd -c1 <(git cat-file -p HEAD) | grep -cPo ': (20|09)'
111

Now we have an more than an initial commit. We have a BADA555 initial commit.

All that’s left to do is to make ALL our commits BADA55 by abusing git hooks.

$ cat > .git/hooks/post-commit && chmod +x .git/hooks/post-commit
#!/usr/bin/env bash

echo 'L337-ifying!'
lucky_commit BADA55
$ echo 'A repo that is very l33t.' >> README.md && git commit -a -m 'l33t'
L337-ifying!
[main 0e00cb2] l33t
1 file changed, 1 insertion(+)
$ git log --oneline
bada552 (HEAD -> main) l33t
bada555 Initial commit

And now I have a git repo almost as cool as the sweet “S” I drew in middle school.


  1. This is a Northern Exposure spin off, right? I’ve only seen 1:48 of the show…↩︎

  2. or SHA-256 for repos that have made the jump to a more secure hash function↩︎

GitHub's Legacy of Terrible Code Review
Tyler Cipriani Posted

GitHub has always been a great code host. But GitHub’s code review system was an afterthought. Even now, Pull Requests still lag behind.

Oh yeah, there’s pull requests now

– GitHub blog, Sat, 23 Feb 2008

When GitHub launched, it had no code review.

GitHub’s Pull Request model—“GitHub flow”—was never designed. It emerged.

But over time Pull Requests became the default way to collaborate via Git, and we’re all still suffering the consequences.

First-generation Pull Requests

Image for: First-generation Pull Requests

GitHub never used git’s built-in git request-pull.

According to Linus Torvalds—Git’s creator—GitHub “decided to replace it with their own totally inferior version.”

When the Pull Request debuted in 2008 it worked like this:

  1. Create a fork and click “Pull Request.”
  2. Send a message to someone1 with a link to your fork, asking them to merge it.

But while git request-pull generates a nicely formatted message with a diff stat and changelog, GitHub hamstrung Pull Requests.

GitHub provided only a small, empty <textarea>—Pull Requests were little more than unstructured emails to other GitHub users.

And Pull Requests still lacked any way to see changes via the web.

“Code Review = Discussion + Code”?

Image for: “Code Review = Discussion + Code”?

It took two years for GitHub to show the git diff between two repos on GitHub.

In 2010, “cross repository compare view” coupled with an unthreaded comments section and became Pull Requests 2.02.

Of course, the code and the comments were still on two different pages. It took another year before you could comment in the code.

Inline code comments

Image for: Inline code comments

In 2011, rtomayko made the first inline comment on a change, writing, in full: “+1”.

Inline code review was far from a revelation. Guido van Rossum’s Mondrian—his 20% project at Google—had inline code comments by 2006. And there was an open-source version of Mondrian on the public web by 2008.

The Linux Kernel (using git format-patch) had code comments since 2005.

GitHub’s code review is still behind.

Image for: GitHub’s code review is still behind.

In 2008, GitHub’s developers created a new kind of code review.

But key features were missing. GitHub slowly tacked on these features:

Now, it’s 2024. And here’s a biased list of what I think is still missing:

  • Commit review – Ability to comment on the commit message.
  • Your turn – Like Gerrit’s attention sets – Microsoft recently did a study on a system called Nudge which was a similar idea, it yielded good results, reducing review completion time 60%.
  • User-defined review labels – “Approve”/“Request changes” is so limited—instead of using a complicated system of tags to mark changes ready for design approval, technical writing approval, style approval, and code approval—let repo owners figure out what approvals make sense for their project.
  • Hide bot comments – Allow me to hide bot comments so I can see the human comments.
  • Push to pull – Push to a special remote to create a pull request using my commit: git push origin <branch>:refs/pull-request/<target-branch>.
  • Review in notes – Annotate commits with metadata in a special git note refs/notes/review.
  • Stacked diffs – Just come on. You have infinite money.

And at this point I made Gerrit, but with more branches.


  1. “Someone” was a person chosen by you from a checklist of the people who had also forked this repository at some point.↩︎

  2. “Code Review = Discussion + Code.” was the headline of a blog post GitHub wrote circa 2010 introducing Pull Requests 2.0↩︎