Skip to content

Commit c3983c6

Browse files
committed
Merge branch 'gix-status'
2 parents 50657cf + 22acf0d commit c3983c6

File tree

26 files changed

+752
-32
lines changed

26 files changed

+752
-32
lines changed

Cargo.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ members = [
242242
"gix-diff",
243243
"gix-date",
244244
"gix-traverse",
245+
"gix-dir",
245246
"gix-index",
246247
"gix-bitmap",
247248
"gix-worktree",

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ is usable to some extent.
133133
* `gitoxide-core`
134134
* **very early** _(possibly without any documentation and many rough edges)_
135135
* [gix-date](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-date)
136+
* [gix-dir](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-dir)
136137
* **idea** _(just a name placeholder)_
137138
* [gix-note](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-note)
138139
* [gix-fetchhead](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-fetchhead)

crate-status.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,18 @@ A plumbing crate with shared functionality regarding EWAH compressed bitmaps, as
608608
* [x] decode on-disk representation
609609
* [ ] encode on-disk representation
610610

611+
### gix-dir
612+
613+
A git directory walk.
614+
615+
* [ ] list untracked files
616+
- [ ] `normal` - files and directories
617+
- [ ] `all` - expand to untracked files in untracked directories
618+
* [ ] list ignored files
619+
- [ ] `matching` mode (show every ignored file, do not aggregate into parent directory)
620+
- [ ] `traditional` mode (aggregate all ignored files of a folder into ignoring the folder itself)
621+
* [ ] accelerated walk with `untracked`-cache (as provided by `UNTR` extension of `gix_index::File`)
622+
611623
### gix-index
612624

613625
The git staging area.
@@ -629,6 +641,7 @@ The git staging area.
629641
* [x] 'link' base indices to take information from, split index
630642
* [x] 'sdir' [sparse directory entries](https://github.blog/2021-08-16-highlights-from-git-2-33/) - marker
631643
* [x] verification of entries and extensions as well as checksum
644+
* [ ] expand sparse directory entries using information of the tree itself
632645
* write
633646
* [x] V2
634647
* [x] V3 - extension bits
@@ -655,7 +668,7 @@ The git staging area.
655668
* [ ] IEOT index entry offset table
656669
* [ ] 'link' base indices to take information from, split index
657670
* [ ] 'sdir' sparse directory entries
658-
* add and remove entries
671+
* [ ] add and remove entries
659672
* [x] API documentation
660673
* [ ] Some examples
661674

gix-dir/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

gix-dir/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "gix-dir"
3+
version = "0.0.0"
4+
repository = "https://github.com/Byron/gitoxide"
5+
license = "MIT OR Apache-2.0"
6+
description = "A crate of the gitoxide project dealing with directory walks"
7+
authors = ["Sebastian Thiel <[email protected]>"]
8+
edition = "2021"
9+
rust-version = "1.65"
10+
11+
[lib]
12+
doctest = false
13+
14+
[dependencies]

gix-dir/LICENSE-APACHE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-APACHE

gix-dir/LICENSE-MIT

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-MIT

gix-dir/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//! A crate for handling a git-style directory walk.
2+
#![deny(rust_2018_idioms)]
3+
#![forbid(unsafe_code)]

gix-ignore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ serde = ["dep:serde", "bstr/serde", "gix-glob/serde"]
1919
[dependencies]
2020
gix-glob = { version = "^0.15.1", path = "../gix-glob" }
2121
gix-path = { version = "^0.10.3", path = "../gix-path" }
22+
gix-trace = { version = "^0.1.6", path = "../gix-trace" }
2223

2324
bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"]}
2425
unicode-bom = "2.0.2"

gix-ignore/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ pub struct Search {
2525
pub patterns: Vec<gix_glob::search::pattern::List<search::Ignore>>,
2626
}
2727

28+
/// The kind of *ignored* item.
29+
///
30+
/// This classification is obtained when checking if a path matches an ignore pattern.
31+
#[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
32+
pub enum Kind {
33+
/// The item is ignored and will be removed to make place for tracked items that are to be checked out.
34+
///
35+
/// This is the default for ignored items.
36+
/// Another way of thinking about this class is to consider these files *trashable*, or talk about them as `ignored-and-expendable`.
37+
#[default]
38+
Expendable,
39+
/// An ignored file was additionally marked as *precious* using the `$` prefix to indicate the file shall be kept.
40+
///
41+
/// This means that precious files are treated like untracked files, which also must not be removed, but won't show up by default
42+
/// as they are also ignored.
43+
/// One can also talk about them as `ignored-and-precious`.
44+
Precious,
45+
}
46+
2847
///
2948
pub mod parse;
3049

gix-ignore/src/parse.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,43 @@ impl<'a> Lines<'a> {
1818
}
1919

2020
impl<'a> Iterator for Lines<'a> {
21-
type Item = (gix_glob::Pattern, usize);
21+
type Item = (gix_glob::Pattern, usize, crate::Kind);
2222

2323
fn next(&mut self) -> Option<Self::Item> {
24-
for line in self.lines.by_ref() {
24+
for mut line in self.lines.by_ref() {
2525
self.line_no += 1;
26-
if line.first() == Some(&b'#') {
27-
continue;
28-
}
29-
match gix_glob::Pattern::from_bytes(truncate_non_escaped_trailing_spaces(line)) {
26+
let first = match line.first().copied() {
27+
Some(b'#') | None => continue,
28+
Some(c) => c,
29+
};
30+
let (kind, can_negate) = if first == b'$' {
31+
line = &line[1..];
32+
(crate::Kind::Precious, false)
33+
} else {
34+
let second = line.get(1);
35+
if first == b'!' && second == Some(&b'$') {
36+
gix_trace::error!(
37+
"Line {} starts with !$ which is not allowed ('{}')",
38+
self.line_no,
39+
line.as_bstr()
40+
);
41+
continue;
42+
}
43+
if first == b'\\' && second == Some(&b'$') {
44+
line = &line[1..];
45+
}
46+
(crate::Kind::Expendable, true)
47+
};
48+
49+
line = truncate_non_escaped_trailing_spaces(line);
50+
let res = if can_negate {
51+
gix_glob::Pattern::from_bytes(line)
52+
} else {
53+
gix_glob::Pattern::from_bytes_without_negation(line)
54+
};
55+
match res {
3056
None => continue,
31-
Some(pattern) => return Some((pattern, self.line_no)),
57+
Some(pattern) => return Some((pattern, self.line_no, kind)),
3258
}
3359
}
3460
None

gix-ignore/src/search.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub struct Match<'a> {
1515
pub pattern: &'a gix_glob::Pattern,
1616
/// The path to the source from which the pattern was loaded, or `None` if it was specified by other means.
1717
pub source: Option<&'a Path>,
18+
/// The kind of pattern this match represents.
19+
pub kind: crate::Kind,
1820
/// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided.
1921
pub sequence_number: usize,
2022
}
@@ -24,13 +26,13 @@ pub struct Match<'a> {
2426
pub struct Ignore;
2527

2628
impl Pattern for Ignore {
27-
type Value = ();
29+
type Value = crate::Kind;
2830

2931
fn bytes_to_patterns(bytes: &[u8], _source: &std::path::Path) -> Vec<pattern::Mapping<Self::Value>> {
3032
crate::parse(bytes)
31-
.map(|(pattern, line_number)| pattern::Mapping {
33+
.map(|(pattern, line_number, kind)| pattern::Mapping {
3234
pattern,
33-
value: (),
35+
value: kind,
3436
sequence_number: line_number,
3537
})
3638
.collect()
@@ -61,7 +63,7 @@ impl Search {
6163
Ok(group)
6264
}
6365

64-
/// Parse a list of patterns, using slashes as path separators
66+
/// Parse a list of ignore patterns, using slashes as path separators.
6567
pub fn from_overrides(patterns: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
6668
Self::from_overrides_inner(&mut patterns.into_iter().map(Into::into))
6769
}
@@ -73,11 +75,13 @@ impl Search {
7375
.enumerate()
7476
.filter_map(|(seq_id, pattern)| {
7577
let pattern = gix_path::try_into_bstr(PathBuf::from(pattern)).ok()?;
76-
gix_glob::parse(pattern.as_ref()).map(|p| pattern::Mapping {
77-
pattern: p,
78-
value: (),
79-
sequence_number: seq_id,
80-
})
78+
crate::parse(pattern.as_ref())
79+
.next()
80+
.map(|(p, _seq_id, kind)| pattern::Mapping {
81+
pattern: p,
82+
value: kind,
83+
sequence_number: seq_id + 1,
84+
})
8185
})
8286
.collect(),
8387
source: None,
@@ -112,7 +116,7 @@ pub fn pattern_matching_relative_path<'a>(
112116
list.patterns.iter().rev().find_map(
113117
|pattern::Mapping {
114118
pattern,
115-
value: (),
119+
value: kind,
116120
sequence_number,
117121
}| {
118122
pattern
@@ -125,6 +129,7 @@ pub fn pattern_matching_relative_path<'a>(
125129
)
126130
.then_some(Match {
127131
pattern,
132+
kind: *kind,
128133
source: list.source.as_deref(),
129134
sequence_number: *sequence_number,
130135
})
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
$.config
2+
\$starts-with-dollar
3+
# html files are now precious and won't be discarded
4+
$*.html
5+
6+
!foo.html
7+
8+
# this isn't allowed and ignored
9+
!$foo.html
10+
11+
# but this is a literal !/* that is precious
12+
$!/*

gix-ignore/tests/parse/mod.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ use bstr::BString;
22
use gix_glob::{pattern::Mode, Pattern};
33
use gix_testtools::fixture_bytes;
44

5+
#[test]
6+
fn precious() {
7+
let input = fixture_bytes("ignore/precious.txt");
8+
let actual: Vec<_> = gix_ignore::parse(&input).map(flat_map).collect();
9+
assert_eq!(
10+
actual,
11+
vec![
12+
pat_precious(".config", Mode::NO_SUB_DIR, 1),
13+
pat("$starts-with-dollar", Mode::NO_SUB_DIR, 2),
14+
pat_precious("*.html", Mode::NO_SUB_DIR | Mode::ENDS_WITH, 4),
15+
pat("foo.html", Mode::NO_SUB_DIR | Mode::NEGATIVE, 6),
16+
pat_precious("!/*", Mode::empty(), 12),
17+
]
18+
);
19+
}
20+
521
#[test]
622
fn byte_order_marks_are_no_patterns() {
723
assert_eq!(
@@ -58,7 +74,7 @@ fn backslashes_before_hashes_are_no_comments() {
5874

5975
#[test]
6076
fn trailing_spaces_can_be_escaped_to_be_literal() {
61-
fn parse_one(input: &str) -> (BString, Mode, usize) {
77+
fn parse_one(input: &str) -> (BString, Mode, usize, gix_ignore::Kind) {
6278
let actual: Vec<_> = gix_ignore::parse(input.as_bytes()).map(flat_map).collect();
6379
assert_eq!(actual.len(), 1, "{input:?} should match");
6480
actual.into_iter().next().expect("present")
@@ -101,14 +117,20 @@ fn trailing_spaces_can_be_escaped_to_be_literal() {
101117
);
102118
}
103119

104-
fn flatten(input: Option<(Pattern, usize)>) -> Option<(BString, gix_glob::pattern::Mode, usize)> {
120+
fn flatten(
121+
input: Option<(Pattern, usize, gix_ignore::Kind)>,
122+
) -> Option<(BString, gix_glob::pattern::Mode, usize, gix_ignore::Kind)> {
105123
input.map(flat_map)
106124
}
107125

108-
fn flat_map(input: (Pattern, usize)) -> (BString, gix_glob::pattern::Mode, usize) {
109-
(input.0.text, input.0.mode, input.1)
126+
fn flat_map(input: (Pattern, usize, gix_ignore::Kind)) -> (BString, gix_glob::pattern::Mode, usize, gix_ignore::Kind) {
127+
(input.0.text, input.0.mode, input.1, input.2)
128+
}
129+
130+
fn pat(pattern: &str, mode: Mode, pos: usize) -> (BString, Mode, usize, gix_ignore::Kind) {
131+
(pattern.into(), mode, pos, gix_ignore::Kind::Expendable)
110132
}
111133

112-
fn pat(pattern: &str, mode: Mode, pos: usize) -> (BString, Mode, usize) {
113-
(pattern.into(), mode, pos)
134+
fn pat_precious(pattern: &str, mode: Mode, pos: usize) -> (BString, Mode, usize, gix_ignore::Kind) {
135+
(pattern.into(), mode, pos, gix_ignore::Kind::Precious)
114136
}

0 commit comments

Comments
 (0)