Visualisierung eines Git Repos - a short (visual) history of your code

Immer wenn ich auf ein neues git repo stoße, suche ich früher oder später den "language breakdown graph" und finde ihn entweder elegant an der Seite (github) oder stolz im Header prangend (gitlab). Ein kleiner bunter Balken, der mir verrät, in welchen Sprachen hier programmiert wurde. Seit wir das Frontend unserer Anwendung Exply von JavaScript zu TypeScript migrieren, beobachte ich den Balken besonders gespannt. Wieviel Prozent haben wir mit dem letzten Merge Request migriert? Wie verändert sich unsere Codebasis? Wie sah das Projekt eigentlich früher aus, bevor ich daran gearbeitet habe?

Da Linguist, githubs Tool zum Erzeugen der language breakdown graphs, open source ist, lässt sich das alles mit Hilfe eines kleinen Python Scripts und Exply beantworten. Linguist lässt sich als cli-tool installieren und spuckt auf Befehl die Sprachen und entsprechenden Prozentanteile aus. Jetzt nur noch schnell über alle commits des repositories iterieren, jeden commit auschecken, analysieren, die Werte in eine CSV Datei speichern und in Exply importieren! 

Nun, ganz so einfach war es leider doch nicht. Beim Probieren geriet ich schnell an zwei Grenzen:

  1. Die Laufzeit: Linguist braucht pro commit zwischen 10 und 60 Sekunden. Exply hat knapp 6000 commits. Eine komplette Analyse aller commits dauerte gute 10 Stunden.
  2. Die Datenmenge: Knapp 6000 commits über acht Jahre mit insgesamt 16 verwendeten Sprachen ergaben 42536 Zeilen CSV - zu viele Datenpunkte, um sie in Exply sinnvoll visualisieren zu können.

Also müssen wir reduzieren. Ich habe mich dafür entschieden, nur einen commit pro Woche zu analysieren und automatisch erstellte Merge-Commits zu ignorieren. Das reduzierte die Laufzeit erheblich (nur noch knapp 3 Stunden) und lieferte 3549 Zeilen. 

So sieht das finale Skript aus:

import git import subprocess import datetime import csv import argparse def main(): begin_time = datetime.datetime.now() parser = argparse.ArgumentParser( description='Create a history of the programming languages used in your git repo.') parser.add_argument('--path', nargs='?', default='.', help='path to your git repo, defaults to the current directory') args = parser.parse_args() repo = git.Repo(args.path) commitCount = len(list(repo.iter_commits())) branch = repo.active_branch writtenLines = 0 print('analysing', commitCount, 'commits. This is going to take some time...') # setup the result CSV File result = csv.writer( open('gitLanguageHistory.csv', 'w'), delimiter=';', quotechar='|', quoting=csv.QUOTE_MINIMAL ) headers = ['commitSha', 'commitMessage', 'commitDate', 'language', 'percent'] result.writerow(headers) yearsAndWeeks = [] for index, commit in enumerate(list(repo.iter_commits()), start=1): # filter out semcolons and linebreaks for parsing commitMsg = commit.message.rstrip().replace(';', ',').replace('\n', ',') if "Merge branch" not in commitMsg: # get commit date and check if there is already a commit for this week date = datetime.datetime.fromtimestamp(commit.committed_date) yearWeek = str(date.year) + '-' + str(date.isocalendar()[1]) if yearWeek not in yearsAndWeeks: yearsAndWeeks.append(yearWeek) # checkout the commit repo.git.checkout(commit.hexsha) # run linguist as subprocess linguistResult = subprocess.check_output( 'github-linguist', cwd=args.path.encode('utf-8') ).decode("utf-8").split('\n') # parse the results for language in linguistResult: row = [ commit.hexsha, commitMsg, commit.committed_datetime.strftime("%d.%m.%Y"), "", "" ] percentAndLanguage = language.split() if len(percentAndLanguage) > 1: row[3] = percentAndLanguage[1] row[4] = percentAndLanguage[0].replace('%', '') result.writerow(row) writtenLines = writtenLines + 1 if (index > 0 and index % 10 == 0): timePassed = datetime.datetime.now() - begin_time print('- done with', index, 'commits (', timePassed.seconds, ')s, wrote', writtenLines, 'lines') # reset the repository repo.git.checkout(branch) timePassed = datetime.datetime.now() - begin_time print('analysed', commitCount, 'commits (100%) in ', timePassed.seconds, 's') print('saved', writtenLines, 'lines to gitLanguageHistory.csv') if __name__ == "__main__": main()

Das Ergebnis habe ich in Exply importiert und in einem Area - Time Series Chart angezeigt. Man erkennt, dass sich zu der ursprünglich fast reinen Groovy Anwendung mit der Zeit neue Sprachen wie Java, JavaScript und zuletzt TypeScript gesellt haben, und manche nach einer gewissen Zeit auch wieder verschwunden sind. Wer mehr über Exply und die darin verwendeten Technologien erfahren will, dem empfehle ich Folge 27 vom Sandpapier: Exply - Unter der Haube!

Danke für's Lesen. Über Tipps zur Performance–Optimierung des Python Scripts oder andere Kommentare zum Thema Visualisierung freue ich mich immer!