We’ve all seen Git commit hashes—the hard-to-remember hexadecimal strings that refer to Git commits.
But it’s rare to see Git commit hashes spark joy:
Creating memorable Git hashes is hard. Git creates hashes with SHA-1 or SHA-2, which are one-way functions—there’s no way to predict a hash.
So, to spread joy with Git history, we need to:
- Brute-force funny Git commit hashes.
- Keep Git usable – Git commit messages and metadata should stay usable by humans and machines.
- Be fast – I need to find commit hashes quickly, without churning forever for a bit of silly fun.
What can I spell with a git commit?
O’RLY Insulting SHA-1 Collisions.
(Copyright 2024 DenITDao via orlybooks)
With hexadecimal 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 hexadecimal 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 fGit hashes use lowercase letters and the numbers 0–9. Some numbers look like lowercase letters, which expands our palette of words—substituting 0 for O, 1 for l, and 5 for s.
I created a script that scours word lists for valid words and phrases.
With my script, I found some masterpieces: dadb0d (dad bod), bada55 (bad ass), 0ff1oad (offload), 0ddba11 (oddball), and the famous last words: fee155afe (feels safe).
What is a Git commit hash?
Git commit hashes are no mystery.
A Git commit object is a compressed file that looks like:
$ < .git/objects/b8/da4201321b5d73297c353a36e09492d6abcdf2 zlib-flate -uncompress | cat -v commit 195^@tree 66dd8a515582fba80ca1e1137a6763f3759ebd65 author Tyler Cipriani <REDACTED> 1760808267 -0600 committer Tyler Cipriani <REDACTED> 1760812806 -0600 Initial commitDecompressing a Git commit object using zlib-flate -uncompress gives you the complete formatted commit object. Commit objects use the format:
commit [Length of commit message in bytes]\0[Commit message]`The commit message includes:
- Pointers to other Git objects (parent commits, trees, or other commits)
- Author name, email, author time
- Committer name, email, commit time
- The commit message
Git stores commits as zlib-compressed files under .git/objects1. Each filename is the SHA-1 (or SHA-2) of the object’s decompressed contents:
$ < .git/objects/b8/da4201321b5d73297c353a36e09492d6abcdf2 zlib-flate -uncompress | sha1sum b8da4201321b5d73297c353a36e09492d6abcdf2 -Recreate a Git commit hash with Bash
Before I can make funny Git commit hashes, I need generate valid ones.
$ mkdir /tmp/git-repo && cd /tmp/git-repo $ git init Initialized empty Git repository in /tmp/git-repo/.git/ $ echo > 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 commitI created a Git repo with an empty README.md and one commit. The commit’s hash is 68ec0dd.
$ git cat-file -p 68ec0dd > commit-msg $ COMMIT_SIZE_BYTES="$(wc -c < commit-msg)" $ printf 'commit %d\0' "$COMMIT_SIZE_BYTES" | \ cat - commit-msg | \ sha1sum 68ec0dd6dead532f18082b72beeb73bd828ee8fc -Here I recreate the commit hash 68ec0dd.
- git cat-file – get the commit message
- wc -c – get the commit message length in bytes
- printf 'commit %d\0%s' – print a formatted Git commit object, complete with the commit message and length
- sha1sum – get the hash of the commit object
Brute-forcing Git commit hashes, naïve attempt
My initial modest goal is:
- Make a commit with a hash that starts 00.
- Leave our code, author, and committer untouched.
- Keep the commit message the same, at least for humans.
To change the hash, I’ll need to change the hash input—the commit message. Modifying a commit message invisibly to users will be tricky.
Git pretty-prints commit messages when you run git log. Pretty printing trims any trailing isspace() characters:
- SPACE - \u0020
- TAB - \u0009
No one will see trailing spaces, but trailing spaces will change the commit hash.
To brute-force a Git commit hash, I’ll tweak the commit message in a loop, adding a random combination of space and tab characters until the commit hash starts with 00:
First, I get the current commit. Then, in the while loop, I append a random space character to the commit and compute the hash until the hash starts with 00.
Once I find a commit that has a hash starting 00, I use git hash-object to create a new Git commit object. Finally, I call git update-ref to point HEAD to my new commit hash.
$ git log --oneline 68ec0dd (HEAD -> main) Initial commit $ time bash git-fiddler.sh 00ba5924057452af287a2be151061f973862b811 real 0m2.618s user 0m3.130s sys 0m1.430s $ git log --oneline -1 00ba592 (HEAD -> main) Initial commitIt took about two seconds to brute-force a Git commit hash starting with 00.
Not bad.
Let’s go big. Let’s try for five leading 0s:
$ time bash git-fiddler.sh 00000a2195aa3d9e85bc5b70e70807e23f2f64fb real 118m37.577s user 133m54.613s sys 72m51.337sFinding a commit hash starting with 00000 took two hours with my silly Bash script.
I may need to optimize.
Brute-forcing Git commit hashes (before the heat death of the universe)
Fortunately, user not-an-aardvark created a tool for that—lucky-commit.
lucky_commit does the same thing as our Bash script; it adds combinations of TAB and SPACE characters until it finds the Git hash with a prefix you specify.
But, unlike my Bash script, it’s fast:
$ time lucky_commit 00000 real 0m0.103s user 0m0.323s sys 0m0.037s $ git log --oneline -1 00000e9 (HEAD -> main) Initial commitFinding a Git commit hash starting with 00000 takes 100ms (as opposed to two hours with my Bash script).
lucky_commit speeds up Git commit hashes in two ways:
- Does less work – Both SHA-1 and SHA-2 break commit messages in 64-byte chunks and then perform operations on those chunks one at a time. Each chunk affects subsequent chunks. By pre-computing the hash state before appending a fixed-length amount of padding, lucky_commit does a fraction of the bit twiddling of our shell script.
- Uses more CPU – Adding a fixed-length (48-bit) amount of padding to a commit using only two characters (TAB and SPACE), you have 248 variations (281 trillion possibilities). And you can split hashing into threads, doing more work in parallel.
- Moves to the GPU – If you have a GPU, you can do even more hashing operations in parallel. This is an option in lucky_commit.
Use Git hooks to spread joy
All that’s left to do is to make all my commits bada55 by abusing Git hooks.
$ cat > .git/hooks/post-commit && chmod +x .git/hooks/post-commit #!/usr/bin/env bash WORDS=( babb1e # BABBLE bada55 # BADASS badc0de # BADCODE: not even sorry. c0ffee # COFFEE 0ddba11 # ODDBALL 5adface # 😿 ) lucky_commit "${WORDS[RANDOM%${#WORDS[*]}]}" printf '✨ %s ✨\n' "$(git log --one line -1)"With the post-commit hook above, after I commit, lucky_commit fiddles with my commit message until my commit hash is a random prefix from the list.
Fast, automated Git joy:
$ git commit --amend --no-edit ✨ 0ddba116 Initial commit ✨ [main bd256a9] Initial commit Date: Mon Oct 20 17:58:32 2025 -0600 1 file changed, 1 insertion(+) create mode 100644 README.md $ git commit --amend --no-edit ✨ badc0de9 Initial commit ✨ [main facd923] Initial commit Date: Mon Oct 20 17:58:32 2025 -0600 1 file changed, 1 insertion(+) create mode 100644 README.md $ git commit --amend --no-edit ✨ babb1e6 Initial commit ✨ [main 441ec61] Initial commit Date: Mon Oct 20 17:58:32 2025 -0600 1 file changed, 1 insertion(+) create mode 100644 README.md.png)

![J.Huang Reveals Nvidia's Quantum and AI Supercomputing Breakthroughs [video]](https://www.youtube.com/img/desktop/supported_browsers/firefox.png)