History

feat: `gix attribute query` as something similar to `git check-attrs`.

Sebastian Thiel committed 3 years ago

Commit bfdcb141 parent 450212e4 files · +116 −0Browse files

Unified · Split

4 files
  1. gitoxide-core/src/repository/attributes.rs +66 −0
  2. gitoxide-core/src/repository/mod.rs +1 −0
  3. src/plumbing/main.rs +29 −0
  4. src/plumbing/options/mod.rs +20 −0
gitoxide-core/src/repository/attributes.rs+66−0
@@ -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+}
gitoxide-core/src/repository/mod.rs+1−0
@@ -15,6 +15,7 @@ pub mod commit; · 14 unmodified lines
1515 pub mod config;
1616 mod credential;
1717 pub use credential::function as credential;
18+pub mod attributes;
1819 #[cfg(feature = "blocking-client")]
1920 pub mod clone;
2021 pub mod exclude;
src/plumbing/main.rs+29−0
@@ -13,6 +13,7 @@ use gitoxide_core as core; · 12 unmodified lines
1313 use gitoxide_core::pack::verify;
1414 use gix::bstr::io::BufReadExt;
1515
16+use crate::plumbing::options::attributes;
1617 use crate::{
1718 plumbing::{
1819 options::{commit, config, credential, exclude, free, index, mailmap, odb, revision, tree, Args, Subcommands},
@@ -831,6 +832,34 @@ pub fn main() -> Result<()> { · 812 unmodified lines
831832 },
832833 ),
833834 },
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+ },
834863 Subcommands::Exclude(cmd) => match cmd {
835864 exclude::Subcommands::Query {
836865 patterns,
src/plumbing/options/mod.rs+20−0
@@ -103,6 +103,9 @@ pub enum Subcommands { · 102 unmodified lines
103103 /// Interact with the remote hosts.
104104 #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))]
105105 Remote(remote::Platform),
106+ /// Interact with the attribute files like .gitattributes.
107+ #[clap(subcommand, visible_alias = "attrs")]
108+ Attributes(attributes::Subcommands),
106109 /// Interact with the exclude files like .gitignore.
107110 #[clap(subcommand)]
108111 Exclude(exclude::Subcommands),
@@ -440,6 +443,23 @@ pub mod revision { · 331 unmodified lines
440443 }
441444 }
442445
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+
443463 pub mod exclude {
444464 use std::ffi::OsString;
445465