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 | 15 | pub mod config; |
| 16 | 16 | mod credential; |
| 17 | 17 | pub use credential::function as credential; |
| 18 | +pub mod attributes; | |
| 18 | 19 | #[cfg(feature = "blocking-client")] |
| 19 | 20 | pub mod clone; |
| 20 | 21 | pub mod exclude; |
| @@ -13,6 +13,7 @@ use gitoxide_core as core; · 12 unmodified lines | ||
| 13 | 13 | use gitoxide_core::pack::verify; |
| 14 | 14 | use gix::bstr::io::BufReadExt; |
| 15 | 15 | |
| 16 | +use crate::plumbing::options::attributes; | |
| 16 | 17 | use crate::{ |
| 17 | 18 | plumbing::{ |
| 18 | 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 | 863 | Subcommands::Exclude(cmd) => match cmd { |
| 835 | 864 | exclude::Subcommands::Query { |
| 836 | 865 | patterns, |
| @@ -103,6 +103,9 @@ pub enum Subcommands { · 102 unmodified lines | ||
| 103 | 103 | /// Interact with the remote hosts. |
| 104 | 104 | #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] |
| 105 | 105 | Remote(remote::Platform), |
| 106 | + /// Interact with the attribute files like .gitattributes. | |
| 107 | + #[clap(subcommand, visible_alias = "attrs")] | |
| 108 | + Attributes(attributes::Subcommands), | |
| 106 | 109 | /// Interact with the exclude files like .gitignore. |
| 107 | 110 | #[clap(subcommand)] |
| 108 | 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 | 463 | pub mod exclude { |
| 444 | 464 | use std::ffi::OsString; |
| 445 | 465 | |