diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 72b316396c0..e69de29bb2d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +0,0 @@ ----- - -Ok for [Byron](https://github.com/Byron) review the PR on video? - -- [ ] I give my permission to record review and upload on YouTube publicly - -If I think the review will be helpful for the community, then I might record and publish a video. diff --git a/gitoxide-core/src/repository/attributes.rs b/gitoxide-core/src/repository/attributes.rs new file mode 100644 index 00000000000..d37e2ea254a --- /dev/null +++ b/gitoxide-core/src/repository/attributes.rs @@ -0,0 +1,66 @@ +use std::io; + +use anyhow::bail; +use gix::prelude::FindExt; + +use crate::OutputFormat; + +pub mod query { + use crate::OutputFormat; + + pub struct Options { + pub format: OutputFormat, + pub statistics: bool, + } +} + +pub fn query( + repo: gix::Repository, + pathspecs: impl Iterator, + mut out: impl io::Write, + mut err: impl io::Write, + query::Options { format, statistics }: query::Options, +) -> anyhow::Result<()> { + if format != OutputFormat::Human { + bail!("JSON output isn't implemented yet"); + } + + let index = repo.index()?; + let mut cache = repo.attributes( + &index, + gix::worktree::cache::state::attributes::Source::WorktreeThenIdMapping, + gix::worktree::cache::state::ignore::Source::IdMapping, + None, + )?; + + let prefix = repo.prefix().expect("worktree - we have an index by now")?; + let mut matches = cache.attribute_matches(); + + for mut spec in pathspecs { + for path in spec.apply_prefix(&prefix).items() { + let is_dir = gix::path::from_bstr(path).metadata().ok().map(|m| m.is_dir()); + let entry = cache.at_entry(path, is_dir, |oid, buf| repo.objects.find_blob(oid, buf))?; + + if !entry.matching_attributes(&mut matches) { + continue; + } + for m in matches.iter() { + writeln!( + out, + "{}:{}:{}\t{}\t{}", + m.location.source.map(|p| p.to_string_lossy()).unwrap_or_default(), + m.location.sequence_number, + m.pattern, + path, + m.assignment + )?; + } + } + } + + if let Some(stats) = statistics.then(|| cache.take_statistics()) { + out.flush()?; + writeln!(err, "{:#?}", stats).ok(); + } + Ok(()) +} diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index 48e3ad87d13..7bac97dca1a 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -15,6 +15,7 @@ pub mod commit; pub mod config; mod credential; pub use credential::function as credential; +pub mod attributes; #[cfg(feature = "blocking-client")] pub mod clone; pub mod exclude; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index d77f48ac91c..27d4232d0ea 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -13,6 +13,7 @@ use gitoxide_core as core; use gitoxide_core::pack::verify; use gix::bstr::io::BufReadExt; +use crate::plumbing::options::attributes; use crate::{ plumbing::{ options::{commit, config, credential, exclude, free, index, mailmap, odb, revision, tree, Args, Subcommands}, @@ -831,6 +832,34 @@ pub fn main() -> Result<()> { }, ), }, + Subcommands::Attributes(cmd) => match cmd { + attributes::Subcommands::Query { statistics, pathspecs } => prepare_and_run( + "attributes-query", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + use gix::bstr::ByteSlice; + core::repository::attributes::query( + repository(Mode::Strict)?, + if pathspecs.is_empty() { + Box::new( + stdin_or_bail()? + .byte_lines() + .filter_map(Result::ok) + .filter_map(|line| gix::path::Spec::from_bytes(line.as_bstr())), + ) as Box> + } else { + Box::new(pathspecs.into_iter()) + }, + out, + err, + core::repository::attributes::query::Options { format, statistics }, + ) + }, + ), + }, Subcommands::Exclude(cmd) => match cmd { exclude::Subcommands::Query { patterns, diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 107da1b414f..bee6a30a2d8 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -103,6 +103,9 @@ pub enum Subcommands { /// Interact with the remote hosts. #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] Remote(remote::Platform), + /// Interact with the attribute files like .gitattributes. + #[clap(subcommand, visible_alias = "attrs")] + Attributes(attributes::Subcommands), /// Interact with the exclude files like .gitignore. #[clap(subcommand)] Exclude(exclude::Subcommands), @@ -440,6 +443,23 @@ pub mod revision { } } +pub mod attributes { + use crate::shared::AsPathSpec; + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// List all attributes of the given path-specs and display the result similar to `git check-attr`. + Query { + /// Print various statistics to stderr + #[clap(long, short = 's')] + statistics: bool, + /// The git path specifications to list attributes for, or unset to read from stdin one per line. + #[clap(value_parser = AsPathSpec)] + pathspecs: Vec, + }, + } +} + pub mod exclude { use std::ffi::OsString;