commit-heatmap-gen

commit heatmap generator
git clone git://brookjeynes.dev/bjeynes/commit-heatmap-gen.git
Log | Files | Refs | README

commit 868dbf75c016b6c2b949a0e1828476e0b8d7bb7b
parent 8ff234c24d132d371aa89b497aacf5ffaa9a1152
Author: brookjeynes <me@brookjeynes.dev>
Date:   Thu, 18 Jun 2026 08:54:50 +1000

feat: write to file

Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
Mmain.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Astyle.css | 29+++++++++++++++++++++++++++++
2 files changed, 87 insertions(+), 10 deletions(-)

diff --git a/main.c b/main.c @@ -5,6 +5,31 @@ #include <string.h> #include <time.h> +#define DAYS_PER_YEAR 365 +#define HOURS_PER_DAY 24 +#define MINUTES_PER_HOUR 60 +#define SECONDS_PER_MINUTE 60 + +char *bubble_variant(int count) { + if (count > 13) { + return "-heavy"; + } + + if (count > 8) { + return "-medium"; + } + + if (count > 4) { + return "-light"; + } + + if (count > 0) { + return "-lightest"; + } + + return ""; +} + typedef struct { char date[11]; // "YYYY-MM-DD\0" int count; @@ -19,13 +44,19 @@ int main(int argc, char *argv[]) { } char *author_email = argv[1]; - commits_by_date commit_tracker[365]; + if (argc == 2) { + fprintf(stderr, "err: abs repo path required"); + exit(1); + } + char *repo_path = argv[2]; + + commits_by_date commit_tracker[DAYS_PER_YEAR]; int unique_dates = 0; git_libgit2_init(); git_repository *repo = NULL; - if ((error = git_repository_open(&repo, "../jido")) < 0) { + if ((error = git_repository_open(&repo, repo_path)) < 0) { const git_error *e = git_error_last(); printf("err: %d/%d: %s\n", error, e->klass, e->message); exit(error); @@ -56,7 +87,8 @@ int main(int argc, char *argv[]) { } const time_t now = time(NULL); - const time_t year_behind = now - (365 * 24 * 60 * 60); + const time_t year_behind = now - (DAYS_PER_YEAR * HOURS_PER_DAY * + MINUTES_PER_HOUR * SECONDS_PER_MINUTE); git_oid id; while (!git_revwalk_next(&id, walker)) { @@ -96,7 +128,7 @@ int main(int argc, char *argv[]) { } if (found == 0) { - if (unique_dates < 365) { + if (unique_dates < DAYS_PER_YEAR) { strcpy(commit_tracker[unique_dates].date, buf); commit_tracker[unique_dates].count = 1; unique_dates++; @@ -104,12 +136,19 @@ int main(int argc, char *argv[]) { } } - time_t curr_day = now; - while (curr_day > year_behind) { + printf("<html lang=\"en\">"); + printf("<head>"); + printf("<link rel=\"stylesheet\" href=\"./style.css\" type=\"text/css\"/>"); + printf("</head>"); + printf("<body>"); + printf("<div class=\"heatmap-grid\">"); + + time_t day = year_behind; + while (day < now) { struct tm *lt; char buf[11]; - lt = localtime(&curr_day); + lt = localtime(&day); if (strftime(buf, sizeof(buf), "%Y-%m-%d", lt) == 0) { fprintf(stderr, "err: failed to get print time\n"); exit(1); @@ -124,14 +163,23 @@ int main(int argc, char *argv[]) { } if (found == -1) { - printf("%s: 0\n", buf); + printf( + "<div class=\"heatmap-bubble %s\" title=\"0 sessions on %s\"></div>", + bubble_variant(0), buf); } else { - printf("%s: %d\n", buf, commit_tracker[found].count); + int commits = commit_tracker[found].count; + printf( + "<div class=\"heatmap-bubble %s\" title=\"%d sessions on %s\"></div>", + bubble_variant(commits), commits, buf); } - curr_day -= (24 * 60 * 60); + day += (24 * 60 * 60); } + printf("</div>"); + printf("</body>"); + printf("</html>"); + git_libgit2_shutdown(); return 0; diff --git a/style.css b/style.css @@ -0,0 +1,29 @@ +.heatmap-grid { + display: grid; + grid-template-rows: repeat(7, minmax(0, 1fr)); + grid-auto-flow: column; + gap: 0.25rem; +} + +.heatmap-bubble { + width: 1rem; + height: 1rem; + border-radius: 0.25rem; + background-color: #eff2f5; + + &.-lightest { + background-color: #aceebb; + } + + &.-light { + background-color: #4ac26b; + } + + &.-medium { + background-color: #2da44e; + } + + &.-heavy { + background-color: #116329; + } +}