git-heatmap/src/main.rs

210 lines
4.3 KiB
Rust
Raw Normal View History

#![feature(byte_slice_trim_ascii)]
#![feature(let_chains)]
#![allow(dead_code)]
use std::{cmp::Reverse, sync::OnceLock};
use anyhow::{Context, Result};
use chrono::{DateTime, Datelike, Duration, Utc};
use clap::Parser;
use gix::{bstr::ByteSlice, ObjectId, Repository};
use heatmap::HeatmapColors;
use rgb::Rgb;
use crate::{cli::CliArgs, heatmap::Heatmap};
mod cli;
mod heatmap;
mod rgb;
pub const ESCAPE: &str = "\x1B";
pub const RESET: &str = "\x1B[0m";
pub const DAYS: [&str; 7] = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
pub static CHAR: OnceLock<char> = OnceLock::new();
pub static COLOR_MAP: OnceLock<Vec<String>> = OnceLock::new();
const GREEN_COLOR_MAP: [Rgb; 5] = [
Rgb(0, 0, 0),
Rgb(14, 68, 41),
Rgb(0, 109, 50),
Rgb(38, 166, 65),
Rgb(57, 211, 83),
];
const RED_COLOR_MAP: [Rgb; 5] = [
Rgb(0, 0, 0),
Rgb(208, 169, 35),
Rgb(208, 128, 35),
Rgb(208, 78, 35),
Rgb(208, 35, 64),
];
struct Commit {
id: ObjectId,
title: String,
author: String,
time: DateTime<Utc>,
}
fn main() -> Result<()> {
clear_screen();
let args = CliArgs::parse();
CHAR.set(args.char).unwrap();
let color_map = match args.color_scheme {
HeatmapColors::Green => GREEN_COLOR_MAP,
HeatmapColors::Red => RED_COLOR_MAP,
};
let color_map = color_map
.into_iter()
.map(|c| c.to_ansi())
.collect::<Vec<_>>();
COLOR_MAP.set(color_map).unwrap();
let repo = gix::open(&args.input).unwrap();
let end_date = Utc::now() - Duration::days(1);
let start_date = end_date - Duration::days(365);
let commits =
get_commits(repo, args, start_date).with_context(|| "Could not fetch commit list")?;
let heatmap = Heatmap::new(start_date, end_date, commits);
heatmap.print();
Ok(())
}
fn get_color(val: u32) -> usize {
match val {
0 => 0,
x if x < 2 => 1,
x if x < 4 => 2,
x if x < 6 => 3,
x if x > 8 => 4,
_ => 0,
}
}
fn clear_screen() {
print!("\x1b[2J\x1b[1;1H");
}
fn get_char() -> char {
*CHAR.get_or_init(|| '▩')
}
fn get_color_map() -> Vec<String> {
COLOR_MAP
.get_or_init(|| {
GREEN_COLOR_MAP
.into_iter()
.map(|c| c.to_ansi())
.collect::<Vec<_>>()
})
.to_vec()
}
fn get_commits(repo: Repository, args: CliArgs, start_date: DateTime<Utc>) -> Result<Vec<Commit>> {
let mut commits: Vec<Commit> = repo
.head()?
.into_peeled_id()?
.ancestors()
.all()?
.filter_map(|c| c.ok())
.filter_map(|c| c.object().ok())
.filter_map(|c| {
let title = c
.message()
.ok()?
.title
.trim_ascii()
.to_str()
.ok()?
.to_string();
let author = c.author().ok()?.name.to_string();
if author != args.author {
return None;
}
let time = c.time().ok()?;
let time = DateTime::from_timestamp_millis(time.seconds * 1000)?;
if time <= start_date + Duration::days(1) {
return None;
}
Some(Commit {
id: c.id,
title,
author,
time,
})
})
.collect();
commits.sort_by_cached_key(|a| Reverse(a.time));
Ok(commits)
}
fn print_streak(commits: Vec<Commit>, args: CliArgs) {
let mut commits_pushed = 0;
let mut days_streak = 0;
let mut last_date: Option<DateTime<Utc>> = None;
let mut start_date: Option<DateTime<Utc>> = None;
let mut end_date: Option<DateTime<Utc>> = None;
for commit in commits {
match last_date {
Some(date) => {
let day = date.ordinal0();
let commit_day = commit.time.ordinal0();
let time_diff = date - commit.time;
if commit_day != day {
days_streak += 1;
}
// if time_diff.num_hours() >= args.split.into() {
// println!(
// "Failing Commit\n{} - {} {} hours difference",
// commit.id,
// commit.time.to_rfc3339(),
// time_diff.num_hours()
// );
// break;
// }
commits_pushed += 1;
last_date = Some(commit.time);
end_date = Some(commit.time);
println!(
"{} - {} {} hours difference",
commit.id,
commit.time.to_rfc3339(),
time_diff.num_hours()
);
}
None => {
last_date = Some(commit.time);
start_date = Some(commit.time);
println!("First Commit\n{} - {}", commit.id, commit.time.to_rfc3339());
continue;
}
}
}
println!("{commits_pushed} commits pushed during a {days_streak} days streak");
if let Some(start_date) = start_date
&& let Some(end_date) = end_date
{
let start_date = start_date.format("%d-%b-%Y").to_string();
let end_date = end_date.format("%d-%b-%Y").to_string();
println!("Between: {start_date} {end_date}");
}
}