Skip to content

Commit 6c8850b

Browse files
committed
fix: assure an ignored repository clone is for deletion is recognized as repository (#1464)
1 parent 0e8508a commit 6c8850b

File tree

4 files changed

+137
-37
lines changed

4 files changed

+137
-37
lines changed

gix-dir/src/walk/classify.rs

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -214,37 +214,14 @@ pub fn path(
214214
};
215215

216216
let mut maybe_upgrade_to_repository = |current_kind, find_harder: bool| {
217-
if recurse_repositories {
218-
return current_kind;
219-
}
220-
if find_harder {
221-
let mut is_nested_repo = gix_discover::is_git(path).is_ok();
222-
if is_nested_repo {
223-
let git_dir_is_our_own =
224-
gix_path::realpath_opts(path, ctx.current_dir, gix_path::realpath::MAX_SYMLINKS)
225-
.ok()
226-
.map_or(false, |realpath_candidate| realpath_candidate == ctx.git_dir_realpath);
227-
is_nested_repo = !git_dir_is_our_own;
228-
}
229-
if is_nested_repo {
230-
return Some(entry::Kind::Repository);
231-
}
232-
}
233-
path.push(gix_discover::DOT_GIT_DIR);
234-
let mut is_nested_nonbare_repo = gix_discover::is_git(path).is_ok();
235-
if is_nested_nonbare_repo {
236-
let git_dir_is_our_own = gix_path::realpath_opts(path, ctx.current_dir, gix_path::realpath::MAX_SYMLINKS)
237-
.ok()
238-
.map_or(false, |realpath_candidate| realpath_candidate == ctx.git_dir_realpath);
239-
is_nested_nonbare_repo = !git_dir_is_our_own;
240-
}
241-
path.pop();
242-
243-
if is_nested_nonbare_repo {
244-
Some(entry::Kind::Repository)
245-
} else {
246-
current_kind
247-
}
217+
maybe_upgrade_to_repository(
218+
current_kind,
219+
find_harder,
220+
recurse_repositories,
221+
path,
222+
ctx.current_dir,
223+
ctx.git_dir_realpath,
224+
)
248225
};
249226
if let Some(status) = maybe_status {
250227
if kind == Some(entry::Kind::Directory) && index_kind == Some(entry::Kind::Repository) {
@@ -302,6 +279,46 @@ pub fn path(
302279
Ok(out.with_status(status).with_kind(kind, index_kind))
303280
}
304281

282+
pub fn maybe_upgrade_to_repository(
283+
current_kind: Option<entry::Kind>,
284+
find_harder: bool,
285+
recurse_repositories: bool,
286+
path: &mut PathBuf,
287+
current_dir: &Path,
288+
git_dir_realpath: &Path,
289+
) -> Option<entry::Kind> {
290+
if recurse_repositories {
291+
return current_kind;
292+
}
293+
if find_harder {
294+
let mut is_nested_repo = gix_discover::is_git(path).is_ok();
295+
if is_nested_repo {
296+
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
297+
.ok()
298+
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
299+
is_nested_repo = !git_dir_is_our_own;
300+
}
301+
if is_nested_repo {
302+
return Some(entry::Kind::Repository);
303+
}
304+
}
305+
path.push(gix_discover::DOT_GIT_DIR);
306+
let mut is_nested_nonbare_repo = gix_discover::is_git(path).is_ok();
307+
if is_nested_nonbare_repo {
308+
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
309+
.ok()
310+
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
311+
is_nested_nonbare_repo = !git_dir_is_our_own;
312+
}
313+
path.pop();
314+
315+
if is_nested_nonbare_repo {
316+
Some(entry::Kind::Repository)
317+
} else {
318+
current_kind
319+
}
320+
}
321+
305322
/// Note that `rela_path` is used as buffer for convenience, but will be left as is when this function returns.
306323
/// Also note `maybe_file_type` will be `None` for entries that aren't up-to-date and files, for directories at least one entry must be uptodate.
307324
/// Returns `(maybe_file_type, Option<index_file_type>, flags)`, with the last option being a flag set only for sparse directories in the index.

gix-dir/src/walk/readdir.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::sync::atomic::Ordering;
66
use crate::entry::{PathspecMatch, Status};
77
use crate::walk::function::{can_recurse, emit_entry};
88
use crate::walk::EmissionMode::CollapseDirectory;
9-
use crate::walk::{classify, Action, CollapsedEntriesEmissionMode, Context, Delegate, Error, Options, Outcome};
9+
use crate::walk::{
10+
classify, Action, CollapsedEntriesEmissionMode, Context, Delegate, Error, ForDeletionMode, Options, Outcome,
11+
};
1012
use crate::{entry, walk, Entry, EntryRef};
1113

1214
/// ### Deviation
@@ -57,7 +59,7 @@ pub(super) fn recursive(
5759
);
5860
current.push(file_name);
5961

60-
let info = classify::path(
62+
let mut info = classify::path(
6163
current,
6264
current_bstr,
6365
if prev_len == 0 { 0 } else { prev_len + 1 },
@@ -90,10 +92,25 @@ pub(super) fn recursive(
9092
if action != Action::Continue {
9193
return Ok((action, prevent_collapse));
9294
}
93-
} else if !state.held_for_directory_collapse(current_bstr.as_bstr(), info, &opts) {
94-
let action = emit_entry(Cow::Borrowed(current_bstr.as_bstr()), info, None, opts, out, delegate);
95-
if action != Action::Continue {
96-
return Ok((action, prevent_collapse));
95+
} else {
96+
if opts.for_deletion == Some(ForDeletionMode::IgnoredDirectoriesCanHideNestedRepositories)
97+
&& info.disk_kind == Some(entry::Kind::Directory)
98+
&& matches!(info.status, Status::Ignored(_))
99+
{
100+
info.disk_kind = classify::maybe_upgrade_to_repository(
101+
info.disk_kind,
102+
true,
103+
false,
104+
current,
105+
ctx.current_dir,
106+
ctx.git_dir_realpath,
107+
);
108+
}
109+
if !state.held_for_directory_collapse(current_bstr.as_bstr(), info, &opts) {
110+
let action = emit_entry(Cow::Borrowed(current_bstr.as_bstr()), info, None, opts, out, delegate);
111+
if action != Action::Continue {
112+
return Ok((action, prevent_collapse));
113+
}
97114
}
98115
}
99116
current_bstr.truncate(prev_len);

gix-dir/tests/fixtures/many.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,10 @@ EOF
431431
git commit -m "init"
432432
)
433433
)
434+
435+
git init with-sub-repo
436+
(cd with-sub-repo
437+
echo '*' > .gitignore
438+
git add -f .gitignore
439+
git clone ../dir-with-file sub-repo
440+
)

gix-dir/tests/walk/mod.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4434,3 +4434,62 @@ fn one_ignored_submodule() -> crate::Result {
44344434
);
44354435
Ok(())
44364436
}
4437+
4438+
#[test]
4439+
fn ignored_sub_repo() -> crate::Result {
4440+
let root = fixture("with-sub-repo");
4441+
let ((out, _root), entries) = collect(&root, None, |keep, ctx| walk(&root, ctx, options_emit_all(), keep));
4442+
assert_eq!(
4443+
out,
4444+
walk::Outcome {
4445+
read_dir_calls: 1,
4446+
returned_entries: entries.len(),
4447+
seen_entries: 3,
4448+
}
4449+
);
4450+
assert_eq!(
4451+
entries,
4452+
&[
4453+
entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4454+
entry(".gitignore", Tracked, File),
4455+
entry("sub-repo", Ignored(Expendable), Directory),
4456+
],
4457+
"without intent to delete, this looks like just like an untracked directory"
4458+
);
4459+
4460+
for ignored_emission_mode in [Matching, CollapseDirectory] {
4461+
for untracked_emission_mode in [Matching, CollapseDirectory] {
4462+
let ((out, _root), entries) = collect(&root, None, |keep, ctx| {
4463+
walk(
4464+
&root,
4465+
ctx,
4466+
walk::Options {
4467+
for_deletion: Some(ForDeletionMode::IgnoredDirectoriesCanHideNestedRepositories),
4468+
emit_tracked: false,
4469+
emit_ignored: Some(ignored_emission_mode),
4470+
emit_untracked: untracked_emission_mode,
4471+
..options_emit_all()
4472+
},
4473+
keep,
4474+
)
4475+
});
4476+
assert_eq!(
4477+
out,
4478+
walk::Outcome {
4479+
read_dir_calls: 1,
4480+
returned_entries: entries.len(),
4481+
seen_entries: 3,
4482+
}
4483+
);
4484+
assert_eq!(
4485+
entries,
4486+
&[
4487+
entry_nokind(".git", Pruned).with_property(DotGit).with_match(Always),
4488+
entry("sub-repo", Ignored(Expendable), Repository),
4489+
],
4490+
"Even when ignored directories can hide repositories, we are able to detect top-level repositories"
4491+
);
4492+
}
4493+
}
4494+
Ok(())
4495+
}

0 commit comments

Comments
 (0)