Skip to content

Commit

Permalink
feat!: add gix blame -L start,end
Browse files Browse the repository at this point in the history
  • Loading branch information
cruessler committed Jan 14, 2025
1 parent 787cf6f commit 1f67966
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 5 deletions.
3 changes: 2 additions & 1 deletion gitoxide-core/src/repository/blame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::ffi::OsStr;
pub fn blame_file(
mut repo: gix::Repository,
file: &OsStr,
range: Option<std::ops::Range<u32>>,
out: impl std::io::Write,
err: Option<&mut dyn std::io::Write>,
) -> anyhow::Result<()> {
Expand Down Expand Up @@ -40,7 +41,7 @@ pub fn blame_file(
.with_commit_graph(repo.commit_graph_if_enabled()?)
.build()?;
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
let outcome = gix::blame::file(&repo.objects, traverse, &mut resource_cache, file.as_bstr())?;
let outcome = gix::blame::file(&repo.objects, traverse, &mut resource_cache, file.as_bstr(), range)?;
let statistics = outcome.statistics;
write_blame_entries(out, outcome)?;

Expand Down
14 changes: 12 additions & 2 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1542,15 +1542,25 @@ pub fn main() -> Result<()> {
},
),
},
Subcommands::Blame { statistics, file } => prepare_and_run(
Subcommands::Blame {
statistics,
file,
range,
} => prepare_and_run(
"blame",
trace,
verbose,
progress,
progress_keep_open,
None,
move |_progress, out, err| {
core::repository::blame::blame_file(repository(Mode::Lenient)?, &file, out, statistics.then_some(err))
core::repository::blame::blame_file(
repository(Mode::Lenient)?,
&file,
range,
out,
statistics.then_some(err),
)
},
),
Subcommands::Completions { shell, out_dir } => {
Expand Down
5 changes: 5 additions & 0 deletions src/plumbing/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use clap_complete::Shell;
use gitoxide_core as core;
use gix::bstr::BString;

use crate::shared::AsRange;

#[derive(Debug, clap::Parser)]
#[clap(name = "gix", about = "The git underworld", version = option_env!("GIX_VERSION"))]
#[clap(subcommand_required = true)]
Expand Down Expand Up @@ -162,6 +164,9 @@ pub enum Subcommands {
statistics: bool,
/// The file to create the blame information for.
file: std::ffi::OsString,
/// Only blame lines in the given 1-based inclusive range '<start>,<end>', e.g. '20,40'.
#[clap(short='L', value_parser=AsRange)]
range: Option<std::ops::Range<u32>>,
},
/// Generate shell completions to stdout or a directory.
#[clap(visible_alias = "generate-completions", visible_alias = "shell-completions")]
Expand Down
42 changes: 40 additions & 2 deletions src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,40 @@ mod clap {
.parse_ref(cmd, arg, value)
}
}

#[derive(Clone)]
pub struct AsRange;

impl TypedValueParser for AsRange {
type Value = std::ops::Range<u32>;

fn parse_ref(&self, cmd: &Command, arg: Option<&Arg>, value: &OsStr) -> Result<Self::Value, Error> {
StringValueParser::new()
.try_map(|arg| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
let parts = arg.split_once(',');
if let Some((start, end)) = parts {
let start = u32::from_str(start)?;
let end = u32::from_str(end)?;

if start <= end {
return Ok(start..end);
}
}

Err(Box::new(Error::new(ErrorKind::ValueValidation)))
})
.parse_ref(cmd, arg, value)
}
}
}
pub use self::clap::{
AsBString, AsHashKind, AsOutputFormat, AsPartialRefName, AsPathSpec, AsTime, CheckPathSpec, ParseRenameFraction,
AsBString, AsHashKind, AsOutputFormat, AsPartialRefName, AsPathSpec, AsRange, AsTime, CheckPathSpec,
ParseRenameFraction,
};

#[cfg(test)]
mod value_parser_tests {
use super::ParseRenameFraction;
use super::{AsRange, ParseRenameFraction};
use clap::Parser;

#[test]
Expand All @@ -441,4 +467,16 @@ mod value_parser_tests {
let c = Cmd::parse_from(["cmd", "-a=75"]);
assert_eq!(c.arg, Some(Some(0.75)));
}

#[test]
fn range() {
#[derive(Debug, clap::Parser)]
pub struct Cmd {
#[clap(long, short='l', value_parser = AsRange)]
pub arg: Option<std::ops::Range<u32>>,
}

let c = Cmd::parse_from(["cmd", "-l=1,10"]);
assert_eq!(c.arg, Some(1..10));
}
}

0 comments on commit 1f67966

Please sign in to comment.