working on translating cli options to usable args.
This commit is contained in:
parent
d113289fd2
commit
0293018f85
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -275,6 +275,7 @@ dependencies = [
|
||||||
"git2",
|
"git2",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"nom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proptest",
|
"proptest",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -439,6 +440,22 @@ version = "2.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
|
@ -15,6 +15,7 @@ clap = { version = "4.4.18", features = ["derive"] }
|
||||||
git2 = "0.18.2"
|
git2 = "0.18.2"
|
||||||
iana-time-zone = "0.1.60"
|
iana-time-zone = "0.1.60"
|
||||||
itertools = "0.12.1"
|
itertools = "0.12.1"
|
||||||
|
nom = "7.1.3"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
proptest = "1.4.0"
|
proptest = "1.4.0"
|
||||||
rand = {version="0.8.5", features=["small_rng"]}
|
rand = {version="0.8.5", features=["small_rng"]}
|
||||||
|
|
87
src/args.rs
87
src/args.rs
|
@ -1,5 +1,15 @@
|
||||||
use chrono::{NaiveDateTime, TimeZone};
|
#![allow(dead_code)]
|
||||||
|
use crate::err::GitErrors;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono_tz::Tz;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use git2::Commit;
|
||||||
|
use git2::Oid;
|
||||||
|
use git2::Repository;
|
||||||
|
use git2::Sort;
|
||||||
|
|
||||||
|
use crate::time::DateTimeRange;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
|
@ -19,7 +29,78 @@ pub(crate) struct Cli {
|
||||||
pub(crate) last_commit: Option<String>,
|
pub(crate) last_commit: Option<String>,
|
||||||
|
|
||||||
/// Timezone to place commits in, will default to local tz. Must be a Tz identifier as listed
|
/// Timezone to place commits in, will default to local tz. Must be a Tz identifier as listed
|
||||||
/// on https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. Defaults to local tz
|
/// on <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>. Defaults to local tz
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub(crate) timezone: Option<chrono_tz::Tz>
|
pub(crate) timezone: Option<chrono_tz::Tz>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ProgramOptions {
|
||||||
|
pub(crate) start_time: DateTime<Tz>,
|
||||||
|
pub(crate) end_time: DateTime<Tz>,
|
||||||
|
pub(crate) allowed_times: Vec<DateTimeRange<Tz>>,
|
||||||
|
pub(crate) first_commit: Oid,
|
||||||
|
pub(crate) last_commit: Oid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Cli> for ProgramOptions {
|
||||||
|
type Error = GitErrors;
|
||||||
|
|
||||||
|
fn try_from(cli: Cli) -> Result<Self, Self::Error> {
|
||||||
|
let tz = cli
|
||||||
|
.timezone
|
||||||
|
.unwrap_or_else(|| get_local_timezone().expect("Could not get local timezone"));
|
||||||
|
let start_time = tz.from_local_datetime(&cli.start_time).unwrap();
|
||||||
|
let end_time = if let Some(time) = cli.end_time {
|
||||||
|
tz.from_local_datetime(&time).unwrap()
|
||||||
|
} else {
|
||||||
|
Utc::now().with_timezone(&tz)
|
||||||
|
};
|
||||||
|
let repo = if let Ok(repo) = Repository::open_from_env() {
|
||||||
|
repo
|
||||||
|
} else {
|
||||||
|
return Err(GitErrors::NotARepo);
|
||||||
|
};
|
||||||
|
let first_commit = if let Some(sha) = cli.first_commit {
|
||||||
|
if let Ok(commit) = repo.find_commit_by_prefix(&sha) {
|
||||||
|
commit
|
||||||
|
} else {
|
||||||
|
return Err(GitErrors::NotACommit(sha));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repo.head()
|
||||||
|
.expect("could not get head")
|
||||||
|
.peel_to_commit()
|
||||||
|
.expect("could not peel head to commit")
|
||||||
|
}
|
||||||
|
.id();
|
||||||
|
let last_commit = if let Some(sha) = cli.last_commit {
|
||||||
|
if let Ok(commit) = repo.find_commit_by_prefix(&sha) {
|
||||||
|
commit.id()
|
||||||
|
} else {
|
||||||
|
return Err(GitErrors::NotACommit(sha));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut sorting = Sort::REVERSE;
|
||||||
|
sorting.insert(Sort::TOPOLOGICAL);
|
||||||
|
sorting.insert(Sort::TIME);
|
||||||
|
let mut revwalker = repo.revwalk().unwrap();
|
||||||
|
revwalker.set_sorting(sorting).unwrap();
|
||||||
|
revwalker.simplify_first_parent().unwrap();
|
||||||
|
revwalker.next().unwrap().unwrap()
|
||||||
|
};
|
||||||
|
Ok(ProgramOptions {
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
allowed_times: todo!(),
|
||||||
|
first_commit,
|
||||||
|
last_commit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_local_timezone() -> Result<Tz, String> {
|
||||||
|
match iana_time_zone::get_timezone() {
|
||||||
|
Ok(tz_string) => tz_string.parse(),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/err.rs
15
src/err.rs
|
@ -1 +1,14 @@
|
||||||
use thiserror;
|
#![allow(dead_code)]
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub(crate) enum GitErrors {
|
||||||
|
#[error("end is not a parent of start")]
|
||||||
|
NotAParent,
|
||||||
|
|
||||||
|
#[error("{0} is not a commit hash in the repo")]
|
||||||
|
NotACommit(String),
|
||||||
|
|
||||||
|
#[error("Could not find repository in current path")]
|
||||||
|
NotARepo,
|
||||||
|
}
|
||||||
|
|
31
src/git.rs
31
src/git.rs
|
@ -1,19 +1,12 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use git2::{Commit, Repository};
|
use git2::{Commit, Repository};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use thiserror::Error;
|
use crate::err::GitErrors;
|
||||||
|
|
||||||
use crate::time::{distribute_across_ranges_with_jitter, TimeRange};
|
use crate::time::{distribute_across_ranges_with_jitter, DateTimeRange};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub(crate) enum GitErrors {
|
|
||||||
#[error("end is not a parent of start")]
|
|
||||||
NotAParent,
|
|
||||||
|
|
||||||
#[error("{0} is not a commit hash in the repo")]
|
|
||||||
NotACommit(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// might be able to replace with graph_ahead_behind and graph_decendant_of?
|
||||||
/// find the number of commits between start and end,
|
/// find the number of commits between start and end,
|
||||||
/// where end is a parent commit of start
|
/// where end is a parent commit of start
|
||||||
fn get_no_of_commits(start: &Commit, end: &Commit) -> Result<u32, GitErrors> {
|
fn get_no_of_commits(start: &Commit, end: &Commit) -> Result<u32, GitErrors> {
|
||||||
|
@ -26,21 +19,11 @@ fn get_no_of_commits(start: &Commit, end: &Commit) -> Result<u32, GitErrors> {
|
||||||
// will need to do an in memory rebase, ammending each commit with a new signature.
|
// will need to do an in memory rebase, ammending each commit with a new signature.
|
||||||
pub fn rebase_commits_across_timerange<Tz: chrono::TimeZone>(
|
pub fn rebase_commits_across_timerange<Tz: chrono::TimeZone>(
|
||||||
repo: &Repository,
|
repo: &Repository,
|
||||||
start_sha: &str,
|
start_commit: &Commit,
|
||||||
end_sha: &str,
|
end_commit: &Commit,
|
||||||
ranges: &[TimeRange<Tz>],
|
ranges: &[DateTimeRange<Tz>],
|
||||||
max_jitter: &Duration,
|
max_jitter: &Duration,
|
||||||
) -> Result<(), GitErrors> {
|
) -> Result<(), GitErrors> {
|
||||||
let start_commit = if let Ok(commit) = repo.find_commit_by_prefix(start_sha) {
|
|
||||||
commit
|
|
||||||
} else {
|
|
||||||
return Err(GitErrors::NotACommit(start_sha.to_string()));
|
|
||||||
};
|
|
||||||
let end_commit = if let Ok(commit) = repo.find_commit_by_prefix(end_sha) {
|
|
||||||
commit
|
|
||||||
} else {
|
|
||||||
return Err(GitErrors::NotACommit(end_sha.to_string()));
|
|
||||||
};
|
|
||||||
let num_commits = get_no_of_commits(&start_commit, &end_commit)?;
|
let num_commits = get_no_of_commits(&start_commit, &end_commit)?;
|
||||||
let times = distribute_across_ranges_with_jitter(ranges, num_commits, max_jitter);
|
let times = distribute_across_ranges_with_jitter(ranges, num_commits, max_jitter);
|
||||||
todo!()
|
todo!()
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -10,24 +10,8 @@ use clap::Parser;
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli = args::Cli::parse();
|
let cli = args::Cli::parse();
|
||||||
println!("{:#?}", cli);
|
println!("{:#?}", cli);
|
||||||
let tz = cli
|
|
||||||
.timezone
|
|
||||||
.unwrap_or_else(|| get_local_timezone().expect("Could not get local timezone"));
|
|
||||||
let start_time = tz.from_local_datetime(&cli.start_time).unwrap();
|
|
||||||
let end_time = if let Some(time) = cli.end_time {
|
|
||||||
tz.from_local_datetime(&time).unwrap()
|
|
||||||
} else {
|
|
||||||
Utc::now().with_timezone(&tz)
|
|
||||||
};
|
|
||||||
println!(
|
println!(
|
||||||
"start_time = {:#?}, end_time = {:#?}, tz = {:#?}",
|
"start_time = {:#?}, end_time = {:#?}, tz = {:#?}",
|
||||||
start_time, end_time, tz
|
start_time, end_time, tz
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_local_timezone() -> Result<Tz, String> {
|
|
||||||
match iana_time_zone::get_timezone() {
|
|
||||||
Ok(tz_string) => tz_string.parse(),
|
|
||||||
Err(e) => Err(e.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
39
src/time.rs
39
src/time.rs
|
@ -1,17 +1,32 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
use chrono::{DateTime, Duration};
|
use chrono::{DateTime, Duration};
|
||||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
|
|
||||||
/// A range of time represented by a start datetime and an end datetime.
|
/// A range of time represented by a start datetime and an end datetime.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct TimeRange<Tz: chrono::TimeZone> {
|
pub struct DateTimeRange<Tz: chrono::TimeZone> {
|
||||||
pub start: DateTime<Tz>,
|
pub start: DateTime<Tz>,
|
||||||
pub end: DateTime<Tz>,
|
pub end: DateTime<Tz>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Tz: chrono::TimeZone> TimeRange<Tz> {
|
impl<Tz: chrono::TimeZone> DateTimeRange<Tz> {
|
||||||
|
/// calculates the time range that is part of both time ranges, if any
|
||||||
|
pub fn intersection(
|
||||||
|
&self,
|
||||||
|
other: &DateTimeRange<Tz>,
|
||||||
|
) -> Option<DateTimeRange<Tz>> {
|
||||||
|
if self.is_in_range(&other.start) || self.is_in_range(&other.end) {
|
||||||
|
Some(DateTimeRange {
|
||||||
|
start: self.start.max(other.start),
|
||||||
|
end: self.end.min(other.end),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
/// whether a time instance lies within the time range
|
/// whether a time instance lies within the time range
|
||||||
pub fn is_in_range<Otz: chrono::TimeZone>(&self, other: &DateTime<Otz>) -> bool {
|
pub fn is_in_range<Otz: chrono::TimeZone>(&self, other: &DateTime<Otz>) -> bool {
|
||||||
(self.start <= *other) && (other <= &self.end)
|
(&self.start <= other) && (other <= &self.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the time between the start and end of the time range.
|
/// the time between the start and end of the time range.
|
||||||
|
@ -52,7 +67,7 @@ impl<Tz: chrono::TimeZone> TimeRange<Tz> {
|
||||||
/// Distribute a number of events across a set of time ranges evenly,
|
/// Distribute a number of events across a set of time ranges evenly,
|
||||||
/// applying a specific amount of jitter.
|
/// applying a specific amount of jitter.
|
||||||
pub fn distribute_across_ranges_with_jitter<Tz: chrono::TimeZone>(
|
pub fn distribute_across_ranges_with_jitter<Tz: chrono::TimeZone>(
|
||||||
ranges: &[TimeRange<Tz>],
|
ranges: &[DateTimeRange<Tz>],
|
||||||
number: u32,
|
number: u32,
|
||||||
max_jitter: &Duration,
|
max_jitter: &Duration,
|
||||||
) -> Vec<DateTime<Tz>> {
|
) -> Vec<DateTime<Tz>> {
|
||||||
|
@ -60,7 +75,7 @@ pub fn distribute_across_ranges_with_jitter<Tz: chrono::TimeZone>(
|
||||||
let total_duration: Duration = ranges
|
let total_duration: Duration = ranges
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Duration::zero(), |a, r| a + r.duration());
|
.fold(Duration::zero(), |a, r| a + r.duration());
|
||||||
let compressed_time = TimeRange {
|
let compressed_time = DateTimeRange {
|
||||||
start: ranges[0].start.clone(),
|
start: ranges[0].start.clone(),
|
||||||
end: ranges[0].start.clone() + total_duration,
|
end: ranges[0].start.clone() + total_duration,
|
||||||
};
|
};
|
||||||
|
@ -93,7 +108,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_distribute_evenly() {
|
fn test_distribute_evenly() {
|
||||||
let timerange = TimeRange::<chrono::Utc> {
|
let timerange = DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||||
};
|
};
|
||||||
|
@ -111,7 +126,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_distribute_with_jitter() {
|
fn test_distribute_with_jitter() {
|
||||||
let timerange = TimeRange::<chrono::Utc> {
|
let timerange = DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||||
};
|
};
|
||||||
|
@ -128,7 +143,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_with_extreme_jitter() {
|
fn test_with_extreme_jitter() {
|
||||||
let timerange = TimeRange::<chrono::Utc> {
|
let timerange = DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||||
};
|
};
|
||||||
|
@ -142,11 +157,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_distribute_across_ranges_without_jitter() {
|
fn test_distribute_across_ranges_without_jitter() {
|
||||||
let ranges = vec![
|
let ranges = vec![
|
||||||
TimeRange::<chrono::Utc> {
|
DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||||
},
|
},
|
||||||
TimeRange::<chrono::Utc> {
|
DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
||||||
},
|
},
|
||||||
|
@ -162,11 +177,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_distribute_across_ranges_with_jitter() {
|
fn test_distribute_across_ranges_with_jitter() {
|
||||||
let ranges = vec![
|
let ranges = vec![
|
||||||
TimeRange::<chrono::Utc> {
|
DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap(),
|
||||||
},
|
},
|
||||||
TimeRange::<chrono::Utc> {
|
DateTimeRange::<chrono::Utc> {
|
||||||
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
start: Utc.with_ymd_and_hms(2024, 1, 5, 0, 0, 0).unwrap(),
|
||||||
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
end: Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap(),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue