Skip to content

Commit 6822689

Browse files
authored
Merge pull request #1730 from GitoxideLabs/fix-1729
fix!: consider non-files as pruned officially.
2 parents ce943c1 + 3614c21 commit 6822689

File tree

15 files changed

+142
-43
lines changed

15 files changed

+142
-43
lines changed

gitoxide-core/src/repository/clean.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,16 @@ pub(crate) mod function {
161161
if entry.disk_kind.is_none() {
162162
entry.disk_kind = workdir
163163
.join(gix::path::from_bstr(entry.rela_path.as_bstr()))
164-
.metadata()
164+
.symlink_metadata()
165165
.ok()
166-
.and_then(|e| gix::dir::entry::Kind::try_from_file_type(e.file_type()));
166+
.map(|e| e.file_type().into());
167167
}
168-
let mut disk_kind = entry.disk_kind.expect("present if not pruned");
168+
let Some(mut disk_kind) = entry.disk_kind else {
169+
if debug {
170+
writeln!(err, "DBG: ignoring unreadable entry at '{}' ", entry.rela_path).ok();
171+
}
172+
continue;
173+
};
169174
if !keep {
170175
if debug {
171176
writeln!(err, "DBG: prune '{}' as -x or -p is missing", entry.rela_path).ok();
@@ -183,6 +188,12 @@ pub(crate) mod function {
183188
}
184189

185190
match disk_kind {
191+
Kind::NonFile => {
192+
if debug {
193+
writeln!(err, "DBG: skipped non-file at '{}'", entry.rela_path).ok();
194+
}
195+
continue;
196+
}
186197
Kind::File | Kind::Symlink => {}
187198
Kind::Directory => {
188199
if !directories {
@@ -254,6 +265,7 @@ pub(crate) mod function {
254265
"WOULD remove"
255266
},
256267
suffix = match disk_kind {
268+
Kind::NonFile => unreachable!("always skipped earlier"),
257269
Kind::Directory if entry.property == Some(gix::dir::entry::Property::EmptyDirectory) => {
258270
" empty"
259271
}

gix-dir/src/entry.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::walk::ForDeletionMode;
22
use crate::{Entry, EntryRef};
33
use std::borrow::Cow;
4+
use std::fs::FileType;
45

56
/// A way of attaching additional information to an [Entry] .
67
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
@@ -25,6 +26,10 @@ pub enum Property {
2526
/// The kind of the entry, seated in their kinds available on disk.
2627
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
2728
pub enum Kind {
29+
/// Something that is not a file, like a named pipe or character device.
30+
///
31+
/// These can only exist in the filesystem.
32+
NonFile,
2833
/// The entry is a blob, executable or not.
2934
File,
3035
/// The entry is a symlink.
@@ -147,20 +152,17 @@ impl Entry {
147152
}
148153
}
149154

150-
impl Kind {
151-
/// Try to represent the file type `t` as `Entry`, or return `None` if it cannot be represented.
152-
///
153-
/// The latter can happen if it's a `pipe` for instance.
154-
pub fn try_from_file_type(t: std::fs::FileType) -> Option<Self> {
155-
Some(if t.is_dir() {
155+
impl From<std::fs::FileType> for Kind {
156+
fn from(value: FileType) -> Self {
157+
if value.is_dir() {
156158
Kind::Directory
157-
} else if t.is_symlink() {
159+
} else if value.is_symlink() {
158160
Kind::Symlink
159-
} else if t.is_file() {
161+
} else if value.is_file() {
160162
Kind::File
161163
} else {
162-
return None;
163-
})
164+
Kind::NonFile
165+
}
164166
}
165167
}
166168

gix-dir/src/walk/classify.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ pub fn root(
2020
let mut last_length = None;
2121
let mut path_buf = worktree_root.to_owned();
2222
// These initial values kick in if worktree_relative_root.is_empty();
23-
let file_kind = path_buf
24-
.symlink_metadata()
25-
.ok()
26-
.and_then(|m| entry::Kind::try_from_file_type(m.file_type()));
23+
let file_kind = path_buf.symlink_metadata().ok().map(|m| m.file_type().into());
2724
let mut out = path(&mut path_buf, buf, 0, file_kind, || None, options, ctx)?;
2825
let worktree_root_is_repository = out
2926
.disk_kind
@@ -35,10 +32,7 @@ pub fn root(
3532
}
3633
path_buf.push(component);
3734
buf.extend_from_slice(gix_path::os_str_into_bstr(component.as_os_str()).expect("no illformed UTF8"));
38-
let file_kind = path_buf
39-
.symlink_metadata()
40-
.ok()
41-
.and_then(|m| entry::Kind::try_from_file_type(m.file_type()));
35+
let file_kind = path_buf.symlink_metadata().ok().map(|m| m.file_type().into());
4236

4337
out = path(
4438
&mut path_buf,

gix-dir/src/walk/readdir.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub(super) fn recursive(
6464
current_bstr,
6565
if prev_len == 0 { 0 } else { prev_len + 1 },
6666
None,
67-
|| entry.file_type().ok().and_then(entry::Kind::try_from_file_type),
67+
|| entry.file_type().ok().map(Into::into),
6868
opts,
6969
ctx,
7070
)?;

gix-dir/tests/dir/walk.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ fn one_top_level_fifo() {
6666
}
6767
);
6868

69-
assert_eq!(entries, &[], "Non-files are simply pruned by default");
69+
assert_eq!(
70+
entries,
71+
&[entry("top", Untracked, NonFile),],
72+
"Non-files are like normal files, but with a different state"
73+
);
7074
}
7175

7276
#[test]
@@ -99,10 +103,11 @@ fn fifo_in_traversal() {
99103
&[
100104
entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
101105
entry("dir-with-file/nested-file", Untracked, File),
106+
entry("dir/nested", Untracked, NonFile),
102107
entry("file", Untracked, File),
108+
entry("top", Untracked, NonFile),
103109
],
104-
"Non-files are not even pruned, they are ignored entirely.\
105-
If one day this isn't what we want, we can create an own filetype for them"
110+
"Non-files only differ by their disk-kind"
106111
);
107112
}
108113

gix-status/src/index_as_worktree/types.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ impl Outcome {
103103
pub enum Change<T = (), U = ()> {
104104
/// This corresponding file does not exist in the worktree anymore.
105105
Removed,
106-
/// The type of file changed compared to the worktree, i.e. a symlink s now a file.
106+
/// The type of file changed compared to the worktree, i.e. a symlink is now a file, or a file was replaced with a named pipe.
107+
///
108+
/// ### Deviation
109+
///
110+
/// A change to a non-file is marked as `modification` in Git, but that's related to the content which we can't evaluate.
111+
/// Hence, a type-change is considered more appropriate.
107112
Type,
108113
/// This worktree file was modified in some form, like a permission change or content change or both,
109114
/// as compared to this entry.

gix-status/src/index_as_worktree_with_renames/mod.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ pub(super) mod function {
158158

159159
let tracker = options
160160
.rewrites
161-
.map(gix_diff::rewrites::Tracker::<rewrite::ModificationOrDirwalkEntry<'index, T, U>>::new)
161+
.map(gix_diff::rewrites::Tracker::<ModificationOrDirwalkEntry<'index, T, U>>::new)
162162
.zip(filter);
163163
let rewrite_outcome = match tracker {
164164
Some((mut tracker, (mut filter, mut attrs))) => {
@@ -168,12 +168,12 @@ pub(super) mod function {
168168
let (change, location) = match event {
169169
Event::IndexEntry(record) => {
170170
let location = Cow::Borrowed(record.relative_path);
171-
(rewrite::ModificationOrDirwalkEntry::Modification(record), location)
171+
(ModificationOrDirwalkEntry::Modification(record), location)
172172
}
173173
Event::DirEntry(entry, collapsed_directory_status) => {
174174
let location = Cow::Owned(entry.rela_path.clone());
175175
(
176-
rewrite::ModificationOrDirwalkEntry::DirwalkEntry {
176+
ModificationOrDirwalkEntry::DirwalkEntry {
177177
id: rewrite::calculate_worktree_id(
178178
options.object_hash,
179179
worktree,
@@ -222,7 +222,7 @@ pub(super) mod function {
222222
}
223223
}
224224
Some(src) => {
225-
let rewrite::ModificationOrDirwalkEntry::DirwalkEntry {
225+
let ModificationOrDirwalkEntry::DirwalkEntry {
226226
id,
227227
entry,
228228
collapsed_directory_status,
@@ -387,8 +387,11 @@ pub(super) mod function {
387387

388388
impl<T, U> gix_dir::walk::Delegate for Delegate<'_, '_, T, U> {
389389
fn emit(&mut self, entry: EntryRef<'_>, collapsed_directory_status: Option<Status>) -> Action {
390-
let entry = entry.to_owned();
391-
self.tx.send(Event::DirEntry(entry, collapsed_directory_status)).ok();
390+
// Status never shows untracked non-files
391+
if entry.disk_kind != Some(gix_dir::entry::Kind::NonFile) {
392+
let entry = entry.to_owned();
393+
self.tx.send(Event::DirEntry(entry, collapsed_directory_status)).ok();
394+
}
392395

393396
if self.should_interrupt.load(Ordering::Relaxed) {
394397
Action::Cancel
@@ -466,6 +469,10 @@ pub(super) mod function {
466469
ModificationOrDirwalkEntry::Modification(c) => c.entry.mode.to_tree_entry_mode(),
467470
ModificationOrDirwalkEntry::DirwalkEntry { entry, .. } => entry.disk_kind.map(|kind| {
468471
match kind {
472+
Kind::NonFile => {
473+
// Trees are never tracked for rewrites, so we 'pretend'.
474+
gix_object::tree::EntryKind::Tree
475+
}
469476
Kind::File => gix_object::tree::EntryKind::Blob,
470477
Kind::Symlink => gix_object::tree::EntryKind::Link,
471478
Kind::Repository | Kind::Directory => gix_object::tree::EntryKind::Tree,
@@ -500,6 +507,10 @@ pub(super) mod function {
500507
};
501508

502509
Ok(match kind {
510+
Kind::NonFile => {
511+
// Go along with unreadable files, they are passed along without rename tracking.
512+
return Ok(object_hash.null());
513+
}
503514
Kind::File => {
504515
let platform = attrs
505516
.at_entry(rela_path, None, objects)

gix-status/tests/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ publish = false
1212
rust-version = "1.65"
1313

1414
[[test]]
15-
name = "worktree"
16-
path = "worktree.rs"
15+
name = "status"
16+
path = "status/mod.rs"
1717

1818
[features]
1919
gix-features-parallel = ["gix-features/parallel"]
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
status_unchanged.tar
22
status_changed.tar
33
symlink_stack.tar
4+
status_nonfile.tar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q untracked
5+
(cd untracked
6+
touch file && git add file && git commit -m "just to get an index for the test-suite"
7+
8+
mkfifo pipe
9+
git status
10+
)
11+
12+
git init -q tracked-swapped
13+
(cd tracked-swapped
14+
touch file && git add file && git commit -m "it starts out as trackable file"
15+
16+
rm file && mkfifo file
17+
git status
18+
)
19+

gix-status/tests/status/index_as_worktree.rs

+17
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ fn fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
3737
fixture_filtered(name, &[], expected_status)
3838
}
3939

40+
fn nonfile_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
41+
fixture_filtered_detailed("status_nonfile", name, &[], expected_status, |_| {}, false)
42+
}
43+
4044
fn fixture_with_index(
4145
name: &str,
4246
prepare_index: impl FnMut(&mut gix_index::State),
@@ -185,6 +189,19 @@ fn status_removed() -> EntryStatus {
185189
Change::Removed.into()
186190
}
187191

192+
#[test]
193+
#[cfg(unix)]
194+
fn nonfile_untracked_are_not_visible() {
195+
// And generally, untracked aren't visible here.
196+
nonfile_fixture("untracked", &[]);
197+
}
198+
199+
#[test]
200+
#[cfg(unix)]
201+
fn tracked_changed_to_non_file() {
202+
nonfile_fixture("tracked-swapped", &[(BStr::new(b"file"), 0, Change::Type.into())]);
203+
}
204+
188205
#[test]
189206
fn removed() {
190207
let out = fixture(

0 commit comments

Comments
 (0)