Unified · Split
| @@ -0,0 +1,66 @@ | |||
| 1 | use std::io; | ||
| 2 | |||
| 3 | use anyhow::bail; | ||
| 4 | use gix::prelude::FindExt; | ||
| 5 | |||
| 6 | use crate::OutputFormat; | ||
| 7 | |||
| 8 | pub mod query { | ||
| 9 | use crate::OutputFormat; | ||
| 10 | |||
| 11 | pub struct Options { | ||
| 12 | pub format: OutputFormat, | ||
| 13 | pub statistics: bool, | ||
| 14 | } | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn query( | ||
| 18 | repo: gix::Repository, | ||
| 19 | pathspecs: impl Iterator<Item = gix::path::Spec>, | ||
| 20 | mut out: impl io::Write, | ||
| 21 | mut err: impl io::Write, | ||
| 22 | query::Options { format, statistics }: query::Options, | ||
| 23 | ) -> anyhow::Result<()> { | ||
| 24 | if format != OutputFormat::Human { | ||
| 25 | bail!("JSON output isn't implemented yet"); | ||
| 26 | } | ||
| 27 | |||
| 28 | let index = repo.index()?; | ||
| 29 | let mut cache = repo.attributes( | ||
| 30 | &index, | ||
| 31 | gix::worktree::cache::state::attributes::Source::WorktreeThenIdMapping, | ||
| 32 | gix::worktree::cache::state::ignore::Source::IdMapping, | ||
| 33 | None, | ||
| 34 | )?; | ||
| 35 | |||
| 36 | let prefix = repo.prefix().expect("worktree - we have an index by now")?; | ||
| 37 | let mut matches = cache.attribute_matches(); | ||
| 38 | |||
| 39 | for mut spec in pathspecs { | ||
| 40 | for path in spec.apply_prefix(&prefix).items() { | ||
| 41 | let is_dir = gix::path::from_bstr(path).metadata().ok().map(|m| m.is_dir()); | ||
| 42 | let entry = cache.at_entry(path, is_dir, |oid, buf| repo.objects.find_blob(oid, buf))?; | ||
| 43 | |||
| 44 | if !entry.matching_attributes(&mut matches) { | ||
| 45 | continue; | ||
| 46 | } | ||
| 47 | for m in matches.iter() { | ||
| 48 | writeln!( | ||
| 49 | out, | ||
| 50 | "{}:{}:{}\t{}\t{}", | ||
| 51 | m.location.source.map(|p| p.to_string_lossy()).unwrap_or_default(), | ||
| 52 | m.location.sequence_number, | ||
| 53 | m.pattern, | ||
| 54 | path, | ||
| 55 | m.assignment | ||
| 56 | )?; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | if let Some(stats) = statistics.then(|| cache.take_statistics()) { | ||
| 62 | out.flush()?; | ||
| 63 | writeln!(err, "{:#?}", stats).ok(); | ||
| 64 | } | ||
| 65 | Ok(()) | ||
| 66 | } | ||
| @@ -15,6 +15,7 @@ pub mod commit; · 14 unmodified lines | |||
| 15 | pub mod config; | 15 | pub mod config; |
| 16 | mod credential; | 16 | mod credential; |
| 17 | pub use credential::function as credential; | 17 | pub use credential::function as credential; |
| 18 | pub mod attributes; | ||
| 18 | #[cfg(feature = "blocking-client")] | 19 | #[cfg(feature = "blocking-client")] |
| 19 | pub mod clone; | 20 | pub mod clone; |
| 20 | pub mod exclude; | 21 | pub mod exclude; |
| @@ -13,6 +13,7 @@ use gitoxide_core as core; · 12 unmodified lines | |||
| 13 | use gitoxide_core::pack::verify; | 13 | use gitoxide_core::pack::verify; |
| 14 | use gix::bstr::io::BufReadExt; | 14 | use gix::bstr::io::BufReadExt; |
| 15 | 15 | ||
| 16 | use crate::plumbing::options::attributes; | ||
| 16 | use crate::{ | 17 | use crate::{ |
| 17 | plumbing::{ | 18 | plumbing::{ |
| 18 | options::{commit, config, credential, exclude, free, index, mailmap, odb, revision, tree, Args, Subcommands}, | 19 | options::{commit, config, credential, exclude, free, index, mailmap, odb, revision, tree, Args, Subcommands}, |
| @@ -831,6 +832,34 @@ pub fn main() -> Result<()> { · 812 unmodified lines | |||
| 831 | }, | 832 | }, |
| 832 | ), | 833 | ), |
| 833 | }, | 834 | }, |
| 835 | Subcommands::Attributes(cmd) => match cmd { | ||
| 836 | attributes::Subcommands::Query { statistics, pathspecs } => prepare_and_run( | ||
| 837 | "attributes-query", | ||
| 838 | verbose, | ||
| 839 | progress, | ||
| 840 | progress_keep_open, | ||
| 841 | None, | ||
| 842 | move |_progress, out, err| { | ||
| 843 | use gix::bstr::ByteSlice; | ||
| 844 | core::repository::attributes::query( | ||
| 845 | repository(Mode::Strict)?, | ||
| 846 | if pathspecs.is_empty() { | ||
| 847 | Box::new( | ||
| 848 | stdin_or_bail()? | ||
| 849 | .byte_lines() | ||
| 850 | .filter_map(Result::ok) | ||
| 851 | .filter_map(|line| gix::path::Spec::from_bytes(line.as_bstr())), | ||
| 852 | ) as Box<dyn Iterator<Item = gix::path::Spec>> | ||
| 853 | } else { | ||
| 854 | Box::new(pathspecs.into_iter()) | ||
| 855 | }, | ||
| 856 | out, | ||
| 857 | err, | ||
| 858 | core::repository::attributes::query::Options { format, statistics }, | ||
| 859 | ) | ||
| 860 | }, | ||
| 861 | ), | ||
| 862 | }, | ||
| 834 | Subcommands::Exclude(cmd) => match cmd { | 863 | Subcommands::Exclude(cmd) => match cmd { |
| 835 | exclude::Subcommands::Query { | 864 | exclude::Subcommands::Query { |
| 836 | patterns, | 865 | patterns, |
| @@ -103,6 +103,9 @@ pub enum Subcommands { · 102 unmodified lines | |||
| 103 | /// Interact with the remote hosts. | 103 | /// Interact with the remote hosts. |
| 104 | #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] | 104 | #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] |
| 105 | Remote(remote::Platform), | 105 | Remote(remote::Platform), |
| 106 | /// Interact with the attribute files like .gitattributes. | ||
| 107 | #[clap(subcommand, visible_alias = "attrs")] | ||
| 108 | Attributes(attributes::Subcommands), | ||
| 106 | /// Interact with the exclude files like .gitignore. | 109 | /// Interact with the exclude files like .gitignore. |
| 107 | #[clap(subcommand)] | 110 | #[clap(subcommand)] |
| 108 | Exclude(exclude::Subcommands), | 111 | Exclude(exclude::Subcommands), |
| @@ -440,6 +443,23 @@ pub mod revision { · 331 unmodified lines | |||
| 440 | } | 443 | } |
| 441 | } | 444 | } |
| 442 | 445 | ||
| 446 | pub mod attributes { | ||
| 447 | use crate::shared::AsPathSpec; | ||
| 448 | |||
| 449 | #[derive(Debug, clap::Subcommand)] | ||
| 450 | pub enum Subcommands { | ||
| 451 | /// List all attributes of the given path-specs and display the result similar to `git check-attr`. | ||
| 452 | Query { | ||
| 453 | /// Print various statistics to stderr | ||
| 454 | #[clap(long, short = 's')] | ||
| 455 | statistics: bool, | ||
| 456 | /// The git path specifications to list attributes for, or unset to read from stdin one per line. | ||
| 457 | #[clap(value_parser = AsPathSpec)] | ||
| 458 | pathspecs: Vec<gix::path::Spec>, | ||
| 459 | }, | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 443 | pub mod exclude { | 463 | pub mod exclude { |
| 444 | use std::ffi::OsString; | 464 | use std::ffi::OsString; |
| 445 | 465 | ||