Added support for multiple branches and start/end dates
parent
6aae792912
commit
893947b6ec
17
src/cli.rs
17
src/cli.rs
|
@ -1,5 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use chrono::{Duration, Local};
|
||||||
use clap::{arg, Parser, ValueHint};
|
use clap::{arg, Parser, ValueHint};
|
||||||
|
|
||||||
use crate::heatmap::HeatmapColors;
|
use crate::heatmap::HeatmapColors;
|
||||||
|
@ -18,6 +19,18 @@ pub struct CliArgs {
|
||||||
|
|
||||||
#[arg(long("color"), value_enum, default_value_t = HeatmapColors::Green)]
|
#[arg(long("color"), value_enum, default_value_t = HeatmapColors::Green)]
|
||||||
pub color_scheme: HeatmapColors,
|
pub color_scheme: HeatmapColors,
|
||||||
// #[arg(short, long, default_value = "24")]
|
|
||||||
// pub split: u32,
|
#[arg(short, long, num_args(0..))]
|
||||||
|
pub branches: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[arg(long("since"), default_value_t = get_since_date())]
|
||||||
|
pub since: String,
|
||||||
|
|
||||||
|
#[arg(long("until"))]
|
||||||
|
pub until: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_since_date() -> String {
|
||||||
|
let date = Local::now() - Duration::days(365);
|
||||||
|
date.format("%Y-%m-%d").to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, fmt::Display};
|
||||||
|
|
||||||
use chrono::{DateTime, Datelike, Duration, Local};
|
use chrono::{Datelike, Duration, NaiveDate};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
|
||||||
use crate::{get_char, get_color, get_color_map, Commit, DAYS, RESET};
|
use crate::{get_char, get_color, get_color_map, Commit, DAYS, RESET};
|
||||||
|
|
||||||
pub struct Heatmap {
|
pub struct Heatmap {
|
||||||
data: [Vec<i32>; 7],
|
data: [Vec<i32>; 7],
|
||||||
start_date: DateTime<Local>,
|
since: NaiveDate,
|
||||||
end_date: DateTime<Local>,
|
until: NaiveDate,
|
||||||
commits: Vec<Commit>,
|
commits: Vec<Commit>,
|
||||||
months: Vec<(usize, String)>,
|
months: Vec<(usize, String)>,
|
||||||
highest_count: i32,
|
highest_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Heatmap {
|
impl Heatmap {
|
||||||
pub fn new(
|
pub fn new(since: NaiveDate, until: NaiveDate, commits: Vec<Commit>) -> Self {
|
||||||
start_date: DateTime<Local>,
|
|
||||||
end_date: DateTime<Local>,
|
|
||||||
commits: Vec<Commit>,
|
|
||||||
) -> Self {
|
|
||||||
let mut heatmap = Self {
|
let mut heatmap = Self {
|
||||||
data: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
|
data: [vec![], vec![], vec![], vec![], vec![], vec![], vec![]],
|
||||||
start_date,
|
since,
|
||||||
end_date,
|
until,
|
||||||
commits,
|
commits,
|
||||||
months: vec![],
|
months: vec![],
|
||||||
highest_count: 0,
|
highest_count: 0,
|
||||||
|
@ -37,7 +33,7 @@ impl Heatmap {
|
||||||
*record += 1;
|
*record += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current_day = start_date;
|
let mut current_day = since;
|
||||||
let mut day_of_week = current_day.weekday().num_days_from_monday() % 7;
|
let mut day_of_week = current_day.weekday().num_days_from_monday() % 7;
|
||||||
|
|
||||||
if day_of_week != 0 {
|
if day_of_week != 0 {
|
||||||
|
@ -46,10 +42,10 @@ impl Heatmap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while current_day <= end_date {
|
while current_day <= until {
|
||||||
let month_name = current_day.format("%b").to_string();
|
let month_name = current_day.format("%b").to_string();
|
||||||
|
|
||||||
if current_day == start_date {
|
if current_day == since {
|
||||||
heatmap.months.push((0, month_name));
|
heatmap.months.push((0, month_name));
|
||||||
}
|
}
|
||||||
else if current_day.day0() == 0 {
|
else if current_day.day0() == 0 {
|
||||||
|
@ -58,7 +54,7 @@ impl Heatmap {
|
||||||
.push((heatmap.data[day_of_week as usize].len(), month_name));
|
.push((heatmap.data[day_of_week as usize].len(), month_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = grouped_commits.get(¤t_day.date_naive());
|
let value = grouped_commits.get(¤t_day);
|
||||||
match value {
|
match value {
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
heatmap.data[day_of_week as usize].push(*val);
|
heatmap.data[day_of_week as usize].push(*val);
|
||||||
|
@ -68,7 +64,7 @@ impl Heatmap {
|
||||||
}
|
}
|
||||||
None => heatmap.data[day_of_week as usize].push(0),
|
None => heatmap.data[day_of_week as usize].push(0),
|
||||||
}
|
}
|
||||||
// println!("{} {value:?} {highest_count}", current_day.date_naive());
|
// println!("{} {value:?}", current_day);
|
||||||
current_day += Duration::days(1);
|
current_day += Duration::days(1);
|
||||||
day_of_week = current_day.weekday().num_days_from_monday() % 7;
|
day_of_week = current_day.weekday().num_days_from_monday() % 7;
|
||||||
}
|
}
|
||||||
|
@ -92,36 +88,42 @@ impl Heatmap {
|
||||||
|
|
||||||
row
|
row
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Heatmap {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let start_date = self.since.format("%Y-%b-%d").to_string();
|
||||||
|
let end_date = self.until.format("%Y-%b-%d").to_string();
|
||||||
|
let commits = self.commits.len().to_string();
|
||||||
|
|
||||||
|
write!(f, "{} - {}\n", start_date, end_date).unwrap();
|
||||||
|
write!(f, "{} commits\n\n", commits).unwrap();
|
||||||
|
write!(f, "{}\n", self.months_row()).unwrap();
|
||||||
|
|
||||||
pub fn print(&self) {
|
|
||||||
println!(
|
|
||||||
"{} - {}",
|
|
||||||
self.start_date.format("%Y-%b-%d"),
|
|
||||||
self.end_date.format("%Y-%b-%d")
|
|
||||||
);
|
|
||||||
println!("{} commits\n", &self.commits.len());
|
|
||||||
println!("{}", self.months_row());
|
|
||||||
for (day, row) in DAYS.iter().zip(&self.data) {
|
for (day, row) in DAYS.iter().zip(&self.data) {
|
||||||
print!("{day} ");
|
write!(f, "{day} ").unwrap();
|
||||||
for val in row {
|
for val in row {
|
||||||
match val {
|
match val {
|
||||||
x if *x >= 0 => {
|
x if *x >= 0 => {
|
||||||
let color = &get_color_map()[get_color(*val, self.highest_count)];
|
let color = &get_color_map()[get_color(*val, self.highest_count)];
|
||||||
print!("{color}{}{RESET} ", get_char());
|
write!(f, "{color}{}{RESET} ", get_char()).unwrap();
|
||||||
}
|
}
|
||||||
x if *x < 0 => {
|
x if *x < 0 => {
|
||||||
print!("{RESET} ");
|
write!(f, "{RESET} ").unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print!("\n");
|
write!(f, "\n").unwrap();
|
||||||
}
|
}
|
||||||
print!("\nLess ");
|
|
||||||
|
write!(f, "\nLess ").unwrap();
|
||||||
for color in get_color_map() {
|
for color in get_color_map() {
|
||||||
print!("{color}{}{RESET} ", get_char())
|
write!(f, "{color}{}{RESET} ", get_char()).unwrap();
|
||||||
}
|
}
|
||||||
print!(" More\n");
|
write!(f, " More\n").unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
192
src/main.rs
192
src/main.rs
|
@ -2,13 +2,14 @@
|
||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::{cmp::Reverse, sync::OnceLock};
|
use std::{cmp::Reverse, collections::HashSet, sync::OnceLock};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use chrono::{DateTime, Duration, Local};
|
use chrono::{DateTime, Duration, Local, NaiveDate, TimeZone};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use gix::{bstr::ByteSlice, ObjectId, Repository};
|
use gix::{bstr::ByteSlice, ObjectId, Repository};
|
||||||
use heatmap::HeatmapColors;
|
use heatmap::HeatmapColors;
|
||||||
|
use itertools::Itertools;
|
||||||
use rgb::Rgb;
|
use rgb::Rgb;
|
||||||
|
|
||||||
use crate::{cli::CliArgs, heatmap::Heatmap};
|
use crate::{cli::CliArgs, heatmap::Heatmap};
|
||||||
|
@ -36,9 +37,10 @@ const RED_COLOR_MAP: [Rgb; 5] = [
|
||||||
Rgb(208, 169, 35),
|
Rgb(208, 169, 35),
|
||||||
Rgb(208, 128, 35),
|
Rgb(208, 128, 35),
|
||||||
Rgb(208, 78, 35),
|
Rgb(208, 78, 35),
|
||||||
Rgb(208, 35, 64),
|
Rgb(255, 0, 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
struct Commit {
|
struct Commit {
|
||||||
id: ObjectId,
|
id: ObjectId,
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -64,18 +66,31 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let repo = gix::open(&args.input).unwrap();
|
let repo = gix::open(&args.input).unwrap();
|
||||||
|
|
||||||
let end_date = Local::now();
|
let since = NaiveDate::parse_from_str(&args.since, "%Y-%m-%d").unwrap();
|
||||||
let start_date = end_date - Duration::days(365);
|
|
||||||
|
|
||||||
let commits =
|
let until = args
|
||||||
get_commits(repo, args, start_date).with_context(|| "Could not fetch commit list")?;
|
.until
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| get_default_until(since));
|
||||||
|
let until = NaiveDate::parse_from_str(&until, "%Y-%m-%d").unwrap();
|
||||||
|
|
||||||
let heatmap = Heatmap::new(start_date, end_date, commits);
|
let commits = get_commits(repo, args, since).with_context(|| "Could not fetch commit list")?;
|
||||||
heatmap.print();
|
|
||||||
|
let heatmap = Heatmap::new(since, until, commits);
|
||||||
|
|
||||||
|
println!("{heatmap}");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_default_until(since: NaiveDate) -> String {
|
||||||
|
let mut until = Local::now().date_naive();
|
||||||
|
if since + Duration::days(365) < until {
|
||||||
|
until = since + Duration::days(365);
|
||||||
|
}
|
||||||
|
until.format("%Y-%m-%d").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_color(val: i32, high: i32) -> usize {
|
fn get_color(val: i32, high: i32) -> usize {
|
||||||
let color = val as f32 / high as f32;
|
let color = val as f32 / high as f32;
|
||||||
match color {
|
match color {
|
||||||
|
@ -107,110 +122,71 @@ fn get_color_map() -> Vec<String> {
|
||||||
.to_vec()
|
.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_commits(
|
fn get_commits(repo: Repository, args: CliArgs, start_date: NaiveDate) -> Result<Vec<Commit>> {
|
||||||
repo: Repository,
|
let mut commits: HashSet<Commit> = HashSet::new();
|
||||||
args: CliArgs,
|
|
||||||
start_date: DateTime<Local>,
|
|
||||||
) -> 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();
|
let branches = match args.branches {
|
||||||
if author != args.author {
|
Some(b) => b,
|
||||||
return None;
|
None => repo
|
||||||
}
|
.branch_names()
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| b.to_string())
|
||||||
|
.collect_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
let time = c.time().ok()?;
|
for branch in branches {
|
||||||
// let offset = Local.timestamp_opt(0, 0).unwrap().offset().fix();
|
let branch_commits = repo
|
||||||
// let time = Local::now() - Duration::seconds(time.seconds);
|
.rev_parse(&*branch)?
|
||||||
let time = DateTime::from_timestamp_millis(time.seconds * 1000)?.with_timezone(&Local);
|
.single()
|
||||||
if time <= start_date + Duration::days(1) {
|
.unwrap()
|
||||||
return None;
|
.ancestors()
|
||||||
}
|
.all()?;
|
||||||
|
|
||||||
Some(Commit {
|
branch_commits
|
||||||
id: c.id,
|
.filter_map(|c| c.ok())
|
||||||
title,
|
.filter_map(|c| c.object().ok())
|
||||||
author,
|
.filter_map(|c| {
|
||||||
time,
|
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 current_time = Local::now().time();
|
||||||
|
let start_date = start_date.and_time(current_time);
|
||||||
|
let start_date = Local.from_local_datetime(&start_date).unwrap();
|
||||||
|
|
||||||
|
let time = c.time().ok()?;
|
||||||
|
let time =
|
||||||
|
DateTime::from_timestamp_millis(time.seconds * 1000)?.with_timezone(&Local);
|
||||||
|
if time <= start_date + Duration::days(1) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Commit {
|
||||||
|
id: c.id,
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
time,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.for_each(|c| {
|
||||||
.collect();
|
commits.insert(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
commits.sort_by_cached_key(|a| Reverse(a.time));
|
let commits = commits
|
||||||
|
.into_iter()
|
||||||
|
.sorted_by_cached_key(|a| Reverse(a.time))
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
Ok(commits)
|
Ok(commits)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn print_streak(commits: Vec<Commit>) {
|
|
||||||
// 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() >= 24 {
|
|
||||||
// // 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}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
Loading…
Reference in New Issue