Skip to content

Commit 33eacfb

Browse files
committed
Merge branch 'fix-clean'
2 parents 0e8508a + 1772f88 commit 33eacfb

File tree

19 files changed

+338
-68
lines changed

19 files changed

+338
-68
lines changed

gitoxide-core/src/repository/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ pub mod status;
5151
pub mod submodule;
5252
pub mod tree;
5353
pub mod verify;
54+
pub mod worktree;

gitoxide-core/src/repository/status.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::bail;
2-
use gix::bstr::{BStr, BString};
2+
use gix::bstr::{BStr, BString, ByteSlice};
33
use gix::status::index_worktree::iter::Item;
44
use gix_status::index_as_worktree::{Change, Conflict, EntryStatus};
55
use std::path::Path;
@@ -152,7 +152,7 @@ pub fn show(
152152
source_rela_path =
153153
gix::path::relativize_with_prefix(&gix::path::from_bstr(source.rela_path()), prefix).display(),
154154
dest_rela_path = gix::path::relativize_with_prefix(
155-
&gix::path::from_bstr(dirwalk_entry.rela_path.as_ref()),
155+
&gix::path::from_bstr(dirwalk_entry.rela_path.as_bstr()),
156156
prefix
157157
)
158158
.display(),
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::OutputFormat;
2+
use anyhow::bail;
3+
4+
pub fn list(repo: gix::Repository, out: &mut dyn std::io::Write, format: OutputFormat) -> anyhow::Result<()> {
5+
if format != OutputFormat::Human {
6+
bail!("JSON output isn't implemented yet");
7+
}
8+
9+
if let Some(worktree) = repo.worktree() {
10+
writeln!(
11+
out,
12+
"{base} [{branch}]",
13+
base = gix::path::realpath(worktree.base())?.display(),
14+
branch = repo
15+
.head_name()?
16+
.map_or("<detached>".into(), |name| name.shorten().to_owned()),
17+
)?;
18+
}
19+
for proxy in repo.worktrees()? {
20+
writeln!(
21+
out,
22+
"{base} [{name}]",
23+
base = proxy.base()?.display(),
24+
name = proxy.id()
25+
)?;
26+
}
27+
Ok(())
28+
}

gix-credentials/src/program/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl Program {
8080
args.insert_str(0, "credential-");
8181
args.insert_str(0, " ");
8282
args.insert_str(0, git_program.to_string_lossy().as_ref());
83-
gix_command::prepare(gix_path::from_bstr(args.as_ref()).into_owned())
83+
gix_command::prepare(gix_path::from_bstr(args.as_bstr()).into_owned())
8484
.arg(action.as_arg(true))
8585
.with_shell_allow_argument_splitting()
8686
.into()

gix-dir/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub struct Entry {
5353
/// Further specify what the entry is on disk, similar to a file mode.
5454
pub disk_kind: Option<entry::Kind>,
5555
/// The kind of entry according to the index, if tracked. *Usually* the same as `disk_kind`.
56+
/// Note that even if tracked, this might be `None` which indicates this is a worktree placed
57+
/// within the parent repository.
5658
pub index_kind: Option<entry::Kind>,
5759
/// Indicate how the pathspec matches the entry. See more in [`EntryRef::pathspec_match`].
5860
pub pathspec_match: Option<entry::PathspecMatch>,

gix-dir/src/walk/classify.rs

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub fn root(
1313
worktree_root: &Path,
1414
buf: &mut BString,
1515
worktree_relative_root: &Path,
16-
options: Options,
16+
options: Options<'_>,
1717
ctx: &mut Context<'_>,
1818
) -> Result<(Outcome, bool), Error> {
1919
buf.clear();
@@ -135,8 +135,9 @@ pub fn path(
135135
for_deletion,
136136
classify_untracked_bare_repositories,
137137
symlinks_to_directories_are_ignored_like_directories,
138+
worktree_relative_worktree_dirs,
138139
..
139-
}: Options,
140+
}: Options<'_>,
140141
ctx: &mut Context<'_>,
141142
) -> Result<Outcome, Error> {
142143
let mut out = Outcome {
@@ -191,19 +192,25 @@ pub fn path(
191192
);
192193
let mut kind = uptodate_index_kind.or(disk_kind).or_else(on_demand_disk_kind);
193194

195+
// We always check the pathspec to have the value filled in reliably.
196+
out.pathspec_match = ctx
197+
.pathspec
198+
.pattern_matching_relative_path(rela_path.as_bstr(), kind.map(|ft| ft.is_dir()), ctx.pathspec_attributes)
199+
.map(Into::into);
200+
201+
if worktree_relative_worktree_dirs.map_or(false, |worktrees| worktrees.contains(&*rela_path)) {
202+
return Ok(out
203+
.with_kind(Some(entry::Kind::Repository), None)
204+
.with_status(entry::Status::Tracked));
205+
}
206+
194207
let maybe_status = if property.is_none() {
195208
(index_kind.map(|k| k.is_dir()) == kind.map(|k| k.is_dir())).then_some(entry::Status::Tracked)
196209
} else {
197210
out.property = property;
198211
Some(entry::Status::Pruned)
199212
};
200213

201-
// We always check the pathspec to have the value filled in reliably.
202-
out.pathspec_match = ctx
203-
.pathspec
204-
.pattern_matching_relative_path(rela_path.as_bstr(), kind.map(|ft| ft.is_dir()), ctx.pathspec_attributes)
205-
.map(Into::into);
206-
207214
let is_dir = if symlinks_to_directories_are_ignored_like_directories
208215
&& ctx.excludes.is_some()
209216
&& kind.map_or(false, |ft| ft == entry::Kind::Symlink)
@@ -214,37 +221,14 @@ pub fn path(
214221
};
215222

216223
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-
}
224+
maybe_upgrade_to_repository(
225+
current_kind,
226+
find_harder,
227+
recurse_repositories,
228+
path,
229+
ctx.current_dir,
230+
ctx.git_dir_realpath,
231+
)
248232
};
249233
if let Some(status) = maybe_status {
250234
if kind == Some(entry::Kind::Directory) && index_kind == Some(entry::Kind::Repository) {
@@ -302,6 +286,46 @@ pub fn path(
302286
Ok(out.with_status(status).with_kind(kind, index_kind))
303287
}
304288

289+
pub fn maybe_upgrade_to_repository(
290+
current_kind: Option<entry::Kind>,
291+
find_harder: bool,
292+
recurse_repositories: bool,
293+
path: &mut PathBuf,
294+
current_dir: &Path,
295+
git_dir_realpath: &Path,
296+
) -> Option<entry::Kind> {
297+
if recurse_repositories {
298+
return current_kind;
299+
}
300+
if find_harder {
301+
let mut is_nested_repo = gix_discover::is_git(path).is_ok();
302+
if is_nested_repo {
303+
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
304+
.ok()
305+
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
306+
is_nested_repo = !git_dir_is_our_own;
307+
}
308+
if is_nested_repo {
309+
return Some(entry::Kind::Repository);
310+
}
311+
}
312+
path.push(gix_discover::DOT_GIT_DIR);
313+
let mut is_nested_nonbare_repo = gix_discover::is_git(path).is_ok();
314+
if is_nested_nonbare_repo {
315+
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
316+
.ok()
317+
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
318+
is_nested_nonbare_repo = !git_dir_is_our_own;
319+
}
320+
path.pop();
321+
322+
if is_nested_nonbare_repo {
323+
Some(entry::Kind::Repository)
324+
} else {
325+
current_kind
326+
}
327+
}
328+
305329
/// Note that `rela_path` is used as buffer for convenience, but will be left as is when this function returns.
306330
/// 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.
307331
/// 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/function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::{entry, EntryRef};
4242
pub fn walk(
4343
worktree_root: &Path,
4444
mut ctx: Context<'_>,
45-
options: Options,
45+
options: Options<'_>,
4646
delegate: &mut dyn Delegate,
4747
) -> Result<(Outcome, PathBuf), Error> {
4848
let root = match ctx.explicit_traversal_root {
@@ -182,7 +182,7 @@ pub(super) fn emit_entry(
182182
emit_ignored,
183183
emit_empty_directories,
184184
..
185-
}: Options,
185+
}: Options<'_>,
186186
out: &mut Outcome,
187187
delegate: &mut dyn Delegate,
188188
) -> Action {

gix-dir/src/walk/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{entry, EntryRef};
2-
use bstr::BStr;
2+
use bstr::{BStr, BString};
3+
use std::collections::BTreeSet;
34
use std::path::PathBuf;
45
use std::sync::atomic::AtomicBool;
56

@@ -143,12 +144,12 @@ pub enum ForDeletionMode {
143144

144145
/// Options for use in [`walk()`](function::walk()) function.
145146
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
146-
pub struct Options {
147-
/// If true, the filesystem will store paths as decomposed unicode, i.e. `ä` becomes `"a\u{308}"`, which means that
147+
pub struct Options<'a> {
148+
/// If `true`, the filesystem will store paths as decomposed unicode, i.e. `ä` becomes `"a\u{308}"`, which means that
148149
/// we have to turn these forms back from decomposed to precomposed unicode before storing it in the index or generally
149150
/// using it. This also applies to input received from the command-line, so callers may have to be aware of this and
150151
/// perform conversions accordingly.
151-
/// If false, no conversions will be performed.
152+
/// If `false`, no conversions will be performed.
152153
pub precompose_unicode: bool,
153154
/// If true, the filesystem ignores the case of input, which makes `A` the same file as `a`.
154155
/// This is also called case-folding.
@@ -192,6 +193,11 @@ pub struct Options {
192193
///
193194
/// In other words, for Git compatibility this flag should be `false`, the default, for `git2` compatibility it should be `true`.
194195
pub symlinks_to_directories_are_ignored_like_directories: bool,
196+
/// A set of all git worktree checkouts that are located within the main worktree directory.
197+
///
198+
/// They will automatically be detected as 'tracked', but without providing index information (as there is no actual index entry).
199+
/// Note that the unicode composition must match the `precompose_unicode` field so that paths will match verbatim.
200+
pub worktree_relative_worktree_dirs: Option<&'a BTreeSet<BString>>,
195201
}
196202

197203
/// All information that is required to perform a dirwalk, and classify paths properly.

gix-dir/src/walk/readdir.rs

Lines changed: 30 additions & 13 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
@@ -19,7 +21,7 @@ pub(super) fn recursive(
1921
current_bstr: &mut BString,
2022
current_info: classify::Outcome,
2123
ctx: &mut Context<'_>,
22-
opts: Options,
24+
opts: Options<'_>,
2325
delegate: &mut dyn Delegate,
2426
out: &mut Outcome,
2527
state: &mut State,
@@ -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);
@@ -124,7 +141,7 @@ pub(super) struct State {
124141

125142
impl State {
126143
/// Hold the entry with the given `status` if it's a candidate for collapsing the containing directory.
127-
fn held_for_directory_collapse(&mut self, rela_path: &BStr, info: classify::Outcome, opts: &Options) -> bool {
144+
fn held_for_directory_collapse(&mut self, rela_path: &BStr, info: classify::Outcome, opts: &Options<'_>) -> bool {
128145
if opts.should_hold(info.status) {
129146
self.on_hold
130147
.push(EntryRef::from_outcome(Cow::Borrowed(rela_path), info).into_owned());
@@ -169,7 +186,7 @@ impl State {
169186
pub(super) fn emit_remaining(
170187
&mut self,
171188
may_collapse: bool,
172-
opts: Options,
189+
opts: Options<'_>,
173190
out: &mut walk::Outcome,
174191
delegate: &mut dyn walk::Delegate,
175192
) {
@@ -200,7 +217,7 @@ impl Mark {
200217
dir_path: &Path,
201218
dir_rela_path: &BStr,
202219
dir_info: classify::Outcome,
203-
opts: Options,
220+
opts: Options<'_>,
204221
out: &mut walk::Outcome,
205222
ctx: &mut Context<'_>,
206223
delegate: &mut dyn walk::Delegate,
@@ -249,7 +266,7 @@ impl Mark {
249266
fn emit_all_held(
250267
&mut self,
251268
state: &mut State,
252-
opts: Options,
269+
opts: Options<'_>,
253270
out: &mut walk::Outcome,
254271
delegate: &mut dyn walk::Delegate,
255272
) -> Action {
@@ -270,7 +287,7 @@ impl Mark {
270287
dir_info: classify::Outcome,
271288
state: &mut State,
272289
out: &mut walk::Outcome,
273-
opts: Options,
290+
opts: Options<'_>,
274291
ctx: &mut Context<'_>,
275292
delegate: &mut dyn walk::Delegate,
276293
) -> Option<Action> {
@@ -391,7 +408,7 @@ fn filter_dir_pathspec(current: Option<PathspecMatch>) -> Option<PathspecMatch>
391408
})
392409
}
393410

394-
impl Options {
411+
impl Options<'_> {
395412
fn should_hold(&self, status: entry::Status) -> bool {
396413
if status.is_pruned() {
397414
return false;

gix-dir/tests/fixtures/many.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,16 @@ 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+
)
441+
442+
git clone dir-with-tracked-file in-repo-worktree
443+
(cd in-repo-worktree
444+
git worktree add worktree
445+
git worktree add -b other-worktree dir/worktree
446+
)

0 commit comments

Comments
 (0)