git-stats

Command line utility showing plots of git commits over time in the terminal
git clone https://noulin.net/git/git-stats.git
Log | Files | Refs | README

commit e2facf9d06fe71e5992132faedcb80f2aac97901
Author: Remy Noulin <loader2x@gmail.com>
Date:   Fri,  4 Jun 2021 20:24:16 +0200

bar chart

.gitignore  |  63 ++++++++++++++++++++
README.md   |  12 ++++
git-stats.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 269 insertions(+)

Diffstat:
A.gitignore | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 12++++++++++++
Agit-stats.c | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 269 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,63 @@ +# Vim +*.sw* + +# Debug +.gdb_history + +# Coverage +*.gcov +*.gcda +*.gcno + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/README.md b/README.md @@ -0,0 +1,12 @@ +This version creates a barchart: + + █ + █ + █ + ▂ █ ▂ █ + ▂▂ ▄ █ █ ▇ █ █ █ ▄ █ + ▂██▄█ ▅ ▂▅█▇ █▇ ▅ ██▄ █ ▅ █▂ █ █▅█▂ █ ██ + █████▇██▄████▂██▄█▂█ ▂████▅█▄█▅ █▇▇ █▄ █ ██ ▇█ ████▅█▂▄██ +▄ ████████████████████▇▇██████████▂███ ▅██▂█▄██▅██▄███████████ +█ ▇▇ ▄████████████████████████████████████▄███████████████████████ +█▇▄██▄█████████████████████████████████████████████████████████████ diff --git a/git-stats.c b/git-stats.c @@ -0,0 +1,194 @@ +#! /usr/bin/env sheepy +/* or direct path to sheepy: #! /usr/local/bin/sheepy */ +/* Libsheepy documentation: https://spartatek.se/libsheepy/ */ +#include "libsheepyObject.h" + +// git commands to get statistics: +// https://devtut.github.io/git/git-statistics.html + +int argc; char **argv; + +/* enable/disable logging */ +/* #undef pLog */ +/* #define pLog(...) */ + +// terminal size type +typ struct {u16 rows; u16 cols;} winSizet; + +/* terminal window size */ +#include <sys/ioctl.h> +#include <unistd.h> + +// get terminal window size +winSizet wsize (void) +{ + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + return (winSizet){w.ws_row, w.ws_col}; +} + +int main(int ARGC, char** ARGV) { + + argc = ARGC; argv = ARGV; + + initLibsheepy(ARGV[0]); + setLogMode(LOG_DATE); + //openProgLogFile(); + //setLogSymbols(LOG_UTF8); + //disableLibsheepyErrorLogs; + + + // Steps + // get git statistics + // sort the dates + // remove time, keep only the date in a slice: slDates + // count daily commits + // compute min and max daily count + // compute earliest and latest dates in unix time + // get window size + // aggregate daily count to window columns + // create a blank display array + // fill the display with the plot + // show plot + + // get git statistics + cleanListP(dates) = execOut("git log --pretty=format:\"%ai\""); + + var c = lenG(dates); + + // sort the dates + sortG(&dates); + + // remove time, keep only the date in a slice: slDates + // count daily commits + typ struct { + char *date; + int count; + } dateCountst; + + sliceT(slDatest, dateCountst); + slDatest slDates; + sliceInit(&slDates); + + dateCountst d = {0}; + forEachS(dates, s) { + s[11] = 0; + if (not d.date) { + // first element + d.date = s; + inc d.count; + } + else { + if (eqG(d.date, s)) { + inc d.count; + } + else { + sliceAppend(&slDates, d); + d.date = s; + d.count = 1; + } + } + } + // append last date + sliceAppend(&slDates, d); + + // compute min and max daily count + int minCount = sliceAt(&slDates, 0).count; + int maxCount = 0; + + sliceForEach(&slDates, date) { + minCount = MIN(date->count, minCount); + maxCount = MAX(date->count, maxCount); + } + + // compute earliest and latest dates in unix time + time_t earliest, latest; + + earliest = strToUnixTime(sliceFirst(&slDates).date, "%Y-%m-%d"); + latest = strToUnixTime(sliceLast (&slDates).date, "%Y-%m-%d"); + + //logI("%"PRIu64" %"PRIu64" = %f", earliest, latest, (f64)((latest-earliest)/86400)/365); + + typ struct { + int col; + int value; + } pet; // plot element type + + sliceT(plt, pet); + plt slPl; + sliceInit(&slPl); + + // get window size + winSizet wz = wsize(); + #define graphHeight 20 + + + //logI("min %d, max %d, w %d h %d, first %s, last %s", minCount, maxCount, wz.cols, wz.rows, sliceFirst(&slDates).date, sliceLast(&slDates).date); + + // aggregate daily count to window columns + pet p = {0}; + + sliceForEach(&slDates, date) { + time_t t = strToUnixTime(date->date, "%Y-%m-%d"); + int col = (f64)(t-earliest)/(f64)(latest-earliest) * (f64)(wz.cols-1/*-1 to make sure it writes inside the display*/); + if (col == p.col) { + p.value = MAX(date->count, p.value); + } + else { + sliceAppend(&slPl, p); + p.col = col; + p.value = date->count; + } + } + sliceAppend(&slPl, p); + + // create a blank display array + char *display[wz.cols][graphHeight]; + + char *BARS[] = {" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; + + // clear display + range(i, wz.cols) { + range(j, graphHeight) { + display[i][j] = BARS[0]; + } + } + + + // fill the display with the plot + sliceForEach(&slPl, elm) { + int dpHeight = (f64)(graphHeight*8) - (f64)elm->value/(f64)maxCount * (f64)(graphHeight*8); + int bits = dpHeight & 7; + int cbar = 8 - bits; + + // draw bar + int h = dpHeight / 8; + + if (bits) { + // print BARS[cbar] + display[elm->col][h] = BARS[cbar]; + inc h; + } + + rangeFrom(i, h, graphHeight) { + display[elm->col][i] = BARS[8]; + } + + //logI("col %d, value %d", elm->col, elm->value); + //logI("col %3d, value %4d %3d cbar %d bits %d", elm->col, dpHeight, h, cbar, bits); + } + + // show plot + range(j, graphHeight) { + range(i, wz.cols) { + printf(display[i][j]); + } + put; + } + + + sliceFree(&slPl); + sliceFree(&slDates); +} +// vim: set expandtab ts=2 sw=2: