Skip to content

Commit 9cb8385

Browse files
committed
Basic prefix support as well the first working version of exclude query (GitoxideLabs#301)
Details are still to be fixed, and reading from stdin should be implemented one day. Issue right now is that the source path is normalized for some reason, let's see where that happens.
1 parent cb1c80f commit 9cb8385

File tree

7 files changed

+110
-15
lines changed

7 files changed

+110
-15
lines changed

git-path/src/lib.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,12 @@
4141
//! ever get into a code-path which does panic though.
4242
4343
/// A dummy type to represent path specs and help finding all spots that take path specs once it is implemented.
44-
#[derive(Clone, Debug)]
45-
pub struct Spec(String);
46-
47-
impl FromStr for Spec {
48-
type Err = std::convert::Infallible;
4944
50-
fn from_str(s: &str) -> Result<Self, Self::Err> {
51-
Ok(Spec(s.to_owned()))
52-
}
53-
}
45+
/// A preliminary version of a path-spec based on glances of the code.
46+
#[derive(Clone, Debug)]
47+
pub struct Spec(bstr::BString);
5448

5549
mod convert;
50+
mod spec;
5651

5752
pub use convert::*;
58-
use std::str::FromStr;

git-path/src/spec.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::Spec;
2+
use bstr::{BStr, ByteSlice, ByteVec};
3+
use std::ffi::OsStr;
4+
use std::str::FromStr;
5+
6+
impl std::convert::TryFrom<&OsStr> for Spec {
7+
type Error = crate::Utf8Error;
8+
9+
fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
10+
crate::os_str_into_bstr(value).map(|value| Spec(value.into()))
11+
}
12+
}
13+
14+
impl FromStr for Spec {
15+
type Err = std::convert::Infallible;
16+
17+
fn from_str(s: &str) -> Result<Self, Self::Err> {
18+
Ok(Spec(s.into()))
19+
}
20+
}
21+
22+
impl Spec {
23+
/// Return all paths described by this path spec, using slashes on all platforms.
24+
pub fn items(&self) -> impl Iterator<Item = &BStr> {
25+
std::iter::once(self.0.as_bstr())
26+
}
27+
/// Adjust this path specification according to the given `prefix`, which may be empty to indicate we are the at work-tree root.
28+
// TODO: this is a hack, needs test and time to do according to spec. This is just a minimum version to have -something-.
29+
pub fn apply_prefix(&mut self, prefix: &std::path::Path) -> &Self {
30+
assert!(!self.0.contains_str(b"/../"));
31+
assert!(!self.0.contains_str(b"/./"));
32+
assert!(!self.0.starts_with_str(b"../"));
33+
assert!(!self.0.starts_with_str(b"./"));
34+
assert!(!self.0.starts_with_str(b"/"));
35+
// many more things we can't handle. `Path` never ends with trailing path separator.
36+
37+
let prefix = crate::into_bstr(prefix);
38+
if !prefix.is_empty() {
39+
let mut prefix = crate::to_unix_separators_on_windows(prefix);
40+
{
41+
let path = prefix.to_mut();
42+
path.push_byte(b'/');
43+
path.extend_from_slice(&self.0);
44+
}
45+
self.0 = prefix.into_owned();
46+
}
47+
self
48+
}
49+
}

git-repository/src/repository/location.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@ impl crate::Repository {
1515
crate::path::install_dir()
1616
}
1717

18+
/// Returns the relative path which is the components between the working tree and the current working dir (CWD).
19+
/// Note that there may be `None` if there is no work tree, even though the `PathBuf` will be empty
20+
/// if the CWD is at the root of the work tree.
21+
// TODO: tests, details - there is a lot about environment variables to change things around.
22+
pub fn prefix(&self) -> Option<std::io::Result<std::path::PathBuf>> {
23+
self.work_tree.as_ref().map(|root| {
24+
root.canonicalize().and_then(|root| {
25+
std::env::current_dir().and_then(|cwd| {
26+
cwd.strip_prefix(&root)
27+
.map_err(|_| {
28+
std::io::Error::new(
29+
std::io::ErrorKind::Other,
30+
format!(
31+
"CWD '{}' isn't within the work tree '{}'",
32+
cwd.display(),
33+
root.display()
34+
),
35+
)
36+
})
37+
.map(ToOwned::to_owned)
38+
})
39+
})
40+
})
41+
}
42+
1843
/// Return the kind of repository, either bare or one with a work tree.
1944
pub fn kind(&self) -> crate::Kind {
2045
match self.work_tree {

git-worktree/src/fs/cache/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl<'paths> Cache<'paths> {
5757
}
5858
}
5959

60+
#[must_use]
6061
pub struct Platform<'a, 'paths> {
6162
parent: &'a Cache<'paths>,
6263
is_dir: Option<bool>,

gitoxide-core/src/repository/exclude.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::io;
33

44
use crate::OutputFormat;
55
use git_repository as git;
6+
use git_repository::prelude::FindExt;
67

78
pub mod query {
89
use crate::OutputFormat;
@@ -18,11 +19,11 @@ pub mod query {
1819

1920
pub fn query(
2021
repo: git::Repository,
21-
_out: impl io::Write,
22+
mut out: impl io::Write,
2223
query::Options {
2324
overrides,
2425
format,
25-
pathspecs: _,
26+
pathspecs,
2627
}: query::Options,
2728
) -> anyhow::Result<()> {
2829
if format != OutputFormat::Human {
@@ -34,10 +35,34 @@ pub fn query(
3435
.current()
3536
.with_context(|| "Cannot check excludes without a current worktree")?;
3637
let index = worktree.open_index()?;
37-
worktree.excludes(
38+
let mut cache = worktree.excludes(
3839
&index.state,
3940
Some(git::attrs::MatchGroup::<git::attrs::Ignore>::from_overrides(overrides)),
4041
)?;
4142

42-
todo!("impl");
43+
let prefix = repo.prefix().expect("worktree - we have an index by now")?;
44+
45+
for mut spec in pathspecs {
46+
for path in spec.apply_prefix(&prefix).items() {
47+
// TODO: what about paths that end in /? Pathspec might handle it, it's definitely something git considers
48+
// even if the directory doesn't exist. Seems to work as long as these are kept in the spec.
49+
let is_dir = git::path::from_bstr(path).metadata().ok().map(|m| m.is_dir());
50+
let entry = cache.at_entry(path, is_dir, |oid, buf| repo.objects.find_blob(oid, buf))?;
51+
let match_ = entry
52+
.matching_exclude_pattern()
53+
.and_then(|m| (!m.pattern.is_negative()).then(|| m));
54+
match match_ {
55+
Some(m) => writeln!(
56+
out,
57+
"{}:{}:{}\t{}",
58+
m.source.map(|p| p.to_string_lossy()).unwrap_or_default(),
59+
m.sequence_number,
60+
m.pattern,
61+
path
62+
)?,
63+
None => writeln!(out, "::\t{}", path)?,
64+
}
65+
}
66+
}
67+
Ok(())
4368
}

src/plumbing/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ pub fn main() -> Result<()> {
157157
},
158158
Subcommands::Repository(repo::Platform { repository, cmd }) => {
159159
use git_repository as git;
160-
let repository = git::ThreadSafeRepository::open(repository)?;
160+
let repository = git::ThreadSafeRepository::discover(repository)?;
161161
match cmd {
162162
repo::Subcommands::Commit { cmd } => match cmd {
163163
repo::commit::Subcommands::Describe {

src/plumbing/options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ pub mod repo {
391391
#[clap(long, short = 'p')]
392392
patterns: Vec<OsString>,
393393
/// The git path specifications to check for exclusion.
394+
#[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))]
394395
pathspecs: Vec<git::path::Spec>,
395396
},
396397
}

0 commit comments

Comments
 (0)