Skip to content

Commit

Permalink
Merge branch 'pathspec-matching'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Aug 14, 2023
2 parents e83c38f + c30ac0c commit 9f4dfe0
Show file tree
Hide file tree
Showing 56 changed files with 2,367 additions and 794 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
**/generated-archives/*.tar.xz filter=lfs diff=lfs merge=lfs -text

# assure line feeds don't interfere with our working copy hash
**/tests/fixtures/*.sh text crlf=input eol=lf
**/tests/fixtures/**/*.sh text crlf=input eol=lf
/justfile text crlf=input eol=lf
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,9 @@ Check out the [performance discussion][gix-traverse-performance] as well.
* [ ] check for match

### gix-pathspec
* [x] parse
* [ ] matching of paths
* [x] parse single
* [ ] parse file line by line (with or without quoting, NUL and LF/CRLF line separation) (see `--pathspec-from-file` and `--pathspec-file-nul`)
* [ ] matching of paths with git-attributes support

### gix-refspec
* [x] parse
Expand Down
16 changes: 11 additions & 5 deletions gitoxide-core/src/query/engine/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;

use anyhow::Context;
use anyhow::{bail, Context};
use gix::{bstr::ByteSlice, prelude::ObjectIdExt, Progress};
use rusqlite::{params, OptionalExtension};

Expand All @@ -18,10 +18,16 @@ impl query::Engine {
) -> anyhow::Result<()> {
match cmd {
Command::TracePath { mut spec } => {
if let Some(prefix) = self.repo.prefix() {
spec.apply_prefix(&prefix?);
};
let relpath = spec.items().next().expect("spec has at least one item");
let is_excluded = spec.is_excluded();
let relpath = spec
.normalize(
self.repo.prefix()?.unwrap_or_default().as_ref(),
self.repo.work_dir().unwrap_or_else(|| self.repo.git_dir()),
)?
.path();
if relpath.is_empty() || is_excluded {
bail!("Invalid pathspec {spec} - path must not be empty, not be excluded, and wildcards are taken literally")
}
let file_id: usize = self
.con
.query_row(
Expand Down
2 changes: 1 addition & 1 deletion gitoxide-core/src/query/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub enum Command {
TracePath {
/// The repo-relative path to the file to trace
spec: gix::path::Spec,
spec: gix::pathspec::Pattern,
},
}

Expand Down
43 changes: 23 additions & 20 deletions gitoxide-core/src/repository/attributes/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub(crate) mod function {

pub fn query(
repo: gix::Repository,
pathspecs: impl Iterator<Item = gix::path::Spec>,
pathspecs: impl Iterator<Item = gix::pathspec::Pattern>,
mut out: impl io::Write,
mut err: impl io::Write,
Options { format, statistics }: Options,
Expand All @@ -28,28 +28,31 @@ pub(crate) mod function {
}

let mut cache = attributes_cache(&repo)?;
let prefix = repo.prefix().expect("worktree - we have an index by now")?;
let mut matches = cache.attribute_matches();
// TODO(pathspec): The search is just used as a shortcut to normalization, but one day should be used for an actual search.
let search = gix::pathspec::Search::from_specs(
pathspecs,
repo.prefix()?.as_deref(),
repo.work_dir().unwrap_or_else(|| repo.git_dir()),
)?;

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))?;
for spec in search.into_patterns() {
let is_dir = gix::path::from_bstr(spec.path()).metadata().ok().map(|m| m.is_dir());
let entry = cache.at_entry(spec.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(Path::to_string_lossy).unwrap_or_default(),
m.location.sequence_number,
m.pattern,
path,
m.assignment
)?;
}
if !entry.matching_attributes(&mut matches) {
continue;
}
for m in matches.iter() {
writeln!(
out,
"{}:{}:{}\t{}\t{}",
m.location.source.map(Path::to_string_lossy).unwrap_or_default(),
m.location.sequence_number,
m.pattern,
spec.path(),
m.assignment
)?;
}
}

Expand Down
39 changes: 17 additions & 22 deletions gitoxide-core/src/repository/attributes/validate_baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) mod function {
};

use anyhow::{anyhow, bail};
use gix::bstr::BString;
use gix::{attrs::Assignment, odb::FindExt, Progress};

use crate::{
Expand All @@ -27,7 +28,7 @@ pub(crate) mod function {

pub fn validate_baseline(
repo: gix::Repository,
pathspecs: Option<impl Iterator<Item = gix::path::Spec> + Send + 'static>,
paths: Option<impl Iterator<Item = BString> + Send + 'static>,
mut progress: impl Progress + 'static,
mut out: impl io::Write,
mut err: impl io::Write,
Expand All @@ -51,26 +52,24 @@ pub(crate) mod function {
ignore = false;
}
let mut num_entries = None;
let pathspecs = pathspecs.map_or_else(
let paths = paths.map_or_else(
{
let repo = repo.clone();
let num_entries = &mut num_entries;
move || -> anyhow::Result<_> {
let index = repo.index_or_load_from_head()?.into_owned();
let (entries, path_backing) = index.into_parts().0.into_entries();
*num_entries = Some(entries.len());
let iter = Box::new(entries.into_iter().map(move |e| {
gix::path::Spec::from_bytes(e.path_in(&path_backing)).expect("each entry path is a valid spec")
}));
Ok(iter as Box<dyn Iterator<Item = gix::path::Spec> + Send + 'static>)
let iter = Box::new(entries.into_iter().map(move |e| e.path_in(&path_backing).to_owned()));
Ok(iter as Box<dyn Iterator<Item = BString> + Send + 'static>)
}
},
|i| anyhow::Result::Ok(Box::new(i)),
|paths| anyhow::Result::Ok(Box::new(paths)),
)?;

let (tx_base, rx_base) = std::sync::mpsc::channel::<(String, Baseline)>();
let feed_attrs = {
let (tx, rx) = std::sync::mpsc::sync_channel::<gix::path::Spec>(1);
let (tx, rx) = std::sync::mpsc::sync_channel::<BString>(100);
std::thread::spawn({
let path = repo.path().to_owned();
let tx_base = tx_base.clone();
Expand All @@ -89,12 +88,10 @@ pub(crate) mod function {
move || -> anyhow::Result<()> {
progress.init(num_entries, gix::progress::count("paths"));
let start = std::time::Instant::now();
for spec in rx {
for path in rx {
progress.inc();
for path in spec.items() {
stdin.write_all(path.as_ref())?;
stdin.write_all(b"\n")?;
}
stdin.write_all(&path)?;
stdin.write_all(b"\n")?;
}
progress.show_throughput(start);
Ok(())
Expand Down Expand Up @@ -123,7 +120,7 @@ pub(crate) mod function {
})
.transpose()?;
let feed_excludes = ignore.then(|| {
let (tx, rx) = std::sync::mpsc::sync_channel::<gix::path::Spec>(1);
let (tx, rx) = std::sync::mpsc::sync_channel::<BString>(100);
std::thread::spawn({
let path = work_dir.expect("present if we are here");
let tx_base = tx_base.clone();
Expand All @@ -142,12 +139,10 @@ pub(crate) mod function {
move || -> anyhow::Result<()> {
progress.init(num_entries, gix::progress::count("paths"));
let start = std::time::Instant::now();
for spec in rx {
for path in rx {
progress.inc();
for path in spec.items() {
stdin.write_all(path.as_ref())?;
stdin.write_all(b"\n")?;
}
stdin.write_all(path.as_ref())?;
stdin.write_all(b"\n")?;
}
progress.show_throughput(start);
Ok(())
Expand Down Expand Up @@ -175,12 +170,12 @@ pub(crate) mod function {
drop(tx_base);

std::thread::spawn(move || {
for spec in pathspecs {
if feed_attrs.send(spec.clone()).is_err() {
for path in paths {
if feed_attrs.send(path.clone()).is_err() {
break;
}
if let Some(ch) = feed_excludes.as_ref() {
if ch.send(spec).is_err() {
if ch.send(path).is_err() {
break;
}
}
Expand Down
46 changes: 24 additions & 22 deletions gitoxide-core/src/repository/exclude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod query {

pub fn query(
repo: gix::Repository,
pathspecs: impl Iterator<Item = gix::path::Spec>,
pathspecs: impl Iterator<Item = gix::pathspec::Pattern>,
mut out: impl io::Write,
mut err: impl io::Write,
query::Options {
Expand All @@ -41,28 +41,30 @@ pub fn query(
Default::default(),
)?;

let prefix = repo.prefix().expect("worktree - we have an index by now")?;
// TODO(pathspec): actually use the search to find items. This looks like `gix` capabilities to put it all together.
let search = gix::pathspec::Search::from_specs(
pathspecs,
repo.prefix()?.as_deref(),
repo.work_dir().unwrap_or_else(|| repo.git_dir()),
)?;

for mut spec in pathspecs {
for path in spec.apply_prefix(&prefix).items() {
// TODO: what about paths that end in /? Pathspec might handle it, it's definitely something git considers
// even if the directory doesn't exist. Seems to work as long as these are kept in the spec.
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))?;
let match_ = entry
.matching_exclude_pattern()
.and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));
match match_ {
Some(m) => writeln!(
out,
"{}:{}:{}\t{}",
m.source.map(std::path::Path::to_string_lossy).unwrap_or_default(),
m.sequence_number,
m.pattern,
path
)?,
None => writeln!(out, "::\t{path}")?,
}
for spec in search.into_patterns() {
let path = spec.path();
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))?;
let match_ = entry
.matching_exclude_pattern()
.and_then(|m| (show_ignore_patterns || !m.pattern.is_negative()).then_some(m));
match match_ {
Some(m) => writeln!(
out,
"{}:{}:{}\t{}",
m.source.map(std::path::Path::to_string_lossy).unwrap_or_default(),
m.sequence_number,
m.pattern,
path
)?,
None => writeln!(out, "::\t{path}")?,
}
}

Expand Down

0 comments on commit 9f4dfe0

Please sign in to comment.