git-story: Show Git activity for the last 52 weeks

2019-11-01


For quickly getting an overview of what happened in the last 52 weeks in a Git project, I use a script git-story, so I can run git story in a Git repo and get a nice overview of what happened in a project (similar to Github's contribution history, but on a per-repo basis).

Example Output

Here's an example output of the vim Git repo as of 2019-11-01:

   ONov Dec Jan  Feb Mar Apr  May Jun Jul  Aug Sep Oct
 M ████████████████████████████████████████
 T ██████████████████████████████████████
 W ████████████████████████████████████
 T ██████████████████████████████
 F ███████████████████████ ← today
 S █████████████████████████████████
 S ████████████████████████████████████

 1779 commits in 52 weeks        Less  More

Source

Download the script here: git-story

#!/usr/bin/env python3
# git-story: Github-like "commit history" on the console
# Copyright 2019 Thomas Perl <m@thp.io>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

import subprocess, collections, re, sys
from datetime import date, datetime, timedelta

# https://en.wikipedia.org/wiki/Xterm#/media/File:Xterm_256color_chart.svg
PALETTE, DARK = [22, 28, 34, 40, 46, 47], 232
DAYS = 52*7
DTFMT = '%Y-%m-%d %H:%M:%S %z'
MONTHS = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split()

today = date.today()
first = today - timedelta(DAYS+today.weekday())

commits_per_day = collections.defaultdict(int)
lines = subprocess.check_output(['git', 'log',
    '--date=iso8601', '--all']).decode(sys.getdefaultencoding())
for line in (line for line in lines.split('\n') if line.startswith('Date:')):
    commits_per_day[datetime.strptime(line[6:].strip(), DTFMT).date()] += 1
commits_per_day = {k: v for k, v in commits_per_day.items() if k >= first}
maxc = max(commits_per_day.values()) if commits_per_day else 1

for i in range(7):
    this_weekday = today + timedelta(days=i-today.weekday())
    for j in range(-DAYS, 1, 7):
        then = this_weekday + timedelta(days=j)
        if then > today:
            break

        if j == -DAYS:
            if i == 0:
                print('\n  ', re.sub(r'([a-l])\1*', lambda m:
                    (MONTHS[ord(m.group(1))-ord('a')]+'  ')[:len(m.group(0))],
                    ''.join((chr(ord('a')+(then+timedelta(days=d)).month-1))
                    for d in range(-DAYS, 1, 7))))
            print('', 'MTWTFSS'[then.weekday()], end=' ')

        n = commits_per_day.get(then, 0)
        color = PALETTE[int(n/maxc*(len(PALETTE)-1))] if n > 0 else DARK
        print(end='\033[38;5;%dm\u2588\033[0m' % (color,))
    print(' ← today' if then == today else '')

legend = ' '.join('\033[38;5;%dm\u2588' % (color,) for color in [DARK]+PALETTE)
summary = '{} commits in 52 weeks'.format(sum(commits_per_day.values()))
print('\n %-31s Less %s\033[0m More\n' % (summary, legend))
Thomas Perl · 2019-11-01