I've thought for a while now that the Github contributions heatmap is a particularly uninteresting page. This thought popped into my head once more after reading a fantastic ebook on the fundamentals of Git, so armed with new confidence and knowledge of Git commits, I decided to fake contributions on my profile in order to spell out words on my heatmap.
I knew that the contributions chart responded to commits in a Git repo, along with issues, pull requests, and other Github specific interactions. The commits seemed to be the easiest to fake (although I am a big fan of the relatively new Github CLI), so I found where dates are considered in a commit object and tried faking them to see if Github would accept them. The only two places I could see were the author date and the commit date. Faking the committer date was a simple matter of prepending the
git commit command with an env variable
GIT_COMMITTER_DATE. Changing the author date was even easier as
git commit accepts a
--date argument! So making a commit for a specific date came up to be:
$ LC_ALL=C GIT_COMMITTER_DATE="$(date --date='01/01/1970 12:00')" git commit -a -m"01/01/1970 12:00" --no-edit --date "$(date --date='01/01/1970 12:00')"
Running this will give you a commit from 01/01/1970 which unsurprisingly is the earliest date you can have a commit on. We're not stopping there though, even though it is funny how the UI is broken on your profile page after doing that.
Now with a basis to build on, I decided to package this up into a simple Python script, and add some helper methods around it. Namely, being able to "draw" in a particular
(x, y) position on the heatmap.
def get_origin_datetime(year): """ Returns the datetime of (0,0) on the contributions heatmap. This is horrific code. """ d = datetime.datetime(year, 1, 1, 12) while d.weekday() != 6: d += datetime.timedelta(1) return d def xy_to_datetime(year, x, y): return get_origin_datetime(year) + datetime.timedelta((7 * x) + y)
Then I used a handy Dot Matrix Tool to create a font which was 3x5 pixels, and translated each character into a simple template:
's': [ (0, 0), (1, 0), (2, 0), (0, 1), (0, 2), (1, 2), (2, 2), (2, 3), (0, 4), (1, 4), (2, 4), ],
With this done, there was only one method left to implement and we were good to go:
def draw_letter(year, letter, letter_number): """ The letter_number is to calculate the offset to apply in the grid. """ x_offset = letter_number * (LETTER_WIDTH + INTER_LETTER_SPACE_WIDTH) # This centres the letter vertically y_offset = 1 for coord in LETTERS[letter]: commit_on_xy(year, x_offset + coord, y_offset + coord)
To view this more sensibly, check out this gist. The only improvement I'm thinking about at the minute is to include an opacity/darkness option on each pixel that is drawn to add a bit more detail to the lettering. But for a few hours on a Saturday morning, I'm happy with the result :)