This commit is contained in:
JMARyA 2024-03-09 23:40:36 +01:00
commit 5802b1104c
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
5 changed files with 541 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

276
Cargo.lock generated Normal file
View file

@ -0,0 +1,276 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "clap"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]]
name = "giterator"
version = "0.1.0"
dependencies = [
"clap",
"csv",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "syn"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "giterator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.5.2", features = ["cargo"] }
csv = "1.3.0"
serde_json = "1.0.114"

46
src/args.rs Normal file
View file

@ -0,0 +1,46 @@
use clap::{arg, command, ArgMatches};
pub fn get_args() -> ArgMatches {
command!()
.about("Iterate over Git commits and run commands")
.arg(arg!([command] "Command to run for each commit").required(true))
.arg(
arg!([repository] "Git Repository")
.required(false)
.default_value("."),
)
.arg(
clap::Arg::new("allow-dirty")
.long("allow-dirty")
.help("Allow working with unclean repository")
.num_args(0)
.required(false),
)
.arg(
clap::Arg::new("script_file")
.short('s')
.long("script")
.help("Use the content of a script file as command")
.num_args(0)
.required(false),
)
.arg(
clap::Arg::new("json")
.short('j')
.long("json")
.conflicts_with("csv")
.help("Output as JSON")
.num_args(0)
.required(false),
)
.arg(
clap::Arg::new("csv")
.short('c')
.long("csv")
.conflicts_with("json")
.help("Output as CSV")
.num_args(0)
.required(false),
)
.get_matches()
}

207
src/main.rs Normal file
View file

@ -0,0 +1,207 @@
use std::process::{Command, Output};
mod args;
#[derive(Debug, Clone)]
struct Commit {
repo: String,
hash: String,
datetime: String,
name: String,
}
struct IterationOutput {
commit: Commit,
stdout: String,
stderr: String,
}
impl IterationOutput {
pub fn new(commit: Commit, out: Output) -> Self {
Self {
commit,
stdout: String::from_utf8(out.stdout).unwrap(),
stderr: String::from_utf8(out.stderr).unwrap(),
}
}
fn print_text(&self) {
println!(
"Commit [{}] ({}): {}",
self.commit.hash, self.commit.datetime, self.commit.name
);
println!("{}", self.stdout);
if !self.stderr.is_empty() {
println!("{}", self.stderr);
}
}
fn as_json(&self) -> serde_json::Value {
serde_json::json!({
"repo": std::fs::canonicalize(self.commit.repo.clone()).unwrap().to_str().unwrap().to_string(),
"hash": self.commit.hash,
"datetime": self.commit.datetime,
"name": self.commit.datetime,
"stdout": self.stdout,
"stderr": self.stderr
})
}
fn as_csv(&self) -> Vec<String> {
vec![
self.commit.hash.clone(),
self.commit.datetime.clone(),
self.commit.name.clone(),
std::fs::canonicalize(self.commit.repo.clone())
.unwrap()
.to_str()
.unwrap()
.to_string(),
self.stdout.replace('\n', "\\n"),
self.stderr.replace('\n', "\\n"),
]
}
}
impl Commit {
pub fn run_command(&self, command: &str) -> IterationOutput {
checkout(&self.repo, &self.hash).unwrap();
// todo : expose env vars
let out = Command::new("sh")
.current_dir(&self.repo)
.arg("-c")
.arg(command)
.output()
.unwrap();
IterationOutput::new(self.clone(), out)
}
}
fn checkout(repo: &str, commit: &str) -> Result<(), std::io::Error> {
let output = Command::new("git")
.current_dir(repo)
.arg("checkout")
.arg(commit)
.output()?;
if output.status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to execute git command",
))
}
}
fn get_commit_list(repo: &str) -> Result<Vec<Commit>, std::io::Error> {
let output = Command::new("git")
.current_dir(repo)
.arg("log")
.arg("--pretty=format:%h - %ad - %s")
.arg("--date=iso")
.output()?;
if output.status.success() {
let mut commits = Vec::new();
let out = String::from_utf8(output.stdout).unwrap();
for line in out.lines() {
let mut split = line.split(" - ");
commits.push(Commit {
repo: repo.to_string(),
hash: split.next().unwrap().to_string(),
datetime: split.next().unwrap().to_string(),
name: split.next().unwrap().to_string(),
});
}
Ok(commits)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to execute git command",
))
}
}
fn is_repository_clean(repo: &str) -> bool {
let output = Command::new("git")
.current_dir(repo)
.arg("status")
.arg("--porcelain")
.output()
.unwrap();
String::from_utf8(output.stdout).unwrap().is_empty()
}
enum OutMode {
Text,
Json,
Csv,
}
impl OutMode {
pub const fn new(json: bool, csv: bool) -> Self {
if json {
Self::Json
} else if csv {
Self::Csv
} else {
Self::Text
}
}
}
fn main() {
let args = args::get_args();
let repo = args.get_one::<String>("repository").unwrap();
let allow_dirty = args.get_flag("allow-dirty");
let command = if args.get_flag("script_file") {
std::fs::read_to_string(args.get_one::<String>("command").unwrap()).unwrap()
} else {
args.get_one::<String>("command").unwrap().clone()
};
let outmode = OutMode::new(args.get_flag("json"), args.get_flag("csv"));
let mut out = Vec::new();
if is_repository_clean(repo) || allow_dirty {
let commits = get_commit_list(repo).unwrap();
for commit in commits {
// todo : add colors
out.push(commit.run_command(&command));
}
checkout(repo, "main").unwrap();
match outmode {
OutMode::Text => {
for i in out {
i.print_text();
}
}
OutMode::Json => {
let json: Vec<_> = out.into_iter().map(|x| x.as_json()).collect();
println!(
"{}",
serde_json::to_string(&serde_json::to_value(json).unwrap()).unwrap()
);
}
OutMode::Csv => {
let csv: Vec<Vec<String>> = out.into_iter().map(|x| x.as_csv()).collect();
let mut wtr = csv::Writer::from_writer(std::io::stdout());
wtr.write_record(["hash", "datetime", "name", "repo", "stdout", "stderr"])
.unwrap();
for record in csv {
wtr.write_record(&record).unwrap();
}
wtr.flush().unwrap();
}
}
} else {
eprintln!("Repository is not clean. If you want to allow operating over an unclean repository, pass the `--allow-dirty` flag.");
}
}