Skip to content

Commit

Permalink
Merge branch 'fix-clean'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Jul 25, 2024
2 parents 0e8508a + 1772f88 commit 33eacfb
Show file tree
Hide file tree
Showing 19 changed files with 338 additions and 68 deletions.
1 change: 1 addition & 0 deletions gitoxide-core/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ pub mod status;
pub mod submodule;
pub mod tree;
pub mod verify;
pub mod worktree;
4 changes: 2 additions & 2 deletions gitoxide-core/src/repository/status.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::bail;
use gix::bstr::{BStr, BString};
use gix::bstr::{BStr, BString, ByteSlice};
use gix::status::index_worktree::iter::Item;
use gix_status::index_as_worktree::{Change, Conflict, EntryStatus};
use std::path::Path;
Expand Down Expand Up @@ -152,7 +152,7 @@ pub fn show(
source_rela_path =
gix::path::relativize_with_prefix(&gix::path::from_bstr(source.rela_path()), prefix).display(),
dest_rela_path = gix::path::relativize_with_prefix(
&gix::path::from_bstr(dirwalk_entry.rela_path.as_ref()),
&gix::path::from_bstr(dirwalk_entry.rela_path.as_bstr()),
prefix
)
.display(),
Expand Down
28 changes: 28 additions & 0 deletions gitoxide-core/src/repository/worktree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::OutputFormat;
use anyhow::bail;

pub fn list(repo: gix::Repository, out: &mut dyn std::io::Write, format: OutputFormat) -> anyhow::Result<()> {
if format != OutputFormat::Human {
bail!("JSON output isn't implemented yet");
}

if let Some(worktree) = repo.worktree() {
writeln!(
out,
"{base} [{branch}]",
base = gix::path::realpath(worktree.base())?.display(),
branch = repo
.head_name()?
.map_or("<detached>".into(), |name| name.shorten().to_owned()),
)?;
}
for proxy in repo.worktrees()? {
writeln!(
out,
"{base} [{name}]",
base = proxy.base()?.display(),
name = proxy.id()
)?;
}
Ok(())
}
2 changes: 1 addition & 1 deletion gix-credentials/src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Program {
args.insert_str(0, "credential-");
args.insert_str(0, " ");
args.insert_str(0, git_program.to_string_lossy().as_ref());
gix_command::prepare(gix_path::from_bstr(args.as_ref()).into_owned())
gix_command::prepare(gix_path::from_bstr(args.as_bstr()).into_owned())
.arg(action.as_arg(true))
.with_shell_allow_argument_splitting()
.into()
Expand Down
2 changes: 2 additions & 0 deletions gix-dir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub struct Entry {
/// Further specify what the entry is on disk, similar to a file mode.
pub disk_kind: Option<entry::Kind>,
/// The kind of entry according to the index, if tracked. *Usually* the same as `disk_kind`.
/// Note that even if tracked, this might be `None` which indicates this is a worktree placed
/// within the parent repository.
pub index_kind: Option<entry::Kind>,
/// Indicate how the pathspec matches the entry. See more in [`EntryRef::pathspec_match`].
pub pathspec_match: Option<entry::PathspecMatch>,
Expand Down
102 changes: 63 additions & 39 deletions gix-dir/src/walk/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn root(
worktree_root: &Path,
buf: &mut BString,
worktree_relative_root: &Path,
options: Options,
options: Options<'_>,
ctx: &mut Context<'_>,
) -> Result<(Outcome, bool), Error> {
buf.clear();
Expand Down Expand Up @@ -135,8 +135,9 @@ pub fn path(
for_deletion,
classify_untracked_bare_repositories,
symlinks_to_directories_are_ignored_like_directories,
worktree_relative_worktree_dirs,
..
}: Options,
}: Options<'_>,
ctx: &mut Context<'_>,
) -> Result<Outcome, Error> {
let mut out = Outcome {
Expand Down Expand Up @@ -191,19 +192,25 @@ pub fn path(
);
let mut kind = uptodate_index_kind.or(disk_kind).or_else(on_demand_disk_kind);

// We always check the pathspec to have the value filled in reliably.
out.pathspec_match = ctx
.pathspec
.pattern_matching_relative_path(rela_path.as_bstr(), kind.map(|ft| ft.is_dir()), ctx.pathspec_attributes)
.map(Into::into);

if worktree_relative_worktree_dirs.map_or(false, |worktrees| worktrees.contains(&*rela_path)) {
return Ok(out
.with_kind(Some(entry::Kind::Repository), None)
.with_status(entry::Status::Tracked));
}

let maybe_status = if property.is_none() {
(index_kind.map(|k| k.is_dir()) == kind.map(|k| k.is_dir())).then_some(entry::Status::Tracked)
} else {
out.property = property;
Some(entry::Status::Pruned)
};

// We always check the pathspec to have the value filled in reliably.
out.pathspec_match = ctx
.pathspec
.pattern_matching_relative_path(rela_path.as_bstr(), kind.map(|ft| ft.is_dir()), ctx.pathspec_attributes)
.map(Into::into);

let is_dir = if symlinks_to_directories_are_ignored_like_directories
&& ctx.excludes.is_some()
&& kind.map_or(false, |ft| ft == entry::Kind::Symlink)
Expand All @@ -214,37 +221,14 @@ pub fn path(
};

let mut maybe_upgrade_to_repository = |current_kind, find_harder: bool| {
if recurse_repositories {
return current_kind;
}
if find_harder {
let mut is_nested_repo = gix_discover::is_git(path).is_ok();
if is_nested_repo {
let git_dir_is_our_own =
gix_path::realpath_opts(path, ctx.current_dir, gix_path::realpath::MAX_SYMLINKS)
.ok()
.map_or(false, |realpath_candidate| realpath_candidate == ctx.git_dir_realpath);
is_nested_repo = !git_dir_is_our_own;
}
if is_nested_repo {
return Some(entry::Kind::Repository);
}
}
path.push(gix_discover::DOT_GIT_DIR);
let mut is_nested_nonbare_repo = gix_discover::is_git(path).is_ok();
if is_nested_nonbare_repo {
let git_dir_is_our_own = gix_path::realpath_opts(path, ctx.current_dir, gix_path::realpath::MAX_SYMLINKS)
.ok()
.map_or(false, |realpath_candidate| realpath_candidate == ctx.git_dir_realpath);
is_nested_nonbare_repo = !git_dir_is_our_own;
}
path.pop();

if is_nested_nonbare_repo {
Some(entry::Kind::Repository)
} else {
current_kind
}
maybe_upgrade_to_repository(
current_kind,
find_harder,
recurse_repositories,
path,
ctx.current_dir,
ctx.git_dir_realpath,
)
};
if let Some(status) = maybe_status {
if kind == Some(entry::Kind::Directory) && index_kind == Some(entry::Kind::Repository) {
Expand Down Expand Up @@ -302,6 +286,46 @@ pub fn path(
Ok(out.with_status(status).with_kind(kind, index_kind))
}

pub fn maybe_upgrade_to_repository(
current_kind: Option<entry::Kind>,
find_harder: bool,
recurse_repositories: bool,
path: &mut PathBuf,
current_dir: &Path,
git_dir_realpath: &Path,
) -> Option<entry::Kind> {
if recurse_repositories {
return current_kind;
}
if find_harder {
let mut is_nested_repo = gix_discover::is_git(path).is_ok();
if is_nested_repo {
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
.ok()
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
is_nested_repo = !git_dir_is_our_own;
}
if is_nested_repo {
return Some(entry::Kind::Repository);
}
}
path.push(gix_discover::DOT_GIT_DIR);
let mut is_nested_nonbare_repo = gix_discover::is_git(path).is_ok();
if is_nested_nonbare_repo {
let git_dir_is_our_own = gix_path::realpath_opts(path, current_dir, gix_path::realpath::MAX_SYMLINKS)
.ok()
.map_or(false, |realpath_candidate| realpath_candidate == git_dir_realpath);
is_nested_nonbare_repo = !git_dir_is_our_own;
}
path.pop();

if is_nested_nonbare_repo {
Some(entry::Kind::Repository)
} else {
current_kind
}
}

/// Note that `rela_path` is used as buffer for convenience, but will be left as is when this function returns.
/// 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.
/// Returns `(maybe_file_type, Option<index_file_type>, flags)`, with the last option being a flag set only for sparse directories in the index.
Expand Down
4 changes: 2 additions & 2 deletions gix-dir/src/walk/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::{entry, EntryRef};
pub fn walk(
worktree_root: &Path,
mut ctx: Context<'_>,
options: Options,
options: Options<'_>,
delegate: &mut dyn Delegate,
) -> Result<(Outcome, PathBuf), Error> {
let root = match ctx.explicit_traversal_root {
Expand Down Expand Up @@ -182,7 +182,7 @@ pub(super) fn emit_entry(
emit_ignored,
emit_empty_directories,
..
}: Options,
}: Options<'_>,
out: &mut Outcome,
delegate: &mut dyn Delegate,
) -> Action {
Expand Down
14 changes: 10 additions & 4 deletions gix-dir/src/walk/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{entry, EntryRef};
use bstr::BStr;
use bstr::{BStr, BString};
use std::collections::BTreeSet;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;

Expand Down Expand Up @@ -143,12 +144,12 @@ pub enum ForDeletionMode {

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

/// All information that is required to perform a dirwalk, and classify paths properly.
Expand Down
43 changes: 30 additions & 13 deletions gix-dir/src/walk/readdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use std::sync::atomic::Ordering;
use crate::entry::{PathspecMatch, Status};
use crate::walk::function::{can_recurse, emit_entry};
use crate::walk::EmissionMode::CollapseDirectory;
use crate::walk::{classify, Action, CollapsedEntriesEmissionMode, Context, Delegate, Error, Options, Outcome};
use crate::walk::{
classify, Action, CollapsedEntriesEmissionMode, Context, Delegate, Error, ForDeletionMode, Options, Outcome,
};
use crate::{entry, walk, Entry, EntryRef};

/// ### Deviation
Expand All @@ -19,7 +21,7 @@ pub(super) fn recursive(
current_bstr: &mut BString,
current_info: classify::Outcome,
ctx: &mut Context<'_>,
opts: Options,
opts: Options<'_>,
delegate: &mut dyn Delegate,
out: &mut Outcome,
state: &mut State,
Expand Down Expand Up @@ -57,7 +59,7 @@ pub(super) fn recursive(
);
current.push(file_name);

let info = classify::path(
let mut info = classify::path(
current,
current_bstr,
if prev_len == 0 { 0 } else { prev_len + 1 },
Expand Down Expand Up @@ -90,10 +92,25 @@ pub(super) fn recursive(
if action != Action::Continue {
return Ok((action, prevent_collapse));
}
} else if !state.held_for_directory_collapse(current_bstr.as_bstr(), info, &opts) {
let action = emit_entry(Cow::Borrowed(current_bstr.as_bstr()), info, None, opts, out, delegate);
if action != Action::Continue {
return Ok((action, prevent_collapse));
} else {
if opts.for_deletion == Some(ForDeletionMode::IgnoredDirectoriesCanHideNestedRepositories)
&& info.disk_kind == Some(entry::Kind::Directory)
&& matches!(info.status, Status::Ignored(_))
{
info.disk_kind = classify::maybe_upgrade_to_repository(
info.disk_kind,
true,
false,
current,
ctx.current_dir,
ctx.git_dir_realpath,
);
}
if !state.held_for_directory_collapse(current_bstr.as_bstr(), info, &opts) {
let action = emit_entry(Cow::Borrowed(current_bstr.as_bstr()), info, None, opts, out, delegate);
if action != Action::Continue {
return Ok((action, prevent_collapse));
}
}
}
current_bstr.truncate(prev_len);
Expand Down Expand Up @@ -124,7 +141,7 @@ pub(super) struct State {

impl State {
/// Hold the entry with the given `status` if it's a candidate for collapsing the containing directory.
fn held_for_directory_collapse(&mut self, rela_path: &BStr, info: classify::Outcome, opts: &Options) -> bool {
fn held_for_directory_collapse(&mut self, rela_path: &BStr, info: classify::Outcome, opts: &Options<'_>) -> bool {
if opts.should_hold(info.status) {
self.on_hold
.push(EntryRef::from_outcome(Cow::Borrowed(rela_path), info).into_owned());
Expand Down Expand Up @@ -169,7 +186,7 @@ impl State {
pub(super) fn emit_remaining(
&mut self,
may_collapse: bool,
opts: Options,
opts: Options<'_>,
out: &mut walk::Outcome,
delegate: &mut dyn walk::Delegate,
) {
Expand Down Expand Up @@ -200,7 +217,7 @@ impl Mark {
dir_path: &Path,
dir_rela_path: &BStr,
dir_info: classify::Outcome,
opts: Options,
opts: Options<'_>,
out: &mut walk::Outcome,
ctx: &mut Context<'_>,
delegate: &mut dyn walk::Delegate,
Expand Down Expand Up @@ -249,7 +266,7 @@ impl Mark {
fn emit_all_held(
&mut self,
state: &mut State,
opts: Options,
opts: Options<'_>,
out: &mut walk::Outcome,
delegate: &mut dyn walk::Delegate,
) -> Action {
Expand All @@ -270,7 +287,7 @@ impl Mark {
dir_info: classify::Outcome,
state: &mut State,
out: &mut walk::Outcome,
opts: Options,
opts: Options<'_>,
ctx: &mut Context<'_>,
delegate: &mut dyn walk::Delegate,
) -> Option<Action> {
Expand Down Expand Up @@ -391,7 +408,7 @@ fn filter_dir_pathspec(current: Option<PathspecMatch>) -> Option<PathspecMatch>
})
}

impl Options {
impl Options<'_> {
fn should_hold(&self, status: entry::Status) -> bool {
if status.is_pruned() {
return false;
Expand Down
13 changes: 13 additions & 0 deletions gix-dir/tests/fixtures/many.sh
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,16 @@ EOF
git commit -m "init"
)
)

git init with-sub-repo
(cd with-sub-repo
echo '*' > .gitignore
git add -f .gitignore
git clone ../dir-with-file sub-repo
)

git clone dir-with-tracked-file in-repo-worktree
(cd in-repo-worktree
git worktree add worktree
git worktree add -b other-worktree dir/worktree
)
Loading

0 comments on commit 33eacfb

Please sign in to comment.