diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ce6e9e5b7f..e2901d1a930 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,18 +47,18 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: Setup dependencies - run: - sudo apt-get install tree - - uses: extractions/setup-just@v1 - - name: test - env: - CI: true - GITOXIDE_TEST_IGNORE_ARCHIVES: 1 - run: just ci-test + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Setup dependencies + run: + sudo apt-get install tree + - uses: extractions/setup-just@v1 + - name: test + env: + CI: true + GITOXIDE_TEST_IGNORE_ARCHIVES: 1 + run: just ci-test test-fast: strategy: @@ -163,7 +163,7 @@ jobs: components: clippy,rustfmt - uses: extractions/setup-just@v1 - name: Run cargo clippy - run: just clippy -D warnings + run: just clippy -D warnings -A unknown-lints - name: Run cargo doc run: just doc - name: Run cargo fmt @@ -189,10 +189,10 @@ jobs: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check ${{ matrix.checks }} + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check ${{ matrix.checks }} wasm: name: WebAssembly runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index d2980c3613c..3d75fb71210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1351,6 +1351,7 @@ dependencies = [ "is_ci", "once_cell", "parking_lot", + "pretty_assertions", "prodash 28.0.0", "regex", "reqwest", @@ -1671,6 +1672,7 @@ dependencies = [ "gix-testtools", "gix-traverse 0.37.0", "gix-worktree 0.31.0", + "pretty_assertions", "shell-words", ] @@ -2510,7 +2512,10 @@ name = "gix-status" version = "0.6.0" dependencies = [ "bstr", + "document-features", "filetime", + "gix-diff", + "gix-dir", "gix-features 0.38.0", "gix-filter", "gix-fs 0.10.0", @@ -2529,15 +2534,21 @@ version = "0.0.0" dependencies = [ "bstr", "filetime", + "gix-diff", + "gix-dir", "gix-features 0.38.0", + "gix-filter", "gix-fs 0.10.0", "gix-hash 0.14.1", "gix-index 0.30.0", "gix-object 0.41.1", + "gix-odb", + "gix-path 0.10.6", "gix-pathspec", "gix-status", "gix-testtools", "gix-worktree 0.31.0", + "pretty_assertions", ] [[package]] diff --git a/crate-status.md b/crate-status.md index 6a43168f4e7..00a8d8e13d0 100644 --- a/crate-status.md +++ b/crate-status.md @@ -552,12 +552,12 @@ Make it the best-performing implementation and the most convenient one. ### gix-status * [x] differences between index and worktree to turn index into worktree - - [ ] rename tracking + - [x] rename tracking + - [x] untracked files + - [ ] support for fs-monitor for modification checks * [ ] differences between index and index to learn what changed - [ ] rename tracking -* [ ] untracked files -* [ ] fast answer to 'is it dirty'. -* + ### gix-worktree-state * handle the working **tree/checkout** - [x] checkout an index of files, executables and symlinks just as fast as git diff --git a/gitoxide-core/src/repository/attributes/query.rs b/gitoxide-core/src/repository/attributes/query.rs index 09d1b0a0b48..b3443cdec95 100644 --- a/gitoxide-core/src/repository/attributes/query.rs +++ b/gitoxide-core/src/repository/attributes/query.rs @@ -1,4 +1,4 @@ -use gix::repository::IndexPersistedOrInMemory; +use gix::worktree::IndexPersistedOrInMemory; use crate::OutputFormat; diff --git a/gitoxide-core/src/repository/commit.rs b/gitoxide-core/src/repository/commit.rs index 17ce99e5f3c..e0070dc2ae3 100644 --- a/gitoxide-core/src/repository/commit.rs +++ b/gitoxide-core/src/repository/commit.rs @@ -52,6 +52,7 @@ pub fn describe( statistics, max_candidates, long_format, + dirty_suffix, }: describe::Options, ) -> Result<()> { repo.object_cache_size_if_unset(4 * 1024 * 1024); @@ -80,7 +81,7 @@ pub fn describe( writeln!(err, "traversed {} commits", resolution.outcome.commits_seen)?; } - let mut describe_id = resolution.format()?; + let mut describe_id = resolution.format_with_dirty_suffix(dirty_suffix)?; describe_id.long(long_format); writeln!(out, "{describe_id}")?; @@ -97,5 +98,6 @@ pub mod describe { pub long_format: bool, pub statistics: bool, pub max_candidates: usize, + pub dirty_suffix: Option, } } diff --git a/gitoxide-core/src/repository/credential.rs b/gitoxide-core/src/repository/credential.rs index b9b93b9d057..c901f06f2ba 100644 --- a/gitoxide-core/src/repository/credential.rs +++ b/gitoxide-core/src/repository/credential.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - #[derive(Debug, thiserror::Error)] enum Error { #[error(transparent)] diff --git a/gitoxide-core/src/repository/dirty.rs b/gitoxide-core/src/repository/dirty.rs new file mode 100644 index 00000000000..8981199d532 --- /dev/null +++ b/gitoxide-core/src/repository/dirty.rs @@ -0,0 +1,32 @@ +use crate::OutputFormat; +use anyhow::bail; + +pub enum Mode { + IsClean, + IsDirty, +} + +pub fn check( + repo: gix::Repository, + mode: Mode, + out: &mut dyn std::io::Write, + format: OutputFormat, +) -> anyhow::Result<()> { + if format != OutputFormat::Human { + bail!("JSON output isn't implemented yet"); + } + let is_dirty = repo.is_dirty()?; + let res = match (is_dirty, mode) { + (false, Mode::IsClean) => Ok("The repository is clean"), + (true, Mode::IsClean) => Err("The repository has changes"), + (false, Mode::IsDirty) => Err("The repository is clean"), + (true, Mode::IsDirty) => Ok("The repository has changes"), + }; + + let suffix = "(not counting untracked files)"; + match res { + Ok(msg) => writeln!(out, "{msg} {suffix}")?, + Err(msg) => bail!("{msg} {suffix}"), + } + Ok(()) +} diff --git a/gitoxide-core/src/repository/index/entries.rs b/gitoxide-core/src/repository/index/entries.rs index b4f335e6ae3..7126849ad4d 100644 --- a/gitoxide-core/src/repository/index/entries.rs +++ b/gitoxide-core/src/repository/index/entries.rs @@ -25,7 +25,7 @@ pub(crate) mod function { use gix::{ bstr::{BStr, BString}, - repository::IndexPersistedOrInMemory, + worktree::IndexPersistedOrInMemory, Repository, }; diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index 35d0c156a95..55816dcb774 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -26,6 +26,7 @@ pub use credential::function as credential; pub mod attributes; #[cfg(feature = "clean")] pub mod clean; +pub mod dirty; #[cfg(feature = "clean")] pub use clean::function::clean; #[cfg(feature = "blocking-client")] diff --git a/gitoxide-core/src/repository/status.rs b/gitoxide-core/src/repository/status.rs index ee36b1b1f3f..d3180f9c251 100644 --- a/gitoxide-core/src/repository/status.rs +++ b/gitoxide-core/src/repository/status.rs @@ -1,12 +1,8 @@ -use anyhow::{bail, Context}; -use gix::bstr::ByteSlice; -use gix::{ - bstr::{BStr, BString}, - index::Entry, - Progress, -}; -use gix_status::index_as_worktree::{traits::FastEq, Change, Conflict, EntryStatus}; -use std::path::{Path, PathBuf}; +use anyhow::bail; +use gix::bstr::{BStr, BString}; +use gix::status::index_worktree::iter::Item; +use gix_status::index_as_worktree::{Change, Conflict, EntryStatus}; +use std::path::Path; use crate::OutputFormat; @@ -17,226 +13,196 @@ pub enum Submodules { RefChange, /// See if there are worktree modifications compared to the index, but do not check for untracked files. Modifications, + /// Ignore all submodule changes. + None, +} + +#[derive(Copy, Clone)] +pub enum Ignored { + Collapsed, + Matching, +} + +#[derive(Copy, Clone)] +pub enum Format { + Simplified, + PorcelainV2, } pub struct Options { - pub format: OutputFormat, - pub submodules: Submodules, + pub ignored: Option, + pub format: Format, + pub output_format: OutputFormat, + pub submodules: Option, pub thread_limit: Option, pub statistics: bool, pub allow_write: bool, + pub index_worktree_renames: Option, } pub fn show( repo: gix::Repository, pathspecs: Vec, - out: impl std::io::Write, + mut out: impl std::io::Write, mut err: impl std::io::Write, - mut progress: impl gix::NestedProgress, + mut progress: impl gix::NestedProgress + 'static, Options { + ignored, format, - // TODO: implement this - submodules: _, + output_format, + submodules, thread_limit, allow_write, statistics, + index_worktree_renames, }: Options, ) -> anyhow::Result<()> { - if format != OutputFormat::Human { + if output_format != OutputFormat::Human { bail!("Only human format is supported right now"); } - let mut index = repo.index_or_empty()?; - let index = gix::threading::make_mut(&mut index); - let mut progress = progress.add_child("traverse index"); - let start = std::time::Instant::now(); - let stack = repo - .attributes_only( - index, - gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping, - )? - .detach(); - let pathspec = gix::Pathspec::new(&repo, false, pathspecs.iter().map(|p| p.as_bstr()), true, || { - Ok(stack.clone()) - })?; - let options = gix_status::index_as_worktree::Options { - fs: repo.filesystem_options()?, - thread_limit, - stat: repo.stat_options()?, - }; - let prefix = repo.prefix()?.unwrap_or(Path::new("")); - let mut printer = Printer { - out, - changes: Vec::new(), - prefix: prefix.to_owned(), - }; - let filter_pipeline = repo - .filter_pipeline(Some(gix::hash::ObjectId::empty_tree(repo.object_hash())))? - .0 - .into_parts() - .0; - let ctx = gix_status::index_as_worktree::Context { - pathspec: pathspec.into_parts().0, - stack, - filter: filter_pipeline, - should_interrupt: &gix::interrupt::IS_INTERRUPTED, - }; - let mut collect = gix::dir::walk::delegate::Collect::default(); - let (outcome, walk_outcome) = gix::features::parallel::threads(|scope| -> anyhow::Result<_> { - // TODO: it's either this, or not running both in parallel and setting UPTODATE flags whereever - // there is no modification. This can save disk queries as dirwalk can then trust what's in - // the index regarding the type. - // NOTE: collect here as rename-tracking needs that anyway. - let walk_outcome = gix::features::parallel::build_thread() - .name("gix status::dirwalk".into()) - .spawn_scoped(scope, { - let repo = repo.clone().into_sync(); - let index = &index; - let collect = &mut collect; - move || -> anyhow::Result<_> { - let repo = repo.to_thread_local(); - let outcome = repo.dirwalk( - index, - pathspecs, - repo.dirwalk_options()? - .emit_untracked(gix::dir::walk::EmissionMode::CollapseDirectory), - collect, - )?; - Ok(outcome.dirwalk) - } - })?; - - let outcome = gix_status::index_as_worktree( - index, - repo.work_dir() - .context("This operation cannot be run on a bare repository")?, - &mut printer, - FastEq, - Submodule, - repo.objects.clone().into_arc()?, - &mut progress, - ctx, - options, - )?; - - let walk_outcome = walk_outcome.join().expect("no panic")?; - Ok((outcome, walk_outcome)) - })?; - - for entry in collect - .into_entries_by_path() - .into_iter() - .filter_map(|(entry, dir_status)| dir_status.is_none().then_some(entry)) - { - writeln!( - printer.out, - "{status: >3} {rela_path}", - status = "?", - rela_path = gix::path::relativize_with_prefix(&gix::path::from_bstr(entry.rela_path), prefix).display() - )?; + if !matches!(format, Format::Simplified) { + bail!("Only the simplified format is currently implemented"); } - if outcome.entries_to_update != 0 && allow_write { - { - let entries = index.entries_mut(); - for (entry_index, change) in printer.changes { - let entry = &mut entries[entry_index]; - match change { - ApplyChange::SetSizeToZero => { - entry.stat.size = 0; - } - ApplyChange::NewStat(new_stat) => { - entry.stat = new_stat; + let start = std::time::Instant::now(); + let prefix = repo.prefix()?.unwrap_or(Path::new("")); + let index_progress = progress.add_child("traverse index"); + let mut iter = repo + .status(index_progress)? + .should_interrupt_shared(&gix::interrupt::IS_INTERRUPTED) + .index_worktree_options_mut(|opts| { + if let Some((opts, ignored)) = opts.dirwalk_options.as_mut().zip(ignored) { + opts.set_emit_ignored(Some(match ignored { + Ignored::Collapsed => gix::dir::walk::EmissionMode::CollapseDirectory, + Ignored::Matching => gix::dir::walk::EmissionMode::Matching, + })); + } + opts.rewrites = index_worktree_renames.map(|percentage| gix::diff::Rewrites { + copies: None, + percentage: Some(percentage), + limit: 0, + }); + if opts.rewrites.is_some() { + if let Some(opts) = opts.dirwalk_options.as_mut() { + opts.set_emit_untracked(gix::dir::walk::EmissionMode::Matching); + if ignored.is_some() { + opts.set_emit_ignored(Some(gix::dir::walk::EmissionMode::Matching)); } } } + opts.thread_limit = thread_limit; + opts.sorting = Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive); + }) + .index_worktree_submodules(match submodules { + Some(mode) => { + let ignore = match mode { + Submodules::All => gix::submodule::config::Ignore::None, + Submodules::RefChange => gix::submodule::config::Ignore::Dirty, + Submodules::Modifications => gix::submodule::config::Ignore::Untracked, + Submodules::None => gix::submodule::config::Ignore::All, + }; + gix::status::Submodule::Given { + ignore, + check_dirty: false, + } + } + None => gix::status::Submodule::AsConfigured { check_dirty: false }, + }) + .into_index_worktree_iter(pathspecs)?; + + for item in iter.by_ref() { + let item = item?; + match item { + Item::Modification { + entry: _, + entry_index: _, + rela_path, + status, + } => print_index_entry_status(&mut out, prefix, rela_path.as_ref(), status)?, + Item::DirectoryContents { + entry, + collapsed_directory_status, + } => { + if collapsed_directory_status.is_none() { + writeln!( + out, + "{status: >3} {rela_path}{slash}", + status = "?", + rela_path = + gix::path::relativize_with_prefix(&gix::path::from_bstr(entry.rela_path), prefix).display(), + slash = if entry.disk_kind.unwrap_or(gix::dir::entry::Kind::File).is_dir() { + "/" + } else { + "" + } + )?; + } + } + Item::Rewrite { + source, + dirwalk_entry, + copy: _, // TODO: how to visualize copies? + .. + } => { + // TODO: handle multi-status characters, there can also be modifications at the same time as determined by their ID and potentially diffstats. + writeln!( + out, + "{status: >3} {source_rela_path} → {dest_rela_path}", + status = "R", + 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()), + prefix + ) + .display(), + )?; + } } - index.write(gix::index::write::Options { - extensions: Default::default(), - skip_hash: false, // TODO: make this based on configuration - })?; } - - if statistics { - writeln!(err, "{outcome:#?}").ok(); - writeln!(err, "{walk_outcome:#?}").ok(); + if gix::interrupt::is_triggered() { + bail!("interrupted by user"); } - writeln!(err, "\nhead -> index and untracked files aren't implemented yet")?; - progress.show_throughput(start); - Ok(()) -} - -#[derive(Clone)] -struct Submodule; - -impl gix_status::index_as_worktree::traits::SubmoduleStatus for Submodule { - type Output = (); - type Error = std::convert::Infallible; + let out = iter.outcome_mut().expect("successful iteration has outcome"); - fn status(&mut self, _entry: &Entry, _rela_path: &BStr) -> Result, Self::Error> { - Ok(None) + if out.has_changes() && allow_write { + out.write_changes().transpose()?; } -} - -struct Printer { - out: W, - changes: Vec<(usize, ApplyChange)>, - prefix: PathBuf, -} - -enum ApplyChange { - SetSizeToZero, - NewStat(gix::index::entry::Stat), -} - -impl<'index, W> gix_status::index_as_worktree::VisitEntry<'index> for Printer -where - W: std::io::Write, -{ - type ContentChange = (); - type SubmoduleStatus = (); - fn visit_entry( - &mut self, - _entries: &'index [Entry], - _entry: &'index Entry, - entry_index: usize, - rela_path: &'index BStr, - status: EntryStatus, - ) { - self.visit_inner(entry_index, rela_path, status).ok(); + if statistics { + writeln!(err, "{outcome:#?}", outcome = out.index_worktree).ok(); } + + writeln!(err, "\nhead -> index isn't implemented yet")?; + progress.init(Some(out.index.entries().len()), gix::progress::count("files")); + progress.set(out.index.entries().len()); + progress.show_throughput(start); + Ok(()) } -impl Printer { - fn visit_inner(&mut self, entry_index: usize, rela_path: &BStr, status: EntryStatus<()>) -> std::io::Result<()> { - let char_storage; - let status = match status { - EntryStatus::Conflict(conflict) => as_str(conflict), - EntryStatus::Change(change) => { - if matches!( - change, - Change::Modification { - set_entry_stat_size_zero: true, - .. - } - ) { - self.changes.push((entry_index, ApplyChange::SetSizeToZero)) - } - char_storage = change_to_char(&change); - std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII") - } - EntryStatus::NeedsUpdate(stat) => { - self.changes.push((entry_index, ApplyChange::NewStat(stat))); - return Ok(()); - } - EntryStatus::IntentToAdd => "A", - }; +fn print_index_entry_status( + out: &mut dyn std::io::Write, + prefix: &Path, + rela_path: &BStr, + status: EntryStatus<(), gix::submodule::Status>, +) -> std::io::Result<()> { + let char_storage; + let status = match status { + EntryStatus::Conflict(conflict) => as_str(conflict), + EntryStatus::Change(change) => { + char_storage = change_to_char(&change); + std::str::from_utf8(std::slice::from_ref(&char_storage)).expect("valid ASCII") + } + EntryStatus::NeedsUpdate(_stat) => { + return Ok(()); + } + EntryStatus::IntentToAdd => "A", + }; - let rela_path = gix::path::from_bstr(rela_path); - let display_path = gix::path::relativize_with_prefix(&rela_path, &self.prefix); - writeln!(&mut self.out, "{status: >3} {}", display_path.display()) - } + let rela_path = gix::path::from_bstr(rela_path); + let display_path = gix::path::relativize_with_prefix(&rela_path, prefix); + writeln!(out, "{status: >3} {}", display_path.display()) } fn as_str(c: Conflict) -> &'static str { @@ -251,7 +217,7 @@ fn as_str(c: Conflict) -> &'static str { } } -fn change_to_char(change: &Change<()>) -> u8 { +fn change_to_char(change: &Change<(), gix::submodule::Status>) -> u8 { // Known status letters: https://github.com/git/git/blob/6807fcfedab84bc8cd0fbf721bc13c4e68cda9ae/diff.h#L613 match change { Change::Removed => b'D', diff --git a/gitoxide-core/src/repository/submodule.rs b/gitoxide-core/src/repository/submodule.rs index c8e8c45eaa2..12ef64870e8 100644 --- a/gitoxide-core/src/repository/submodule.rs +++ b/gitoxide-core/src/repository/submodule.rs @@ -3,7 +3,12 @@ use gix::{commit::describe::SelectRef, prelude::ObjectIdExt, Repository, Submodu use crate::OutputFormat; -pub fn list(repo: Repository, mut out: impl std::io::Write, format: OutputFormat) -> anyhow::Result<()> { +pub fn list( + repo: Repository, + mut out: impl std::io::Write, + format: OutputFormat, + dirty_suffix: Option, +) -> anyhow::Result<()> { if format != OutputFormat::Human { bail!("Only human output is supported for now") } @@ -12,12 +17,12 @@ pub fn list(repo: Repository, mut out: impl std::io::Write, format: OutputFormat return Ok(()); }; for sm in submodules { - print_sm(sm, &mut out)?; + print_sm(sm, dirty_suffix.as_deref(), &mut out)?; } Ok(()) } -fn print_sm(sm: Submodule<'_>, out: &mut impl std::io::Write) -> anyhow::Result<()> { +fn print_sm(sm: Submodule<'_>, dirty_suffix: Option<&str>, out: &mut impl std::io::Write) -> anyhow::Result<()> { let _span = gix::trace::coarse!("print_sm", path = ?sm.path()); let state = sm.state()?; let mut sm_repo = sm.open()?; @@ -48,7 +53,10 @@ fn print_sm(sm: Submodule<'_>, out: &mut impl std::io::Write) -> anyhow::Result< repo.head_commit()? .describe() .names(SelectRef::AllRefs) - .format()? + .id_as_fallback(true) + .try_resolve()? + .expect("resolution present if ID can be used as fallback") + .format_with_dirty_suffix(dirty_suffix.map(ToOwned::to_owned))? .to_string() } None => { diff --git a/gix-actor/src/lib.rs b/gix-actor/src/lib.rs index e582541cb1a..e7e94770098 100644 --- a/gix-actor/src/lib.rs +++ b/gix-actor/src/lib.rs @@ -22,6 +22,7 @@ use gix_date::Time; mod identity; /// +#[allow(clippy::empty_docs)] pub mod signature; /// A person with name and email. diff --git a/gix-actor/src/signature/mod.rs b/gix-actor/src/signature/mod.rs index 057d4293e7d..34a0f7e28f3 100644 --- a/gix-actor/src/signature/mod.rs +++ b/gix-actor/src/signature/mod.rs @@ -129,5 +129,6 @@ pub(crate) mod write { } /// +#[allow(clippy::empty_docs)] pub mod decode; pub use decode::function::decode; diff --git a/gix-attributes/src/lib.rs b/gix-attributes/src/lib.rs index 812ffe49a75..4b1090fa9bb 100644 --- a/gix-attributes/src/lib.rs +++ b/gix-attributes/src/lib.rs @@ -13,14 +13,18 @@ use kstring::{KString, KStringRef}; mod assignment; /// +#[allow(clippy::empty_docs)] pub mod name; /// +#[allow(clippy::empty_docs)] pub mod state; /// +#[allow(clippy::empty_docs)] pub mod search; /// +#[allow(clippy::empty_docs)] pub mod parse; /// Parse attribute assignments line by line from `bytes`, and fail the operation on error. diff --git a/gix-attributes/src/search/outcome.rs b/gix-attributes/src/search/outcome.rs index 41d70e5806c..2a0df7e478a 100644 --- a/gix-attributes/src/search/outcome.rs +++ b/gix-attributes/src/search/outcome.rs @@ -26,7 +26,7 @@ impl Outcome { for (order, macro_attributes) in collection.iter().filter_map(|(_, meta)| { (!meta.macro_attributes.is_empty()).then_some((meta.id.0, &meta.macro_attributes)) }) { - self.matches_by_id[order].macro_attributes = macro_attributes.clone() + self.matches_by_id[order].macro_attributes.clone_from(macro_attributes) } for (name, id) in self.selected.iter_mut().filter(|(_, id)| id.is_none()) { @@ -88,7 +88,7 @@ impl Outcome { /// Note that it's safe to call it multiple times, so that it can be called after this instance was used to store a search result. pub fn copy_into(&self, collection: &MetadataCollection, dest: &mut Self) { dest.initialize(collection); - dest.matches_by_id = self.matches_by_id.clone(); + dest.matches_by_id.clone_from(&self.matches_by_id); if dest.patterns.len() != self.patterns.len() { dest.patterns = self.patterns.clone(); } @@ -325,7 +325,11 @@ impl MetadataCollection { }; self.assign_order_to_attributes(attrs); - self.name_to_meta.get_mut(name).expect("just added").macro_attributes = attrs.clone(); + self.name_to_meta + .get_mut(name) + .expect("just added") + .macro_attributes + .clone_from(attrs); order } diff --git a/gix-bitmap/src/ewah.rs b/gix-bitmap/src/ewah.rs index e2575f90500..64d363beb28 100644 --- a/gix-bitmap/src/ewah.rs +++ b/gix-bitmap/src/ewah.rs @@ -1,6 +1,5 @@ -use std::convert::TryInto; - /// +#[allow(clippy::empty_docs)] pub mod decode { /// The error returned by [`decode()`][super::decode()]. #[derive(Debug, thiserror::Error)] @@ -52,8 +51,6 @@ pub fn decode(data: &[u8]) -> Result<(Vec, &[u8]), decode::Error> { } mod access { - use std::convert::{TryFrom, TryInto}; - use super::Vec; impl Vec { diff --git a/gix-bitmap/src/lib.rs b/gix-bitmap/src/lib.rs index 83756d98897..0ccc5824843 100644 --- a/gix-bitmap/src/lib.rs +++ b/gix-bitmap/src/lib.rs @@ -7,8 +7,6 @@ pub mod ewah; pub(crate) mod decode { - use std::convert::TryInto; - #[inline] pub(crate) fn split_at_pos(data: &[u8], pos: usize) -> Option<(&[u8], &[u8])> { if data.len() < pos { diff --git a/gix-chunk/src/file/decode.rs b/gix-chunk/src/file/decode.rs index 22bdc169bd9..5f397d415df 100644 --- a/gix-chunk/src/file/decode.rs +++ b/gix-chunk/src/file/decode.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, ops::Range}; +use std::ops::Range; mod error { /// The value returned by [`crate::file::Index::from_bytes()`] diff --git a/gix-chunk/src/file/index.rs b/gix-chunk/src/file/index.rs index c9211ab6fec..e511781a0e4 100644 --- a/gix-chunk/src/file/index.rs +++ b/gix-chunk/src/file/index.rs @@ -3,6 +3,7 @@ use std::ops::Range; use crate::file::Index; /// +#[allow(clippy::empty_docs)] pub mod offset_by_kind { use std::fmt::{Display, Formatter}; @@ -27,6 +28,7 @@ pub mod offset_by_kind { } /// +#[allow(clippy::empty_docs)] pub mod data_by_kind { /// The error returned by [`Index::data_by_id()`][super::Index::data_by_id()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-chunk/src/file/mod.rs b/gix-chunk/src/file/mod.rs index 4ddd94999ab..46b82a7d256 100644 --- a/gix-chunk/src/file/mod.rs +++ b/gix-chunk/src/file/mod.rs @@ -1,9 +1,12 @@ /// +#[allow(clippy::empty_docs)] pub mod decode; /// +#[allow(clippy::empty_docs)] pub mod index; /// +#[allow(clippy::empty_docs)] pub mod write; /// The offset to a chunk as seen relative to the beginning of the file containing it. diff --git a/gix-chunk/src/lib.rs b/gix-chunk/src/lib.rs index 0647e320b1c..e7dc725c7d0 100644 --- a/gix-chunk/src/lib.rs +++ b/gix-chunk/src/lib.rs @@ -10,8 +10,9 @@ pub type Id = [u8; 4]; pub const SENTINEL: Id = [0u8; 4]; /// +#[allow(clippy::empty_docs)] pub mod range { - use std::{convert::TryInto, ops::Range}; + use std::ops::Range; use crate::file; @@ -33,4 +34,5 @@ pub mod range { } /// +#[allow(clippy::empty_docs)] pub mod file; diff --git a/gix-command/src/lib.rs b/gix-command/src/lib.rs index 87759b472dc..e5437c552ed 100644 --- a/gix-command/src/lib.rs +++ b/gix-command/src/lib.rs @@ -330,6 +330,7 @@ pub fn extract_interpreter(executable: &Path) -> Option { } /// +#[allow(clippy::empty_docs)] pub mod shebang { use bstr::{BStr, ByteSlice}; use std::ffi::OsString; diff --git a/gix-commitgraph/src/file/access.rs b/gix-commitgraph/src/file/access.rs index 57c75ae569c..6ccb98cc322 100644 --- a/gix-commitgraph/src/file/access.rs +++ b/gix-commitgraph/src/file/access.rs @@ -1,5 +1,4 @@ use std::{ - convert::TryInto, fmt::{Debug, Formatter}, path::Path, }; diff --git a/gix-commitgraph/src/file/commit.rs b/gix-commitgraph/src/file/commit.rs index 14fe15e8413..db47cfcead9 100644 --- a/gix-commitgraph/src/file/commit.rs +++ b/gix-commitgraph/src/file/commit.rs @@ -1,6 +1,5 @@ //! Low-level operations on individual commits. use std::{ - convert::TryInto, fmt::{Debug, Formatter}, slice::Chunks, }; diff --git a/gix-commitgraph/src/file/init.rs b/gix-commitgraph/src/file/init.rs index 78e621ab0ae..47dc3616f80 100644 --- a/gix-commitgraph/src/file/init.rs +++ b/gix-commitgraph/src/file/init.rs @@ -1,8 +1,5 @@ +use std::path::Path; use std::path::PathBuf; -use std::{ - convert::{TryFrom, TryInto}, - path::Path, -}; use crate::{ file::{ diff --git a/gix-commitgraph/src/init.rs b/gix-commitgraph/src/init.rs index 0b03ba9462d..a1f98f3d24a 100644 --- a/gix-commitgraph/src/init.rs +++ b/gix-commitgraph/src/init.rs @@ -1,5 +1,4 @@ use std::{ - convert::TryFrom, io::{BufRead, BufReader}, path::{Path, PathBuf}, }; diff --git a/gix-commitgraph/src/lib.rs b/gix-commitgraph/src/lib.rs index d9f4d36bf2f..5d60b9c2ed4 100644 --- a/gix-commitgraph/src/lib.rs +++ b/gix-commitgraph/src/lib.rs @@ -51,6 +51,7 @@ pub fn at(path: impl AsRef) -> Result { mod access; pub mod file; /// +#[allow(clippy::empty_docs)] pub mod init; pub mod verify; diff --git a/gix-commitgraph/src/verify.rs b/gix-commitgraph/src/verify.rs index 56eb721e6e3..9a37cff83ad 100644 --- a/gix-commitgraph/src/verify.rs +++ b/gix-commitgraph/src/verify.rs @@ -2,7 +2,6 @@ use std::{ cmp::{max, min}, collections::BTreeMap, - convert::TryInto, path::PathBuf, }; diff --git a/gix-commitgraph/tests/commitgraph.rs b/gix-commitgraph/tests/commitgraph.rs index 94568fe5cfa..3444536ef8a 100644 --- a/gix-commitgraph/tests/commitgraph.rs +++ b/gix-commitgraph/tests/commitgraph.rs @@ -1,6 +1,5 @@ use std::{ collections::{HashMap, HashSet}, - convert::{TryFrom, TryInto}, hash::BuildHasher, io::{BufRead, Cursor}, path::Path, diff --git a/gix-config-value/src/boolean.rs b/gix-config-value/src/boolean.rs index 908e11a300a..ff146e7f17c 100644 --- a/gix-config-value/src/boolean.rs +++ b/gix-config-value/src/boolean.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom, ffi::OsString, fmt::Display}; +use std::{borrow::Cow, ffi::OsString, fmt::Display}; use bstr::{BStr, BString, ByteSlice}; diff --git a/gix-config-value/src/color.rs b/gix-config-value/src/color.rs index 5cc0b997f40..884a346d37a 100644 --- a/gix-config-value/src/color.rs +++ b/gix-config-value/src/color.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; diff --git a/gix-config-value/src/integer.rs b/gix-config-value/src/integer.rs index 4286a83b5be..4864b4b3e41 100644 --- a/gix-config-value/src/integer.rs +++ b/gix-config-value/src/integer.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, fmt::Display, str::FromStr}; use bstr::{BStr, BString}; diff --git a/gix-config-value/src/lib.rs b/gix-config-value/src/lib.rs index b3be069e328..7dd7e89c9e1 100644 --- a/gix-config-value/src/lib.rs +++ b/gix-config-value/src/lib.rs @@ -37,10 +37,13 @@ impl Error { mod boolean; /// +#[allow(clippy::empty_docs)] pub mod color; /// +#[allow(clippy::empty_docs)] pub mod integer; /// +#[allow(clippy::empty_docs)] pub mod path; mod types; diff --git a/gix-config-value/src/path.rs b/gix-config-value/src/path.rs index 58b5970eecd..23550f7cb16 100644 --- a/gix-config-value/src/path.rs +++ b/gix-config-value/src/path.rs @@ -5,6 +5,7 @@ use bstr::BStr; use crate::Path; /// +#[allow(clippy::empty_docs)] pub mod interpolate { use std::path::PathBuf; diff --git a/gix-config-value/tests/value/boolean.rs b/gix-config-value/tests/value/boolean.rs index 8fe8b43f0d4..7af370c8c97 100644 --- a/gix-config-value/tests/value/boolean.rs +++ b/gix-config-value/tests/value/boolean.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use gix_config_value::Boolean; use crate::b; diff --git a/gix-config-value/tests/value/color.rs b/gix-config-value/tests/value/color.rs index 18baaba4c92..7ff5b2bd649 100644 --- a/gix-config-value/tests/value/color.rs +++ b/gix-config-value/tests/value/color.rs @@ -110,8 +110,6 @@ mod attribute { } mod from_git { - use std::convert::TryFrom; - use bstr::BStr; use gix_config_value::Color; diff --git a/gix-config-value/tests/value/integer.rs b/gix-config-value/tests/value/integer.rs index 8a5d8016021..6caa7f3492e 100644 --- a/gix-config-value/tests/value/integer.rs +++ b/gix-config-value/tests/value/integer.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use gix_config_value::{integer::Suffix, Integer}; use crate::b; diff --git a/gix-config/benches/large_config_file.rs b/gix-config/benches/large_config_file.rs index 51f6e1fd86b..ff42afb6f1c 100644 --- a/gix-config/benches/large_config_file.rs +++ b/gix-config/benches/large_config_file.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use criterion::{black_box, criterion_group, criterion_main, Criterion}; use gix_config::{parse::Events, File}; diff --git a/gix-config/src/file/access/comfort.rs b/gix-config/src/file/access/comfort.rs index ed62e779269..0a18bd18491 100644 --- a/gix-config/src/file/access/comfort.rs +++ b/gix-config/src/file/access/comfort.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; use bstr::BStr; diff --git a/gix-config/src/file/access/raw.rs b/gix-config/src/file/access/raw.rs index 3736bf3a286..a3d65ce2a45 100644 --- a/gix-config/src/file/access/raw.rs +++ b/gix-config/src/file/access/raw.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, convert::TryInto}; +use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; use smallvec::ToSmallVec; diff --git a/gix-config/src/file/access/read_only.rs b/gix-config/src/file/access/read_only.rs index eb1071fe2e1..6072776a7d6 100644 --- a/gix-config/src/file/access/read_only.rs +++ b/gix-config/src/file/access/read_only.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; use bstr::{BStr, ByteSlice}; use gix_features::threading::OwnShared; diff --git a/gix-config/src/file/impls.rs b/gix-config/src/file/impls.rs index c26df5fb81a..884e7ce346b 100644 --- a/gix-config/src/file/impls.rs +++ b/gix-config/src/file/impls.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, fmt::Display, str::FromStr}; use bstr::{BStr, BString, ByteVec}; diff --git a/gix-config/src/file/includes/types.rs b/gix-config/src/file/includes/types.rs index 64306bd9ca0..54b1eb5bfe0 100644 --- a/gix-config/src/file/includes/types.rs +++ b/gix-config/src/file/includes/types.rs @@ -115,6 +115,7 @@ impl Default for Options<'_> { } /// +#[allow(clippy::empty_docs)] pub mod conditional { /// Options to handle conditional includes like `includeIf..path`. #[derive(Clone, Copy, Default)] diff --git a/gix-config/src/file/init/comfort.rs b/gix-config/src/file/init/comfort.rs index 4a5a1c68b25..dd772518366 100644 --- a/gix-config/src/file/init/comfort.rs +++ b/gix-config/src/file/init/comfort.rs @@ -141,6 +141,7 @@ impl File<'static> { } /// +#[allow(clippy::empty_docs)] pub mod from_git_dir { use crate::file::init; diff --git a/gix-config/src/file/init/from_env.rs b/gix-config/src/file/init/from_env.rs index 2c487e5957a..6a59f10324d 100644 --- a/gix-config/src/file/init/from_env.rs +++ b/gix-config/src/file/init/from_env.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use bstr::{BStr, ByteSlice}; use crate::{file, file::init, parse, parse::section, path::interpolate, File}; diff --git a/gix-config/src/file/init/mod.rs b/gix-config/src/file/init/mod.rs index 90fa70cbcf9..1076011fcc6 100644 --- a/gix-config/src/file/init/mod.rs +++ b/gix-config/src/file/init/mod.rs @@ -10,8 +10,10 @@ pub use types::{Error, Options}; mod comfort; /// +#[allow(clippy::empty_docs)] pub mod from_env; /// +#[allow(clippy::empty_docs)] pub mod from_paths; impl<'a> File<'a> { diff --git a/gix-config/src/file/mod.rs b/gix-config/src/file/mod.rs index e99c6eb94d9..8611ad0c7e1 100644 --- a/gix-config/src/file/mod.rs +++ b/gix-config/src/file/mod.rs @@ -13,19 +13,23 @@ mod mutable; pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; /// +#[allow(clippy::empty_docs)] pub mod init; mod access; mod impls; /// +#[allow(clippy::empty_docs)] pub mod includes; mod meta; mod util; /// +#[allow(clippy::empty_docs)] pub mod section; /// +#[allow(clippy::empty_docs)] pub mod rename_section { /// The error returned by [`File::rename_section(…)`][crate::File::rename_section()]. #[derive(Debug, thiserror::Error)] @@ -39,6 +43,7 @@ pub mod rename_section { } /// +#[allow(clippy::empty_docs)] pub mod set_raw_value { /// The error returned by [`File::set_raw_value(…)`][crate::File::set_raw_value()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-config/src/file/tests.rs b/gix-config/src/file/tests.rs index 58f16ec1547..adb11e5a291 100644 --- a/gix-config/src/file/tests.rs +++ b/gix-config/src/file/tests.rs @@ -6,7 +6,7 @@ use crate::{ }; mod try_from { - use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; + use std::{borrow::Cow, collections::HashMap}; use super::{bodies, headers}; use crate::{ diff --git a/gix-config/src/file/util.rs b/gix-config/src/file/util.rs index 5c60f1fd5a0..78bc4736f63 100644 --- a/gix-config/src/file/util.rs +++ b/gix-config/src/file/util.rs @@ -118,8 +118,7 @@ impl<'event> File<'event> { &'a self, section_name: &'a str, subsection_name: Option<&BStr>, - ) -> Result + ExactSizeIterator + DoubleEndedIterator + '_, lookup::existing::Error> - { + ) -> Result + DoubleEndedIterator + '_, lookup::existing::Error> { let section_name = section::Name::from_str_unchecked(section_name); let section_ids = self .section_lookup_tree diff --git a/gix-config/src/lib.rs b/gix-config/src/lib.rs index b1740cdd879..bbbe36d8ded 100644 --- a/gix-config/src/lib.rs +++ b/gix-config/src/lib.rs @@ -40,13 +40,16 @@ pub mod file; /// +#[allow(clippy::empty_docs)] pub mod lookup; pub mod parse; /// +#[allow(clippy::empty_docs)] pub mod value; pub use gix_config_value::{color, integer, path, Boolean, Color, Integer, Path}; mod types; pub use types::{File, Source}; /// +#[allow(clippy::empty_docs)] pub mod source; diff --git a/gix-config/src/lookup.rs b/gix-config/src/lookup.rs index 7814978125d..25d8d9d1751 100644 --- a/gix-config/src/lookup.rs +++ b/gix-config/src/lookup.rs @@ -9,6 +9,7 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod existing { /// The error when looking up a value that doesn't exist, for example via [`File::value()`][crate::File::value()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-config/src/parse/events.rs b/gix-config/src/parse/events.rs index d742bafe04c..5e0c8f06bf1 100644 --- a/gix-config/src/parse/events.rs +++ b/gix-config/src/parse/events.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use smallvec::SmallVec; use crate::{ diff --git a/gix-config/src/parse/mod.rs b/gix-config/src/parse/mod.rs index ac75853a0ca..e11e77c846a 100644 --- a/gix-config/src/parse/mod.rs +++ b/gix-config/src/parse/mod.rs @@ -23,9 +23,11 @@ pub use events_type::{Events, FrontMatterEvents}; mod comment; mod error; /// +#[allow(clippy::empty_docs)] pub mod section; /// +#[allow(clippy::empty_docs)] mod key; pub use key::{parse_unvalidated as key, Key}; diff --git a/gix-config/src/parse/section/mod.rs b/gix-config/src/parse/section/mod.rs index fade4fa147f..5c4489ea670 100644 --- a/gix-config/src/parse/section/mod.rs +++ b/gix-config/src/parse/section/mod.rs @@ -5,6 +5,7 @@ use bstr::BStr; use crate::parse::{Event, Section}; /// +#[allow(clippy::empty_docs)] pub mod header; pub(crate) mod unvalidated; @@ -48,6 +49,7 @@ mod types { macro_rules! generate_case_insensitive { ($name:ident, $module:ident, $err_doc:literal, $validate:ident, $cow_inner_type:ty, $comment:literal) => { /// + #[allow(clippy::empty_docs)] pub mod $module { /// The error returned when `TryFrom` is invoked to create an instance. #[derive(Debug, thiserror::Error, Copy, Clone)] diff --git a/gix-config/src/parse/tests.rs b/gix-config/src/parse/tests.rs index edb5dee6ee3..33b015173a5 100644 --- a/gix-config/src/parse/tests.rs +++ b/gix-config/src/parse/tests.rs @@ -109,7 +109,7 @@ pub(crate) mod util { //! This module is only included for tests, and contains common unit test helper //! functions. - use std::{borrow::Cow, convert::TryFrom}; + use std::borrow::Cow; use crate::parse::{section, Comment, Event}; diff --git a/gix-config/tests/file/access/mutate.rs b/gix-config/tests/file/access/mutate.rs index 0788a3a31a4..48826593f6c 100644 --- a/gix-config/tests/file/access/mutate.rs +++ b/gix-config/tests/file/access/mutate.rs @@ -1,6 +1,4 @@ mod remove_section { - use std::convert::TryFrom; - #[test] fn removal_of_all_sections_programmatically_with_sections_and_ids_by_name() { let mut file = gix_config::File::try_from("[core] \na = b\nb=c\n\n[core \"name\"]\nd = 1\ne = 2").unwrap(); @@ -47,7 +45,7 @@ mod remove_section { } } mod rename_section { - use std::{borrow::Cow, convert::TryFrom}; + use std::borrow::Cow; use gix_config::{file::rename_section, parse::section}; diff --git a/gix-config/tests/file/access/raw/raw_multi_value.rs b/gix-config/tests/file/access/raw/raw_multi_value.rs index 75fa1e20e74..3309713ea15 100644 --- a/gix-config/tests/file/access/raw/raw_multi_value.rs +++ b/gix-config/tests/file/access/raw/raw_multi_value.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use gix_config::{lookup, File}; use crate::file::cow_str; diff --git a/gix-config/tests/file/access/raw/raw_value.rs b/gix-config/tests/file/access/raw/raw_value.rs index 8b6c52bd149..e6f6d1c3a8b 100644 --- a/gix-config/tests/file/access/raw/raw_value.rs +++ b/gix-config/tests/file/access/raw/raw_value.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use gix_config::{lookup, File}; #[test] diff --git a/gix-config/tests/file/access/read_only.rs b/gix-config/tests/file/access/read_only.rs index a5d7fa8abd2..f8f8cb9e4f9 100644 --- a/gix-config/tests/file/access/read_only.rs +++ b/gix-config/tests/file/access/read_only.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; use bstr::BStr; use gix_config::{ diff --git a/gix-config/tests/file/impls/mod.rs b/gix-config/tests/file/impls/mod.rs index 885f326e1bb..5247d2ea7a2 100644 --- a/gix-config/tests/file/impls/mod.rs +++ b/gix-config/tests/file/impls/mod.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use gix_config::File; #[test] diff --git a/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 988a787094a..b6f3d9933c6 100644 --- a/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/gix-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -1,7 +1,4 @@ -use std::{ - convert::{TryFrom, TryInto}, - fs, -}; +use std::fs; use bstr::{BString, ByteSlice}; use gix_config::file::{ diff --git a/gix-config/tests/file/mutable/section.rs b/gix-config/tests/file/mutable/section.rs index 17ef1df586b..6ee00b30c87 100644 --- a/gix-config/tests/file/mutable/section.rs +++ b/gix-config/tests/file/mutable/section.rs @@ -90,8 +90,6 @@ mod pop { } mod set { - use std::convert::TryInto; - use super::multi_value_section; #[test] @@ -121,8 +119,6 @@ mod set { } mod push { - use std::convert::{TryFrom, TryInto}; - use gix_config::parse::section::Key; use crate::file::cow_str; @@ -230,7 +226,7 @@ mod push_with_comment { } mod set_leading_whitespace { - use std::{borrow::Cow, convert::TryFrom}; + use std::borrow::Cow; use bstr::BString; use gix_config::parse::section::Key; diff --git a/gix-config/tests/file/write.rs b/gix-config/tests/file/write.rs index 26c72c7f72b..224d800fed9 100644 --- a/gix-config/tests/file/write.rs +++ b/gix-config/tests/file/write.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use bstr::ByteVec; use gix_config::file::{init, Metadata}; diff --git a/gix-config/tests/parse/from_bytes.rs b/gix-config/tests/parse/from_bytes.rs index df58531ba01..736ddfedca0 100644 --- a/gix-config/tests/parse/from_bytes.rs +++ b/gix-config/tests/parse/from_bytes.rs @@ -1,5 +1,3 @@ -use gix_config::parse::Events; - use super::*; #[test] diff --git a/gix-config/tests/parse/mod.rs b/gix-config/tests/parse/mod.rs index a4bb1ab5bb8..01c4a53c90c 100644 --- a/gix-config/tests/parse/mod.rs +++ b/gix-config/tests/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; use gix_config::parse::{Event, Events, Section}; diff --git a/gix-config/tests/parse/section.rs b/gix-config/tests/parse/section.rs index 872052e9031..afdff7d9350 100644 --- a/gix-config/tests/parse/section.rs +++ b/gix-config/tests/parse/section.rs @@ -70,8 +70,6 @@ mod header { } } mod name { - use std::convert::TryFrom; - use gix_config::parse::section::Name; #[test] @@ -90,7 +88,7 @@ mod name { } mod key { - use std::{cmp::Ordering, convert::TryFrom}; + use std::cmp::Ordering; use gix_config::parse::section::Key; diff --git a/gix-credentials/examples/git-credential-lite.rs b/gix-credentials/examples/git-credential-lite.rs index c4e40c29177..c91dec18d3e 100644 --- a/gix-credentials/examples/git-credential-lite.rs +++ b/gix-credentials/examples/git-credential-lite.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - /// Run like this `echo url=https://example.com | cargo run --example git-credential-light -- fill` pub fn main() -> Result<(), gix_credentials::program::main::Error> { gix_credentials::program::main( diff --git a/gix-credentials/examples/invoke-git-credential.rs b/gix-credentials/examples/invoke-git-credential.rs index cba3f5f706f..5d4f780f7aa 100644 --- a/gix-credentials/examples/invoke-git-credential.rs +++ b/gix-credentials/examples/invoke-git-credential.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - /// Invokes `git credential` with the passed url as argument and prints obtained credentials. pub fn main() -> Result<(), Box> { let out = gix_credentials::builtin(gix_credentials::helper::Action::get_for_url( diff --git a/gix-credentials/src/helper/mod.rs b/gix-credentials/src/helper/mod.rs index 107d6db7a16..015ca0aaf9f 100644 --- a/gix-credentials/src/helper/mod.rs +++ b/gix-credentials/src/helper/mod.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use bstr::{BStr, BString}; use crate::{protocol, protocol::Context, Program}; diff --git a/gix-credentials/src/lib.rs b/gix-credentials/src/lib.rs index 4e3e21d99a3..89e72e53d96 100644 --- a/gix-credentials/src/lib.rs +++ b/gix-credentials/src/lib.rs @@ -21,12 +21,15 @@ pub struct Program { } /// +#[allow(clippy::empty_docs)] pub mod helper; /// +#[allow(clippy::empty_docs)] pub mod program; /// +#[allow(clippy::empty_docs)] pub mod protocol; /// Call the `git credential` helper program performing the given `action`, which reads all context from the git configuration diff --git a/gix-credentials/src/program/main.rs b/gix-credentials/src/program/main.rs index b3417f92372..34f4163a01e 100644 --- a/gix-credentials/src/program/main.rs +++ b/gix-credentials/src/program/main.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, ffi::OsString}; +use std::ffi::OsString; use bstr::BString; @@ -60,7 +60,7 @@ pub enum Error { } pub(crate) mod function { - use std::{convert::TryInto, ffi::OsString}; + use std::ffi::OsString; use crate::{ program::main::{Action, Error}, diff --git a/gix-credentials/src/program/mod.rs b/gix-credentials/src/program/mod.rs index b7b1150d3dd..213c0272128 100644 --- a/gix-credentials/src/program/mod.rs +++ b/gix-credentials/src/program/mod.rs @@ -144,5 +144,6 @@ impl Program { } /// +#[allow(clippy::empty_docs)] pub mod main; pub use main::function::main; diff --git a/gix-credentials/src/protocol/context/serde.rs b/gix-credentials/src/protocol/context/serde.rs index 0f3b4b02487..76d1673a776 100644 --- a/gix-credentials/src/protocol/context/serde.rs +++ b/gix-credentials/src/protocol/context/serde.rs @@ -49,9 +49,8 @@ mod write { } /// +#[allow(clippy::empty_docs)] pub mod decode { - use std::convert::TryFrom; - use bstr::{BString, ByteSlice}; use crate::protocol::{context, context::serde::validate, Context}; diff --git a/gix-credentials/src/protocol/mod.rs b/gix-credentials/src/protocol/mod.rs index ec168ffb3fc..2386af8fe98 100644 --- a/gix-credentials/src/protocol/mod.rs +++ b/gix-credentials/src/protocol/mod.rs @@ -83,4 +83,5 @@ pub fn helper_outcome_to_result(outcome: Option, action: helper } /// +#[allow(clippy::empty_docs)] pub mod context; diff --git a/gix-credentials/tests/helper/cascade.rs b/gix-credentials/tests/helper/cascade.rs index f97a64dcb4e..32ffaaa7edb 100644 --- a/gix-credentials/tests/helper/cascade.rs +++ b/gix-credentials/tests/helper/cascade.rs @@ -1,6 +1,4 @@ mod invoke { - use std::convert::TryInto; - use bstr::{ByteSlice, ByteVec}; use gix_credentials::{ helper::{Action, Cascade}, diff --git a/gix-date/src/lib.rs b/gix-date/src/lib.rs index 0ada471473f..42dda4a533c 100644 --- a/gix-date/src/lib.rs +++ b/gix-date/src/lib.rs @@ -11,9 +11,11 @@ #![forbid(unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod time; /// +#[allow(clippy::empty_docs)] pub mod parse; pub use parse::function::parse; diff --git a/gix-date/src/parse.rs b/gix-date/src/parse.rs index 323292cba4c..da0691271b2 100644 --- a/gix-date/src/parse.rs +++ b/gix-date/src/parse.rs @@ -86,7 +86,7 @@ pub(crate) mod function { } mod relative { - use std::{convert::TryInto, str::FromStr, time::SystemTime}; + use std::{str::FromStr, time::SystemTime}; use time::{Duration, OffsetDateTime}; diff --git a/gix-date/src/time/mod.rs b/gix-date/src/time/mod.rs index 22c4e42f0a6..e2e1ff77afd 100644 --- a/gix-date/src/time/mod.rs +++ b/gix-date/src/time/mod.rs @@ -29,6 +29,7 @@ pub enum Format<'a> { } /// +#[allow(clippy::empty_docs)] pub mod format; mod init; mod write; diff --git a/gix-diff/src/blob/mod.rs b/gix-diff/src/blob/mod.rs index 0c76c2d918c..9af819c9877 100644 --- a/gix-diff/src/blob/mod.rs +++ b/gix-diff/src/blob/mod.rs @@ -6,9 +6,11 @@ use bstr::BString; pub use imara_diff::*; /// +#[allow(clippy::empty_docs)] pub mod pipeline; /// +#[allow(clippy::empty_docs)] pub mod platform; /// Information about the diff performed to detect similarity. diff --git a/gix-diff/src/blob/pipeline.rs b/gix-diff/src/blob/pipeline.rs index 45018218426..f67de77a14c 100644 --- a/gix-diff/src/blob/pipeline.rs +++ b/gix-diff/src/blob/pipeline.rs @@ -111,6 +111,7 @@ impl Mode { } /// +#[allow(clippy::empty_docs)] pub mod convert_to_diffable { use std::collections::TryReserveError; diff --git a/gix-diff/src/blob/platform.rs b/gix-diff/src/blob/platform.rs index 1c2d8fa0180..091c5a9cf3c 100644 --- a/gix-diff/src/blob/platform.rs +++ b/gix-diff/src/blob/platform.rs @@ -90,6 +90,7 @@ pub struct Resource<'a> { } /// +#[allow(clippy::empty_docs)] pub mod resource { use crate::blob::{ pipeline, @@ -150,6 +151,7 @@ pub mod resource { } /// +#[allow(clippy::empty_docs)] pub mod set_resource { use bstr::BString; @@ -179,6 +181,7 @@ pub mod set_resource { } /// +#[allow(clippy::empty_docs)] pub mod prepare_diff { use bstr::BStr; @@ -245,6 +248,7 @@ pub mod prepare_diff { } /// +#[allow(clippy::empty_docs)] pub mod prepare_diff_command { use std::ops::{Deref, DerefMut}; diff --git a/gix-diff/src/lib.rs b/gix-diff/src/lib.rs index 1fe8d2e6bb4..ac528ccb2be 100644 --- a/gix-diff/src/lib.rs +++ b/gix-diff/src/lib.rs @@ -44,6 +44,7 @@ pub struct Rewrites { pub mod rewrites; /// +#[allow(clippy::empty_docs)] pub mod tree; /// diff --git a/gix-diff/src/rewrites/tracker.rs b/gix-diff/src/rewrites/tracker.rs index 4caefcd34d4..7a255c456a1 100644 --- a/gix-diff/src/rewrites/tracker.rs +++ b/gix-diff/src/rewrites/tracker.rs @@ -83,7 +83,7 @@ pub mod visit { /// The source of a rewrite, rename or copy. #[derive(Debug, Clone, PartialEq, PartialOrd)] - pub struct Source<'a> { + pub struct Source<'a, T> { /// The kind of entry. pub entry_mode: EntryMode, /// The hash of the state of the source as seen in the object database. @@ -92,6 +92,8 @@ pub mod visit { pub kind: SourceKind, /// The repository-relative location of this entry. pub location: &'a BStr, + /// The change that was registered as source. + pub change: &'a T, /// If this is a rewrite, indicate how many lines would need to change to turn this source into the destination. pub diff: Option, } @@ -116,6 +118,7 @@ pub mod visit { } /// +#[allow(clippy::empty_docs)] pub mod emit { /// The error returned by [Tracker::emit()](super::Tracker::emit()). #[derive(Debug, thiserror::Error)] @@ -193,7 +196,7 @@ impl Tracker { /// will panic if `change` is not a modification, and it's valid to not call `push` at all. pub fn emit( &mut self, - mut cb: impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, + mut cb: impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, diff_cache: &mut crate::blob::Platform, objects: &impl gix_object::FindObjectOrHeader, mut push_source_tree: PushSourceTreeFn, @@ -283,7 +286,7 @@ impl Tracker { fn match_pairs_of_kind( &mut self, kind: visit::SourceKind, - cb: &mut impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, + cb: &mut impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, percentage: Option, out: &mut Outcome, diff_cache: &mut crate::blob::Platform, @@ -326,7 +329,7 @@ impl Tracker { fn match_pairs( &mut self, - cb: &mut impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, + cb: &mut impl FnMut(visit::Destination<'_, T>, Option>) -> crate::tree::visit::Action, percentage: Option, kind: visit::SourceKind, stats: &mut Outcome, @@ -360,6 +363,7 @@ impl Tracker { id, kind, location, + change: &src.change, diff, }, src_idx, @@ -371,11 +375,15 @@ impl Tracker { let location = dest.location(&self.path_backing); let change = dest.change.clone(); let dest = visit::Destination { change, location }; + let src_idx = src.as_ref().map(|t| t.1); + let res = cb(dest, src.map(|t| t.0)); + self.items[dest_idx].emitted = true; - if let Some(src_idx) = src.as_ref().map(|t| t.1) { + if let Some(src_idx) = src_idx { self.items[src_idx].emitted = true; } - if cb(dest, src.map(|t| t.0)) == crate::tree::visit::Action::Cancel { + + if res == crate::tree::visit::Action::Cancel { return Ok(crate::tree::visit::Action::Cancel); } } diff --git a/gix-diff/src/tree/mod.rs b/gix-diff/src/tree/mod.rs index 77e125e910e..0c87e203806 100644 --- a/gix-diff/src/tree/mod.rs +++ b/gix-diff/src/tree/mod.rs @@ -34,9 +34,11 @@ where } /// +#[allow(clippy::empty_docs)] pub mod changes; /// +#[allow(clippy::empty_docs)] pub mod visit; #[doc(inline)] pub use visit::Visit; diff --git a/gix-diff/tests/Cargo.toml b/gix-diff/tests/Cargo.toml index e63c3d5a320..695508cf1aa 100644 --- a/gix-diff/tests/Cargo.toml +++ b/gix-diff/tests/Cargo.toml @@ -25,3 +25,4 @@ gix-filter = { path = "../../gix-filter" } gix-traverse = { path = "../../gix-traverse" } gix-testtools = { path = "../../tests/tools" } shell-words = "1" +pretty_assertions = "1.4.0" diff --git a/gix-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz b/gix-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz index 18e3c75e651..64826afc646 100644 Binary files a/gix-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz and b/gix-diff/tests/fixtures/generated-archives/make_diff_repo.tar.xz differ diff --git a/gix-diff/tests/rewrites/mod.rs b/gix-diff/tests/rewrites/mod.rs index be44ef39eda..ddcb12dfc86 100644 --- a/gix-diff/tests/rewrites/mod.rs +++ b/gix-diff/tests/rewrites/mod.rs @@ -4,7 +4,7 @@ use gix_object::tree::{EntryKind, EntryMode}; mod tracker; -#[derive(Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Change { id: ObjectId, kind: ChangeKind, diff --git a/gix-diff/tests/rewrites/tracker.rs b/gix-diff/tests/rewrites/tracker.rs index f522fbec312..9b3f4f5a928 100644 --- a/gix-diff/tests/rewrites/tracker.rs +++ b/gix-diff/tests/rewrites/tracker.rs @@ -12,6 +12,7 @@ use gix_diff::{ Rewrites, }; use gix_object::tree::EntryKind; +use pretty_assertions::assert_eq; use crate::{ hex_to_id, @@ -51,6 +52,7 @@ fn rename_by_id() -> crate::Result { id: NULL_ID, kind: SourceKind::Rename, location: "b".into(), + change: &Change::deletion(), diff: None, } ); @@ -146,11 +148,16 @@ fn copy_by_id() -> crate::Result { let out = util::assert_emit_with_objects( &mut track, |dst, src| { + let id = hex_to_id("2e65efe2a145dda7ee51d1741299f848e5bf752e"); let source_a = Source { entry_mode: EntryKind::Blob.into(), - id: hex_to_id("2e65efe2a145dda7ee51d1741299f848e5bf752e"), + id, kind: SourceKind::Copy, location: "a".into(), + change: &Change { + id, + ..Change::modification() + }, diff: None, }; match calls { @@ -219,6 +226,10 @@ fn copy_by_id_search_in_all_sources() -> crate::Result { id: content_id, kind: SourceKind::Copy, location: "a-src".into(), + change: &Change { + id: content_id, + ..Change::modification() + }, diff: None, }; match calls { @@ -289,11 +300,16 @@ fn copy_by_50_percent_similarity() -> crate::Result { let out = util::assert_emit_with_objects( &mut track, |dst, src| { + let id = hex_to_id("78981922613b2afb6025042ff6bd878ac1994e85"); let source_a = Source { entry_mode: EntryKind::Blob.into(), - id: hex_to_id("78981922613b2afb6025042ff6bd878ac1994e85"), + id, kind: SourceKind::Copy, location: "a".into(), + change: &Change { + id, + ..Change::modification() + }, diff: Some(DiffLineStats { removals: 0, insertions: 1, @@ -459,13 +475,18 @@ fn rename_by_50_percent_similarity() -> crate::Result { |dst, src| { match calls { 0 => { + let id = hex_to_id("66a52ee7a1d803dc57859c3e95ac9dcdc87c0164"); assert_eq!( src.unwrap(), Source { entry_mode: EntryKind::Blob.into(), - id: hex_to_id("66a52ee7a1d803dc57859c3e95ac9dcdc87c0164"), + id, kind: SourceKind::Rename, location: "a".into(), + change: &Change { + id, + ..Change::deletion() + }, diff: Some(DiffLineStats { removals: 1, insertions: 1, @@ -532,7 +553,7 @@ fn add_only() -> crate::Result { let out = util::assert_emit(&mut track, |dst, src| { assert!(!called); called = true; - assert_eq!(src, None, "there is just a single deletion, no pair"); + assert!(src.is_none(), "there is just a single deletion, no pair"); assert_eq!(dst.location, "a"); assert_eq!(dst.change.kind, ChangeKind::Addition); Action::Continue @@ -569,14 +590,14 @@ mod util { pub fn assert_emit( tracker: &mut rewrites::Tracker, - cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, + cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, ) -> rewrites::Outcome { assert_emit_with_objects(tracker, cb, gix_object::find::Never) } pub fn assert_emit_with_objects( tracker: &mut rewrites::Tracker, - cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, + cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, objects: impl gix_object::FindObjectOrHeader, ) -> rewrites::Outcome { assert_emit_with_objects_and_sources(tracker, cb, objects, None) @@ -584,7 +605,7 @@ mod util { pub fn assert_emit_with_objects_and_sources<'a>( tracker: &mut rewrites::Tracker, - cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, + cb: impl FnMut(Destination<'_, Change>, Option>) -> Action, objects: impl gix_object::FindObjectOrHeader, sources: impl IntoIterator, ) -> rewrites::Outcome { diff --git a/gix-dir/Cargo.toml b/gix-dir/Cargo.toml index aa532d5f654..a05672c1905 100644 --- a/gix-dir/Cargo.toml +++ b/gix-dir/Cargo.toml @@ -24,7 +24,7 @@ gix-ignore = { version = "^0.11.1", path = "../gix-ignore" } gix-utils = { version = "^0.1.10", path = "../gix-utils", features = ["bstr"] } bstr = { version = "1.5.0", default-features = false } -thiserror = "1.0.56" +thiserror = "1.0.34" [dev-dependencies] gix-testtools = { path = "../tests/tools" } diff --git a/gix-dir/src/entry.rs b/gix-dir/src/entry.rs index 81c6fae5204..4bfc02e02a4 100644 --- a/gix-dir/src/entry.rs +++ b/gix-dir/src/entry.rs @@ -168,6 +168,9 @@ impl Status { /// This implements the default rules of `git status`, which is good for a minimal traversal through /// tracked and non-ignored portions of a worktree. /// `for_deletion` is used to determine if recursion into a directory is allowed even though it otherwise wouldn't be. + /// If `worktree_root_is_repository` is `true`, then this status is part of the root of an iteration, and the corresponding + /// worktree root is a repository itself. This typically happens for submodules. In this case, recursion rules are relaxed + /// to allow traversing submodule worktrees. /// /// Use `pathspec_match` to determine if a pathspec matches in any way, affecting the decision to recurse. pub fn can_recurse( @@ -175,8 +178,15 @@ impl Status { file_type: Option, pathspec_match: Option, for_deletion: Option, + worktree_root_is_repository: bool, ) -> bool { - let is_dir_on_disk = file_type.map_or(false, |ft| ft.is_recursable_dir()); + let is_dir_on_disk = file_type.map_or(false, |ft| { + if worktree_root_is_repository { + ft.is_dir() + } else { + ft.is_recursable_dir() + } + }); if !is_dir_on_disk { return false; } diff --git a/gix-dir/src/lib.rs b/gix-dir/src/lib.rs index fffe2d46eb4..79565773bd2 100644 --- a/gix-dir/src/lib.rs +++ b/gix-dir/src/lib.rs @@ -59,8 +59,10 @@ pub struct Entry { } /// +#[allow(clippy::empty_docs)] pub mod entry; /// +#[allow(clippy::empty_docs)] pub mod walk; pub use walk::function::walk; diff --git a/gix-dir/src/walk/classify.rs b/gix-dir/src/walk/classify.rs index 4b89911d3d1..3d5f5c3f913 100644 --- a/gix-dir/src/walk/classify.rs +++ b/gix-dir/src/walk/classify.rs @@ -4,8 +4,7 @@ use std::borrow::Cow; use crate::entry::PathspecMatch; use crate::walk::{Context, Error, ForDeletionMode, Options}; use bstr::{BStr, BString, ByteSlice}; -use std::ffi::OsStr; -use std::path::{Component, Path, PathBuf}; +use std::path::{Path, PathBuf}; /// Classify the `worktree_relative_root` path and return the first `PathKind` that indicates that /// it isn't a directory, leaving `buf` with the path matching the returned `PathKind`, @@ -16,20 +15,18 @@ pub fn root( worktree_relative_root: &Path, options: Options, ctx: &mut Context<'_>, -) -> Result { +) -> Result<(Outcome, bool), Error> { buf.clear(); let mut last_length = None; let mut path_buf = worktree_root.to_owned(); // These initial values kick in if worktree_relative_root.is_empty(); - let mut out = None; - for component in worktree_relative_root - .components() - .chain(if worktree_relative_root.as_os_str().is_empty() { - Some(Component::Normal(OsStr::new(""))) - } else { - None - }) - { + let file_kind = path_buf.symlink_metadata().map(|m| m.file_type().into()).ok(); + let mut out = path(&mut path_buf, buf, 0, file_kind, || None, options, ctx)?; + let worktree_root_is_repository = out + .disk_kind + .map_or(false, |kind| matches!(kind, entry::Kind::Repository)); + + for component in worktree_relative_root.components() { if last_length.is_some() { buf.push(b'/'); } @@ -37,7 +34,7 @@ pub fn root( buf.extend_from_slice(gix_path::os_str_into_bstr(component.as_os_str()).expect("no illformed UTF8")); let file_kind = path_buf.symlink_metadata().map(|m| m.file_type().into()).ok(); - let res = path( + out = path( &mut path_buf, buf, last_length.map(|l| l + 1 /* slash */).unwrap_or_default(), @@ -46,16 +43,17 @@ pub fn root( options, ctx, )?; - out = Some(res); - if !res - .status - .can_recurse(res.disk_kind, res.pathspec_match, options.for_deletion) - { + if !out.status.can_recurse( + out.disk_kind, + out.pathspec_match, + options.for_deletion, + worktree_root_is_repository, + ) { break; } last_length = Some(buf.len()); } - Ok(out.expect("One iteration of the loop at least")) + Ok((out, worktree_root_is_repository)) } /// The product of [`path()`] calls. #[derive(Debug, Copy, Clone)] diff --git a/gix-dir/src/walk/function.rs b/gix-dir/src/walk/function.rs index a7ec42f7a3d..5d75311eaf3 100644 --- a/gix-dir/src/walk/function.rs +++ b/gix-dir/src/walk/function.rs @@ -60,15 +60,21 @@ pub fn walk( let (mut current, worktree_root_relative) = assure_no_symlink_in_root(worktree_root, &root)?; let mut out = Outcome::default(); let mut buf = BString::default(); - let root_info = classify::root( + let (root_info, worktree_root_is_repository) = classify::root( worktree_root, &mut buf, worktree_root_relative.as_ref(), options, &mut ctx, )?; - if !can_recurse(buf.as_bstr(), root_info, options.for_deletion, delegate) { - if buf.is_empty() && !matches!(root_info.disk_kind, Some(entry::Kind::Directory { .. })) { + if !can_recurse( + buf.as_bstr(), + root_info, + options.for_deletion, + worktree_root_is_repository, /* is root */ + delegate, + ) { + if buf.is_empty() && !root_info.disk_kind.map_or(false, |kind| kind.is_dir()) { return Err(Error::WorktreeRootIsFile { root: root.to_owned() }); } if options.precompose_unicode { @@ -141,12 +147,17 @@ pub(super) fn can_recurse( rela_path: &BStr, info: classify::Outcome, for_deletion: Option, + is_root: bool, delegate: &mut dyn Delegate, ) -> bool { if info.disk_kind.map_or(true, |k| !k.is_dir()) { return false; } - delegate.can_recurse(EntryRef::from_outcome(Cow::Borrowed(rela_path), info), for_deletion) + delegate.can_recurse( + EntryRef::from_outcome(Cow::Borrowed(rela_path), info), + for_deletion, + is_root, + ) } /// Possibly emit an entry to `for_each` in case the provided information makes that possible. diff --git a/gix-dir/src/walk/mod.rs b/gix-dir/src/walk/mod.rs index 3f593c40d9b..026edd485c3 100644 --- a/gix-dir/src/walk/mod.rs +++ b/gix-dir/src/walk/mod.rs @@ -69,12 +69,24 @@ pub trait Delegate { /// Use `for_deletion` to specify if the seen entries should ultimately be deleted, which may affect the decision /// of whether to resource or not. /// + /// If `worktree_root_is_repository` is `true`, then this status is part of the root of an iteration, and the corresponding + /// worktree root is a repository itself. This typically happens for submodules. In this case, recursion rules are relaxed + /// to allow traversing submodule worktrees. + /// /// Note that this method will see all directories, even though not all of them may end up being [emitted](Self::emit()). /// If this method returns `false`, the `entry` will always be emitted. - fn can_recurse(&mut self, entry: EntryRef<'_>, for_deletion: Option) -> bool { - entry - .status - .can_recurse(entry.disk_kind, entry.pathspec_match, for_deletion) + fn can_recurse( + &mut self, + entry: EntryRef<'_>, + for_deletion: Option, + worktree_root_is_repository: bool, + ) -> bool { + entry.status.can_recurse( + entry.disk_kind, + entry.pathspec_match, + for_deletion, + worktree_root_is_repository, + ) } } diff --git a/gix-dir/src/walk/readdir.rs b/gix-dir/src/walk/readdir.rs index 05bc5a93301..cda368a05fc 100644 --- a/gix-dir/src/walk/readdir.rs +++ b/gix-dir/src/walk/readdir.rs @@ -63,7 +63,13 @@ pub(super) fn recursive( ctx, )?; - if can_recurse(current_bstr.as_bstr(), info, opts.for_deletion, delegate) { + if can_recurse( + current_bstr.as_bstr(), + info, + opts.for_deletion, + false, /* is root */ + delegate, + ) { let subdir_may_collapse = state.may_collapse(current); let (action, subdir_prevent_collapse) = recursive( subdir_may_collapse, diff --git a/gix-dir/tests/fixtures/many.sh b/gix-dir/tests/fixtures/many.sh index 0c17c4d3a3b..ee4250dbb97 100644 --- a/gix-dir/tests/fixtures/many.sh +++ b/gix-dir/tests/fixtures/many.sh @@ -29,6 +29,13 @@ git init dir-with-tracked-file git commit -m "init" ) +git init repo-with-submodule +(cd repo-with-submodule + git submodule add ../dir-with-tracked-file submodule + git commit -m "add submodule" + touch submodule/untracked +) + git init ignored-dir (cd ignored-dir mkdir dir diff --git a/gix-dir/tests/walk/mod.rs b/gix-dir/tests/walk/mod.rs index 823bf360370..34e8ec7d42f 100644 --- a/gix-dir/tests/walk/mod.rs +++ b/gix-dir/tests/walk/mod.rs @@ -2631,6 +2631,130 @@ fn root_may_not_go_through_dot_git() -> crate::Result { Ok(()) } +#[test] +fn root_at_submodule_repository_allows_walk() -> crate::Result { + let root = fixture("repo-with-submodule"); + let troot = root.join("submodule"); + let ((out, _root), entries) = try_collect_filtered_opts_collect_with_root( + &troot, + None, + Some(&troot), + |keep, ctx| { + walk( + &troot, + ctx, + walk::Options { + emit_tracked: true, + emit_untracked: Matching, + ..options() + }, + keep, + ) + }, + None::<&str>, + Options::git_dir("../.git/modules/submodule"), + )?; + + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 2, + returned_entries: entries.len(), + seen_entries: 3, + } + ); + + assert_eq!( + entries, + [entry("dir/file", Tracked, File), entry("untracked", Untracked, File)], + "this is a special case to allow walking submodules specifically, like a normal repository" + ); + Ok(()) +} + +#[test] +fn root_in_submodule_repository_allows_walk() -> crate::Result { + let root = fixture("repo-with-submodule"); + let troot = root.join("submodule"); + let ((out, _root), entries) = try_collect_filtered_opts_collect_with_root( + &troot, + None, + Some(&troot.join("dir")), + |keep, ctx| { + walk( + &troot, + ctx, + walk::Options { + emit_tracked: true, + emit_untracked: Matching, + ..options() + }, + keep, + ) + }, + None::<&str>, + Options::git_dir("../.git/modules/submodule"), + )?; + + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 1, + returned_entries: entries.len(), + seen_entries: 1, + } + ); + + assert_eq!( + entries, + [entry("dir/file", Tracked, File)], + "it's also working if the traversal root is inside the subdmodule" + ); + Ok(()) +} + +#[test] +fn root_in_submodule_from_superproject_repository_allows_walk() -> crate::Result { + let root = fixture("repo-with-submodule"); + let troot = root.join("submodule").join("dir"); + let ((out, _root), entries) = try_collect_filtered_opts_collect_with_root( + &root, + None, + Some(&troot), + |keep, ctx| { + walk( + &troot, + ctx, + walk::Options { + emit_tracked: true, + emit_untracked: Matching, + ..options() + }, + keep, + ) + }, + None::<&str>, + Default::default(), + )?; + + assert_eq!( + out, + walk::Outcome { + read_dir_calls: 1, + returned_entries: entries.len(), + seen_entries: 1, + } + ); + + assert_eq!( + entries, + [entry("file", Untracked, File)], + "there is no index that has 'file' in it (it's 'dir/file'), hence it's untracked.\ + But the traversal is possible, even though it might not make the most sense." + ); + Ok(()) +} + #[test] fn root_enters_directory_with_dot_git_in_reconfigured_worktree_tracked() -> crate::Result { let root = fixture("nonstandard-worktree"); @@ -2797,7 +2921,8 @@ fn root_may_not_go_through_submodule() -> crate::Result { assert_eq!( entries, [entry("submodule", Tracked, Repository)], - "it refuses to start traversal in a submodule, thus it ends in the directory that is the submodule" + "it refuses to start traversal in a submodule, thus it ends in the directory that is the submodule, \ + if the root is another repository" ); Ok(()) } diff --git a/gix-discover/src/lib.rs b/gix-discover/src/lib.rs index a035c5cf6bb..d86fa0e1f75 100644 --- a/gix-discover/src/lib.rs +++ b/gix-discover/src/lib.rs @@ -11,9 +11,11 @@ pub const DOT_GIT_DIR: &str = ".git"; pub const MODULES: &str = "modules"; /// +#[allow(clippy::empty_docs)] pub mod repository; /// +#[allow(clippy::empty_docs)] pub mod is_git { use std::path::PathBuf; @@ -46,11 +48,14 @@ mod is; pub use is::{bare as is_bare, git as is_git, submodule_git_dir as is_submodule_git_dir}; /// +#[allow(clippy::empty_docs)] pub mod upwards; pub use upwards::function::{discover as upwards, discover_opts as upwards_opts}; /// +#[allow(clippy::empty_docs)] pub mod path; /// +#[allow(clippy::empty_docs)] pub mod parse; diff --git a/gix-discover/src/parse.rs b/gix-discover/src/parse.rs index 58971832755..7ff11fb7da2 100644 --- a/gix-discover/src/parse.rs +++ b/gix-discover/src/parse.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use bstr::ByteSlice; /// +#[allow(clippy::empty_docs)] pub mod gitdir { use bstr::BString; diff --git a/gix-discover/src/path.rs b/gix-discover/src/path.rs index f1e446f15d2..2eb841b3d79 100644 --- a/gix-discover/src/path.rs +++ b/gix-discover/src/path.rs @@ -3,6 +3,7 @@ use std::{io::Read, path::PathBuf}; use crate::DOT_GIT_DIR; /// +#[allow(clippy::empty_docs)] pub mod from_gitdir_file { /// The error returned by [`from_gitdir_file()`][crate::path::from_gitdir_file()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-discover/tests/path/mod.rs b/gix-discover/tests/path/mod.rs index b047810bbbd..b0bac5468fa 100644 --- a/gix-discover/tests/path/mod.rs +++ b/gix-discover/tests/path/mod.rs @@ -4,7 +4,7 @@ mod from_git_dir_file { path::{Path, PathBuf}, }; - use gix_testtools::{tempfile, tempfile::NamedTempFile}; + use gix_testtools::tempfile::NamedTempFile; #[cfg(not(windows))] #[test] diff --git a/gix-features/src/fs.rs b/gix-features/src/fs.rs index 6f9d2191d67..97f40c367f9 100644 --- a/gix-features/src/fs.rs +++ b/gix-features/src/fs.rs @@ -116,6 +116,7 @@ mod walkdir_precompose { } /// +#[allow(clippy::empty_docs)] #[cfg(feature = "fs-read-dir")] pub mod read_dir { use std::borrow::Cow; @@ -142,6 +143,7 @@ pub mod read_dir { } /// +#[allow(clippy::empty_docs)] #[cfg(feature = "fs-walkdir-parallel")] pub mod walkdir { use std::borrow::Cow; @@ -263,6 +265,7 @@ pub mod walkdir { } /// +#[allow(clippy::empty_docs)] #[cfg(all(feature = "walkdir", not(feature = "fs-walkdir-parallel")))] pub mod walkdir { use std::borrow::Cow; diff --git a/gix-features/src/lib.rs b/gix-features/src/lib.rs index 18342b1d92e..a5674d28206 100644 --- a/gix-features/src/lib.rs +++ b/gix-features/src/lib.rs @@ -15,8 +15,10 @@ #![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod cache; /// +#[allow(clippy::empty_docs)] pub mod decode; pub mod fs; pub mod hash; @@ -30,10 +32,12 @@ pub mod threading; pub use gix_trace as trace; /// +#[allow(clippy::empty_docs)] #[cfg(feature = "zlib")] pub mod zlib; /// +#[allow(clippy::empty_docs)] pub mod iter { /// An iterator over chunks of input, producing `Vec` with a size of `size`, with the last chunk being the remainder and thus /// potentially smaller than `size`. diff --git a/gix-features/src/parallel/mod.rs b/gix-features/src/parallel/mod.rs index 5a0a4b5890d..a3000c86c31 100644 --- a/gix-features/src/parallel/mod.rs +++ b/gix-features/src/parallel/mod.rs @@ -174,5 +174,6 @@ where } /// +#[allow(clippy::empty_docs)] pub mod reduce; pub use reduce::Reduce; diff --git a/gix-features/src/zlib/mod.rs b/gix-features/src/zlib/mod.rs index f55660075eb..cc977b4faa8 100644 --- a/gix-features/src/zlib/mod.rs +++ b/gix-features/src/zlib/mod.rs @@ -49,4 +49,5 @@ impl Inflate { } /// +#[allow(clippy::empty_docs)] pub mod stream; diff --git a/gix-features/src/zlib/stream/mod.rs b/gix-features/src/zlib/stream/mod.rs index 7fb239d3644..c944412e093 100644 --- a/gix-features/src/zlib/stream/mod.rs +++ b/gix-features/src/zlib/stream/mod.rs @@ -1,4 +1,6 @@ /// +#[allow(clippy::empty_docs)] pub mod deflate; /// +#[allow(clippy::empty_docs)] pub mod inflate; diff --git a/gix-filter/src/driver/delayed.rs b/gix-filter/src/driver/delayed.rs index 4599ac61098..434afb38c81 100644 --- a/gix-filter/src/driver/delayed.rs +++ b/gix-filter/src/driver/delayed.rs @@ -6,6 +6,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod list { use crate::driver; @@ -23,6 +24,7 @@ pub mod list { } /// +#[allow(clippy::empty_docs)] pub mod fetch { use crate::driver; diff --git a/gix-filter/src/driver/mod.rs b/gix-filter/src/driver/mod.rs index 43d3edb2f8b..f85c996ee0c 100644 --- a/gix-filter/src/driver/mod.rs +++ b/gix-filter/src/driver/mod.rs @@ -3,18 +3,23 @@ use std::collections::HashMap; use bstr::{BStr, BString, ByteSlice, ByteVec}; /// +#[allow(clippy::empty_docs)] pub mod init; /// +#[allow(clippy::empty_docs)] pub mod apply; /// +#[allow(clippy::empty_docs)] pub mod shutdown; /// +#[allow(clippy::empty_docs)] pub mod delayed; /// +#[allow(clippy::empty_docs)] pub mod process; /// A literal driver process. diff --git a/gix-filter/src/driver/process/client.rs b/gix-filter/src/driver/process/client.rs index 912b1629091..40a3c97e3c8 100644 --- a/gix-filter/src/driver/process/client.rs +++ b/gix-filter/src/driver/process/client.rs @@ -8,6 +8,7 @@ use crate::driver::{ }; /// +#[allow(clippy::empty_docs)] pub mod handshake { /// The error returned by [Client::handshake()][super::Client::handshake()]. #[derive(Debug, thiserror::Error)] @@ -23,6 +24,7 @@ pub mod handshake { } /// +#[allow(clippy::empty_docs)] pub mod invoke { /// The error returned by [Client::invoke()][super::Client::invoke()]. #[derive(Debug, thiserror::Error)] @@ -33,6 +35,7 @@ pub mod invoke { } /// + #[allow(clippy::empty_docs)] pub mod without_content { /// The error returned by [Client::invoke_without_content()][super::super::Client::invoke_without_content()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-filter/src/driver/process/mod.rs b/gix-filter/src/driver/process/mod.rs index c2f62ddd260..d8e95806ef7 100644 --- a/gix-filter/src/driver/process/mod.rs +++ b/gix-filter/src/driver/process/mod.rs @@ -104,9 +104,11 @@ impl Status { } /// +#[allow(clippy::empty_docs)] pub mod client; /// +#[allow(clippy::empty_docs)] pub mod server; type PacketlineReader<'a, T = std::process::ChildStdout> = diff --git a/gix-filter/src/driver/process/server.rs b/gix-filter/src/driver/process/server.rs index 9a634e5ad38..7b2ad17afd7 100644 --- a/gix-filter/src/driver/process/server.rs +++ b/gix-filter/src/driver/process/server.rs @@ -14,6 +14,7 @@ pub struct Request<'a> { } /// +#[allow(clippy::empty_docs)] pub mod next_request { use bstr::BString; @@ -31,6 +32,7 @@ pub mod next_request { } /// +#[allow(clippy::empty_docs)] pub mod handshake { /// The error returned by [Server::handshake()][super::Server::handshake()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-filter/src/eol/mod.rs b/gix-filter/src/eol/mod.rs index ad1553826e0..7cd7be7eb3f 100644 --- a/gix-filter/src/eol/mod.rs +++ b/gix-filter/src/eol/mod.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod convert_to_git; pub use convert_to_git::function::convert_to_git; diff --git a/gix-filter/src/lib.rs b/gix-filter/src/lib.rs index fbe250607e5..b816237b80c 100644 --- a/gix-filter/src/lib.rs +++ b/gix-filter/src/lib.rs @@ -29,6 +29,7 @@ pub mod worktree; pub mod driver; /// +#[allow(clippy::empty_docs)] pub mod pipeline; /// The standard git filter pipeline comprised of multiple standard filters and support for external filters. diff --git a/gix-filter/src/pipeline/convert.rs b/gix-filter/src/pipeline/convert.rs index 0572dd451b1..8154f98e40b 100644 --- a/gix-filter/src/pipeline/convert.rs +++ b/gix-filter/src/pipeline/convert.rs @@ -5,6 +5,7 @@ use bstr::BStr; use crate::{driver, eol, ident, pipeline::util::Configuration, worktree, Pipeline}; /// +#[allow(clippy::empty_docs)] pub mod configuration { use bstr::BString; @@ -20,6 +21,7 @@ pub mod configuration { } /// +#[allow(clippy::empty_docs)] pub mod to_git { /// A function that fills `buf` `fn(&mut buf)` with the data stored in the index of the file that should be converted. pub type IndexObjectFn<'a> = dyn FnMut(&mut Vec) -> Result, gix_object::find::Error> + 'a; @@ -44,6 +46,7 @@ pub mod to_git { } /// +#[allow(clippy::empty_docs)] pub mod to_worktree { /// The error returned by [Pipeline::convert_to_worktree()][super::Pipeline::convert_to_worktree()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-filter/src/pipeline/mod.rs b/gix-filter/src/pipeline/mod.rs index 7dff070a4ce..a5f556ee8d5 100644 --- a/gix-filter/src/pipeline/mod.rs +++ b/gix-filter/src/pipeline/mod.rs @@ -110,6 +110,7 @@ impl Pipeline { } /// +#[allow(clippy::empty_docs)] pub mod convert; pub(crate) mod util; diff --git a/gix-filter/src/worktree/encoding.rs b/gix-filter/src/worktree/encoding.rs index 0b75adc96aa..df4b1ee7985 100644 --- a/gix-filter/src/worktree/encoding.rs +++ b/gix-filter/src/worktree/encoding.rs @@ -2,6 +2,7 @@ use bstr::BStr; use encoding_rs::Encoding; /// +#[allow(clippy::empty_docs)] pub mod for_label { use bstr::BString; diff --git a/gix-filter/src/worktree/mod.rs b/gix-filter/src/worktree/mod.rs index 3b13ea49ede..167d1266e32 100644 --- a/gix-filter/src/worktree/mod.rs +++ b/gix-filter/src/worktree/mod.rs @@ -5,12 +5,15 @@ //! can be taken, which we do not yet take unless there is specific examples or problems to solve. /// +#[allow(clippy::empty_docs)] pub mod encoding; /// +#[allow(clippy::empty_docs)] pub mod encode_to_git; pub use encode_to_git::function::encode_to_git; /// +#[allow(clippy::empty_docs)] pub mod encode_to_worktree; pub use encode_to_worktree::function::encode_to_worktree; diff --git a/gix-fs/src/dir/mod.rs b/gix-fs/src/dir/mod.rs index 4c709a6a88a..ae181a3f01f 100644 --- a/gix-fs/src/dir/mod.rs +++ b/gix-fs/src/dir/mod.rs @@ -1,4 +1,6 @@ /// +#[allow(clippy::empty_docs)] pub mod create; /// +#[allow(clippy::empty_docs)] pub mod remove; diff --git a/gix-fs/src/lib.rs b/gix-fs/src/lib.rs index 9d02ea33426..c63d6fe02ea 100644 --- a/gix-fs/src/lib.rs +++ b/gix-fs/src/lib.rs @@ -36,13 +36,16 @@ mod snapshot; pub use snapshot::{FileSnapshot, SharedFileSnapshot, SharedFileSnapshotMut}; /// +#[allow(clippy::empty_docs)] pub mod symlink; /// +#[allow(clippy::empty_docs)] pub mod read_dir; pub use read_dir::function::read_dir; /// +#[allow(clippy::empty_docs)] pub mod dir; /// Like [`std::env::current_dir()`], but it will `precompose_unicode` if that value is true, if the current directory @@ -90,4 +93,5 @@ pub fn is_executable(_metadata: &std::fs::Metadata) -> bool { } /// +#[allow(clippy::empty_docs)] pub mod stack; diff --git a/gix-glob/src/lib.rs b/gix-glob/src/lib.rs index ee8599fc8ec..4ddf98699de 100644 --- a/gix-glob/src/lib.rs +++ b/gix-glob/src/lib.rs @@ -25,11 +25,13 @@ pub struct Pattern { } /// +#[allow(clippy::empty_docs)] pub mod pattern; pub mod search; /// +#[allow(clippy::empty_docs)] pub mod wildmatch; pub use wildmatch::function::wildmatch; diff --git a/gix-glob/src/search/mod.rs b/gix-glob/src/search/mod.rs index b6fb2a49025..da58e1a1fcb 100644 --- a/gix-glob/src/search/mod.rs +++ b/gix-glob/src/search/mod.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; /// +#[allow(clippy::empty_docs)] pub mod pattern; /// A trait to convert bytes into patterns and their associated value. diff --git a/gix-hash/src/kind.rs b/gix-hash/src/kind.rs index ee8600b1b64..5c7a0316b8d 100644 --- a/gix-hash/src/kind.rs +++ b/gix-hash/src/kind.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, str::FromStr}; +use std::str::FromStr; use crate::{oid, Kind, ObjectId}; diff --git a/gix-hash/src/lib.rs b/gix-hash/src/lib.rs index 4c5b91231b7..baa5b9ea9a4 100644 --- a/gix-hash/src/lib.rs +++ b/gix-hash/src/lib.rs @@ -17,9 +17,10 @@ mod object_id; pub use object_id::{decode, ObjectId}; /// +#[allow(clippy::empty_docs)] pub mod prefix; -/// An partial, owned hash possibly identifying an object uniquely, whose non-prefix bytes are zeroed. +/// A partial, owned hash possibly identifying an object uniquely, whose non-prefix bytes are zeroed. /// /// An example would `0000000000000000000000000000000032bd3242`, where `32bd3242` is the prefix, /// which would be able to match all hashes that *start with* `32bd3242`. diff --git a/gix-hash/src/oid.rs b/gix-hash/src/oid.rs index 4bf6648e751..6360712c5b3 100644 --- a/gix-hash/src/oid.rs +++ b/gix-hash/src/oid.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, hash}; +use std::hash; use crate::{Kind, ObjectId, SIZE_OF_SHA1_DIGEST}; diff --git a/gix-hash/src/prefix.rs b/gix-hash/src/prefix.rs index b9d3849ab78..dc1752eba81 100644 --- a/gix-hash/src/prefix.rs +++ b/gix-hash/src/prefix.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, convert::TryFrom}; +use std::cmp::Ordering; use crate::{oid, ObjectId, Prefix}; @@ -16,6 +16,7 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod from_hex { /// The error returned by [`Prefix::from_hex`][super::Prefix::from_hex()]. #[derive(Debug, Eq, PartialEq, thiserror::Error)] diff --git a/gix-hash/tests/prefix/mod.rs b/gix-hash/tests/prefix/mod.rs index 14dbed67af3..357a18869a7 100644 --- a/gix-hash/tests/prefix/mod.rs +++ b/gix-hash/tests/prefix/mod.rs @@ -73,7 +73,7 @@ mod new { } mod try_from { - use std::{cmp::Ordering, convert::TryFrom}; + use std::cmp::Ordering; use gix_hash::{prefix::from_hex::Error, Prefix}; diff --git a/gix-hashtable/src/lib.rs b/gix-hashtable/src/lib.rs index 54935bcbca7..397b754df17 100644 --- a/gix-hashtable/src/lib.rs +++ b/gix-hashtable/src/lib.rs @@ -34,6 +34,7 @@ pub mod sync { } /// +#[allow(clippy::empty_docs)] pub mod hash { /// A Hasher for usage with `HashMap` keys that are already robust hashes (like an `ObjectId`). /// The first `8` bytes of the hash are used as the `HashMap` hash diff --git a/gix-ignore/src/lib.rs b/gix-ignore/src/lib.rs index a9ba2351e3c..5f7cc0c5d1c 100644 --- a/gix-ignore/src/lib.rs +++ b/gix-ignore/src/lib.rs @@ -12,6 +12,7 @@ pub use gix_glob as glob; /// +#[allow(clippy::empty_docs)] pub mod search; /// A grouping of lists of patterns while possibly keeping associated to their base path in order to find matches. /// @@ -45,6 +46,7 @@ pub enum Kind { } /// +#[allow(clippy::empty_docs)] pub mod parse; /// Parse git ignore patterns, line by line, from `bytes`. diff --git a/gix-index/src/access/mod.rs b/gix-index/src/access/mod.rs index 431a47e5805..d5da3c9934a 100644 --- a/gix-index/src/access/mod.rs +++ b/gix-index/src/access/mod.rs @@ -173,7 +173,7 @@ impl State { } } } - gix_features::trace::detail!("stored directories", directories = out.icase_dirs.len()); + gix_features::trace::debug!(directories = out.icase_dirs.len(), "stored directories"); out } diff --git a/gix-index/src/decode/entries.rs b/gix-index/src/decode/entries.rs index 78f544691ae..d3bda1cb6a3 100644 --- a/gix-index/src/decode/entries.rs +++ b/gix-index/src/decode/entries.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, ops::Range}; +use std::ops::Range; use crate::{ decode::{self, header}, diff --git a/gix-index/src/decode/mod.rs b/gix-index/src/decode/mod.rs index aea189248bd..6c77109877a 100644 --- a/gix-index/src/decode/mod.rs +++ b/gix-index/src/decode/mod.rs @@ -4,6 +4,7 @@ use crate::{entry, extension, Entry, State, Version}; mod entries; /// +#[allow(clippy::empty_docs)] pub mod header; mod error { diff --git a/gix-index/src/entry/mod.rs b/gix-index/src/entry/mod.rs index b7fb13f7baf..15fc1d67e36 100644 --- a/gix-index/src/entry/mod.rs +++ b/gix-index/src/entry/mod.rs @@ -6,6 +6,7 @@ pub type Stage = u32; /// +#[allow(clippy::empty_docs)] pub mod mode; mod flags; @@ -13,6 +14,7 @@ pub(crate) use flags::at_rest; pub use flags::Flags; /// +#[allow(clippy::empty_docs)] pub mod stat; mod write; diff --git a/gix-index/src/entry/write.rs b/gix-index/src/entry/write.rs index 2a6ad55884c..66ef24d9a9e 100644 --- a/gix-index/src/entry/write.rs +++ b/gix-index/src/entry/write.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::{entry, Entry, State}; impl Entry { diff --git a/gix-index/src/extension/decode.rs b/gix-index/src/extension/decode.rs index fa0a624104b..4114f8c8f20 100644 --- a/gix-index/src/extension/decode.rs +++ b/gix-index/src/extension/decode.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::{extension, extension::Signature, util::from_be_u32}; pub(crate) fn header(data: &[u8]) -> (Signature, u32, &[u8]) { diff --git a/gix-index/src/extension/iter.rs b/gix-index/src/extension/iter.rs index f791b064eb4..ed0a457343d 100644 --- a/gix-index/src/extension/iter.rs +++ b/gix-index/src/extension/iter.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::{extension, extension::Iter, util::from_be_u32}; impl<'a> Iter<'a> { diff --git a/gix-index/src/extension/link.rs b/gix-index/src/extension/link.rs index 722cbd17971..f2d52ca3d43 100644 --- a/gix-index/src/extension/link.rs +++ b/gix-index/src/extension/link.rs @@ -16,6 +16,7 @@ pub struct Bitmaps { } /// +#[allow(clippy::empty_docs)] pub mod decode { /// The error returned when decoding link extensions. diff --git a/gix-index/src/extension/mod.rs b/gix-index/src/extension/mod.rs index 33b91c5517b..3b51de6b20d 100644 --- a/gix-index/src/extension/mod.rs +++ b/gix-index/src/extension/mod.rs @@ -74,23 +74,29 @@ mod iter; pub(crate) mod fs_monitor; /// +#[allow(clippy::empty_docs)] pub mod decode; /// +#[allow(clippy::empty_docs)] pub mod tree; /// +#[allow(clippy::empty_docs)] pub mod end_of_index_entry; pub(crate) mod index_entry_offset_table; /// +#[allow(clippy::empty_docs)] pub mod link; pub(crate) mod resolve_undo; /// +#[allow(clippy::empty_docs)] pub mod untracked_cache; /// +#[allow(clippy::empty_docs)] pub mod sparse; diff --git a/gix-index/src/extension/tree/decode.rs b/gix-index/src/extension/tree/decode.rs index a55d12269a1..71e881ac38a 100644 --- a/gix-index/src/extension/tree/decode.rs +++ b/gix-index/src/extension/tree/decode.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_hash::ObjectId; use crate::{ diff --git a/gix-index/src/extension/tree/mod.rs b/gix-index/src/extension/tree/mod.rs index f2075939978..8831530e590 100644 --- a/gix-index/src/extension/tree/mod.rs +++ b/gix-index/src/extension/tree/mod.rs @@ -4,6 +4,7 @@ use crate::extension::Signature; pub const SIGNATURE: Signature = *b"TREE"; /// +#[allow(clippy::empty_docs)] pub mod verify; mod decode; diff --git a/gix-index/src/extension/tree/write.rs b/gix-index/src/extension/tree/write.rs index 2b1e3d94950..354ff0d393e 100644 --- a/gix-index/src/extension/tree/write.rs +++ b/gix-index/src/extension/tree/write.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use crate::extension::{tree, Tree}; impl Tree { diff --git a/gix-index/src/extension/untracked_cache.rs b/gix-index/src/extension/untracked_cache.rs index f3b2d8afdcb..c47d6883745 100644 --- a/gix-index/src/extension/untracked_cache.rs +++ b/gix-index/src/extension/untracked_cache.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use bstr::BString; use gix_hash::ObjectId; diff --git a/gix-index/src/file/mod.rs b/gix-index/src/file/mod.rs index 40332abbd0e..68f6e6268c7 100644 --- a/gix-index/src/file/mod.rs +++ b/gix-index/src/file/mod.rs @@ -83,8 +83,11 @@ mod mutation { } /// +#[allow(clippy::empty_docs)] pub mod init; /// +#[allow(clippy::empty_docs)] pub mod verify; /// +#[allow(clippy::empty_docs)] pub mod write; diff --git a/gix-index/src/lib.rs b/gix-index/src/lib.rs index 96a218c6355..580b8e59718 100644 --- a/gix-index/src/lib.rs +++ b/gix-index/src/lib.rs @@ -13,12 +13,15 @@ use filetime::FileTime; pub use gix_hash as hash; /// +#[allow(clippy::empty_docs)] pub mod file; /// +#[allow(clippy::empty_docs)] pub mod extension; /// +#[allow(clippy::empty_docs)] pub mod entry; mod access; @@ -26,12 +29,15 @@ mod access; mod init; /// +#[allow(clippy::empty_docs)] pub mod decode; /// +#[allow(clippy::empty_docs)] pub mod verify; /// +#[allow(clippy::empty_docs)] pub mod write; pub mod fs; @@ -171,8 +177,6 @@ mod impls { } pub(crate) mod util { - use std::convert::TryInto; - #[inline] pub fn var_int(data: &[u8]) -> Option<(u64, &[u8])> { let (num, consumed) = gix_features::decode::leb64_from_read(data).ok()?; diff --git a/gix-index/src/verify.rs b/gix-index/src/verify.rs index 776f13b3c5f..e01a26fe306 100644 --- a/gix-index/src/verify.rs +++ b/gix-index/src/verify.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use crate::State; /// +#[allow(clippy::empty_docs)] pub mod entries { use bstr::BString; @@ -22,6 +23,7 @@ pub mod entries { } /// +#[allow(clippy::empty_docs)] pub mod extensions { use crate::extension; diff --git a/gix-index/src/write.rs b/gix-index/src/write.rs index 3e90d52e5be..d52ba640ee0 100644 --- a/gix-index/src/write.rs +++ b/gix-index/src/write.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, io::Write}; +use std::io::Write; use crate::{entry, extension, write::util::CountBytes, State, Version}; @@ -183,8 +183,6 @@ fn entries(out: &mut CountBytes, state: &State, header_siz } mod util { - use std::convert::TryFrom; - pub struct CountBytes { pub count: u32, pub inner: T, diff --git a/gix-lock/src/lib.rs b/gix-lock/src/lib.rs index f07d48350cc..04cf1766fdb 100644 --- a/gix-lock/src/lib.rs +++ b/gix-lock/src/lib.rs @@ -24,10 +24,12 @@ use gix_tempfile::handle::{Closed, Writable}; const DOT_LOCK_SUFFIX: &str = ".lock"; /// +#[allow(clippy::empty_docs)] pub mod acquire; pub use gix_utils::backoff; /// +#[allow(clippy::empty_docs)] pub mod commit; /// Locks a resource to eventually be overwritten with the content of this file. @@ -53,4 +55,5 @@ pub struct Marker { } /// +#[allow(clippy::empty_docs)] pub mod file; diff --git a/gix-macros/src/momo.rs b/gix-macros/src/momo.rs index 802f4b5b024..74b1636d30c 100644 --- a/gix-macros/src/momo.rs +++ b/gix-macros/src/momo.rs @@ -98,18 +98,18 @@ pub(crate) fn inner(code: proc_macro2::TokenStream) -> proc_macro2::TokenStream #[derive(Copy, Clone)] // All conversions we support. Check references to this type for an idea how to add more. -enum Conversion<'a> { - Into(&'a Type), - AsRef(&'a Type), - AsMut(&'a Type), +enum Conversion { + Into, + AsRef, + AsMut, } -impl<'a> Conversion<'a> { +impl Conversion { fn conversion_expr(&self, i: &Ident) -> Expr { match *self { - Conversion::Into(_) => parse_quote!(#i.into()), - Conversion::AsRef(_) => parse_quote!(#i.as_ref()), - Conversion::AsMut(_) => parse_quote!(#i.as_mut()), + Conversion::Into => parse_quote!(#i.into()), + Conversion::AsRef => parse_quote!(#i.as_ref()), + Conversion::AsMut => parse_quote!(#i.as_mut()), } } } @@ -128,13 +128,13 @@ fn parse_bounds(bounds: &Punctuated) -> Option) -> Option HashMap> { +fn parse_generics(decl: &Signature) -> HashMap { let mut ty_conversions = HashMap::new(); for gp in decl.generics.params.iter() { if let GenericParam::Type(ref tp) = gp { @@ -167,9 +167,9 @@ fn parse_generics(decl: &Signature) -> HashMap> { ty_conversions } -fn convert<'a>( - inputs: &'a Punctuated, - ty_conversions: &HashMap>, +fn convert( + inputs: &Punctuated, + ty_conversions: &HashMap, ) -> (bool, Punctuated, Punctuated, bool) { let mut has_conversion_in_effect = false; let mut argtypes = Punctuated::new(); @@ -212,7 +212,7 @@ fn convert<'a>( if let Some(conv) = parse_bounds(bounds) { has_conversion_in_effect = true; argexprs.push(conv.conversion_expr(ident)); - if let Conversion::AsMut(_) = conv { + if let Conversion::AsMut = conv { pat_ident.mutability = Some(Default::default()); } } else { @@ -223,7 +223,7 @@ fn convert<'a>( if let Some(conv) = parse_bounded_type(&pat_type.ty).and_then(|ident| ty_conversions.get(&ident)) { has_conversion_in_effect = true; argexprs.push(conv.conversion_expr(ident)); - if let Conversion::AsMut(_) = conv { + if let Conversion::AsMut = conv { pat_ident.mutability = Some(Default::default()); } } else { diff --git a/gix-mailmap/src/lib.rs b/gix-mailmap/src/lib.rs index f6d9821eeba..64844666448 100644 --- a/gix-mailmap/src/lib.rs +++ b/gix-mailmap/src/lib.rs @@ -12,6 +12,7 @@ use bstr::BStr; /// +#[allow(clippy::empty_docs)] pub mod parse; /// Parse the given `buf` of bytes line by line into mapping [Entries][Entry]. @@ -30,6 +31,7 @@ pub fn parse_ignore_errors(buf: &[u8]) -> impl Iterator> { mod entry; /// +#[allow(clippy::empty_docs)] pub mod snapshot; /// A data-structure to efficiently store a list of entries for optimal, case-insensitive lookup by email and diff --git a/gix-object/src/commit/message/mod.rs b/gix-object/src/commit/message/mod.rs index ac5e3224c0d..5bb0046ee90 100644 --- a/gix-object/src/commit/message/mod.rs +++ b/gix-object/src/commit/message/mod.rs @@ -7,6 +7,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod body; mod decode; diff --git a/gix-object/src/commit/mod.rs b/gix-object/src/commit/mod.rs index 17dc12bebd6..dd79b24ae84 100644 --- a/gix-object/src/commit/mod.rs +++ b/gix-object/src/commit/mod.rs @@ -7,6 +7,7 @@ use crate::{Commit, CommitRef, TagRef}; mod decode; /// +#[allow(clippy::empty_docs)] pub mod message; /// A parsed commit message that assumes a title separated from the body by two consecutive newlines. @@ -53,6 +54,7 @@ impl From> for BString { } /// +#[allow(clippy::empty_docs)] pub mod ref_iter; mod write; diff --git a/gix-object/src/find.rs b/gix-object/src/find.rs index 8471592cbc9..5a8c5e36cc2 100644 --- a/gix-object/src/find.rs +++ b/gix-object/src/find.rs @@ -1,6 +1,7 @@ /// The error type returned by the [`Find`](crate::Find) trait. pub type Error = Box; /// +#[allow(clippy::empty_docs)] pub mod existing { use gix_hash::ObjectId; @@ -16,6 +17,7 @@ pub mod existing { } /// +#[allow(clippy::empty_docs)] pub mod existing_object { use gix_hash::ObjectId; @@ -42,6 +44,7 @@ pub mod existing_object { } /// +#[allow(clippy::empty_docs)] pub mod existing_iter { use gix_hash::ObjectId; diff --git a/gix-object/src/lib.rs b/gix-object/src/lib.rs index 43cb5f5bfd0..f8af6a81c94 100644 --- a/gix-object/src/lib.rs +++ b/gix-object/src/lib.rs @@ -19,18 +19,23 @@ pub use gix_date as date; use smallvec::SmallVec; /// +#[allow(clippy::empty_docs)] pub mod commit; mod object; /// +#[allow(clippy::empty_docs)] pub mod tag; /// +#[allow(clippy::empty_docs)] pub mod tree; mod blob; /// +#[allow(clippy::empty_docs)] pub mod data; /// +#[allow(clippy::empty_docs)] pub mod find; mod traits; @@ -40,6 +45,7 @@ pub mod encode; pub(crate) mod parse; /// +#[allow(clippy::empty_docs)] pub mod kind; /// The four types of objects that git differentiates. @@ -267,6 +273,7 @@ pub struct Header { } /// +#[allow(clippy::empty_docs)] pub mod decode { #[cfg(feature = "verbose-object-parsing-errors")] mod _decode { diff --git a/gix-object/src/object/convert.rs b/gix-object/src/object/convert.rs index 5e6e634869d..87ee35b1c48 100644 --- a/gix-object/src/object/convert.rs +++ b/gix-object/src/object/convert.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use crate::{tree, Blob, BlobRef, Commit, CommitRef, Object, ObjectRef, Tag, TagRef, Tree, TreeRef}; impl From> for Tag { diff --git a/gix-object/src/parse.rs b/gix-object/src/parse.rs index e241f79b45e..9401e505cea 100644 --- a/gix-object/src/parse.rs +++ b/gix-object/src/parse.rs @@ -4,7 +4,6 @@ use winnow::{ error::{AddContext, ParserError, StrContext}, prelude::*, token::{take_till, take_until, take_while}, - Parser, }; use crate::ByteSlice; diff --git a/gix-object/src/tag/mod.rs b/gix-object/src/tag/mod.rs index ecd56c9f7db..cc0d50e32ba 100644 --- a/gix-object/src/tag/mod.rs +++ b/gix-object/src/tag/mod.rs @@ -5,9 +5,11 @@ use crate::TagRef; mod decode; /// +#[allow(clippy::empty_docs)] pub mod write; /// +#[allow(clippy::empty_docs)] pub mod ref_iter; impl<'a> TagRef<'a> { diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index 1014fcfde29..0699d1a46db 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -7,6 +7,7 @@ use crate::{ mod ref_iter; /// +#[allow(clippy::empty_docs)] pub mod write; /// The mode of items storable in a tree, similar to the file mode on a unix file system. diff --git a/gix-object/src/tree/ref_iter.rs b/gix-object/src/tree/ref_iter.rs index bdec622d265..2b82a968732 100644 --- a/gix-object/src/tree/ref_iter.rs +++ b/gix-object/src/tree/ref_iter.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use bstr::BStr; use winnow::{error::ParserError, prelude::*}; @@ -124,8 +122,6 @@ impl TryFrom for tree::EntryMode { } mod decode { - use std::convert::TryFrom; - use bstr::ByteSlice; use winnow::{error::ParserError, prelude::*}; diff --git a/gix-odb/src/alternate/mod.rs b/gix-odb/src/alternate/mod.rs index c4e9fc8c040..919a3249ebd 100644 --- a/gix-odb/src/alternate/mod.rs +++ b/gix-odb/src/alternate/mod.rs @@ -21,6 +21,7 @@ use std::{fs, io, path::PathBuf}; use gix_path::realpath::MAX_SYMLINKS; /// +#[allow(clippy::empty_docs)] pub mod parse; /// Returned by [`resolve()`] diff --git a/gix-odb/src/lib.rs b/gix-odb/src/lib.rs index 40d7c2aea90..f034fdaadc9 100644 --- a/gix-odb/src/lib.rs +++ b/gix-odb/src/lib.rs @@ -47,6 +47,7 @@ pub struct Cache { } /// +#[allow(clippy::empty_docs)] pub mod cache; /// @@ -69,6 +70,7 @@ pub fn sink(object_hash: gix_hash::Kind) -> Sink { mod sink; /// +#[allow(clippy::empty_docs)] pub mod find; /// An object database equivalent to `/dev/null`, dropping all objects stored into it. @@ -77,6 +79,7 @@ mod traits; pub use traits::{Header, HeaderExt, Write}; /// +#[allow(clippy::empty_docs)] pub mod write { /// The error type returned by the [`Write`](crate::Write) trait. pub type Error = Box; diff --git a/gix-odb/src/store_impls/dynamic/find.rs b/gix-odb/src/store_impls/dynamic/find.rs index 99b58d7477f..b420dc6ec73 100644 --- a/gix-odb/src/store_impls/dynamic/find.rs +++ b/gix-odb/src/store_impls/dynamic/find.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, ops::Deref}; +use std::ops::Deref; use gix_pack::cache::DecodeEntry; diff --git a/gix-odb/src/store_impls/dynamic/handle.rs b/gix-odb/src/store_impls/dynamic/handle.rs index 655bdfa430c..883e2c1ccb8 100644 --- a/gix-odb/src/store_impls/dynamic/handle.rs +++ b/gix-odb/src/store_impls/dynamic/handle.rs @@ -1,6 +1,5 @@ use std::{ cell::RefCell, - convert::{TryFrom, TryInto}, ops::Deref, rc::Rc, sync::{atomic::Ordering, Arc}, diff --git a/gix-odb/src/store_impls/dynamic/init.rs b/gix-odb/src/store_impls/dynamic/init.rs index 980bacf767b..2031e48c837 100644 --- a/gix-odb/src/store_impls/dynamic/init.rs +++ b/gix-odb/src/store_impls/dynamic/init.rs @@ -1,4 +1,4 @@ -use std::{iter::FromIterator, path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use arc_swap::ArcSwap; diff --git a/gix-odb/src/store_impls/dynamic/mod.rs b/gix-odb/src/store_impls/dynamic/mod.rs index 7ea462b24a1..65ea5361415 100644 --- a/gix-odb/src/store_impls/dynamic/mod.rs +++ b/gix-odb/src/store_impls/dynamic/mod.rs @@ -51,20 +51,25 @@ impl RefreshMode { } /// +#[allow(clippy::empty_docs)] pub mod find; /// +#[allow(clippy::empty_docs)] pub mod prefix; mod header; /// +#[allow(clippy::empty_docs)] pub mod iter; /// +#[allow(clippy::empty_docs)] pub mod write; /// +#[allow(clippy::empty_docs)] pub mod init; pub(crate) mod types; @@ -73,9 +78,11 @@ pub use types::Metrics; pub(crate) mod handle; /// +#[allow(clippy::empty_docs)] pub mod load_index; /// +#[allow(clippy::empty_docs)] pub mod verify; mod load_one; @@ -85,4 +92,5 @@ mod metrics; mod access; /// +#[allow(clippy::empty_docs)] pub mod structure; diff --git a/gix-odb/src/store_impls/dynamic/prefix.rs b/gix-odb/src/store_impls/dynamic/prefix.rs index 0d671400448..b6869d8c2b6 100644 --- a/gix-odb/src/store_impls/dynamic/prefix.rs +++ b/gix-odb/src/store_impls/dynamic/prefix.rs @@ -5,6 +5,7 @@ use gix_object::Exists; use crate::store::{load_index, Handle}; /// +#[allow(clippy::empty_docs)] pub mod lookup { use crate::loose; @@ -24,6 +25,7 @@ pub mod lookup { } /// +#[allow(clippy::empty_docs)] pub mod disambiguate { /// A potentially ambiguous prefix for use with `Handle::disambiguate_prefix()`. #[derive(Debug, Copy, Clone)] diff --git a/gix-odb/src/store_impls/dynamic/verify.rs b/gix-odb/src/store_impls/dynamic/verify.rs index a59ee2c5367..b9a1f605911 100644 --- a/gix-odb/src/store_impls/dynamic/verify.rs +++ b/gix-odb/src/store_impls/dynamic/verify.rs @@ -13,6 +13,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod integrity { use std::{marker::PhantomData, path::PathBuf}; diff --git a/gix-odb/src/store_impls/loose/mod.rs b/gix-odb/src/store_impls/loose/mod.rs index 17e4a33d65a..3dddf03660a 100644 --- a/gix-odb/src/store_impls/loose/mod.rs +++ b/gix-odb/src/store_impls/loose/mod.rs @@ -50,10 +50,13 @@ fn hash_path(id: &gix_hash::oid, mut root: PathBuf) -> PathBuf { } /// +#[allow(clippy::empty_docs)] pub mod find; /// +#[allow(clippy::empty_docs)] pub mod iter; /// +#[allow(clippy::empty_docs)] pub mod verify; /// The type for an iterator over `Result)` @@ -63,4 +66,5 @@ pub struct Iter { } /// +#[allow(clippy::empty_docs)] pub mod write; diff --git a/gix-odb/src/store_impls/loose/verify.rs b/gix-odb/src/store_impls/loose/verify.rs index ae83c1d01cb..2bacfa0cd27 100644 --- a/gix-odb/src/store_impls/loose/verify.rs +++ b/gix-odb/src/store_impls/loose/verify.rs @@ -8,6 +8,7 @@ use gix_features::progress::{Count, DynNestedProgress, Progress}; use crate::{loose::Store, Write}; /// +#[allow(clippy::empty_docs)] pub mod integrity { /// The error returned by [`verify_integrity()`][super::Store::verify_integrity()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-pack/src/bundle/mod.rs b/gix-pack/src/bundle/mod.rs index d8ef1107d70..473e51dbf72 100644 --- a/gix-pack/src/bundle/mod.rs +++ b/gix-pack/src/bundle/mod.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod init; mod find; @@ -7,12 +8,14 @@ mod find; pub mod write; /// +#[allow(clippy::empty_docs)] pub mod verify { use std::sync::atomic::AtomicBool; use gix_features::progress::DynNestedProgress; /// + #[allow(clippy::empty_docs)] pub mod integrity { /// Returned by [`Bundle::verify_integrity()`][crate::Bundle::verify_integrity()]. pub struct Outcome { diff --git a/gix-pack/src/cache/delta/from_offsets.rs b/gix-pack/src/cache/delta/from_offsets.rs index d790dcc0f71..ee52f9ab9dd 100644 --- a/gix-pack/src/cache/delta/from_offsets.rs +++ b/gix-pack/src/cache/delta/from_offsets.rs @@ -1,5 +1,4 @@ use std::{ - convert::TryFrom, fs, io, io::{BufRead, Read, Seek, SeekFrom}, sync::atomic::{AtomicBool, Ordering}, diff --git a/gix-pack/src/cache/delta/mod.rs b/gix-pack/src/cache/delta/mod.rs index c923b5d1078..161432e8c2b 100644 --- a/gix-pack/src/cache/delta/mod.rs +++ b/gix-pack/src/cache/delta/mod.rs @@ -12,9 +12,11 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod traverse; /// +#[allow(clippy::empty_docs)] pub mod from_offsets; /// Tree datastructure diff --git a/gix-pack/src/cache/mod.rs b/gix-pack/src/cache/mod.rs index eabb6a3ed85..d41467c05b9 100644 --- a/gix-pack/src/cache/mod.rs +++ b/gix-pack/src/cache/mod.rs @@ -52,6 +52,7 @@ pub mod lru; pub mod object; /// +#[allow(clippy::empty_docs)] pub(crate) mod delta; /// Replaces content of the given `Vec` with the slice. The vec will have the same length diff --git a/gix-pack/src/data/file/decode/entry.rs b/gix-pack/src/data/file/decode/entry.rs index 2bec3efbd68..dc306baf33a 100644 --- a/gix-pack/src/data/file/decode/entry.rs +++ b/gix-pack/src/data/file/decode/entry.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, ops::Range}; +use std::ops::Range; use gix_features::zlib; use smallvec::SmallVec; diff --git a/gix-pack/src/data/file/decode/mod.rs b/gix-pack/src/data/file/decode/mod.rs index aa4177424a5..3db3eec51ad 100644 --- a/gix-pack/src/data/file/decode/mod.rs +++ b/gix-pack/src/data/file/decode/mod.rs @@ -1,8 +1,10 @@ use std::collections::TryReserveError; /// +#[allow(clippy::empty_docs)] pub mod entry; /// +#[allow(clippy::empty_docs)] pub mod header; /// Returned by [`File::decode_header()`][crate::data::File::decode_header()], diff --git a/gix-pack/src/data/file/init.rs b/gix-pack/src/data/file/init.rs index b160724175d..a005e517fd7 100644 --- a/gix-pack/src/data/file/init.rs +++ b/gix-pack/src/data/file/init.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, path::Path}; +use std::path::Path; use crate::data; diff --git a/gix-pack/src/data/file/mod.rs b/gix-pack/src/data/file/mod.rs index 6bfe0e2721b..936ab51ecb9 100644 --- a/gix-pack/src/data/file/mod.rs +++ b/gix-pack/src/data/file/mod.rs @@ -1,8 +1,10 @@ mod init; /// +#[allow(clippy::empty_docs)] pub mod verify; /// +#[allow(clippy::empty_docs)] pub mod decode; /// The bytes used as header in a pack data file. diff --git a/gix-pack/src/data/file/verify.rs b/gix-pack/src/data/file/verify.rs index ea99144a1e9..9b12209e634 100644 --- a/gix-pack/src/data/file/verify.rs +++ b/gix-pack/src/data/file/verify.rs @@ -5,6 +5,7 @@ use gix_features::progress::Progress; use crate::data::File; /// +#[allow(clippy::empty_docs)] pub mod checksum { /// Returned by [`data::File::verify_checksum()`][crate::data::File::verify_checksum()]. pub type Error = crate::verify::checksum::Error; diff --git a/gix-pack/src/data/header.rs b/gix-pack/src/data/header.rs index 348a4ca24ec..c6c7b8350fd 100644 --- a/gix-pack/src/data/header.rs +++ b/gix-pack/src/data/header.rs @@ -37,6 +37,7 @@ pub fn encode(version: data::Version, num_objects: u32) -> [u8; 12] { } /// +#[allow(clippy::empty_docs)] pub mod decode { /// Returned by [`decode()`][super::decode()]. #[derive(thiserror::Error, Debug)] diff --git a/gix-pack/src/data/input/lookup_ref_delta_objects.rs b/gix-pack/src/data/input/lookup_ref_delta_objects.rs index f6036e20f0e..6b1995293e1 100644 --- a/gix-pack/src/data/input/lookup_ref_delta_objects.rs +++ b/gix-pack/src/data/input/lookup_ref_delta_objects.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_hash::ObjectId; use crate::data::{entry::Header, input}; diff --git a/gix-pack/src/data/mod.rs b/gix-pack/src/data/mod.rs index 9808ae8530c..385aa3d719c 100644 --- a/gix-pack/src/data/mod.rs +++ b/gix-pack/src/data/mod.rs @@ -1,5 +1,5 @@ //! a pack data file -use std::{convert::TryInto, path::Path}; +use std::path::Path; /// The offset to an entry into the pack data file, relative to its beginning. pub type Offset = u64; @@ -26,14 +26,17 @@ pub struct Entry { mod file; pub use file::{decode, verify, Header}; /// +#[allow(clippy::empty_docs)] pub mod header; /// +#[allow(clippy::empty_docs)] pub mod init { pub use super::header::decode::Error; } /// +#[allow(clippy::empty_docs)] pub mod entry; /// diff --git a/gix-pack/src/data/output/count/mod.rs b/gix-pack/src/data/output/count/mod.rs index 481ff65d3c3..c10ec1e51a6 100644 --- a/gix-pack/src/data/output/count/mod.rs +++ b/gix-pack/src/data/output/count/mod.rs @@ -44,6 +44,7 @@ mod objects_impl; pub use objects_impl::{objects, objects_unthreaded}; /// +#[allow(clippy::empty_docs)] pub mod objects { pub use super::objects_impl::{Error, ObjectExpansion, Options, Outcome}; } diff --git a/gix-pack/src/data/output/entry/mod.rs b/gix-pack/src/data/output/entry/mod.rs index 4ab4879eb69..ed79a404d5a 100644 --- a/gix-pack/src/data/output/entry/mod.rs +++ b/gix-pack/src/data/output/entry/mod.rs @@ -1,10 +1,11 @@ -use std::{convert::TryFrom, io::Write}; +use std::io::Write; use gix_hash::ObjectId; use crate::{data, data::output, find}; /// +#[allow(clippy::empty_docs)] pub mod iter_from_counts; pub use iter_from_counts::function::iter_from_counts; diff --git a/gix-pack/src/data/output/mod.rs b/gix-pack/src/data/output/mod.rs index 338bb92b3ea..0e1b97256aa 100644 --- a/gix-pack/src/data/output/mod.rs +++ b/gix-pack/src/data/output/mod.rs @@ -1,6 +1,7 @@ use gix_hash::ObjectId; /// +#[allow(clippy::empty_docs)] pub mod count; /// An item representing a future Entry in the leanest way possible. @@ -35,7 +36,9 @@ pub struct Entry { } /// +#[allow(clippy::empty_docs)] pub mod entry; /// +#[allow(clippy::empty_docs)] pub mod bytes; diff --git a/gix-pack/src/index/mod.rs b/gix-pack/src/index/mod.rs index 8d880744241..f83eb51fd0e 100644 --- a/gix-pack/src/index/mod.rs +++ b/gix-pack/src/index/mod.rs @@ -136,6 +136,7 @@ impl File { const V2_SIGNATURE: &[u8] = b"\xfftOc"; /// +#[allow(clippy::empty_docs)] pub mod init; pub(crate) mod access; @@ -143,9 +144,11 @@ pub use access::Entry; pub(crate) mod encode; /// +#[allow(clippy::empty_docs)] pub mod traverse; mod util; /// +#[allow(clippy::empty_docs)] pub mod verify; /// #[cfg(feature = "streaming-input")] diff --git a/gix-pack/src/index/traverse/mod.rs b/gix-pack/src/index/traverse/mod.rs index 1edf0b1d5dd..29abeac9701 100644 --- a/gix-pack/src/index/traverse/mod.rs +++ b/gix-pack/src/index/traverse/mod.rs @@ -6,8 +6,10 @@ use crate::index; mod reduce; /// +#[allow(clippy::empty_docs)] pub mod with_index; /// +#[allow(clippy::empty_docs)] pub mod with_lookup; use reduce::Reducer; diff --git a/gix-pack/src/index/verify.rs b/gix-pack/src/index/verify.rs index 0ee4259a8fc..6dfa817e08e 100644 --- a/gix-pack/src/index/verify.rs +++ b/gix-pack/src/index/verify.rs @@ -6,6 +6,7 @@ use gix_object::WriteTo; use crate::index; /// +#[allow(clippy::empty_docs)] pub mod integrity { use std::marker::PhantomData; @@ -88,6 +89,7 @@ pub mod integrity { } /// +#[allow(clippy::empty_docs)] pub mod checksum { /// Returned by [`index::File::verify_checksum()`][crate::index::File::verify_checksum()]. pub type Error = crate::verify::checksum::Error; diff --git a/gix-pack/src/index/write/mod.rs b/gix-pack/src/index/write/mod.rs index 3ecb7347b8f..2247d8a1ebe 100644 --- a/gix-pack/src/index/write/mod.rs +++ b/gix-pack/src/index/write/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, io, sync::atomic::AtomicBool}; +use std::{io, sync::atomic::AtomicBool}; pub use error::Error; use gix_features::progress::{self, prodash::DynNestedProgress, Count, Progress}; diff --git a/gix-pack/src/lib.rs b/gix-pack/src/lib.rs index ae6f3ffe99c..34c9e1c9d55 100755 --- a/gix-pack/src/lib.rs +++ b/gix-pack/src/lib.rs @@ -19,6 +19,7 @@ #![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod bundle; /// A bundle of pack data and the corresponding pack index pub struct Bundle { @@ -29,22 +30,28 @@ pub struct Bundle { } /// +#[allow(clippy::empty_docs)] pub mod find; /// +#[allow(clippy::empty_docs)] pub mod cache; /// +#[allow(clippy::empty_docs)] pub mod data; mod find_traits; pub use find_traits::{Find, FindExt}; /// +#[allow(clippy::empty_docs)] pub mod index; /// +#[allow(clippy::empty_docs)] pub mod multi_index; /// +#[allow(clippy::empty_docs)] pub mod verify; mod mmap { @@ -60,8 +67,6 @@ mod mmap { } } -use std::convert::TryInto; - #[inline] fn read_u32(b: &[u8]) -> u32 { u32::from_be_bytes(b.try_into().unwrap()) diff --git a/gix-pack/src/multi_index/chunk.rs b/gix-pack/src/multi_index/chunk.rs index e9a9aac9cdb..81c640bbf57 100644 --- a/gix-pack/src/multi_index/chunk.rs +++ b/gix-pack/src/multi_index/chunk.rs @@ -8,6 +8,7 @@ pub mod index_names { pub const ID: gix_chunk::Id = *b"PNAM"; /// + #[allow(clippy::empty_docs)] pub mod decode { use gix_object::bstr::BString; @@ -105,8 +106,6 @@ pub mod index_names { /// Information for the chunk with the fanout table pub mod fanout { - use std::convert::TryInto; - use crate::multi_index; /// The size of the fanout table @@ -173,7 +172,7 @@ pub mod lookup { /// Information about the offsets table. pub mod offsets { - use std::{convert::TryInto, ops::Range}; + use std::ops::Range; use crate::multi_index; diff --git a/gix-pack/src/multi_index/init.rs b/gix-pack/src/multi_index/init.rs index 190b40a7b04..beccf78e403 100644 --- a/gix-pack/src/multi_index/init.rs +++ b/gix-pack/src/multi_index/init.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, path::Path}; +use std::path::Path; use crate::multi_index::{chunk, File, Version}; diff --git a/gix-pack/src/multi_index/mod.rs b/gix-pack/src/multi_index/mod.rs index d88ae0baed1..6fbb53676a9 100644 --- a/gix-pack/src/multi_index/mod.rs +++ b/gix-pack/src/multi_index/mod.rs @@ -37,16 +37,21 @@ pub struct File { } /// +#[allow(clippy::empty_docs)] pub mod write; /// +#[allow(clippy::empty_docs)] mod access; /// +#[allow(clippy::empty_docs)] pub mod verify; /// +#[allow(clippy::empty_docs)] pub mod chunk; /// +#[allow(clippy::empty_docs)] pub mod init; diff --git a/gix-pack/src/multi_index/verify.rs b/gix-pack/src/multi_index/verify.rs index 0903b3568cf..1e38ae7d9cf 100644 --- a/gix-pack/src/multi_index/verify.rs +++ b/gix-pack/src/multi_index/verify.rs @@ -5,6 +5,7 @@ use gix_features::progress::{Count, DynNestedProgress, Progress}; use crate::{index, multi_index::File}; /// +#[allow(clippy::empty_docs)] pub mod integrity { use crate::multi_index::EntryIndex; @@ -68,6 +69,7 @@ pub mod integrity { } /// +#[allow(clippy::empty_docs)] pub mod checksum { /// Returned by [`multi_index::File::verify_checksum()`][crate::multi_index::File::verify_checksum()]. pub type Error = crate::verify::checksum::Error; diff --git a/gix-pack/src/multi_index/write.rs b/gix-pack/src/multi_index/write.rs index 881033091bc..3625cc47c7a 100644 --- a/gix-pack/src/multi_index/write.rs +++ b/gix-pack/src/multi_index/write.rs @@ -1,5 +1,4 @@ use std::{ - convert::TryInto, path::PathBuf, sync::atomic::{AtomicBool, Ordering}, time::{Instant, SystemTime}, diff --git a/gix-pack/src/verify.rs b/gix-pack/src/verify.rs index fff99c75d12..3fad0e2467f 100644 --- a/gix-pack/src/verify.rs +++ b/gix-pack/src/verify.rs @@ -3,6 +3,7 @@ use std::{path::Path, sync::atomic::AtomicBool}; use gix_features::progress::Progress; /// +#[allow(clippy::empty_docs)] pub mod checksum { /// Returned by various methods to verify the checksum of a memory mapped file that might also exist on disk. #[derive(thiserror::Error, Debug)] diff --git a/gix-pack/tests/pack/data/header.rs b/gix-pack/tests/pack/data/header.rs index 45132c9f8a5..8c05e24302e 100644 --- a/gix-pack/tests/pack/data/header.rs +++ b/gix-pack/tests/pack/data/header.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::pack::fixture_path; #[test] diff --git a/gix-packetline/src/decode.rs b/gix-packetline/src/decode.rs index 66dec96becb..77dad4dd7b2 100644 --- a/gix-packetline/src/decode.rs +++ b/gix-packetline/src/decode.rs @@ -21,6 +21,7 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod band { /// The error used in [`PacketLineRef::decode_band()`][super::PacketLineRef::decode_band()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-packetline/src/lib.rs b/gix-packetline/src/lib.rs index 95ecd59d25c..62978dd7bb1 100644 --- a/gix-packetline/src/lib.rs +++ b/gix-packetline/src/lib.rs @@ -31,9 +31,11 @@ pub enum Channel { mod line; /// +#[allow(clippy::empty_docs)] pub mod read; /// +#[allow(clippy::empty_docs)] #[cfg(any(feature = "async-io", feature = "blocking-io"))] mod write; #[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] diff --git a/gix-path/src/lib.rs b/gix-path/src/lib.rs index 888141d3c23..7f913042fac 100644 --- a/gix-path/src/lib.rs +++ b/gix-path/src/lib.rs @@ -57,6 +57,7 @@ mod util; pub use util::is_absolute; /// +#[allow(clippy::empty_docs)] pub mod realpath; pub use realpath::function::{realpath, realpath_opts}; diff --git a/gix-pathspec/src/defaults.rs b/gix-pathspec/src/defaults.rs index 81be53b38c1..c2bc641f8eb 100644 --- a/gix-pathspec/src/defaults.rs +++ b/gix-pathspec/src/defaults.rs @@ -3,6 +3,7 @@ use std::ffi::OsString; use crate::{Defaults, MagicSignature, SearchMode}; /// +#[allow(clippy::empty_docs)] pub mod from_environment { /// The error returned by [Defaults::from_environment()](super::Defaults::from_environment()). #[derive(Debug, thiserror::Error)] diff --git a/gix-pathspec/src/lib.rs b/gix-pathspec/src/lib.rs index e271cdb06f9..e31d5d50ba1 100644 --- a/gix-pathspec/src/lib.rs +++ b/gix-pathspec/src/lib.rs @@ -11,6 +11,7 @@ use bstr::BString; pub use gix_attributes as attributes; /// +#[allow(clippy::empty_docs)] pub mod normalize { use std::path::PathBuf; @@ -28,9 +29,11 @@ pub mod normalize { mod pattern; /// +#[allow(clippy::empty_docs)] pub mod search; /// +#[allow(clippy::empty_docs)] pub mod parse; /// Default settings for some fields of a [`Pattern`]. @@ -51,6 +54,7 @@ pub struct Defaults { } /// +#[allow(clippy::empty_docs)] pub mod defaults; /// A lists of pathspec patterns, possibly from a file. diff --git a/gix-prompt/src/lib.rs b/gix-prompt/src/lib.rs index dba6cf80405..e25f6058fff 100644 --- a/gix-prompt/src/lib.rs +++ b/gix-prompt/src/lib.rs @@ -11,6 +11,7 @@ mod types; pub use types::{Error, Mode, Options}; /// +#[allow(clippy::empty_docs)] pub mod unix; #[cfg(unix)] use unix::imp; diff --git a/gix-prompt/src/types.rs b/gix-prompt/src/types.rs index 6f3bb2c0430..ccf0f7c9493 100644 --- a/gix-prompt/src/types.rs +++ b/gix-prompt/src/types.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom, path::Path}; +use std::{borrow::Cow, path::Path}; /// The error returned by [ask()][crate::ask()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-protocol/src/fetch/mod.rs b/gix-protocol/src/fetch/mod.rs index 0828ea733a2..56e85f4babd 100644 --- a/gix-protocol/src/fetch/mod.rs +++ b/gix-protocol/src/fetch/mod.rs @@ -2,6 +2,7 @@ mod arguments; pub use arguments::Arguments; /// +#[allow(clippy::empty_docs)] pub mod delegate; #[cfg(any(feature = "async-client", feature = "blocking-client"))] pub use delegate::Delegate; @@ -10,6 +11,7 @@ pub use delegate::{Action, DelegateBlocking}; mod error; pub use error::Error; /// +#[allow(clippy::empty_docs)] pub mod response; pub use response::Response; diff --git a/gix-protocol/src/handshake/mod.rs b/gix-protocol/src/handshake/mod.rs index 28243e96da7..cc388e96d2e 100644 --- a/gix-protocol/src/handshake/mod.rs +++ b/gix-protocol/src/handshake/mod.rs @@ -98,4 +98,5 @@ pub use error::Error; pub(crate) mod function; /// +#[allow(clippy::empty_docs)] pub mod refs; diff --git a/gix-protocol/src/handshake/refs/mod.rs b/gix-protocol/src/handshake/refs/mod.rs index 39c6c85a942..cbefc358aab 100644 --- a/gix-protocol/src/handshake/refs/mod.rs +++ b/gix-protocol/src/handshake/refs/mod.rs @@ -3,6 +3,7 @@ use bstr::BStr; use super::Ref; /// +#[allow(clippy::empty_docs)] pub mod parse { use bstr::BString; diff --git a/gix-protocol/src/lib.rs b/gix-protocol/src/lib.rs index 6b4ee69d2b6..63f26e632aa 100644 --- a/gix-protocol/src/lib.rs +++ b/gix-protocol/src/lib.rs @@ -32,6 +32,7 @@ pub use gix_transport as transport; pub use maybe_async; /// +#[allow(clippy::empty_docs)] #[cfg(any(feature = "blocking-client", feature = "async-client"))] pub mod fetch; @@ -48,11 +49,13 @@ compile_error!("Cannot set both 'blocking-client' and 'async-client' features as /// #[cfg(any(feature = "blocking-client", feature = "async-client"))] +#[allow(clippy::empty_docs)] pub mod handshake; #[cfg(any(feature = "blocking-client", feature = "async-client"))] pub use handshake::function::handshake; /// +#[allow(clippy::empty_docs)] #[cfg(any(feature = "blocking-client", feature = "async-client"))] pub mod ls_refs; #[cfg(any(feature = "blocking-client", feature = "async-client"))] diff --git a/gix-protocol/src/remote_progress.rs b/gix-protocol/src/remote_progress.rs index d7baa3bf9f5..deba3e6749d 100644 --- a/gix-protocol/src/remote_progress.rs +++ b/gix-protocol/src/remote_progress.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use bstr::ByteSlice; use winnow::{ combinator::{opt, preceded, terminated}, diff --git a/gix-protocol/tests/fetch/mod.rs b/gix-protocol/tests/fetch/mod.rs index a7c29fbd6ec..b579b080f80 100644 --- a/gix-protocol/tests/fetch/mod.rs +++ b/gix-protocol/tests/fetch/mod.rs @@ -89,7 +89,7 @@ impl fetch::DelegateBlocking for CloneRefInWantDelegate { _features: &mut Vec<(&str, Option>)>, refs: &[handshake::Ref], ) -> io::Result { - self.refs = refs.to_owned(); + refs.clone_into(&mut self.refs); Ok(Action::Continue) } @@ -135,7 +135,7 @@ impl fetch::DelegateBlocking for LsRemoteDelegate { _features: &mut Vec<(&str, Option>)>, refs: &[handshake::Ref], ) -> io::Result { - self.refs = refs.to_owned(); + refs.clone_into(&mut self.refs); Ok(fetch::Action::Cancel) } fn negotiate( diff --git a/gix-quote/src/ansi_c.rs b/gix-quote/src/ansi_c.rs index 43856f8992e..1b3bf3a66fd 100644 --- a/gix-quote/src/ansi_c.rs +++ b/gix-quote/src/ansi_c.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod undo { use bstr::{BStr, BString}; diff --git a/gix-quote/src/lib.rs b/gix-quote/src/lib.rs index b86336b3b0b..f0ed9785995 100644 --- a/gix-quote/src/lib.rs +++ b/gix-quote/src/lib.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod ansi_c; mod single; diff --git a/gix-ref/src/fullname.rs b/gix-ref/src/fullname.rs index 5957943dc72..e74dd05dd04 100644 --- a/gix-ref/src/fullname.rs +++ b/gix-ref/src/fullname.rs @@ -1,4 +1,4 @@ -use std::{borrow::Borrow, convert::TryFrom, path::Path}; +use std::{borrow::Borrow, path::Path}; use gix_object::bstr::{BStr, BString, ByteSlice}; diff --git a/gix-ref/src/lib.rs b/gix-ref/src/lib.rs index 2a0b8987632..ecf54ed49fe 100644 --- a/gix-ref/src/lib.rs +++ b/gix-ref/src/lib.rs @@ -35,10 +35,13 @@ pub use store_impl::{file, packed}; mod fullname; /// +#[allow(clippy::empty_docs)] pub mod name; /// +#[allow(clippy::empty_docs)] pub mod namespace; /// +#[allow(clippy::empty_docs)] pub mod transaction; mod parse; @@ -49,12 +52,15 @@ pub use raw::Reference; mod target; /// +#[allow(clippy::empty_docs)] pub mod log; /// +#[allow(clippy::empty_docs)] pub mod peel; /// +#[allow(clippy::empty_docs)] pub mod store { /// The way a file store handles the reflog #[derive(Default, Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone, Copy)] diff --git a/gix-ref/src/namespace.rs b/gix-ref/src/namespace.rs index 34040f606a0..1483b1c47e8 100644 --- a/gix-ref/src/namespace.rs +++ b/gix-ref/src/namespace.rs @@ -1,7 +1,4 @@ -use std::{ - convert::TryInto, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; use gix_object::bstr::{BStr, BString, ByteSlice, ByteVec}; diff --git a/gix-ref/src/peel.rs b/gix-ref/src/peel.rs index b119a907334..02bee162050 100644 --- a/gix-ref/src/peel.rs +++ b/gix-ref/src/peel.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod to_id { use std::path::PathBuf; diff --git a/gix-ref/src/store/file/find.rs b/gix-ref/src/store/file/find.rs index 6bf7767043b..b8a45e86b2e 100644 --- a/gix-ref/src/store/file/find.rs +++ b/gix-ref/src/store/file/find.rs @@ -1,6 +1,5 @@ use std::{ borrow::Cow, - convert::TryInto, io::{self, Read}, path::{Path, PathBuf}, }; @@ -271,9 +270,8 @@ impl file::Store { } /// +#[allow(clippy::empty_docs)] pub mod existing { - use std::convert::TryInto; - pub use error::Error; use crate::{ diff --git a/gix-ref/src/store/file/log/iter.rs b/gix-ref/src/store/file/log/iter.rs index d62df680014..e3147151513 100644 --- a/gix-ref/src/store/file/log/iter.rs +++ b/gix-ref/src/store/file/log/iter.rs @@ -8,6 +8,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod decode { use crate::store_impl::file::log; @@ -143,6 +144,7 @@ where } /// +#[allow(clippy::empty_docs)] pub mod reverse { use super::decode; diff --git a/gix-ref/src/store/file/log/line.rs b/gix-ref/src/store/file/log/line.rs index 01de31a7c60..ff8fe490c30 100644 --- a/gix-ref/src/store/file/log/line.rs +++ b/gix-ref/src/store/file/log/line.rs @@ -72,6 +72,7 @@ impl<'a> From> for Line { } /// +#[allow(clippy::empty_docs)] pub mod decode { use gix_object::bstr::{BStr, ByteSlice}; use winnow::{ @@ -84,6 +85,7 @@ pub mod decode { use crate::{file::log::LineRef, parse::hex_hash}; /// + #[allow(clippy::empty_docs)] mod error { use gix_object::bstr::{BString, ByteSlice}; @@ -167,10 +169,8 @@ pub mod decode { #[cfg(test)] mod test { - use gix_date::{time::Sign, Time}; - use gix_object::bstr::ByteSlice; - use super::*; + use gix_date::{time::Sign, Time}; /// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_. fn hex_to_oid(hex: &str) -> gix_hash::ObjectId { diff --git a/gix-ref/src/store/file/log/mod.rs b/gix-ref/src/store/file/log/mod.rs index a9199be2b79..eae3abe7ef9 100644 --- a/gix-ref/src/store/file/log/mod.rs +++ b/gix-ref/src/store/file/log/mod.rs @@ -3,6 +3,7 @@ use gix_object::bstr::BStr; pub use super::loose::reflog::{create_or_update, Error}; /// +#[allow(clippy::empty_docs)] pub mod iter; mod line; diff --git a/gix-ref/src/store/file/loose/mod.rs b/gix-ref/src/store/file/loose/mod.rs index 0e271247793..f1fd8735163 100644 --- a/gix-ref/src/store/file/loose/mod.rs +++ b/gix-ref/src/store/file/loose/mod.rs @@ -17,11 +17,14 @@ impl Reference { } /// +#[allow(clippy::empty_docs)] pub(crate) mod reflog; /// +#[allow(clippy::empty_docs)] pub(crate) mod iter; /// +#[allow(clippy::empty_docs)] pub mod reference; mod init { diff --git a/gix-ref/src/store/file/loose/reference/decode.rs b/gix-ref/src/store/file/loose/reference/decode.rs index 7ba89837383..68fb4f65d70 100644 --- a/gix-ref/src/store/file/loose/reference/decode.rs +++ b/gix-ref/src/store/file/loose/reference/decode.rs @@ -1,5 +1,3 @@ -use std::convert::{TryFrom, TryInto}; - use gix_hash::ObjectId; use gix_object::bstr::BString; use winnow::{ diff --git a/gix-ref/src/store/file/loose/reference/mod.rs b/gix-ref/src/store/file/loose/reference/mod.rs index 3e5ce06838b..e79ff6b95bd 100644 --- a/gix-ref/src/store/file/loose/reference/mod.rs +++ b/gix-ref/src/store/file/loose/reference/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod logiter; /// +#[allow(clippy::empty_docs)] pub mod decode; diff --git a/gix-ref/src/store/file/loose/reflog.rs b/gix-ref/src/store/file/loose/reflog.rs index 614dd59a9e0..cdf342bcb88 100644 --- a/gix-ref/src/store/file/loose/reflog.rs +++ b/gix-ref/src/store/file/loose/reflog.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, io::Read, path::PathBuf}; +use std::{io::Read, path::PathBuf}; use crate::{ store_impl::{file, file::log}, @@ -84,6 +84,7 @@ impl file::Store { } /// +#[allow(clippy::empty_docs)] pub mod create_or_update { use std::{ borrow::Cow, diff --git a/gix-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/gix-ref/src/store/file/loose/reflog/create_or_update/tests.rs index 4173b175875..c2b487ad9e2 100644 --- a/gix-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/gix-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -1,12 +1,9 @@ -use std::{convert::TryInto, path::Path}; - use gix_actor::Signature; use gix_date::{time::Sign, Time}; use gix_object::bstr::ByteSlice; use gix_testtools::tempfile::TempDir; use super::*; -use crate::{file::WriteReflog, FullNameRef}; type Result = std::result::Result>; diff --git a/gix-ref/src/store/file/mod.rs b/gix-ref/src/store/file/mod.rs index 758dcb438cc..19296b0af3f 100644 --- a/gix-ref/src/store/file/mod.rs +++ b/gix-ref/src/store/file/mod.rs @@ -95,29 +95,36 @@ pub(in crate::store_impl::file) fn path_to_name<'a>(path: impl Into { } /// +#[allow(clippy::empty_docs)] pub mod prepare; /// +#[allow(clippy::empty_docs)] pub mod commit; diff --git a/gix-ref/src/store/general/handle/find.rs b/gix-ref/src/store/general/handle/find.rs index 2f11f17557d..40be732dbac 100644 --- a/gix-ref/src/store/general/handle/find.rs +++ b/gix-ref/src/store/general/handle/find.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::{store, PartialNameRef, Reference}; mod error { @@ -57,8 +55,6 @@ mod existing { } } - use std::convert::TryInto; - pub use error::Error; use crate::{store, PartialNameRef, Reference}; diff --git a/gix-ref/src/store/general/handle/mod.rs b/gix-ref/src/store/general/handle/mod.rs index 44d9e060d81..50264084088 100644 --- a/gix-ref/src/store/general/handle/mod.rs +++ b/gix-ref/src/store/general/handle/mod.rs @@ -34,6 +34,7 @@ impl crate::Store { } /// +#[allow(clippy::empty_docs)] pub mod find; mod iter { diff --git a/gix-ref/src/store/mod.rs b/gix-ref/src/store/mod.rs index 6691098e211..d8e0a01c432 100644 --- a/gix-ref/src/store/mod.rs +++ b/gix-ref/src/store/mod.rs @@ -1,5 +1,7 @@ /// +#[allow(clippy::empty_docs)] pub mod file; /// +#[allow(clippy::empty_docs)] pub mod packed; diff --git a/gix-ref/src/store/packed/buffer.rs b/gix-ref/src/store/packed/buffer.rs index 2283cf9873b..76e0f3bc422 100644 --- a/gix-ref/src/store/packed/buffer.rs +++ b/gix-ref/src/store/packed/buffer.rs @@ -16,6 +16,7 @@ impl AsRef<[u8]> for packed::Backing { } /// +#[allow(clippy::empty_docs)] pub mod open { use std::path::PathBuf; diff --git a/gix-ref/src/store/packed/decode.rs b/gix-ref/src/store/packed/decode.rs index b4f8e8562e6..84856af13d0 100644 --- a/gix-ref/src/store/packed/decode.rs +++ b/gix-ref/src/store/packed/decode.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_object::bstr::{BStr, ByteSlice}; use winnow::{ combinator::{delimited, opt, preceded, terminated}, diff --git a/gix-ref/src/store/packed/find.rs b/gix-ref/src/store/packed/find.rs index 002f76b0f50..b2c2c7565a2 100644 --- a/gix-ref/src/store/packed/find.rs +++ b/gix-ref/src/store/packed/find.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_object::bstr::{BStr, BString, ByteSlice}; use winnow::prelude::*; @@ -131,6 +129,7 @@ mod error { pub use error::Error; /// +#[allow(clippy::empty_docs)] pub mod existing { /// The error returned by [`find_existing()`][super::packed::Buffer::find()] diff --git a/gix-ref/src/store/packed/mod.rs b/gix-ref/src/store/packed/mod.rs index 12aee9f815f..59a7ab02d24 100644 --- a/gix-ref/src/store/packed/mod.rs +++ b/gix-ref/src/store/packed/mod.rs @@ -84,13 +84,17 @@ pub struct Iter<'a> { mod decode; /// +#[allow(clippy::empty_docs)] pub mod iter; /// +#[allow(clippy::empty_docs)] pub mod buffer; /// +#[allow(clippy::empty_docs)] pub mod find; /// +#[allow(clippy::empty_docs)] pub mod transaction; diff --git a/gix-ref/src/store/packed/transaction.rs b/gix-ref/src/store/packed/transaction.rs index e03f9b987da..8c615a10d4b 100644 --- a/gix-ref/src/store/packed/transaction.rs +++ b/gix-ref/src/store/packed/transaction.rs @@ -273,6 +273,7 @@ pub(crate) fn buffer_into_transaction( } /// +#[allow(clippy::empty_docs)] pub mod prepare { /// The error used in [`Transaction::prepare(…)`][crate::file::Transaction::prepare()]. #[derive(Debug, thiserror::Error)] @@ -286,6 +287,7 @@ pub mod prepare { } /// +#[allow(clippy::empty_docs)] pub mod commit { use crate::store_impl::packed; diff --git a/gix-ref/src/target.rs b/gix-ref/src/target.rs index 6d4f69991dd..15d1e3fc0e9 100644 --- a/gix-ref/src/target.rs +++ b/gix-ref/src/target.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, fmt}; +use std::fmt; use gix_hash::{oid, ObjectId}; diff --git a/gix-ref/tests/file/store/find.rs b/gix-ref/tests/file/store/find.rs index b4003e3fc62..64c51c499bb 100644 --- a/gix-ref/tests/file/store/find.rs +++ b/gix-ref/tests/file/store/find.rs @@ -1,6 +1,4 @@ mod existing { - use std::convert::{TryFrom, TryInto}; - use gix_ref::{PartialName, PartialNameRef}; use crate::{file::store_at, hex_to_id}; diff --git a/gix-ref/tests/file/store/iter.rs b/gix-ref/tests/file/store/iter.rs index 4fa6b8e54db..0ed5a971843 100644 --- a/gix-ref/tests/file/store/iter.rs +++ b/gix-ref/tests/file/store/iter.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_object::bstr::ByteSlice; use crate::{ diff --git a/gix-ref/tests/file/transaction/mod.rs b/gix-ref/tests/file/transaction/mod.rs index aafdaeba66f..e7f8d344e38 100644 --- a/gix-ref/tests/file/transaction/mod.rs +++ b/gix-ref/tests/file/transaction/mod.rs @@ -1,6 +1,4 @@ pub(crate) mod prepare_and_commit { - use std::convert::TryInto; - use gix_date::{time::Sign, Time}; use gix_hash::ObjectId; use gix_object::bstr::BString; diff --git a/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/collisions.rs b/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/collisions.rs index 97cdb1af335..5ef7a0ad1eb 100644 --- a/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/collisions.rs +++ b/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/collisions.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_lock::acquire::Fail; use gix_ref::{ file::transaction::PackedRefs, diff --git a/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/mod.rs b/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/mod.rs index bbc42d54d85..8070385a63d 100644 --- a/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/mod.rs +++ b/gix-ref/tests/file/transaction/prepare_and_commit/create_or_update/mod.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_hash::ObjectId; use gix_lock::acquire::Fail; use gix_object::bstr::{BString, ByteSlice}; diff --git a/gix-ref/tests/file/transaction/prepare_and_commit/delete.rs b/gix-ref/tests/file/transaction/prepare_and_commit/delete.rs index 41114643df2..c4a8c94c4fd 100644 --- a/gix-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/gix-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_lock::acquire::Fail; use gix_ref::{ file::ReferenceExt, diff --git a/gix-ref/tests/file/worktree.rs b/gix-ref/tests/file/worktree.rs index e9ee71edbe6..196ac3d1260 100644 --- a/gix-ref/tests/file/worktree.rs +++ b/gix-ref/tests/file/worktree.rs @@ -195,8 +195,6 @@ mod read_only { } mod writable { - use std::convert::TryInto; - use gix_lock::acquire::Fail; use gix_ref::{ file::{transaction::PackedRefs, Store}, diff --git a/gix-ref/tests/fullname/mod.rs b/gix-ref/tests/fullname/mod.rs index 94c99d9a483..5cb5b07f5a9 100644 --- a/gix-ref/tests/fullname/mod.rs +++ b/gix-ref/tests/fullname/mod.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryInto}; +use std::borrow::Cow; use gix_ref::{Category, FullNameRef, PartialNameRef}; diff --git a/gix-ref/tests/packed/find.rs b/gix-ref/tests/packed/find.rs index f8146b66579..9cc6753c6c4 100644 --- a/gix-ref/tests/packed/find.rs +++ b/gix-ref/tests/packed/find.rs @@ -1,5 +1,3 @@ -use std::convert::{TryFrom, TryInto}; - use gix_ref::packed; use gix_testtools::fixture_path_standalone; diff --git a/gix-ref/tests/packed/iter.rs b/gix-ref/tests/packed/iter.rs index 3a546340695..28d15ab1ca3 100644 --- a/gix-ref/tests/packed/iter.rs +++ b/gix-ref/tests/packed/iter.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_object::bstr::ByteSlice; use gix_ref::packed; diff --git a/gix-ref/tests/reference/mod.rs b/gix-ref/tests/reference/mod.rs index d93729473a5..9662462818d 100644 --- a/gix-ref/tests/reference/mod.rs +++ b/gix-ref/tests/reference/mod.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_ref::{FullName, Target}; #[test] diff --git a/gix-ref/tests/transaction/mod.rs b/gix-ref/tests/transaction/mod.rs index 59766eb1802..64d7627c0dd 100644 --- a/gix-ref/tests/transaction/mod.rs +++ b/gix-ref/tests/transaction/mod.rs @@ -1,5 +1,5 @@ mod refedit_ext { - use std::{cell::RefCell, collections::BTreeMap, convert::TryInto}; + use std::{cell::RefCell, collections::BTreeMap}; use gix_object::bstr::{BString, ByteSlice}; use gix_ref::{ @@ -96,7 +96,7 @@ mod refedit_ext { } mod splitting { - use std::{cell::Cell, convert::TryInto}; + use std::cell::Cell; use gix_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefEditsExt, RefLog}, diff --git a/gix-refspec/src/lib.rs b/gix-refspec/src/lib.rs index 54d5f3057a9..ec5b4afca80 100644 --- a/gix-refspec/src/lib.rs +++ b/gix-refspec/src/lib.rs @@ -3,10 +3,12 @@ #![forbid(unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod parse; pub use parse::function::parse; /// +#[allow(clippy::empty_docs)] pub mod instruction; /// A refspec with references to the memory it was parsed from. @@ -32,6 +34,7 @@ mod spec; mod write; /// +#[allow(clippy::empty_docs)] pub mod match_group; pub use match_group::types::MatchGroup; diff --git a/gix-refspec/src/match_group/mod.rs b/gix-refspec/src/match_group/mod.rs index 2d859cde82f..da5b0f54165 100644 --- a/gix-refspec/src/match_group/mod.rs +++ b/gix-refspec/src/match_group/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod types; pub use types::{Item, Mapping, Outcome, Source, SourceRef}; /// +#[allow(clippy::empty_docs)] pub mod validate; /// Initialization diff --git a/gix-refspec/tests/impls/mod.rs b/gix-refspec/tests/impls/mod.rs index e64c29341bb..c32f5d92060 100644 --- a/gix-refspec/tests/impls/mod.rs +++ b/gix-refspec/tests/impls/mod.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{BTreeSet, HashSet}, - iter::FromIterator, -}; +use std::collections::{BTreeSet, HashSet}; use gix_refspec::{parse::Operation, RefSpec}; diff --git a/gix-revision/src/lib.rs b/gix-revision/src/lib.rs index 1593c25c1a8..9fcf73abfaa 100644 --- a/gix-revision/src/lib.rs +++ b/gix-revision/src/lib.rs @@ -9,12 +9,14 @@ #![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// +#[allow(clippy::empty_docs)] #[cfg(feature = "describe")] pub mod describe; #[cfg(feature = "describe")] pub use describe::function::describe; /// +#[allow(clippy::empty_docs)] pub mod spec; pub use gix_revwalk::{graph, Graph, PriorityQueue}; pub use spec::types::Spec; diff --git a/gix-revision/src/spec/mod.rs b/gix-revision/src/spec/mod.rs index 616ad3a26b2..e05b1bc807a 100644 --- a/gix-revision/src/spec/mod.rs +++ b/gix-revision/src/spec/mod.rs @@ -105,5 +105,6 @@ pub(crate) mod types { } /// +#[allow(clippy::empty_docs)] pub mod parse; pub use parse::function::parse; diff --git a/gix-revision/src/spec/parse/function.rs b/gix-revision/src/spec/parse/function.rs index fad845deb18..502ca3e7c8d 100644 --- a/gix-revision/src/spec/parse/function.rs +++ b/gix-revision/src/spec/parse/function.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, str::FromStr, time::SystemTime}; +use std::{str::FromStr, time::SystemTime}; use bstr::{BStr, BString, ByteSlice, ByteVec}; diff --git a/gix-revision/src/spec/parse/mod.rs b/gix-revision/src/spec/parse/mod.rs index 5a64012c666..bd5381fd200 100644 --- a/gix-revision/src/spec/parse/mod.rs +++ b/gix-revision/src/spec/parse/mod.rs @@ -46,6 +46,7 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod delegate; /// A delegate to be informed about parse events, with methods split into categories. diff --git a/gix-revwalk/src/graph/commit.rs b/gix-revwalk/src/graph/commit.rs index 8bfc8afc8a0..f7bb39109c5 100644 --- a/gix-revwalk/src/graph/commit.rs +++ b/gix-revwalk/src/graph/commit.rs @@ -123,6 +123,7 @@ impl<'graph> Iterator for Parents<'graph> { } /// +#[allow(clippy::empty_docs)] pub mod iter_parents { /// The error returned by the [`Parents`][super::Parents] iterator. #[derive(Debug, thiserror::Error)] @@ -136,6 +137,7 @@ pub mod iter_parents { } /// +#[allow(clippy::empty_docs)] pub mod to_owned { /// The error returned by [`to_owned()`][crate::graph::LazyCommit::to_owned()]. #[derive(Debug, thiserror::Error)] diff --git a/gix-revwalk/src/graph/mod.rs b/gix-revwalk/src/graph/mod.rs index fa3b16870fe..bd004826bb4 100644 --- a/gix-revwalk/src/graph/mod.rs +++ b/gix-revwalk/src/graph/mod.rs @@ -9,10 +9,12 @@ use crate::Graph; pub type IdMap = gix_hashtable::HashMap; /// +#[allow(clippy::empty_docs)] pub mod commit; mod errors { /// + #[allow(clippy::empty_docs)] pub mod insert_parents { use crate::graph::commit::iter_parents; @@ -30,6 +32,7 @@ mod errors { } /// + #[allow(clippy::empty_docs)] pub mod try_lookup_or_insert_default { use crate::graph::commit::to_owned; diff --git a/gix-revwalk/src/lib.rs b/gix-revwalk/src/lib.rs index 67bfc6dea6b..a05b1bb2868 100644 --- a/gix-revwalk/src/lib.rs +++ b/gix-revwalk/src/lib.rs @@ -39,6 +39,7 @@ pub struct Graph<'find, T> { } /// +#[allow(clippy::empty_docs)] pub mod graph; /// A utility type implementing a queue which can be used to automatically sort data by its time in ascending order. diff --git a/gix-sec/src/lib.rs b/gix-sec/src/lib.rs index 35b7b34d524..309c734ed31 100644 --- a/gix-sec/src/lib.rs +++ b/gix-sec/src/lib.rs @@ -22,6 +22,7 @@ pub enum Trust { } /// +#[allow(clippy::empty_docs)] pub mod trust; /// Allow, deny or forbid using a resource or performing an action. @@ -37,6 +38,7 @@ pub enum Permission { } /// +#[allow(clippy::empty_docs)] pub mod permission; bitflags::bitflags! { diff --git a/gix-status/Cargo.toml b/gix-status/Cargo.toml index 0d75b7dc3c0..6d36fd1c85e 100644 --- a/gix-status/Cargo.toml +++ b/gix-status/Cargo.toml @@ -13,17 +13,29 @@ autotests = false [lib] doctest = false +[features] +## Add support for tracking rewrites along with checking for worktree modifications. +worktree-rewrites = ["dep:gix-dir", "dep:gix-diff"] + [dependencies] gix-index = { version = "^0.30.0", path = "../gix-index" } gix-fs = { version = "^0.10.0", path = "../gix-fs" } gix-hash = { version = "^0.14.1", path = "../gix-hash" } gix-object = { version = "^0.41.1", path = "../gix-object" } gix-path = { version = "^0.10.6", path = "../gix-path" } -gix-features = { version = "^0.38.0", path = "../gix-features" } +gix-features = { version = "^0.38.0", path = "../gix-features", features = ["progress"] } gix-filter = { version = "^0.10.0", path = "../gix-filter" } gix-worktree = { version = "^0.31.0", path = "../gix-worktree", default-features = false, features = ["attributes"] } gix-pathspec = { version = "^0.7.0", path = "../gix-pathspec" } +gix-dir = { version = "^0.1.0", path = "../gix-dir", optional = true } +gix-diff = { version = "^0.41.0", path = "../gix-diff", default-features = false, features = ["blob"], optional = true } + thiserror = "1.0.26" filetime = "0.2.15" bstr = { version = "1.3.0", default-features = false } + +document-features = { version = "0.2.0", optional = true } + +[package.metadata.docs.rs] +features = ["document-features", "worktree-rewrites"] diff --git a/gix-status/src/index_as_worktree/mod.rs b/gix-status/src/index_as_worktree/mod.rs index 96694078bb8..9f1c38f6ab1 100644 --- a/gix-status/src/index_as_worktree/mod.rs +++ b/gix-status/src/index_as_worktree/mod.rs @@ -1,11 +1,13 @@ //! Changes between an index and a worktree. /// +#[allow(clippy::empty_docs)] mod types; pub use types::{Change, Conflict, Context, EntryStatus, Error, Options, Outcome, VisitEntry}; mod recorder; pub use recorder::{Record, Recorder}; -pub(crate) mod function; +pub(super) mod function; /// +#[allow(clippy::empty_docs)] pub mod traits; diff --git a/gix-status/src/index_as_worktree/recorder.rs b/gix-status/src/index_as_worktree/recorder.rs index 0cf1aa6f367..407abfd557c 100644 --- a/gix-status/src/index_as_worktree/recorder.rs +++ b/gix-status/src/index_as_worktree/recorder.rs @@ -6,7 +6,7 @@ use crate::index_as_worktree::{EntryStatus, VisitEntry}; /// A record of a change. /// /// It's created either if there is a conflict or a change, or both. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Record<'index, T, U> { /// The index entry that is changed. pub entry: &'index index::Entry, diff --git a/gix-status/src/index_as_worktree/traits.rs b/gix-status/src/index_as_worktree/traits.rs index 8d86b579aaa..9865d1514e2 100644 --- a/gix-status/src/index_as_worktree/traits.rs +++ b/gix-status/src/index_as_worktree/traits.rs @@ -50,6 +50,7 @@ pub trait ReadData<'a> { } /// +#[allow(clippy::empty_docs)] pub mod read_data { use std::sync::atomic::{AtomicU64, Ordering}; diff --git a/gix-status/src/index_as_worktree/types.rs b/gix-status/src/index_as_worktree/types.rs index 6d93f784f12..818ee5840ea 100644 --- a/gix-status/src/index_as_worktree/types.rs +++ b/gix-status/src/index_as_worktree/types.rs @@ -21,7 +21,7 @@ pub enum Error { } /// Options that control how the index status with a worktree is computed. -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] pub struct Options { /// Capabilities of the file system which affect the status computation. pub fs: gix_fs::Capabilities, @@ -37,6 +37,9 @@ pub struct Options { #[derive(Clone)] pub struct Context<'a> { /// The pathspec to limit the amount of paths that are checked. Can be empty to allow all paths. + /// + /// Note that these are expected to have a [commont_prefix()](gix_pathspec::Search::common_prefix()) according + /// to the prefix of the repository to efficiently limit the scope of the paths we process. pub pathspec: gix_pathspec::Search, /// A stack pre-configured to allow accessing attributes for each entry, as required for `filter` /// and possibly pathspecs. diff --git a/gix-status/src/index_as_worktree_with_renames/mod.rs b/gix-status/src/index_as_worktree_with_renames/mod.rs new file mode 100644 index 00000000000..39f255f3788 --- /dev/null +++ b/gix-status/src/index_as_worktree_with_renames/mod.rs @@ -0,0 +1,564 @@ +//! Changes between the index and the worktree along with optional rename tracking. +mod types; +pub use types::{Context, DirwalkContext, Entry, Error, Options, Outcome, RewriteSource, Sorting, Summary, VisitEntry}; + +mod recorder; +pub use recorder::Recorder; + +pub(super) mod function { + use crate::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus}; + use crate::index_as_worktree_with_renames::function::rewrite::ModificationOrDirwalkEntry; + use crate::index_as_worktree_with_renames::{Context, Entry, Error, Options, Outcome, RewriteSource, VisitEntry}; + use bstr::ByteSlice; + use gix_worktree::stack::State; + use std::borrow::Cow; + use std::path::Path; + + /// Similar to [`index_as_worktree(…)`](crate::index_as_worktree()), except that it will automatically + /// track renames if enabled, while additionally providing information about untracked files + /// (or more, depending on the configuration). + /// + /// * `index` + /// - used for checking modifications, and also for knowing which files are tracked during + /// the working-dir traversal. + /// * `worktree` + /// - The root of the worktree, in a format that respects `core.precomposeUnicode`. + /// * `collector` + /// - A [`VisitEntry`] implementation that sees the results of this operation. + /// * `compare` + /// - An implementation to compare two blobs for equality, used during index modification checks. + /// * `submodule` + /// - An implementation to determine the status of a submodule when encountered during + /// index modification checks. + /// * `objects` + /// - A way to obtain objects from the git object database. + /// * `progress` + /// - A way to send progress information for the index modification checks. + /// * `ctx` + /// - Additional information that will be accessed during index modification checks and traversal. + /// * `options` + /// - a way to configure both paths of the operation. + #[allow(clippy::too_many_arguments)] + pub fn index_as_worktree_with_renames<'index, T, U, Find, E>( + index: &'index gix_index::State, + worktree: &Path, + collector: &mut impl VisitEntry<'index, ContentChange = T, SubmoduleStatus = U>, + compare: impl CompareBlobs + Send + Clone, + submodule: impl SubmoduleStatus + Send + Clone, + objects: Find, + progress: &mut dyn gix_features::progress::Progress, + mut ctx: Context<'_>, + options: Options, + ) -> Result + where + T: Send + Clone, + U: Send + Clone, + E: std::error::Error + Send + Sync + 'static, + Find: gix_object::Find + gix_object::FindHeader + Send + Clone, + { + gix_features::parallel::threads(|scope| -> Result { + let (tx, rx) = std::sync::mpsc::channel(); + let walk_outcome = options + .dirwalk + .map(|options| { + gix_features::parallel::build_thread() + .name("gix_status::dirwalk".into()) + .spawn_scoped(scope, { + let tx = tx.clone(); + let mut collect = dirwalk::Delegate { + tx, + should_interrupt: ctx.should_interrupt, + }; + let dirwalk_ctx = ctx.dirwalk; + let objects = objects.clone(); + let mut excludes = match ctx.resource_cache.attr_stack.state() { + State::CreateDirectoryAndAttributesStack { .. } | State::AttributesStack(_) => None, + State::AttributesAndIgnoreStack { .. } | State::IgnoreStack(_) => { + Some(ctx.resource_cache.attr_stack.clone()) + } + }; + let mut pathspec_attr_stack = ctx + .pathspec + .patterns() + .any(|p| !p.attributes.is_empty()) + .then(|| ctx.resource_cache.attr_stack.clone()); + let mut pathspec = ctx.pathspec.clone(); + move || -> Result<_, Error> { + gix_dir::walk( + worktree, + gix_dir::walk::Context { + git_dir_realpath: dirwalk_ctx.git_dir_realpath, + current_dir: dirwalk_ctx.current_dir, + index, + ignore_case_index_lookup: dirwalk_ctx.ignore_case_index_lookup, + pathspec: &mut pathspec, + pathspec_attributes: &mut |relative_path, case, is_dir, out| { + let stack = pathspec_attr_stack + .as_mut() + .expect("can only be called if attributes are used in patterns"); + stack + .set_case(case) + .at_entry(relative_path, Some(is_dir), &objects) + .map_or(false, |platform| platform.matching_attributes(out)) + }, + excludes: excludes.as_mut(), + objects: &objects, + explicit_traversal_root: Some(worktree), + }, + options, + &mut collect, + ) + .map_err(Error::DirWalk) + } + }) + .map_err(Error::SpawnThread) + }) + .transpose()?; + + let entries = &index.entries()[index + .prefixed_entries_range(ctx.pathspec.common_prefix()) + .unwrap_or(0..index.entries().len())]; + + let filter = options.rewrites.is_some().then(|| { + ( + ctx.resource_cache.filter.worktree_filter.clone(), + ctx.resource_cache.attr_stack.clone(), + ) + }); + let tracked_modifications_outcome = gix_features::parallel::build_thread() + .name("gix_status::index_as_worktree".into()) + .spawn_scoped(scope, { + let mut collect = tracked_modifications::Delegate { tx }; + let objects = objects.clone(); + let stack = ctx.resource_cache.attr_stack.clone(); + let filter = ctx.resource_cache.filter.worktree_filter.clone(); + move || -> Result<_, Error> { + crate::index_as_worktree( + index, + worktree, + &mut collect, + compare, + submodule, + objects, + progress, + crate::index_as_worktree::Context { + pathspec: ctx.pathspec, + stack, + filter, + should_interrupt: ctx.should_interrupt, + }, + options.tracked_file_modifications, + ) + .map_err(Error::TrackedFileModifications) + } + }) + .map_err(Error::SpawnThread)?; + + let tracker = options + .rewrites + .map(gix_diff::rewrites::Tracker::>::new) + .zip(filter); + let rewrite_outcome = match tracker { + Some((mut tracker, (mut filter, mut attrs))) => { + let mut entries_for_sorting = options.sorting.map(|_| Vec::new()); + let mut buf = Vec::new(); + for event in rx { + let (change, location) = match event { + Event::IndexEntry(record) => { + let location = Cow::Borrowed(record.relative_path); + (rewrite::ModificationOrDirwalkEntry::Modification(record), location) + } + Event::DirEntry(entry, collapsed_directory_status) => { + let location = Cow::Owned(entry.rela_path.clone()); + ( + rewrite::ModificationOrDirwalkEntry::DirwalkEntry { + id: rewrite::calculate_worktree_id( + options.object_hash, + worktree, + entry.disk_kind, + entry.rela_path.as_bstr(), + &mut filter, + &mut attrs, + &objects, + &mut buf, + ctx.should_interrupt, + )?, + entry, + collapsed_directory_status, + }, + location, + ) + } + }; + if let Some(v) = entries_for_sorting.as_mut() { + v.push((change, location)); + } else if let Some(change) = tracker.try_push_change(change, location.as_ref()) { + collector.visit_entry(rewrite::change_to_entry(change, entries)) + } + } + + let mut entries_for_sorting = entries_for_sorting.map(|mut v| { + v.sort_by(|a, b| a.1.cmp(&b.1)); + let mut remaining = Vec::new(); + for (change, location) in v { + if let Some(change) = tracker.try_push_change(change, location.as_ref()) { + remaining.push(rewrite::change_to_entry(change, entries)); + } + } + remaining + }); + + let outcome = tracker.emit( + |dest, src| { + match src { + None => { + let entry = rewrite::change_to_entry(dest.change, entries); + if let Some(v) = entries_for_sorting.as_mut() { + v.push(entry); + } else { + collector.visit_entry(entry) + } + } + Some(src) => { + let rewrite::ModificationOrDirwalkEntry::DirwalkEntry { + id, + entry, + collapsed_directory_status, + } = dest.change + else { + unreachable!("BUG: only possible destinations are dirwalk entries (additions)"); + }; + let source = match src.change { + ModificationOrDirwalkEntry::Modification(record) => { + RewriteSource::RewriteFromIndex { + index_entries: entries, + source_entry: record.entry, + source_entry_index: record.entry_index, + source_rela_path: record.relative_path, + source_status: record.status.clone(), + } + } + ModificationOrDirwalkEntry::DirwalkEntry { + id, + entry, + collapsed_directory_status, + } => RewriteSource::CopyFromDirectoryEntry { + source_dirwalk_entry: entry.clone(), + source_dirwalk_entry_collapsed_directory_status: + *collapsed_directory_status, + source_dirwalk_entry_id: *id, + }, + }; + + let entry = Entry::Rewrite { + source, + dirwalk_entry: entry, + dirwalk_entry_collapsed_directory_status: collapsed_directory_status, + dirwalk_entry_id: id, + diff: src.diff, + copy: src.kind == gix_diff::rewrites::tracker::visit::SourceKind::Copy, + }; + if let Some(v) = entries_for_sorting.as_mut() { + v.push(entry); + } else { + collector.visit_entry(entry); + } + } + } + gix_diff::tree::visit::Action::Continue + }, + &mut ctx.resource_cache, + &objects, + |_cb| { + // NOTE: to make this work, we'd want to wait the index modification check to complete. + // Then it's possible to efficiently emit the tracked files along with what we already sent, + // i.e. untracked and ignored files. + gix_features::trace::debug!("full-tree copy tracking isn't currently supported"); + Ok::<_, std::io::Error>(()) + }, + )?; + + if let Some(mut v) = entries_for_sorting { + v.sort_by(|a, b| a.destination_rela_path().cmp(b.destination_rela_path())); + for entry in v { + collector.visit_entry(entry); + } + } + Some(outcome) + } + None => { + let mut entries_for_sorting = options.sorting.map(|_| Vec::new()); + for event in rx { + let entry = match event { + Event::IndexEntry(record) => Entry::Modification { + entries, + entry: record.entry, + entry_index: record.entry_index, + rela_path: record.relative_path, + status: record.status, + }, + Event::DirEntry(entry, collapsed_directory_status) => Entry::DirectoryContents { + entry, + collapsed_directory_status, + }, + }; + + if let Some(v) = entries_for_sorting.as_mut() { + v.push(entry); + } else { + collector.visit_entry(entry); + } + } + + if let Some(mut v) = entries_for_sorting { + v.sort_by(|a, b| a.destination_rela_path().cmp(b.destination_rela_path())); + for entry in v { + collector.visit_entry(entry); + } + } + None + } + }; + + let walk_outcome = walk_outcome + .map(|handle| handle.join().expect("no panic")) + .transpose()?; + let tracked_modifications_outcome = tracked_modifications_outcome.join().expect("no panic")?; + Ok(Outcome { + dirwalk: walk_outcome.map(|t| t.0), + tracked_file_modification: tracked_modifications_outcome, + rewrites: rewrite_outcome, + }) + }) + } + + enum Event<'index, T, U> { + IndexEntry(crate::index_as_worktree::Record<'index, T, U>), + DirEntry(gix_dir::Entry, Option), + } + + mod tracked_modifications { + use crate::index_as_worktree::{EntryStatus, Record}; + use crate::index_as_worktree_with_renames::function::Event; + use bstr::BStr; + use gix_index::Entry; + + pub(super) struct Delegate<'index, T, U> { + pub(super) tx: std::sync::mpsc::Sender>, + } + + impl<'index, T, U> crate::index_as_worktree::VisitEntry<'index> for Delegate<'index, T, U> { + type ContentChange = T; + type SubmoduleStatus = U; + + fn visit_entry( + &mut self, + _entries: &'index [Entry], + entry: &'index Entry, + entry_index: usize, + rela_path: &'index BStr, + status: EntryStatus, + ) { + self.tx + .send(Event::IndexEntry(Record { + entry, + entry_index, + relative_path: rela_path, + status, + })) + .ok(); + } + } + } + + mod dirwalk { + use super::Event; + use gix_dir::entry::Status; + use gix_dir::walk::Action; + use gix_dir::EntryRef; + use std::sync::atomic::{AtomicBool, Ordering}; + + pub(super) struct Delegate<'index, 'a, T, U> { + pub(super) tx: std::sync::mpsc::Sender>, + pub(super) should_interrupt: &'a AtomicBool, + } + + impl<'index, 'a, T, U> gix_dir::walk::Delegate for Delegate<'index, 'a, T, U> { + fn emit(&mut self, entry: EntryRef<'_>, collapsed_directory_status: Option) -> Action { + let entry = entry.to_owned(); + self.tx.send(Event::DirEntry(entry, collapsed_directory_status)).ok(); + + if self.should_interrupt.load(Ordering::Relaxed) { + Action::Cancel + } else { + Action::Continue + } + } + } + } + + mod rewrite { + use crate::index_as_worktree::{Change, EntryStatus}; + use crate::index_as_worktree_with_renames::{Entry, Error}; + use bstr::BStr; + use gix_diff::rewrites::tracker::ChangeKind; + use gix_dir::entry::Kind; + use gix_filter::pipeline::convert::ToGitOutcome; + use gix_hash::oid; + use gix_object::tree::EntryMode; + use std::io::Read; + use std::path::Path; + + #[derive(Clone)] + pub enum ModificationOrDirwalkEntry<'index, T, U> + where + T: Clone, + U: Clone, + { + Modification(crate::index_as_worktree::Record<'index, T, U>), + DirwalkEntry { + id: gix_hash::ObjectId, + entry: gix_dir::Entry, + collapsed_directory_status: Option, + }, + } + + impl<'index, T, U> gix_diff::rewrites::tracker::Change for ModificationOrDirwalkEntry<'index, T, U> + where + T: Clone, + U: Clone, + { + fn id(&self) -> &oid { + match self { + ModificationOrDirwalkEntry::Modification(m) => &m.entry.id, + ModificationOrDirwalkEntry::DirwalkEntry { id, .. } => id, + } + } + + fn kind(&self) -> ChangeKind { + match self { + ModificationOrDirwalkEntry::Modification(m) => match &m.status { + EntryStatus::Conflict(_) | EntryStatus::IntentToAdd | EntryStatus::NeedsUpdate(_) => { + ChangeKind::Modification + } + EntryStatus::Change(c) => match c { + Change::Removed => ChangeKind::Deletion, + Change::Type | Change::Modification { .. } | Change::SubmoduleModification(_) => { + ChangeKind::Modification + } + }, + }, + ModificationOrDirwalkEntry::DirwalkEntry { .. } => ChangeKind::Addition, + } + } + + fn entry_mode(&self) -> EntryMode { + match self { + ModificationOrDirwalkEntry::Modification(c) => c.entry.mode.to_tree_entry_mode(), + ModificationOrDirwalkEntry::DirwalkEntry { entry, .. } => entry.disk_kind.map(|kind| { + match kind { + Kind::File => gix_object::tree::EntryKind::Blob, + Kind::Symlink => gix_object::tree::EntryKind::Link, + Kind::Repository | Kind::Directory => gix_object::tree::EntryKind::Tree, + } + .into() + }), + } + .unwrap_or(gix_object::tree::EntryKind::Blob.into()) + } + + fn id_and_entry_mode(&self) -> (&oid, EntryMode) { + (self.id(), self.entry_mode()) + } + } + + /// Note that for non-files, we always return a null-sha and assume that the rename-tracking + /// does nothing for these anyway. + #[allow(clippy::too_many_arguments)] + pub(super) fn calculate_worktree_id( + object_hash: gix_hash::Kind, + worktree_root: &Path, + disk_kind: Option, + rela_path: &BStr, + filter: &mut gix_filter::Pipeline, + attrs: &mut gix_worktree::Stack, + objects: &dyn gix_object::Find, + buf: &mut Vec, + should_interrupt: &std::sync::atomic::AtomicBool, + ) -> Result { + let Some(kind) = disk_kind else { + return Ok(object_hash.null()); + }; + + Ok(match kind { + Kind::File => { + let platform = attrs + .at_entry(rela_path, Some(false), objects) + .map_err(Error::SetAttributeContext)?; + let rela_path = gix_path::from_bstr(rela_path); + let file_path = worktree_root.join(rela_path.as_ref()); + let file = std::fs::File::open(&file_path).map_err(Error::OpenWorktreeFile)?; + let out = filter.convert_to_git( + file, + rela_path.as_ref(), + &mut |_path, attrs| { + platform.matching_attributes(attrs); + }, + &mut |_buf| Ok(None), + )?; + match out { + ToGitOutcome::Unchanged(mut file) => gix_object::compute_stream_hash( + object_hash, + gix_object::Kind::Blob, + &mut file, + file_path.metadata().map_err(Error::OpenWorktreeFile)?.len(), + &mut gix_features::progress::Discard, + should_interrupt, + ) + .map_err(Error::HashFile)?, + ToGitOutcome::Buffer(buf) => gix_object::compute_hash(object_hash, gix_object::Kind::Blob, buf), + ToGitOutcome::Process(mut stream) => { + buf.clear(); + stream.read_to_end(buf).map_err(Error::HashFile)?; + gix_object::compute_hash(object_hash, gix_object::Kind::Blob, buf) + } + } + } + Kind::Symlink => { + let path = worktree_root.join(gix_path::from_bstr(rela_path)); + let target = gix_path::into_bstr(std::fs::read_link(path).map_err(Error::ReadLink)?); + gix_object::compute_hash(object_hash, gix_object::Kind::Blob, &target) + } + Kind::Directory | Kind::Repository => object_hash.null(), + }) + } + + #[inline] + pub(super) fn change_to_entry<'index, T, U>( + change: ModificationOrDirwalkEntry<'index, T, U>, + entries: &'index [gix_index::Entry], + ) -> Entry<'index, T, U> + where + T: Clone, + U: Clone, + { + match change { + ModificationOrDirwalkEntry::Modification(r) => Entry::Modification { + entries, + entry: r.entry, + entry_index: r.entry_index, + rela_path: r.relative_path, + status: r.status, + }, + ModificationOrDirwalkEntry::DirwalkEntry { + id: _, + entry, + collapsed_directory_status, + } => Entry::DirectoryContents { + entry, + collapsed_directory_status, + }, + } + } + } +} diff --git a/gix-status/src/index_as_worktree_with_renames/recorder.rs b/gix-status/src/index_as_worktree_with_renames/recorder.rs new file mode 100644 index 00000000000..81d05f1b064 --- /dev/null +++ b/gix-status/src/index_as_worktree_with_renames/recorder.rs @@ -0,0 +1,17 @@ +use crate::index_as_worktree_with_renames::{Entry, VisitEntry}; + +/// Convenience implementation of [`VisitEntry`] that collects all changes into a `Vec`. +#[derive(Debug, Default)] +pub struct Recorder<'index, T = (), U = ()> { + /// The collected changes. + pub records: Vec>, +} + +impl<'index, T: Send, U: Send> VisitEntry<'index> for Recorder<'index, T, U> { + type ContentChange = T; + type SubmoduleStatus = U; + + fn visit_entry(&mut self, entry: Entry<'index, Self::ContentChange, Self::SubmoduleStatus>) { + self.records.push(entry) + } +} diff --git a/gix-status/src/index_as_worktree_with_renames/types.rs b/gix-status/src/index_as_worktree_with_renames/types.rs new file mode 100644 index 00000000000..870a4e4e4cd --- /dev/null +++ b/gix-status/src/index_as_worktree_with_renames/types.rs @@ -0,0 +1,371 @@ +use crate::index_as_worktree::{Change, EntryStatus}; +use bstr::{BStr, ByteSlice}; +use std::sync::atomic::AtomicBool; + +/// The error returned by [index_as_worktree_with_renames()`](crate::index_as_worktree_with_renames()). +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + TrackedFileModifications(#[from] crate::index_as_worktree::Error), + #[error(transparent)] + DirWalk(gix_dir::walk::Error), + #[error(transparent)] + SpawnThread(std::io::Error), + #[error("Failed to change the context for querying gitattributes to the respective path")] + SetAttributeContext(std::io::Error), + #[error("Could not open worktree file for reading")] + OpenWorktreeFile(std::io::Error), + #[error(transparent)] + HashFile(std::io::Error), + #[error("Could not read worktree link content")] + ReadLink(std::io::Error), + #[error(transparent)] + ConvertToGit(#[from] gix_filter::pipeline::convert::to_git::Error), + #[error(transparent)] + RewriteTracker(#[from] gix_diff::rewrites::tracker::emit::Error), +} + +/// The way all output should be sorted. +#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum Sorting { + /// The entries are sorted by their path in a case-sensitive fashion. + #[default] + ByPathCaseSensitive, +} + +/// Provide additional information collected during the runtime of [`index_as_worktree_with_renames()`](crate::index_as_worktree_with_renames()). +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Outcome { + /// The outcome of the modification check of tracked files. + pub tracked_file_modification: crate::index_as_worktree::Outcome, + /// The outcome of the directory walk, or `None` if its [options](Options::dirwalk) also weren't present which means + /// the dirwalk never ran. + pub dirwalk: Option, + /// The result of the rewrite operation, if [rewrites were configured](Options::rewrites). + pub rewrites: Option, +} + +/// Either an index entry for renames or another directory entry in case of copies. +#[derive(Clone, PartialEq, Debug)] +pub enum RewriteSource<'index, ContentChange, SubmoduleStatus> { + /// The source originates in the index and is detected as missing in the working tree. + /// This can also happen for copies. + RewriteFromIndex { + /// All entries in the index. + index_entries: &'index [gix_index::Entry], + /// The entry that is the source of the rewrite, which means it was removed on disk, + /// equivalent to [Change::Removed](crate::index_as_worktree::Change::Removed). + /// + /// Note that the [entry-id](gix_index::Entry::id) is the content-id of the source of the rewrite. + source_entry: &'index gix_index::Entry, + /// The index of the `source_entry` for lookup in `index_entries` - useful to look at neighbors. + source_entry_index: usize, + /// The repository-relative path of the `source_entry`. + source_rela_path: &'index BStr, + /// The computed status of the `source_entry`. + source_status: EntryStatus, + }, + /// This source originates in the directory tree and is always the source of copies. + CopyFromDirectoryEntry { + /// The source of the copy operation, which is also an entry of the directory walk. + /// + /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the source of the rewrite. + source_dirwalk_entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `source_dirwalk_entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `source_dirwalk_entry` and if [gix_dir::walk::Options::emit_collapsed] was + /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + source_dirwalk_entry_collapsed_directory_status: Option, + /// The object id as it would appear if the entry was written to the object database. + /// It's the same as `dirwalk_entry_id`, or `diff` is `Some(_)` to indicate that the copy was determined by similarity. + source_dirwalk_entry_id: gix_hash::ObjectId, + }, +} + +/// An 'entry' in the sense of a merge of modified tracked files and results from a directory walk. +#[derive(Clone, PartialEq, Debug)] +pub enum Entry<'index, ContentChange, SubmoduleStatus> { + /// A tracked file was modified, and index-specific information is passed. + Modification { + /// All entries in the index. + entries: &'index [gix_index::Entry], + /// The entry with modifications. + entry: &'index gix_index::Entry, + /// The index of the `entry` for lookup in `entries` - useful to look at neighbors. + entry_index: usize, + /// The repository-relative path of the entry. + rela_path: &'index BStr, + /// The computed status of the entry. + status: EntryStatus, + }, + /// An entry returned by the directory walk, without any relation to the index. + /// + /// This can happen if ignored files are returned as well, or if rename-tracking is disabled. + DirectoryContents { + /// The entry found during the disk traversal. + entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was + /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + collapsed_directory_status: Option, + }, + /// The rewrite tracking discovered a match between a deleted and added file, and considers them equal enough, + /// depending on the tracker settings. + /// + /// Note that the source of the rewrite is always the index as it detects the absence of entries, something that + /// can't be done during a directory walk. + Rewrite { + /// The source of the rewrite operation. + source: RewriteSource<'index, ContentChange, SubmoduleStatus>, + /// The untracked entry found during the disk traversal, the destination of the rewrite. + /// + /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the destination of the rewrite, and the current + /// location of the entry. + dirwalk_entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `dirwalk_entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `dirwalk_entry` and if [gix_dir::walk::Options::emit_collapsed] was + /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + dirwalk_entry_collapsed_directory_status: Option, + /// The object id after the rename, specifically hashed in order to determine equality. + dirwalk_entry_id: gix_hash::ObjectId, + /// It's `None` if the 'source.id' is equal to `dirwalk_entry_id`, as identity made an actual diff computation unnecessary. + /// Otherwise, and if enabled, it's `Some(stats)` to indicate how similar both entries were. + diff: Option, + /// If true, this rewrite is created by copy, and 'source.id' is pointing to its source. + /// Otherwise, it's a rename, and 'source.id' points to a deleted object, + /// as renames are tracked as deletions and additions of the same or similar content. + copy: bool, + }, +} + +/// An easy to grasp summary of the changes of the worktree compared to the index. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum Summary { + /// An entry exists in the index but doesn't in the worktree. + Removed, + /// A file exists in the worktree but doesn't have a corresponding entry in the index. + /// + /// In a `git status`, this would be an untracked file. + Added, + /// A file or submodule was modified, compared to the state recorded in the index. + /// On Unix, the change of executable bit also counts as modification. + /// + /// If the modification is a submodule, it could also stem from various other factors, like + /// having modified or untracked files, or changes in the index. + Modified, + /// The type of the entry in the worktree changed compared to the index. + /// + /// This can happen if a file in the worktree now is a directory, or a symlink, for example. + TypeChange, + /// A match between an entry in the index and a differently named file in the worktree was detected, + /// considering the index the source of a rename operation, and the worktree file the destination. + /// + /// Note that the renamed file may also have been modified, but is considered similar enough. + /// + /// To obtain this state, rewrite-tracking must have been enabled, as otherwise the source would be + /// considered `Removed` and the destination would be considered `Added`. + Renamed, + /// A match between an entry in the index and a differently named file in the worktree was detected, + /// considering the index the source of the copy of a worktree file. + /// + /// Note that the copied file may also have been modified, but is considered similar enough. + /// + /// To obtain this state, rewrite-and-copy-tracking must have been enabled, as otherwise the source would be + /// considered `Removed` and the destination would be considered `Added`. + Copied, + /// An index entry with a corresponding worktree file that corresponds to an untracked worktree + /// file marked with `git add --intent-to-add`. + /// + /// This means it's not available in the object database yet even though now an entry exists + /// that represents the worktree file. + /// The entry represents the promise of adding a new file, no matter the actual stat or content. + /// Effectively this means nothing changed. + /// This also means the file is still present, and that no detailed change checks were performed. + IntentToAdd, + /// Describes a conflicting entry in the index, which also means that + /// no further comparison to the worktree file was performed. + /// + /// As this variant only describes the state of the index, the corresponding worktree file may + /// or may not exist. + Conflict, +} + +/// Access +impl RewriteSource<'_, ContentChange, SubmoduleStatus> { + /// The repository-relative path of this source. + pub fn rela_path(&self) -> &BStr { + match self { + RewriteSource::RewriteFromIndex { source_rela_path, .. } => source_rela_path, + RewriteSource::CopyFromDirectoryEntry { + source_dirwalk_entry, .. + } => source_dirwalk_entry.rela_path.as_bstr(), + } + } +} + +/// Access +impl Entry<'_, ContentChange, SubmoduleStatus> { + /// Return a summary of the entry as digest of its status, or `None` if this entry is + /// created from the directory walk and is *not untracked*, or if it is merely to communicate + /// a needed update to the index entry. + pub fn summary(&self) -> Option { + Some(match self { + Entry::Modification { + status: EntryStatus::Conflict(_), + .. + } => Summary::Conflict, + Entry::Modification { + status: EntryStatus::IntentToAdd, + .. + } => Summary::IntentToAdd, + Entry::Modification { + status: EntryStatus::NeedsUpdate(_), + .. + } => return None, + Entry::Modification { + status: EntryStatus::Change(change), + .. + } => match change { + Change::SubmoduleModification(_) | Change::Modification { .. } => Summary::Modified, + Change::Type => Summary::TypeChange, + Change::Removed => Summary::Removed, + }, + Entry::DirectoryContents { entry, .. } => { + if matches!(entry.status, gix_dir::entry::Status::Untracked) { + Summary::Added + } else { + return None; + } + } + Entry::Rewrite { copy, .. } => { + if *copy { + Summary::Copied + } else { + Summary::Renamed + } + } + }) + } + /// The repository-relative path at which the source of a rewrite is located. + /// + /// If this isn't a rewrite, the path is the location of the entry itself. + pub fn source_rela_path(&self) -> &BStr { + match self { + Entry::Modification { rela_path, .. } => rela_path, + Entry::DirectoryContents { entry, .. } => entry.rela_path.as_bstr(), + Entry::Rewrite { source, .. } => source.rela_path(), + } + } + + /// The repository-relative path at which the destination of a rewrite is located. + /// + /// If this isn't a rewrite, the path is the location of the entry itself. + pub fn destination_rela_path(&self) -> &BStr { + match self { + Entry::Modification { rela_path, .. } => rela_path, + Entry::DirectoryContents { entry, .. } => entry.rela_path.as_bstr(), + Entry::Rewrite { dirwalk_entry, .. } => dirwalk_entry.rela_path.as_bstr(), + } + } +} + +/// Options for use in [index_as_worktree_with_renames()](crate::index_as_worktree_with_renames()). +#[derive(Clone, Default)] +pub struct Options { + /// The way all output should be sorted. + /// + /// If `None`, and depending on the `rewrites` field, output will be immediate but the output order + /// isn't determined, and may differ between two runs. `rewrites` also depend on the order of entries that + /// are presented to it, hence for deterministic results, sorting needs to be enabled. + /// + /// If `Some(_)`, all entries are collected beforehand, so they can be sorted before outputting any of them + /// to the user. + /// + /// If immediate output of entries in any order is desired, this should be `None`, + /// along with `rewrites` being `None` as well. + pub sorting: Option, + /// The kind of hash to create when hashing worktree entries. + pub object_hash: gix_hash::Kind, + /// Options to configure how modifications to tracked files should be obtained. + pub tracked_file_modifications: crate::index_as_worktree::Options, + /// Options to control the directory walk that informs about untracked files. + /// + /// Note that we forcefully disable emission of tracked files to avoid any overlap + /// between emissions to indicate modifications, and those that are obtained by + /// the directory walk. + /// + /// If `None`, the directory walk portion will not run at all, yielding data similar + /// to a bare [index_as_worktree()](crate::index_as_worktree()) call. + pub dirwalk: Option, + /// The configuration for the rewrite tracking. Note that if set, the [`dirwalk`](Self::dirwalk) should be configured + /// to *not* collapse untracked and ignored entries, as rewrite tracking is on a file-by-file basis. + /// Also note that when `Some(_)`, it will collect certain changes depending on the exact configuration, which typically increases + /// the latency until the first entries are received. Note that some entries are never candidates for renames, which means + /// they are forwarded to the caller right away. + /// + /// If `None`, no tracking will occour, which means that all output becomes visible to the delegate immediately. + pub rewrites: Option, +} + +/// The context for [index_as_worktree_with_renames()`](crate::index_as_worktree_with_renames()). +pub struct Context<'a> { + /// The pathspec to limit the amount of paths that are checked. Can be empty to allow all paths. + /// + /// Note that these are expected to have a [commont_prefix()](gix_pathspec::Search::common_prefix()) according + /// to the prefix of the repository to efficiently limit the scope of the paths we process, both for the + /// index modifications as well as for the directory walk. + pub pathspec: gix_pathspec::Search, + /// A fully-configured platform capable of producing diffable buffers similar to what Git would do, for use + /// with rewrite tracking. + /// + /// Note that it contains resources that are additionally used here: + /// + /// * `attr_stack` + /// - A stack pre-configured to allow accessing attributes for each entry, as required for `filter` + /// and possibly pathspecs. + /// It *may* also allow accessing `.gitignore` information for use in the directory walk. + /// If no excludes information is present, the directory walk will identify ignored files as untracked, which + /// might be desirable under certain circumstances. + /// * `filter` + /// - A filter to be able to perform conversions from and to the worktree format. + /// It is needed to potentially refresh the index with data read from the worktree, which needs to be converted back + /// to the form stored in Git. + pub resource_cache: gix_diff::blob::Platform, + /// A flag to query to learn if cancellation is requested. + pub should_interrupt: &'a AtomicBool, + /// The context for the directory walk. + pub dirwalk: DirwalkContext<'a>, +} + +/// All information that is required to perform a [dirwalk](gix_dir::walk()). +pub struct DirwalkContext<'a> { + /// The `git_dir` of the parent repository, after a call to [`gix_path::realpath()`]. + /// + /// It's used to help us differentiate our own `.git` directory from nested unrelated repositories, + /// which is needed if `core.worktree` is used to nest the `.git` directory deeper within. + pub git_dir_realpath: &'a std::path::Path, + /// The current working directory as returned by `gix_fs::current_dir()` to assure it respects `core.precomposeUnicode`. + /// It's used to produce the realpath of the git-dir of a repository candidate to assure it's not our own repository. + pub current_dir: &'a std::path::Path, + /// A utility to lookup index entries faster, and deal with ignore-case handling. + /// + /// Must be set if [`ignore_case`](gix_dir::walk::Options::ignore_case) is `true`, or else some entries won't be found if their case is different. + /// + /// [Read more in `gix-dir`](gix_dir::walk::Context::ignore_case_index_lookup). + pub ignore_case_index_lookup: Option<&'a gix_index::AccelerateLookup<'a>>, +} + +/// Observe the status of an entry by comparing an index entry to the worktree, along +/// with potential directory walk results. +pub trait VisitEntry<'a> { + /// Data generated by comparing an entry with a file. + type ContentChange; + /// Data obtained when checking the submodule status. + type SubmoduleStatus; + /// Observe the `status` of `entry` at the repository-relative `rela_path` at `entry_index` + /// (for accessing `entry` and surrounding in the complete list of `entries`). + fn visit_entry(&mut self, entry: Entry<'a, Self::ContentChange, Self::SubmoduleStatus>); +} diff --git a/gix-status/src/lib.rs b/gix-status/src/lib.rs index 0749c5bd6cb..a2dbf6a4c51 100644 --- a/gix-status/src/lib.rs +++ b/gix-status/src/lib.rs @@ -6,11 +6,23 @@ //! * find untracked files //! //! While also being able to check check if the working tree is dirty, quickly. +//! +//! ### Feature Flags +#![cfg_attr( + all(doc, feature = "document-features"), + doc = ::document_features::document_features!() +)] +#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms, unsafe_code)] pub mod index_as_worktree; pub use index_as_worktree::function::index_as_worktree; +#[cfg(feature = "worktree-rewrites")] +pub mod index_as_worktree_with_renames; +#[cfg(feature = "worktree-rewrites")] +pub use index_as_worktree_with_renames::function::index_as_worktree_with_renames; + /// A stack that validates we are not going through a symlink in a way that is read-only. /// /// It can efficiently validate paths when these are queried in sort-order, which leads to each component diff --git a/gix-status/tests/Cargo.toml b/gix-status/tests/Cargo.toml index 2e138b9a184..576a81829c4 100644 --- a/gix-status/tests/Cargo.toml +++ b/gix-status/tests/Cargo.toml @@ -17,10 +17,15 @@ path = "worktree.rs" gix-features-parallel = ["gix-features/parallel"] [dev-dependencies] -gix-status = { path = ".." } +gix-status = { path = "..", features = ["worktree-rewrites"] } gix-testtools = { path = "../../tests/tools" } gix-index = { path = "../../gix-index" } gix-fs = { path = "../../gix-fs" } +gix-diff = { path = "../../gix-diff" } +gix-filter = { path = "../../gix-filter" } +gix-path = { path = "../../gix-path" } +gix-dir = { path = "../../gix-dir" } +gix-odb = { path = "../../gix-odb" } gix-hash = { path = "../../gix-hash" } gix-object = { path = "../../gix-object" } gix-features = { path = "../../gix-features" } @@ -28,4 +33,4 @@ gix-pathspec = { path = "../../gix-pathspec" } gix-worktree = { path = "../../gix-worktree" } filetime = "0.2.15" bstr = { version = "1.3.0", default-features = false } - +pretty_assertions = "1.4.0" diff --git a/gix-status/tests/fixtures/generated-archives/status_many.tar.xz b/gix-status/tests/fixtures/generated-archives/status_many.tar.xz new file mode 100644 index 00000000000..0ba30dc3e29 Binary files /dev/null and b/gix-status/tests/fixtures/generated-archives/status_many.tar.xz differ diff --git a/gix-status/tests/fixtures/status_many.sh b/gix-status/tests/fixtures/status_many.sh new file mode 100644 index 00000000000..2315ddc2a96 --- /dev/null +++ b/gix-status/tests/fixtures/status_many.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q changed-and-untracked +(cd changed-and-untracked + touch empty + echo "content" > executable + chmod +x executable + + mkdir dir + echo "other content" > dir/content + echo "different content" > dir/content2 + + git add -A + git commit -m "Commit" + echo "change" >> executable + + + mkdir dir/empty + >dir/untracked + >untracked + + git status +) + +cp -R changed-and-untracked changed-and-untracked-and-renamed +(cd changed-and-untracked-and-renamed + # it has a local change compared to the indexed version, hence it's rewritten + mv executable rewritten-executable + + cp dir/content content-copy + cp dir/content content-copy-with-rewrite + echo change >> content-copy-with-rewrite + + mv dir/content plainly-renamed-content + + mv dir/content2 content-with-rewrite + echo change >> content-with-rewrite + +) diff --git a/gix-status/tests/status/index_as_worktree.rs b/gix-status/tests/status/index_as_worktree.rs index d6753e26943..e485f0759c7 100644 --- a/gix-status/tests/status/index_as_worktree.rs +++ b/gix-status/tests/status/index_as_worktree.rs @@ -22,7 +22,7 @@ use crate::fixture_path; // changes when extracting the data so we need to disable all advanced stat // changes and only look at mtime seconds and file size to properly // test all code paths (and to trigger racy git). -const TEST_OPTIONS: index::entry::stat::Options = index::entry::stat::Options { +pub(super) const TEST_OPTIONS: index::entry::stat::Options = index::entry::stat::Options { trust_ctime: false, check_stat: false, use_nsec: false, @@ -128,7 +128,9 @@ fn fixture_filtered_detailed( } /// Note that we also reset certain information to assure there is no flakiness - everything regarding race-detection otherwise can cause failures. -fn records_to_tuple<'index>(records: impl IntoIterator>) -> Vec> { +pub(super) fn records_to_tuple<'index>( + records: impl IntoIterator>, +) -> Vec> { records .into_iter() .filter_map(|r| deracify_status(r.status).map(|status| (r.relative_path, r.entry_index, status))) @@ -159,8 +161,8 @@ fn deracify_status(status: EntryStatus) -> Option { } #[derive(Clone)] -struct SubmoduleStatusMock { - dirty: bool, +pub(super) struct SubmoduleStatusMock { + pub(super) dirty: bool, } impl SubmoduleStatus for SubmoduleStatusMock { @@ -172,7 +174,7 @@ impl SubmoduleStatus for SubmoduleStatusMock { } } -fn to_pathspecs(input: &[&str]) -> Vec { +pub(super) fn to_pathspecs(input: &[&str]) -> Vec { input .iter() .map(|pattern| gix_pathspec::parse(pattern.as_bytes(), Default::default()).expect("known to be valid")) diff --git a/gix-status/tests/status/index_as_worktree_with_renames.rs b/gix-status/tests/status/index_as_worktree_with_renames.rs new file mode 100644 index 00000000000..0706362a8b9 --- /dev/null +++ b/gix-status/tests/status/index_as_worktree_with_renames.rs @@ -0,0 +1,384 @@ +use crate::status::fixture_path; +use bstr::ByteSlice; +use gix_diff::blob::pipeline::WorktreeRoots; +use gix_diff::rewrites::CopySource; +use gix_status::index_as_worktree::traits::FastEq; +use gix_status::index_as_worktree::{Change, EntryStatus}; +use gix_status::index_as_worktree_with_renames; +use gix_status::index_as_worktree_with_renames::{ + Context, DirwalkContext, Entry, Options, Outcome, Recorder, Sorting, Summary, +}; +use pretty_assertions::assert_eq; + +#[test] +fn changed_and_untracked_and_renamed() { + let expectations_with_dirwalk = [ + // Not always will we match the right source to destinations, there is ambiguity. + Expectation::Rewrite { + source_rela_path: "dir/content", + dest_rela_path: "content-copy", + dest_dirwalk_status: gix_dir::entry::Status::Untracked, + diff: None, + copy: false, + }, + Expectation::DirwalkEntry { + rela_path: "content-copy-with-rewrite", + status: gix_dir::entry::Status::Untracked, + disk_kind: Some(gix_dir::entry::Kind::File), + }, + Expectation::Rewrite { + source_rela_path: "dir/content2", + dest_rela_path: "content-with-rewrite", + dest_dirwalk_status: gix_dir::entry::Status::Untracked, + diff: Some(gix_diff::blob::DiffLineStats { + removals: 0, + insertions: 1, + before: 1, + after: 2, + similarity: 0.72, + }), + copy: false, + }, + Expectation::Rewrite { + source_rela_path: "empty", + dest_rela_path: "dir/untracked", + dest_dirwalk_status: gix_dir::entry::Status::Untracked, + diff: None, + copy: true, + }, + // This is just detected as untracked, related to how the rename-tracker matches pairs + Expectation::DirwalkEntry { + rela_path: "plainly-renamed-content", + status: gix_dir::entry::Status::Untracked, + disk_kind: Some(gix_dir::entry::Kind::File), + }, + Expectation::Rewrite { + source_rela_path: "executable", + dest_rela_path: "rewritten-executable", + dest_dirwalk_status: gix_dir::entry::Status::Untracked, + diff: Some(gix_diff::blob::DiffLineStats { + removals: 0, + insertions: 1, + before: 1, + after: 2, + similarity: 0.53333336, + }), + copy: false, + }, + Expectation::Rewrite { + source_rela_path: "empty", + dest_rela_path: "untracked", + dest_dirwalk_status: gix_dir::entry::Status::Untracked, + diff: None, + copy: true, + }, + ]; + let rewrites = gix_diff::Rewrites { + copies: Some(gix_diff::rewrites::Copies { + source: CopySource::FromSetOfModifiedFiles, + percentage: Some(0.3), + }), + percentage: Some(0.3), + limit: 0, + }; + let out = fixture_filtered_detailed( + "changed-and-untracked-and-renamed", + &[], + &expectations_with_dirwalk, + Some(rewrites), + Some(Default::default()), + ); + assert_eq!( + out.rewrites, + Some(gix_diff::rewrites::Outcome { + options: rewrites, + num_similarity_checks: 11, + num_similarity_checks_skipped_for_rename_tracking_due_to_limit: 0, + num_similarity_checks_skipped_for_copy_tracking_due_to_limit: 0, + }) + ) +} + +#[test] +fn changed_and_untracked() { + let out = fixture_filtered_detailed( + "changed-and-untracked", + &[], + &[Expectation::Modification { + rela_path: "executable", + status: EntryStatus::Change(Change::Modification { + executable_bit_changed: false, + content_change: Some(()), + set_entry_stat_size_zero: false, + }), + }], + None, + None, + ); + assert_eq!(out.tracked_file_modification.entries_processed, 4); + assert_eq!( + out.dirwalk, None, + "we didn't configure the dirwalk, so it's just like a modification check" + ); + assert_eq!(out.rewrites, None, "rewrite checking isn't configured either"); + + let expectations_with_dirwalk = [ + Expectation::DirwalkEntry { + rela_path: "dir/untracked", + status: gix_dir::entry::Status::Untracked, + disk_kind: Some(gix_dir::entry::Kind::File), + }, + Expectation::Modification { + rela_path: "executable", + status: EntryStatus::Change(Change::Modification { + executable_bit_changed: false, + content_change: Some(()), + set_entry_stat_size_zero: false, + }), + }, + Expectation::DirwalkEntry { + rela_path: "untracked", + status: gix_dir::entry::Status::Untracked, + disk_kind: Some(gix_dir::entry::Kind::File), + }, + ]; + let out = fixture_filtered_detailed( + "changed-and-untracked", + &[], + &expectations_with_dirwalk, + None, + Some(gix_dir::walk::Options::default()), + ); + + let dirwalk = out.dirwalk.expect("configured thus has output"); + assert_eq!( + dirwalk, + gix_dir::walk::Outcome { + read_dir_calls: 3, + returned_entries: 2, + seen_entries: 8, + } + ); + assert_eq!(out.rewrites, None, "rewrites are still not configured"); + + let out = fixture_filtered_detailed( + "changed-and-untracked", + &[], + &expectations_with_dirwalk, + Some(Default::default()), + Some(gix_dir::walk::Options::default()), + ); + + let rewrites = out.rewrites.expect("configured thus has output"); + assert_eq!( + rewrites, + gix_diff::rewrites::Outcome::default(), + "there actually is no candidates pairs as there are no deletions" + ); +} + +fn fixture_filtered_detailed( + subdir: &str, + pathspecs: &[&str], + expected: &[Expectation<'_>], + rewrites: Option, + dirwalk: Option, +) -> Outcome { + fn cleanup(mut out: Outcome) -> Outcome { + out.tracked_file_modification.worktree_bytes = 0; + out.tracked_file_modification.worktree_files_read = 0; + out.tracked_file_modification.entries_to_update = 0; + out.tracked_file_modification.racy_clean = 0; + out + } + + let worktree = fixture_path("status_many.sh").join(subdir); + let git_dir = worktree.join(".git"); + let index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, false, Default::default()).unwrap(); + let search = gix_pathspec::Search::from_specs( + crate::status::index_as_worktree::to_pathspecs(pathspecs), + None, + std::path::Path::new(""), + ) + .expect("valid specs can be normalized"); + let stack = gix_worktree::Stack::from_state_and_ignore_case( + worktree.clone(), + false, + gix_worktree::stack::State::AttributesAndIgnoreStack { + attributes: Default::default(), + ignore: Default::default(), + }, + &index, + index.path_backing(), + ); + let capabilities = gix_fs::Capabilities::probe(&git_dir); + let resource_cache = gix_diff::blob::Platform::new( + Default::default(), + gix_diff::blob::Pipeline::new( + WorktreeRoots { + old_root: None, + new_root: Some(worktree.to_owned()), + }, + gix_filter::Pipeline::new(Default::default(), Default::default()), + vec![], + gix_diff::blob::pipeline::Options { + large_file_threshold_bytes: 0, + fs: capabilities, + }, + ), + gix_diff::blob::pipeline::Mode::ToGit, + stack, + ); + + let git_dir_real = gix_path::realpath(&git_dir).unwrap(); + let cwd = gix_fs::current_dir(capabilities.precompose_unicode).unwrap(); + let context = Context { + pathspec: search, + resource_cache, + should_interrupt: &Default::default(), + dirwalk: DirwalkContext { + git_dir_realpath: &git_dir_real, + current_dir: &cwd, + ignore_case_index_lookup: None, + }, + }; + let options = Options { + object_hash: gix_hash::Kind::Sha1, + tracked_file_modifications: gix_status::index_as_worktree::Options { + fs: capabilities, + stat: crate::status::index_as_worktree::TEST_OPTIONS, + ..Default::default() + }, + dirwalk, + sorting: Some(Sorting::ByPathCaseSensitive), + rewrites, + }; + + let mut recorder = Recorder::default(); + let objects = gix_odb::at(git_dir.join("objects")).unwrap().into_arc().unwrap(); + let outcome = index_as_worktree_with_renames( + &index, + &worktree, + &mut recorder, + FastEq, + crate::status::index_as_worktree::SubmoduleStatusMock { dirty: false }, + objects, + &mut gix_features::progress::Discard, + context, + options, + ) + .unwrap(); + + let actual = records_to_expectations(&recorder.records); + assert_eq!(actual, expected); + assert_summary(&recorder.records, expected); + cleanup(outcome) +} + +fn assert_summary(entries: &[Entry<(), ()>], expected: &[Expectation]) { + let entries: Vec<_> = entries + .iter() + .filter(|r| { + !matches!( + r, + Entry::Modification { + status: EntryStatus::NeedsUpdate(..), + .. + } + ) + }) + .collect(); + assert_eq!(entries.len(), expected.len()); + for (entry, expected) in entries.iter().zip(expected) { + assert_eq!(entry.summary(), expected.summary()); + } +} + +fn records_to_expectations<'a>(recs: &'a [Entry<'_, (), ()>]) -> Vec> { + recs.iter() + .filter(|r| { + !matches!( + r, + Entry::Modification { + status: EntryStatus::NeedsUpdate(..), + .. + } + ) + }) + .map(|r| match r { + Entry::Modification { rela_path, status, .. } => Expectation::Modification { + rela_path: rela_path.to_str().unwrap(), + status: status.clone(), + }, + Entry::DirectoryContents { entry, .. } => Expectation::DirwalkEntry { + rela_path: entry.rela_path.to_str().unwrap(), + status: entry.status, + disk_kind: entry.disk_kind, + }, + Entry::Rewrite { + source, + dirwalk_entry, + diff, + copy, + .. + } => Expectation::Rewrite { + source_rela_path: source.rela_path().to_str().unwrap(), + dest_rela_path: dirwalk_entry.rela_path.to_str().unwrap(), + dest_dirwalk_status: dirwalk_entry.status, + diff: *diff, + copy: *copy, + }, + }) + .collect() +} + +#[derive(Debug, Clone, PartialEq)] +enum Expectation<'a> { + Modification { + rela_path: &'a str, + status: EntryStatus<(), ()>, + }, + DirwalkEntry { + rela_path: &'a str, + status: gix_dir::entry::Status, + disk_kind: Option, + }, + Rewrite { + source_rela_path: &'a str, + dest_rela_path: &'a str, + dest_dirwalk_status: gix_dir::entry::Status, + diff: Option, + copy: bool, + }, +} + +impl Expectation<'_> { + pub fn summary(&self) -> Option { + Some(match self { + Expectation::Modification { status, .. } => match status { + EntryStatus::Conflict(_) => Summary::Conflict, + EntryStatus::Change(change) => match change { + Change::Removed => Summary::Removed, + Change::Type => Summary::TypeChange, + Change::Modification { .. } | Change::SubmoduleModification(_) => Summary::Modified, + }, + EntryStatus::NeedsUpdate(_) => return None, + EntryStatus::IntentToAdd => Summary::IntentToAdd, + }, + Expectation::DirwalkEntry { status, .. } => { + if matches!(status, gix_dir::entry::Status::Untracked) { + Summary::Added + } else { + return None; + } + } + Expectation::Rewrite { copy, .. } => { + if *copy { + Summary::Copied + } else { + Summary::Renamed + } + } + }) + } +} diff --git a/gix-status/tests/status/mod.rs b/gix-status/tests/status/mod.rs index e758770cf90..4da345b7a8d 100644 --- a/gix-status/tests/status/mod.rs +++ b/gix-status/tests/status/mod.rs @@ -1,4 +1,5 @@ mod index_as_worktree; +mod index_as_worktree_with_renames; pub fn fixture_path(name: &str) -> std::path::PathBuf { let dir = gix_testtools::scripted_fixture_read_only_standalone(std::path::Path::new(name).with_extension("sh")) diff --git a/gix-submodule/src/config.rs b/gix-submodule/src/config.rs index 2026966917c..98148b71cc8 100644 --- a/gix-submodule/src/config.rs +++ b/gix-submodule/src/config.rs @@ -4,8 +4,6 @@ use bstr::{BStr, BString, ByteSlice}; #[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum Ignore { /// Submodule changes won't be considered at all, which is the fastest option. - /// - /// Note that changes to the submodule hash in the superproject will still be observable. All, /// Ignore any changes to the submodule working tree, only show committed differences between the `HEAD` of the submodule /// compared to the recorded commit in the superproject. @@ -151,6 +149,7 @@ pub struct Error { } /// +#[allow(clippy::empty_docs)] pub mod branch { use bstr::BString; @@ -166,6 +165,7 @@ pub mod branch { } /// +#[allow(clippy::empty_docs)] pub mod update { use bstr::BString; @@ -181,6 +181,7 @@ pub mod update { } /// +#[allow(clippy::empty_docs)] pub mod url { use bstr::BString; @@ -199,6 +200,7 @@ pub mod url { } /// +#[allow(clippy::empty_docs)] pub mod path { use bstr::BString; diff --git a/gix-submodule/src/lib.rs b/gix-submodule/src/lib.rs index 639af30fae3..7b3068b7739 100644 --- a/gix-submodule/src/lib.rs +++ b/gix-submodule/src/lib.rs @@ -19,9 +19,11 @@ pub struct File { mod access; /// +#[allow(clippy::empty_docs)] pub mod config; /// +#[allow(clippy::empty_docs)] pub mod is_active_platform; /// A platform to keep the state necessary to perform repeated active checks, created by [File::is_active_platform()]. @@ -85,6 +87,7 @@ impl File { } /// +#[allow(clippy::empty_docs)] mod init { use std::path::PathBuf; diff --git a/gix-tempfile/src/handle.rs b/gix-tempfile/src/handle.rs index a4b6627c806..4365a2ef849 100644 --- a/gix-tempfile/src/handle.rs +++ b/gix-tempfile/src/handle.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use std::{io, path::Path}; use tempfile::{NamedTempFile, TempPath}; @@ -249,6 +250,7 @@ mod io_impls { } /// +#[allow(clippy::empty_docs)] pub mod persist { use std::path::Path; diff --git a/gix-tempfile/src/lib.rs b/gix-tempfile/src/lib.rs index fc1580903b5..9a5d52405b6 100644 --- a/gix-tempfile/src/lib.rs +++ b/gix-tempfile/src/lib.rs @@ -108,6 +108,7 @@ pub mod handle; use crate::handle::{Closed, Writable}; /// +#[allow(clippy::empty_docs)] pub mod registry; static NEXT_MAP_INDEX: AtomicUsize = AtomicUsize::new(0); diff --git a/gix-tempfile/src/signal.rs b/gix-tempfile/src/signal.rs index d7c0f83684b..2591dc517e8 100644 --- a/gix-tempfile/src/signal.rs +++ b/gix-tempfile/src/signal.rs @@ -16,6 +16,7 @@ pub fn setup(mode: handler::Mode) { } /// +#[allow(clippy::empty_docs)] pub mod handler { use std::sync::atomic::AtomicUsize; diff --git a/gix-trace/src/lib.rs b/gix-trace/src/lib.rs index 283716405d1..db90f77ad03 100644 --- a/gix-trace/src/lib.rs +++ b/gix-trace/src/lib.rs @@ -54,6 +54,7 @@ mod disabled; pub use disabled::Span; /// +#[allow(clippy::empty_docs)] pub mod event { #[cfg(feature = "tracing")] pub use tracing_core::Level; diff --git a/gix-transport/src/client/async_io/connect.rs b/gix-transport/src/client/async_io/connect.rs index 4d09d0e98d2..72ba2289613 100644 --- a/gix-transport/src/client/async_io/connect.rs +++ b/gix-transport/src/client/async_io/connect.rs @@ -2,8 +2,6 @@ pub use crate::client::non_io_types::connect::{Error, Options}; #[cfg(feature = "async-std")] pub(crate) mod function { - use std::convert::TryInto; - use crate::client::{git, non_io_types::connect::Error}; /// A general purpose connector connecting to a repository identified by the given `url`. diff --git a/gix-transport/src/client/async_io/mod.rs b/gix-transport/src/client/async_io/mod.rs index 1ea85cdcf58..e2e2cab99a9 100644 --- a/gix-transport/src/client/async_io/mod.rs +++ b/gix-transport/src/client/async_io/mod.rs @@ -8,6 +8,7 @@ mod traits; pub use traits::{SetServiceResponse, Transport, TransportV2Ext}; /// +#[allow(clippy::empty_docs)] pub mod connect; #[cfg(feature = "async-std")] pub use connect::function::connect; diff --git a/gix-transport/src/client/blocking_io/connect.rs b/gix-transport/src/client/blocking_io/connect.rs index 557a5914b8e..59f9e02c33c 100644 --- a/gix-transport/src/client/blocking_io/connect.rs +++ b/gix-transport/src/client/blocking_io/connect.rs @@ -1,8 +1,6 @@ pub use crate::client::non_io_types::connect::{Error, Options}; pub(crate) mod function { - use std::convert::TryInto; - use crate::client::{non_io_types::connect::Error, Transport}; /// A general purpose connector connecting to a repository identified by the given `url`. diff --git a/gix-transport/src/client/blocking_io/http/mod.rs b/gix-transport/src/client/blocking_io/http/mod.rs index d42be09014f..de717006eae 100644 --- a/gix-transport/src/client/blocking_io/http/mod.rs +++ b/gix-transport/src/client/blocking_io/http/mod.rs @@ -27,6 +27,7 @@ compile_error!("Cannot set both 'http-client-reqwest' and 'http-client-curl' fea #[cfg(feature = "http-client-curl")] /// +#[allow(clippy::empty_docs)] pub mod curl; /// The experimental `reqwest` backend. @@ -39,6 +40,7 @@ pub mod reqwest; mod traits; /// +#[allow(clippy::empty_docs)] pub mod options { /// A function to authenticate a URL. pub type AuthenticateFn = diff --git a/gix-transport/src/client/blocking_io/http/reqwest/mod.rs b/gix-transport/src/client/blocking_io/http/reqwest/mod.rs index 7c68b166ea2..9f07c41f64b 100644 --- a/gix-transport/src/client/blocking_io/http/reqwest/mod.rs +++ b/gix-transport/src/client/blocking_io/http/reqwest/mod.rs @@ -25,4 +25,5 @@ pub struct Options { } /// +#[allow(clippy::empty_docs)] pub mod remote; diff --git a/gix-transport/src/client/blocking_io/http/reqwest/remote.rs b/gix-transport/src/client/blocking_io/http/reqwest/remote.rs index 7f8e8284604..1aa8bdb08e8 100644 --- a/gix-transport/src/client/blocking_io/http/reqwest/remote.rs +++ b/gix-transport/src/client/blocking_io/http/reqwest/remote.rs @@ -1,6 +1,5 @@ use std::{ any::Any, - convert::TryFrom, io::{Read, Write}, str::FromStr, sync::{atomic, Arc}, diff --git a/gix-transport/src/client/blocking_io/mod.rs b/gix-transport/src/client/blocking_io/mod.rs index dfb3752af95..087aa6cc0c4 100644 --- a/gix-transport/src/client/blocking_io/mod.rs +++ b/gix-transport/src/client/blocking_io/mod.rs @@ -1,7 +1,9 @@ /// +#[allow(clippy::empty_docs)] pub mod connect; /// +#[allow(clippy::empty_docs)] pub mod file; /// #[cfg(feature = "http-client")] @@ -14,6 +16,7 @@ mod request; pub use request::RequestWriter; /// +#[allow(clippy::empty_docs)] pub mod ssh; mod traits; diff --git a/gix-transport/src/client/blocking_io/ssh/mod.rs b/gix-transport/src/client/blocking_io/ssh/mod.rs index 3d83493ca85..16f47bd25f4 100644 --- a/gix-transport/src/client/blocking_io/ssh/mod.rs +++ b/gix-transport/src/client/blocking_io/ssh/mod.rs @@ -34,6 +34,7 @@ pub enum ProgramKind { mod program_kind; /// +#[allow(clippy::empty_docs)] pub mod invocation { use std::ffi::OsString; @@ -54,6 +55,7 @@ pub mod invocation { } /// +#[allow(clippy::empty_docs)] pub mod connect { use std::ffi::{OsStr, OsString}; diff --git a/gix-transport/src/client/capabilities.rs b/gix-transport/src/client/capabilities.rs index 21513ace664..408bf8bddeb 100644 --- a/gix-transport/src/client/capabilities.rs +++ b/gix-transport/src/client/capabilities.rs @@ -167,6 +167,7 @@ impl Capabilities { #[cfg(feature = "blocking-client")] /// +#[allow(clippy::empty_docs)] pub mod recv { use std::io; @@ -252,6 +253,7 @@ pub mod recv { #[cfg(feature = "async-client")] #[allow(missing_docs)] /// +#[allow(clippy::empty_docs)] pub mod recv { use bstr::ByteVec; use futures_io::AsyncRead; diff --git a/gix-transport/src/client/git/blocking_io.rs b/gix-transport/src/client/git/blocking_io.rs index 42f253dfb3a..eb072c8ec5d 100644 --- a/gix-transport/src/client/git/blocking_io.rs +++ b/gix-transport/src/client/git/blocking_io.rs @@ -132,6 +132,7 @@ where } /// +#[allow(clippy::empty_docs)] pub mod connect { use std::net::{TcpStream, ToSocketAddrs}; diff --git a/gix-transport/src/client/mod.rs b/gix-transport/src/client/mod.rs index b5296543e41..427a55f4683 100644 --- a/gix-transport/src/client/mod.rs +++ b/gix-transport/src/client/mod.rs @@ -23,6 +23,7 @@ pub use blocking_io::{ pub use connect::function::connect; /// +#[allow(clippy::empty_docs)] pub mod capabilities; #[doc(inline)] pub use capabilities::Capabilities; diff --git a/gix-transport/src/lib.rs b/gix-transport/src/lib.rs index 60f2d538403..6a698741f4f 100644 --- a/gix-transport/src/lib.rs +++ b/gix-transport/src/lib.rs @@ -82,6 +82,7 @@ mod traits { pub use traits::IsSpuriousError; /// +#[allow(clippy::empty_docs)] pub mod client; #[doc(inline)] diff --git a/gix-traverse/src/commit.rs b/gix-traverse/src/commit.rs index 4bf49d2a0b4..c1d18ebf6ff 100644 --- a/gix-traverse/src/commit.rs +++ b/gix-traverse/src/commit.rs @@ -87,6 +87,7 @@ pub struct Info { } /// +#[allow(clippy::empty_docs)] pub mod ancestors { use std::{ borrow::{Borrow, BorrowMut}, diff --git a/gix-traverse/src/tree/mod.rs b/gix-traverse/src/tree/mod.rs index 6a74ada28f3..2e41a1cfd50 100644 --- a/gix-traverse/src/tree/mod.rs +++ b/gix-traverse/src/tree/mod.rs @@ -41,6 +41,7 @@ pub struct Recorder { } /// +#[allow(clippy::empty_docs)] pub mod visit { /// What to do after an entry was [recorded][super::Visit::visit_tree()]. #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] @@ -62,8 +63,10 @@ pub mod visit { } /// +#[allow(clippy::empty_docs)] pub mod recorder; /// +#[allow(clippy::empty_docs)] pub mod breadthfirst; pub use breadthfirst::impl_::traverse as breadthfirst; diff --git a/gix-url/src/impls.rs b/gix-url/src/impls.rs index a579121cb65..d153d707fde 100644 --- a/gix-url/src/impls.rs +++ b/gix-url/src/impls.rs @@ -1,7 +1,4 @@ -use std::{ - convert::TryFrom, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; use bstr::BStr; diff --git a/gix-url/src/lib.rs b/gix-url/src/lib.rs index a5b438901ec..a810ad66c7d 100644 --- a/gix-url/src/lib.rs +++ b/gix-url/src/lib.rs @@ -13,6 +13,7 @@ use std::{borrow::Cow, path::PathBuf}; use bstr::{BStr, BString}; /// +#[allow(clippy::empty_docs)] pub mod expand_path; mod scheme; @@ -20,6 +21,7 @@ pub use scheme::Scheme; mod impls; /// +#[allow(clippy::empty_docs)] pub mod parse; /// Parse the given `bytes` as a [git url](Url). diff --git a/gix-utils/src/lib.rs b/gix-utils/src/lib.rs index 5e2e32d1365..bd61a08b0d1 100644 --- a/gix-utils/src/lib.rs +++ b/gix-utils/src/lib.rs @@ -5,15 +5,19 @@ #![forbid(unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod backoff; /// +#[allow(clippy::empty_docs)] pub mod buffers; /// +#[allow(clippy::empty_docs)] pub mod str; /// +#[allow(clippy::empty_docs)] pub mod btoi; /// A utility to do buffer-swapping with. diff --git a/gix-utils/tests/backoff/mod.rs b/gix-utils/tests/backoff/mod.rs index a84ed754360..ca9077841ca 100644 --- a/gix-utils/tests/backoff/mod.rs +++ b/gix-utils/tests/backoff/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, time::Duration}; +use std::time::Duration; use gix_utils::backoff::Exponential; diff --git a/gix-validate/src/lib.rs b/gix-validate/src/lib.rs index f7b6cc4aa76..f0493960c73 100644 --- a/gix-validate/src/lib.rs +++ b/gix-validate/src/lib.rs @@ -3,10 +3,13 @@ #![forbid(unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod reference; /// +#[allow(clippy::empty_docs)] pub mod tag; /// +#[allow(clippy::empty_docs)] pub mod submodule; diff --git a/gix-validate/src/reference.rs b/gix-validate/src/reference.rs index fff87e3b40c..ac222ed105c 100644 --- a/gix-validate/src/reference.rs +++ b/gix-validate/src/reference.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod name { use std::convert::Infallible; diff --git a/gix-validate/src/submodule.rs b/gix-validate/src/submodule.rs index 6811f4ff2eb..c524cb22a5a 100644 --- a/gix-validate/src/submodule.rs +++ b/gix-validate/src/submodule.rs @@ -1,6 +1,7 @@ use bstr::{BStr, ByteSlice}; /// +#[allow(clippy::empty_docs)] pub mod name { /// The error used in [name()](super::name()). #[derive(Debug, thiserror::Error)] diff --git a/gix-validate/src/tag.rs b/gix-validate/src/tag.rs index 91ceec18549..eb5b7e12066 100644 --- a/gix-validate/src/tag.rs +++ b/gix-validate/src/tag.rs @@ -1,6 +1,7 @@ use bstr::BStr; /// +#[allow(clippy::empty_docs)] pub mod name { use bstr::BString; diff --git a/gix-worktree-state/src/lib.rs b/gix-worktree-state/src/lib.rs index 2c2cf67f64d..32ab5653bc3 100644 --- a/gix-worktree-state/src/lib.rs +++ b/gix-worktree-state/src/lib.rs @@ -2,5 +2,6 @@ #![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// +#[allow(clippy::empty_docs)] pub mod checkout; pub use checkout::function::checkout; diff --git a/gix-worktree-stream/src/lib.rs b/gix-worktree-stream/src/lib.rs index 8169f888d46..82c57b809cb 100644 --- a/gix-worktree-stream/src/lib.rs +++ b/gix-worktree-stream/src/lib.rs @@ -26,6 +26,7 @@ pub struct Stream { } /// +#[allow(clippy::empty_docs)] pub mod entry; pub(crate) mod protocol; diff --git a/gix-worktree/src/lib.rs b/gix-worktree/src/lib.rs index 240285a9128..7238538e73d 100644 --- a/gix-worktree/src/lib.rs +++ b/gix-worktree/src/lib.rs @@ -57,4 +57,5 @@ pub struct Stack { pub(crate) type PathIdMapping = (BString, gix_hash::ObjectId); /// +#[allow(clippy::empty_docs)] pub mod stack; diff --git a/gix-worktree/src/stack/mod.rs b/gix-worktree/src/stack/mod.rs index b6c03d175bf..4629a7a08ed 100644 --- a/gix-worktree/src/stack/mod.rs +++ b/gix-worktree/src/stack/mod.rs @@ -195,9 +195,11 @@ impl Stack { } /// +#[allow(clippy::empty_docs)] pub mod delegate; use delegate::StackDelegate; mod platform; /// +#[allow(clippy::empty_docs)] pub mod state; diff --git a/gix-worktree/src/stack/state/mod.rs b/gix-worktree/src/stack/state/mod.rs index 30e3c609f1b..ba6383fee85 100644 --- a/gix-worktree/src/stack/state/mod.rs +++ b/gix-worktree/src/stack/state/mod.rs @@ -53,6 +53,7 @@ pub struct Ignore { #[cfg(feature = "attributes")] pub mod attributes; /// +#[allow(clippy::empty_docs)] pub mod ignore; /// Initialization diff --git a/gix/Cargo.toml b/gix/Cargo.toml index 7bac0835b57..eaf2092b00a 100644 --- a/gix/Cargo.toml +++ b/gix/Cargo.toml @@ -65,7 +65,7 @@ comfort = ["gix-features/progress-unit-bytes", "gix-features/progress-unit-human command = ["dep:gix-command"] ## Obtain information similar to `git status`. -status = ["gix-status"] +status = ["gix-status", "dirwalk", "index", "blob-diff"] ## Utilities for interrupting computations and cleaning up tempfiles. interrupt = ["dep:signal-hook", "gix-tempfile/signals"] @@ -131,12 +131,12 @@ blocking-http-transport-curl-rustls = ["blocking-http-transport-curl", "dep:curl ## Stacks with `blocking-network-client` to provide support for HTTP/S using **reqwest**, and implies blocking networking as a whole, making the `https://` transport available. blocking-http-transport-reqwest = ["blocking-network-client", "gix-transport/http-client-reqwest"] ## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `rustls` crate. -blocking-http-transport-reqwest-rust-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls" ] +blocking-http-transport-reqwest-rust-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls"] ## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `rustls` crate. ## This also makes use of `trust-dns` to avoid `getaddrinfo`, but note it comes with its own problems. blocking-http-transport-reqwest-rust-tls-trust-dns = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls", "reqwest-for-configuration-only/trust-dns"] ## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `native-tls` crate. -blocking-http-transport-reqwest-native-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/default-tls" ] +blocking-http-transport-reqwest-native-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/default-tls"] #! #### Performance @@ -186,11 +186,11 @@ pack-cache-lru-dynamic = ["gix-pack/pack-cache-lru-dynamic"] ## Activate other features that maximize performance, like usage of threads, `zlib-ng` and access to caching in object databases. ## Note that some platforms might suffer from compile failures, which is when `max-performance-safe` should be used. -max-performance = [ "max-performance-safe", "zlib-ng", "fast-sha1" ] +max-performance = ["max-performance-safe", "zlib-ng", "fast-sha1"] ## If enabled, use assembly versions of sha1 on supported platforms. ## This might cause compile failures as well which is why it can be turned off separately. -fast-sha1 = [ "gix-features/fast-sha1" ] +fast-sha1 = ["gix-features/fast-sha1"] ## Use the C-based zlib-ng backend, which can compress and decompress significantly faster. ## Note that this will cause duplicate symbol errors if the application also depends on `zlib` - use `zlib-ng-compat` in that case. @@ -215,7 +215,7 @@ zlib-stock = ["gix-features/zlib-stock"] verbose-object-parsing-errors = ["gix-object/verbose-object-parsing-errors"] ## Data structures implement `serde::Serialize` and `serde::Deserialize`. -serde = [ "dep:serde", +serde = ["dep:serde", "gix-pack/serde", "gix-object/serde", "gix-protocol?/serde", @@ -286,7 +286,7 @@ gix-hashtable = { version = "^0.5.1", path = "../gix-hashtable" } gix-commitgraph = { version = "^0.24.1", path = "../gix-commitgraph" } gix-pathspec = { version = "^0.7.0", path = "../gix-pathspec", optional = true } gix-submodule = { version = "^0.9.0", path = "../gix-submodule", optional = true } -gix-status = { version = "^0.6.0", path = "../gix-status", optional = true } +gix-status = { version = "^0.6.0", path = "../gix-status", optional = true, features = ["worktree-rewrites"] } gix-command = { version = "^0.3.5", path = "../gix-command", optional = true } gix-worktree-stream = { version = "^0.10.0", path = "../gix-worktree-stream", optional = true } @@ -301,7 +301,7 @@ prodash = { workspace = true, optional = true, features = ["progress-tree"] } once_cell = "1.14.0" signal-hook = { version = "0.3.9", default-features = false, optional = true } thiserror = "1.0.26" -serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} +serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } smallvec = "1.9.0" async-std = { version = "1.12.0", optional = true } @@ -323,6 +323,7 @@ parking_lot = "0.12.1" document-features = { version = "0.2.0", optional = true } [dev-dependencies] +pretty_assertions = "1.4.0" gix-testtools = { path = "../tests/tools" } is_ci = "1.1.1" anyhow = "1" diff --git a/gix/src/clone/checkout.rs b/gix/src/clone/checkout.rs index 84a3fedbddb..e241adb18a1 100644 --- a/gix/src/clone/checkout.rs +++ b/gix/src/clone/checkout.rs @@ -1,6 +1,7 @@ use crate::{clone::PrepareCheckout, Repository}; /// +#[allow(clippy::empty_docs)] pub mod main_worktree { use std::{path::PathBuf, sync::atomic::AtomicBool}; diff --git a/gix/src/clone/fetch/util.rs b/gix/src/clone/fetch/util.rs index 627201301de..e50ec7509d0 100644 --- a/gix/src/clone/fetch/util.rs +++ b/gix/src/clone/fetch/util.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryInto, io::Write}; +use std::{borrow::Cow, io::Write}; use gix_ref::{ transaction::{LogChange, RefLog}, diff --git a/gix/src/clone/mod.rs b/gix/src/clone/mod.rs index a6022c1c6f6..03328253e78 100644 --- a/gix/src/clone/mod.rs +++ b/gix/src/clone/mod.rs @@ -1,6 +1,4 @@ #![allow(clippy::result_large_err)] -use std::convert::TryInto; - use crate::{bstr::BString, config::tree::gitoxide, remote}; type ConfigureRemoteFn = diff --git a/gix/src/commit.rs b/gix/src/commit.rs index ce5dee4d6f7..a6f4da4488d 100644 --- a/gix/src/commit.rs +++ b/gix/src/commit.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] /// An empty array of a type usable with the `gix::easy` API to help declaring no parents should be used pub const NO_PARENT_IDS: [gix_hash::ObjectId; 0] = []; @@ -22,6 +23,7 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] #[cfg(feature = "revision")] pub mod describe { use std::borrow::Cow; @@ -40,11 +42,32 @@ pub mod describe { } impl<'repo> Resolution<'repo> { - /// Turn this instance into something displayable + /// Turn this instance into something displayable. pub fn format(self) -> Result, Error> { let prefix = self.id.shorten()?; Ok(self.outcome.into_format(prefix.hex_len())) } + + /// Turn this instance into something displayable, possibly with dirty-suffix. + /// + /// If `dirty_suffix` is `Some(suffix)`, a possibly expensive [dirty check](crate::Repository::is_dirty()) will be + /// performed so that the `suffix` is appended to the output. If it is `None`, no check will be performed and + /// there will be no suffix. + /// Note that obtaining the dirty-state of the repository can be expensive. + #[cfg(feature = "status")] + pub fn format_with_dirty_suffix( + self, + dirty_suffix: impl Into>, + ) -> Result, Error> { + let prefix = self.id.shorten()?; + let mut dirty_suffix = dirty_suffix.into(); + if dirty_suffix.is_some() && !self.id.repo.is_dirty()? { + dirty_suffix.take(); + } + let mut format = self.outcome.into_format(prefix.hex_len()); + format.dirty_suffix = dirty_suffix; + Ok(format) + } } /// The error returned by [`try_format()`][Platform::try_format()]. @@ -59,6 +82,9 @@ pub mod describe { RefIter(#[from] crate::reference::iter::Error), #[error(transparent)] RefIterInit(#[from] crate::reference::iter::init::Error), + #[error(transparent)] + #[cfg(feature = "status")] + DetermineIsDirty(#[from] crate::status::is_dirty::Error), } /// A selector to choose what kind of references should contribute to names. @@ -179,9 +205,7 @@ pub mod describe { } /// Try to find a name for the configured commit id using all prior configuration, returning `Some(describe::Format)` - /// if one was found. - /// - /// Note that there will always be `Some(format)` + /// if one was found, or `None` if that wasn't the case. pub fn try_format(&self) -> Result>, Error> { self.try_resolve()?.map(Resolution::format).transpose() } @@ -193,10 +217,9 @@ pub mod describe { /// /// # Performance /// - /// It is greatly recommended to [assure an object cache is set][crate::Repository::object_cache_size_if_unset()] + /// It is greatly recommended to [assure an object cache is set](crate::Repository::object_cache_size_if_unset()) /// to save ~40% of time. pub fn try_resolve(&self) -> Result>, Error> { - // TODO: dirty suffix with respective dirty-detection let mut graph = gix_revwalk::Graph::new( &self.repo.objects, gix_commitgraph::Graph::from_info_dir(self.repo.objects.store_ref().path().join("info").as_ref()).ok(), @@ -218,7 +241,7 @@ pub mod describe { })) } - /// Like [`try_format()`][Platform::try_format()], but turns `id_as_fallback()` on to always produce a format. + /// Like [`try_format()`](Self::try_format()), but turns `id_as_fallback()` on to always produce a format. pub fn format(&mut self) -> Result, Error> { self.id_as_fallback = true; Ok(self.try_format()?.expect("BUG: fallback must always produce a format")) diff --git a/gix/src/config/cache/access.rs b/gix/src/config/cache/access.rs index 464a0bf4dc3..660ab771a7a 100644 --- a/gix/src/config/cache/access.rs +++ b/gix/src/config/cache/access.rs @@ -112,7 +112,7 @@ impl Cache { } #[cfg(feature = "blob-diff")] - pub(crate) fn diff_renames(&self) -> Result, crate::diff::new_rewrites::Error> { + pub(crate) fn diff_renames(&self) -> Result, crate::diff::new_rewrites::Error> { self.diff_renames .get_or_try_init(|| crate::diff::new_rewrites(&self.resolved, self.lenient_config)) .copied() diff --git a/gix/src/config/cache/init.rs b/gix/src/config/cache/init.rs index 449eb1611e3..76e6dd81e41 100644 --- a/gix/src/config/cache/init.rs +++ b/gix/src/config/cache/init.rs @@ -303,7 +303,7 @@ impl crate::Repository { fn apply_changed_values(&mut self) { self.refs.write_reflog = util::reflog_or_default(self.config.reflog, self.work_dir().is_some()); - self.refs.namespace = self.config.refs_namespace.clone(); + self.refs.namespace.clone_from(&self.config.refs_namespace); } } diff --git a/gix/src/config/mod.rs b/gix/src/config/mod.rs index 6f00337b1b1..258d07e89ab 100644 --- a/gix/src/config/mod.rs +++ b/gix/src/config/mod.rs @@ -9,6 +9,7 @@ mod snapshot; pub use snapshot::credential_helpers; /// +#[allow(clippy::empty_docs)] pub mod overrides; pub mod tree; @@ -48,6 +49,7 @@ pub(crate) mod section { } /// +#[allow(clippy::empty_docs)] pub mod set_value { /// The error produced when calling [`SnapshotMut::set(_subsection)?_value()`][crate::config::SnapshotMut::set_value()] #[derive(Debug, thiserror::Error)] @@ -107,8 +109,10 @@ pub enum Error { } /// +#[allow(clippy::empty_docs)] pub mod diff { /// + #[allow(clippy::empty_docs)] pub mod algorithm { use crate::bstr::BString; @@ -124,6 +128,7 @@ pub mod diff { } /// + #[allow(clippy::empty_docs)] pub mod pipeline_options { /// The error produced when obtaining options needed to fill in [gix_diff::blob::pipeline::Options]. #[derive(Debug, thiserror::Error)] @@ -137,6 +142,7 @@ pub mod diff { } /// + #[allow(clippy::empty_docs)] pub mod drivers { use crate::bstr::BString; @@ -155,6 +161,7 @@ pub mod diff { } /// +#[allow(clippy::empty_docs)] pub mod stat_options { /// The error produced when collecting stat information, and returned by [Repository::stat_options()](crate::Repository::stat_options()). #[derive(Debug, thiserror::Error)] @@ -207,6 +214,7 @@ pub mod command_context { } /// +#[allow(clippy::empty_docs)] pub mod exclude_stack { use std::path::PathBuf; @@ -224,6 +232,7 @@ pub mod exclude_stack { } /// +#[allow(clippy::empty_docs)] pub mod attribute_stack { /// The error produced when setting up the attribute stack to query `gitattributes`. #[derive(Debug, thiserror::Error)] @@ -237,8 +246,10 @@ pub mod attribute_stack { } /// +#[allow(clippy::empty_docs)] pub mod protocol { /// + #[allow(clippy::empty_docs)] pub mod allow { use crate::bstr::BString; @@ -254,6 +265,7 @@ pub mod protocol { } /// +#[allow(clippy::empty_docs)] pub mod ssh_connect_options { /// The error produced when obtaining ssh connection configuration. #[derive(Debug, thiserror::Error)] @@ -263,6 +275,7 @@ pub mod ssh_connect_options { } /// +#[allow(clippy::empty_docs)] pub mod key { use crate::bstr::BString; @@ -363,6 +376,7 @@ pub mod key { } /// +#[allow(clippy::empty_docs)] pub mod encoding { use crate::bstr::BString; @@ -380,8 +394,10 @@ pub mod encoding { } /// +#[allow(clippy::empty_docs)] pub mod checkout { /// + #[allow(clippy::empty_docs)] pub mod workers { use crate::config; @@ -391,6 +407,7 @@ pub mod checkout { } /// +#[allow(clippy::empty_docs)] pub mod abbrev { use crate::bstr::BString; @@ -406,8 +423,10 @@ pub mod abbrev { } /// +#[allow(clippy::empty_docs)] pub mod remote { /// + #[allow(clippy::empty_docs)] pub mod symbolic_name { /// The error produced when failing to produce a symbolic remote name from configuration. pub type Error = super::super::key::Error; @@ -415,66 +434,77 @@ pub mod remote { } /// +#[allow(clippy::empty_docs)] pub mod time { /// The error produced when failing to parse time from configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod lock_timeout { /// The error produced when failing to parse timeout for locks. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod duration { /// The error produced when failing to parse durations (in milliseconds). pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod boolean { /// The error produced when failing to parse time from configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod unsigned_integer { /// The error produced when failing to parse a signed integer from configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod url { /// The error produced when failing to parse a url from the configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod string { /// The error produced when failing to interpret configuration as UTF-8 encoded string. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod refspec { /// The error produced when failing to parse a refspec from the configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod refs_namespace { /// The error produced when failing to parse a refspec from the configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod ssl_version { /// The error produced when failing to parse a refspec from the configuration. pub type Error = super::key::Error; } /// +#[allow(clippy::empty_docs)] pub mod transport { use std::borrow::Cow; @@ -514,6 +544,7 @@ pub mod transport { } /// + #[allow(clippy::empty_docs)] pub mod http { use std::borrow::Cow; diff --git a/gix/src/config/overrides.rs b/gix/src/config/overrides.rs index 6b3fc728cc6..7fbd43148bb 100644 --- a/gix/src/config/overrides.rs +++ b/gix/src/config/overrides.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use crate::bstr::{BStr, BString, ByteSlice}; /// The error returned by [`SnapshotMut::apply_cli_overrides()`][crate::config::SnapshotMut::append_config()]. diff --git a/gix/src/config/snapshot/credential_helpers.rs b/gix/src/config/snapshot/credential_helpers.rs index 54499a1c34a..fdac608f541 100644 --- a/gix/src/config/snapshot/credential_helpers.rs +++ b/gix/src/config/snapshot/credential_helpers.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; pub use error::Error; diff --git a/gix/src/config/tree/mod.rs b/gix/src/config/tree/mod.rs index 6610a091fb3..7340da3dd1b 100644 --- a/gix/src/config/tree/mod.rs +++ b/gix/src/config/tree/mod.rs @@ -59,6 +59,9 @@ pub(crate) mod root { pub const SAFE: sections::Safe = sections::Safe; /// The `ssh` section. pub const SSH: sections::Ssh = sections::Ssh; + /// The `status` section. + #[cfg(feature = "status")] + pub const STATUS: sections::Status = sections::Status; /// The `user` section. pub const USER: sections::User = sections::User; /// The `url` section. @@ -89,6 +92,8 @@ pub(crate) mod root { &Self::REMOTE, &Self::SAFE, &Self::SSH, + #[cfg(feature = "status")] + &Self::STATUS, &Self::USER, &Self::URL, ] @@ -104,13 +109,17 @@ pub use sections::{ }; #[cfg(feature = "blob-diff")] pub use sections::{diff, Diff}; +#[cfg(feature = "status")] +pub use sections::{status, Status}; /// Generic value implementations for static instantiation. pub mod keys; /// +#[allow(clippy::empty_docs)] pub mod key { /// + #[allow(clippy::empty_docs)] pub mod validate { /// The error returned by [`Key::validate()`][crate::config::tree::Key::validate()]. #[derive(Debug, thiserror::Error)] @@ -122,6 +131,7 @@ pub mod key { } } /// + #[allow(clippy::empty_docs)] pub mod validate_assignment { /// The error returned by [`Key::validated_assignment`*()][crate::config::tree::Key::validated_assignment_fmt()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/config/tree/sections/branch.rs b/gix/src/config/tree/sections/branch.rs index 8e1e0a4b862..e63227fc90f 100644 --- a/gix/src/config/tree/sections/branch.rs +++ b/gix/src/config/tree/sections/branch.rs @@ -49,6 +49,7 @@ mod merge { } /// +#[allow(clippy::empty_docs)] pub mod validate { use crate::{ bstr::BStr, diff --git a/gix/src/config/tree/sections/checkout.rs b/gix/src/config/tree/sections/checkout.rs index 27f31ee841f..03d274b918c 100644 --- a/gix/src/config/tree/sections/checkout.rs +++ b/gix/src/config/tree/sections/checkout.rs @@ -42,6 +42,7 @@ mod workers { } /// +#[allow(clippy::empty_docs)] pub mod validate { use crate::{bstr::BStr, config::tree::keys}; diff --git a/gix/src/config/tree/sections/mod.rs b/gix/src/config/tree/sections/mod.rs index de6b44d4c1d..ab2b9542a2c 100644 --- a/gix/src/config/tree/sections/mod.rs +++ b/gix/src/config/tree/sections/mod.rs @@ -106,6 +106,13 @@ mod safe; pub struct Ssh; pub mod ssh; +/// The `status` top-level section. +#[derive(Copy, Clone, Default)] +#[cfg(feature = "status")] +pub struct Status; +#[cfg(feature = "status")] +pub mod status; + /// The `user` top-level section. #[derive(Copy, Clone, Default)] pub struct User; diff --git a/gix/src/config/tree/sections/status.rs b/gix/src/config/tree/sections/status.rs new file mode 100644 index 00000000000..f60600e214b --- /dev/null +++ b/gix/src/config/tree/sections/status.rs @@ -0,0 +1,58 @@ +use crate::config; +use crate::config::tree::sections::Status; +use crate::config::tree::{keys, Key, Section}; + +impl Status { + /// The `status.showUntrackedFiles` key + pub const SHOW_UNTRACKED_FILES: ShowUntrackedFiles = ShowUntrackedFiles::new_with_validate( + "showUntrackedFiles", + &config::Tree::STATUS, + validate::ShowUntrackedFiles, + ); +} + +/// The `status.showUntrackedFiles` key. +pub type ShowUntrackedFiles = keys::Any; + +mod show_untracked_files { + use std::borrow::Cow; + + use crate::{bstr::BStr, config, config::tree::status::ShowUntrackedFiles, status}; + + impl ShowUntrackedFiles { + pub fn try_into_show_untracked_files( + &'static self, + value: Cow<'_, BStr>, + ) -> Result { + use crate::bstr::ByteSlice; + Ok(match value.as_ref().as_bytes() { + b"no" => status::UntrackedFiles::None, + b"normal" => status::UntrackedFiles::Collapsed, + b"all" => status::UntrackedFiles::Files, + _ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())), + }) + } + } +} + +impl Section for Status { + fn name(&self) -> &str { + "status" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::SHOW_UNTRACKED_FILES] + } +} + +mod validate { + use crate::{bstr::BStr, config::tree::keys}; + + pub struct ShowUntrackedFiles; + impl keys::Validate for ShowUntrackedFiles { + fn validate(&self, value: &BStr) -> Result<(), Box> { + super::Status::SHOW_UNTRACKED_FILES.try_into_show_untracked_files(value.into())?; + Ok(()) + } + } +} diff --git a/gix/src/create.rs b/gix/src/create.rs index b03045cddf7..f2e97aac57b 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -1,5 +1,4 @@ use std::{ - convert::TryFrom, fs::{self, OpenOptions}, io::Write, path::{Path, PathBuf}, @@ -89,6 +88,7 @@ fn write_file(data: &[u8], path: &Path) -> Result<(), Error> { let mut file = OpenOptions::new() .write(true) .create(true) + .truncate(true) .append(false) .open(path) .map_err(|e| Error::IoOpen { diff --git a/gix/src/diff.rs b/gix/src/diff.rs index af3c987048a..bfe3feb0635 100644 --- a/gix/src/diff.rs +++ b/gix/src/diff.rs @@ -1,6 +1,7 @@ pub use gix_diff::*; /// +#[allow(clippy::empty_docs)] pub mod rename { /// Determine how to do rename tracking. #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -28,6 +29,7 @@ mod utils { }; /// + #[allow(clippy::empty_docs)] pub mod new_rewrites { /// The error returned by [`new_rewrites()`](super::new_rewrites()). #[derive(Debug, thiserror::Error)] @@ -41,6 +43,7 @@ mod utils { } /// + #[allow(clippy::empty_docs)] pub mod resource_cache { /// The error returned by [`resource_cache()`](super::resource_cache()). #[derive(Debug, thiserror::Error)] @@ -56,8 +59,6 @@ mod utils { DiffPipelineOptions(#[from] crate::config::diff::pipeline_options::Error), #[error(transparent)] CommandContext(#[from] crate::config::command_context::Error), - #[error(transparent)] - AttributeStack(#[from] crate::config::attribute_stack::Error), } } @@ -102,18 +103,16 @@ mod utils { /// Return a low-level utility to efficiently prepare a the blob-level diff operation between two resources, /// and cache these diffable versions so that matrix-like MxN diffs are efficient. /// - /// `repo` is used to obtain the needed configuration values, and `index` is used to potentially read `.gitattributes` - /// files from which may affect the diff operation. + /// `repo` is used to obtain the needed configuration values. /// `mode` determines how the diffable files will look like, and also how fast, in average, these conversions are. - /// `attribute_source` controls where `.gitattributes` will be read from, and it's typically adjusted based on the + /// `attr_stack` is for accessing `.gitattributes` for knowing how to apply filters. Noow that it's typically adjusted based on the /// `roots` - if there are no worktree roots, `.gitattributes` are also not usually read from worktrees. /// `roots` provide information about where to get diffable data from, so source and destination can either be sourced from /// a worktree, or from the object database, or both. pub fn resource_cache( repo: &Repository, - index: &gix_index::State, mode: gix_diff::blob::pipeline::Mode, - attribute_source: gix_worktree::stack::state::attributes::Source, + attr_stack: gix_worktree::Stack, roots: gix_diff::blob::pipeline::WorktreeRoots, ) -> Result { let diff_algo = repo.config.diff_algorithm()?; @@ -129,13 +128,7 @@ mod utils { repo.config.diff_pipeline_options()?, ), mode, - repo.attributes_only( - // TODO(perf): this could benefit from not having to build an intermediate index, - // and traverse the a tree directly. - index, - attribute_source, - )? - .inner, + attr_stack, ); Ok(diff_cache) } diff --git a/gix/src/dirwalk.rs b/gix/src/dirwalk.rs index 59357662c63..ab159cff2f5 100644 --- a/gix/src/dirwalk.rs +++ b/gix/src/dirwalk.rs @@ -68,23 +68,43 @@ impl Options { self.empty_patterns_match_prefix = toggle; self } + /// Like [`empty_patterns_match_prefix()`](Self::empty_patterns_match_prefix), but only requires a mutably borrowed instance. + pub fn set_empty_patterns_match_prefix(&mut self, toggle: bool) -> &mut Self { + self.empty_patterns_match_prefix = toggle; + self + } /// If `toggle` is `true`, we will stop figuring out if any directory that is a candidate for recursion is also a nested repository, /// which saves time but leads to recurse into it. If `false`, nested repositories will not be traversed. pub fn recurse_repositories(mut self, toggle: bool) -> Self { self.recurse_repositories = toggle; self } + /// Like [`recurse_repositories()`](Self::recurse_repositories), but only requires a mutably borrowed instance. + pub fn set_recurse_repositories(&mut self, toggle: bool) -> &mut Self { + self.recurse_repositories = toggle; + self + } /// If `toggle` is `true`, entries that are pruned and whose [Kind](gix_dir::entry::Kind) is known will be emitted. pub fn emit_pruned(mut self, toggle: bool) -> Self { self.emit_pruned = toggle; self } + /// Like [`emit_pruned()`](Self::emit_pruned), but only requires a mutably borrowed instance. + pub fn set_emit_pruned(&mut self, toggle: bool) -> &mut Self { + self.emit_pruned = toggle; + self + } /// If `value` is `Some(mode)`, entries that are ignored will be emitted according to the given `mode`. /// If `None`, ignored entries will not be emitted at all. pub fn emit_ignored(mut self, value: Option) -> Self { self.emit_ignored = value; self } + /// Like [`emit_ignored()`](Self::emit_ignored), but only requires a mutably borrowed instance. + pub fn set_emit_ignored(&mut self, value: Option) -> &mut Self { + self.emit_ignored = value; + self + } /// When the walk is for deletion, `value` must be `Some(_)` to assure we don't collapse directories that have precious files in /// them, and otherwise assure that no entries are observable that shouldn't be deleted. /// If `None`, precious files are treated like expendable files, which is usually what you want when displaying them @@ -93,17 +113,32 @@ impl Options { self.for_deletion = value; self } + /// Like [`for_deletion()`](Self::for_deletion), but only requires a mutably borrowed instance. + pub fn set_for_deletion(&mut self, value: Option) -> &mut Self { + self.for_deletion = value; + self + } /// If `toggle` is `true`, we will also emit entries for tracked items. Otherwise these will remain 'hidden', /// even if a pathspec directly refers to it. pub fn emit_tracked(mut self, toggle: bool) -> Self { self.emit_tracked = toggle; self } + /// Like [`emit_tracked()`](Self::emit_tracked), but only requires a mutably borrowed instance. + pub fn set_emit_tracked(&mut self, toggle: bool) -> &mut Self { + self.emit_tracked = toggle; + self + } /// Controls the way untracked files are emitted. By default, this is happening immediately and without any simplification. pub fn emit_untracked(mut self, toggle: EmissionMode) -> Self { self.emit_untracked = toggle; self } + /// Like [`emit_untracked()`](Self::emit_untracked), but only requires a mutably borrowed instance. + pub fn set_emit_untracked(&mut self, toggle: EmissionMode) -> &mut Self { + self.emit_untracked = toggle; + self + } /// If `toggle` is `true`, emit empty directories as well. Note that a directory also counts as empty if it has any /// amount or depth of nested subdirectories, as long as none of them includes a file. /// Thus, this makes leaf-level empty directories visible, as those don't have any content. @@ -112,6 +147,12 @@ impl Options { self } + /// Like [`emit_empty_directories()`](Self::emit_empty_directories), but only requires a mutably borrowed instance. + pub fn set_emit_empty_directories(&mut self, toggle: bool) -> &mut Self { + self.emit_empty_directories = toggle; + self + } + /// If `toggle` is `true`, we will not only find non-bare repositories in untracked directories, but also bare ones. /// /// Note that this is very costly, but without it, bare repositories will appear like untracked directories when collapsed, @@ -121,10 +162,22 @@ impl Options { self } + /// Like [`classify_untracked_bare_repositories()`](Self::classify_untracked_bare_repositories), but only requires a mutably borrowed instance. + pub fn set_classify_untracked_bare_repositories(&mut self, toggle: bool) -> &mut Self { + self.classify_untracked_bare_repositories = toggle; + self + } + /// Control whether entries that are in an about-to-be collapsed directory will be emitted. The default is `None`, /// so entries in a collapsed directory are not observable. pub fn emit_collapsed(mut self, value: Option) -> Self { self.emit_collapsed = value; self } + + /// Like [`emit_collapsed()`](Self::emit_collapsed), but only requires a mutably borrowed instance. + pub fn set_emit_collapsed(&mut self, value: Option) -> &mut Self { + self.emit_collapsed = value; + self + } } diff --git a/gix/src/env.rs b/gix/src/env.rs index eddc5f71656..307138b8fb3 100644 --- a/gix/src/env.rs +++ b/gix/src/env.rs @@ -50,6 +50,7 @@ pub fn os_str_to_bstring(input: &OsStr) -> Option { pub mod collate { /// + #[allow(clippy::empty_docs)] pub mod fetch { /// An error which combines all possible errors when opening a repository, finding remotes and using them to fetch. /// diff --git a/gix/src/ext/reference.rs b/gix/src/ext/reference.rs index 57e4e4fe7c9..c479fa2ec8f 100644 --- a/gix/src/ext/reference.rs +++ b/gix/src/ext/reference.rs @@ -1,7 +1,3 @@ -pub trait Sealed {} - -impl Sealed for gix_ref::Reference {} - /// Extensions for [references][gix_ref::Reference]. pub trait ReferenceExt { /// Attach [`Repository`][crate::Repository] to the given reference. It can be detached later with [`detach()]`. diff --git a/gix/src/ext/rev_spec.rs b/gix/src/ext/rev_spec.rs index caa58e2c77f..f9b38e29cb4 100644 --- a/gix/src/ext/rev_spec.rs +++ b/gix/src/ext/rev_spec.rs @@ -1,7 +1,3 @@ -pub trait Sealed {} - -impl Sealed for gix_ref::Reference {} - /// Extensions for [revision specifications][gix_revision::Spec]. pub trait RevSpecExt { /// Attach [`Repository`][crate::Repository] to the given rev-spec. diff --git a/gix/src/filter.rs b/gix/src/filter.rs index f05e332a255..c856fe521da 100644 --- a/gix/src/filter.rs +++ b/gix/src/filter.rs @@ -14,8 +14,10 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod pipeline { /// + #[allow(clippy::empty_docs)] pub mod options { use crate::{bstr::BString, config}; @@ -38,6 +40,7 @@ pub mod pipeline { } /// + #[allow(clippy::empty_docs)] pub mod convert_to_git { /// The error returned by [Pipeline::convert_to_git()][crate::filter::Pipeline::convert_to_git()]. #[derive(Debug, thiserror::Error)] @@ -51,6 +54,7 @@ pub mod pipeline { } /// + #[allow(clippy::empty_docs)] pub mod convert_to_worktree { /// The error returned by [Pipeline::convert_to_worktree()][crate::filter::Pipeline::convert_to_worktree()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/head/log.rs b/gix/src/head/log.rs index 6aa7ed1d39e..88d90da890e 100644 --- a/gix/src/head/log.rs +++ b/gix/src/head/log.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_hash::ObjectId; use crate::{ diff --git a/gix/src/head/mod.rs b/gix/src/head/mod.rs index 399b872ba95..8740a932959 100644 --- a/gix/src/head/mod.rs +++ b/gix/src/head/mod.rs @@ -1,6 +1,5 @@ //! -use std::convert::TryInto; - +#![allow(clippy::empty_docs)] use gix_hash::ObjectId; use gix_ref::FullNameRef; @@ -118,7 +117,9 @@ mod remote { } /// +#[allow(clippy::empty_docs)] pub mod log; /// +#[allow(clippy::empty_docs)] pub mod peel; diff --git a/gix/src/head/peel.rs b/gix/src/head/peel.rs index 4ee116ed0db..2f16b8c6cf6 100644 --- a/gix/src/head/peel.rs +++ b/gix/src/head/peel.rs @@ -22,6 +22,7 @@ mod error { pub use error::Error; /// +#[allow(clippy::empty_docs)] pub mod into_id { use crate::object; @@ -39,6 +40,7 @@ pub mod into_id { } /// +#[allow(clippy::empty_docs)] pub mod to_commit { use crate::object; @@ -54,6 +56,7 @@ pub mod to_commit { } /// +#[allow(clippy::empty_docs)] pub mod to_object { /// The error returned by [`Head::peel_to_object_in_place()`](super::Head::peel_to_object_in_place()). #[derive(Debug, thiserror::Error)] diff --git a/gix/src/id.rs b/gix/src/id.rs index 7214ec320da..2cbe8a72e28 100644 --- a/gix/src/id.rs +++ b/gix/src/id.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use std::ops::Deref; use gix_hash::{oid, ObjectId}; @@ -68,6 +69,7 @@ fn calculate_auto_hex_len(num_packed_objects: u64) -> usize { } /// +#[allow(clippy::empty_docs)] pub mod shorten { /// Returned by [`Id::prefix()`][super::Id::shorten()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/init.rs b/gix/src/init.rs index f8e574eab70..624c4a248cc 100644 --- a/gix/src/init.rs +++ b/gix/src/init.rs @@ -1,5 +1,5 @@ #![allow(clippy::result_large_err)] -use std::{borrow::Cow, convert::TryInto, path::Path}; +use std::{borrow::Cow, path::Path}; use gix_macros::momo; use gix_ref::{ diff --git a/gix/src/lib.rs b/gix/src/lib.rs index 8c2124270e8..96bd413f8ff 100644 --- a/gix/src/lib.rs +++ b/gix/src/lib.rs @@ -133,8 +133,6 @@ pub use gix_ref as refs; pub use gix_refspec as refspec; pub use gix_revwalk as revwalk; pub use gix_sec as sec; -#[cfg(feature = "status")] -pub use gix_status as status; pub use gix_tempfile as tempfile; pub use gix_trace as trace; pub use gix_traverse as traverse; @@ -148,12 +146,14 @@ pub mod interrupt; mod ext; /// +#[allow(clippy::empty_docs)] pub mod prelude; #[cfg(feature = "excludes")] mod attribute_stack; /// +#[allow(clippy::empty_docs)] pub mod path; /// The standard type for a store to handle git references. @@ -174,10 +174,12 @@ pub use types::{ pub use types::{Pathspec, PathspecDetached, Submodule}; /// +#[allow(clippy::empty_docs)] pub mod clone; pub mod commit; #[cfg(feature = "dirwalk")] /// +#[allow(clippy::empty_docs)] pub mod dirwalk; pub mod head; pub mod id; @@ -191,11 +193,14 @@ pub mod submodule; pub mod tag; /// +#[allow(clippy::empty_docs)] pub mod progress; /// +#[allow(clippy::empty_docs)] pub mod push; /// +#[allow(clippy::empty_docs)] pub mod diff; /// See [`ThreadSafeRepository::discover()`], but returns a [`Repository`] instead. @@ -288,19 +293,24 @@ pub fn open_opts(directory: impl Into, options: open::Option } /// +#[allow(clippy::empty_docs)] pub mod create; /// +#[allow(clippy::empty_docs)] pub mod open; /// +#[allow(clippy::empty_docs)] pub mod config; /// +#[allow(clippy::empty_docs)] #[cfg(feature = "mailmap")] pub mod mailmap; /// +#[allow(clippy::empty_docs)] pub mod worktree; pub mod revision; @@ -309,18 +319,27 @@ pub mod revision; pub mod filter; /// +#[allow(clippy::empty_docs)] pub mod remote; /// +#[allow(clippy::empty_docs)] pub mod init; /// Not to be confused with 'status'. pub mod state; /// +#[allow(clippy::empty_docs)] +#[cfg(feature = "status")] +pub mod status; + +/// +#[allow(clippy::empty_docs)] pub mod shallow; /// +#[allow(clippy::empty_docs)] pub mod discover; pub mod env; diff --git a/gix/src/mailmap.rs b/gix/src/mailmap.rs index fdf7ee948da..b7244549546 100644 --- a/gix/src/mailmap.rs +++ b/gix/src/mailmap.rs @@ -1,6 +1,7 @@ pub use gix_mailmap::*; /// +#[allow(clippy::empty_docs)] pub mod load { /// The error returned by [`crate::Repository::open_mailmap_into()`]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/object/blob.rs b/gix/src/object/blob.rs index 59629727f3a..8925685b018 100644 --- a/gix/src/object/blob.rs +++ b/gix/src/object/blob.rs @@ -20,6 +20,7 @@ pub mod diff { } /// + #[allow(clippy::empty_docs)] pub mod init { /// The error returned by [`Platform::from_tree_change()`][super::Platform::from_tree_change()]. pub type Error = gix_diff::blob::platform::set_resource::Error; @@ -115,6 +116,7 @@ pub mod diff { } /// + #[allow(clippy::empty_docs)] pub mod lines { use crate::bstr::BStr; diff --git a/gix/src/object/errors.rs b/gix/src/object/errors.rs index db81daacb23..4331c795d81 100644 --- a/gix/src/object/errors.rs +++ b/gix/src/object/errors.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod conversion { /// The error returned by [`crate::object::try_to_()`][crate::Object::try_to_commit_ref()]. @@ -16,6 +17,7 @@ pub mod conversion { } /// +#[allow(clippy::empty_docs)] pub mod find { /// Indicate that an error occurred when trying to find an object. #[derive(Debug, thiserror::Error)] @@ -23,6 +25,7 @@ pub mod find { pub struct Error(#[from] pub gix_object::find::Error); /// + #[allow(clippy::empty_docs)] pub mod existing { /// An object could not be found in the database, or an error occurred when trying to obtain it. pub type Error = gix_object::find::existing::Error; @@ -30,6 +33,7 @@ pub mod find { } /// +#[allow(clippy::empty_docs)] pub mod write { /// An error to indicate writing to the loose object store failed. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/object/impls.rs b/gix/src/object/impls.rs index 5d2ad81604b..58e068e402d 100644 --- a/gix/src/object/impls.rs +++ b/gix/src/object/impls.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use crate::{object, Blob, Commit, Object, ObjectDetached, Tag, Tree}; impl<'repo> From> for ObjectDetached { diff --git a/gix/src/object/mod.rs b/gix/src/object/mod.rs index cd047c4d3a4..c0815a19250 100644 --- a/gix/src/object/mod.rs +++ b/gix/src/object/mod.rs @@ -1,6 +1,5 @@ //! -use std::convert::TryInto; - +#![allow(clippy::empty_docs)] use gix_hash::ObjectId; pub use gix_object::Kind; @@ -12,16 +11,20 @@ pub(crate) mod cache { } pub use errors::{conversion, find, write}; /// +#[allow(clippy::empty_docs)] pub mod blob; /// +#[allow(clippy::empty_docs)] pub mod commit; mod impls; pub mod peel; mod tag; /// +#[allow(clippy::empty_docs)] pub mod tree; /// +#[allow(clippy::empty_docs)] pub mod try_into { #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] diff --git a/gix/src/object/peel.rs b/gix/src/object/peel.rs index c906c0c7500..6ac77726927 100644 --- a/gix/src/object/peel.rs +++ b/gix/src/object/peel.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use crate::{ object, object::{peel, Kind}, @@ -6,6 +7,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod to_kind { mod error { diff --git a/gix/src/object/tree/diff/mod.rs b/gix/src/object/tree/diff/mod.rs index 85877561083..299075781fa 100644 --- a/gix/src/object/tree/diff/mod.rs +++ b/gix/src/object/tree/diff/mod.rs @@ -24,6 +24,7 @@ pub struct Change<'a, 'old, 'new> { } /// +#[allow(clippy::empty_docs)] pub mod change; /// Diffing @@ -86,4 +87,5 @@ impl<'a, 'repo> Platform<'a, 'repo> { } /// +#[allow(clippy::empty_docs)] pub mod for_each; diff --git a/gix/src/object/tree/mod.rs b/gix/src/object/tree/mod.rs index 0523477c9ff..0a1ed46d66d 100644 --- a/gix/src/object/tree/mod.rs +++ b/gix/src/object/tree/mod.rs @@ -174,9 +174,11 @@ impl<'repo> Tree<'repo> { pub mod diff; /// +#[allow(clippy::empty_docs)] pub mod traverse; /// +#[allow(clippy::empty_docs)] mod iter; pub use iter::EntryRef; diff --git a/gix/src/open/repository.rs b/gix/src/open/repository.rs index 829e9dece45..7c5b065bd78 100644 --- a/gix/src/open/repository.rs +++ b/gix/src/open/repository.rs @@ -299,7 +299,7 @@ impl ThreadSafeRepository { } refs.write_reflog = config::cache::util::reflog_or_default(config.reflog, worktree_dir.is_some()); - refs.namespace = config.refs_namespace.clone(); + refs.namespace.clone_from(&config.refs_namespace); let replacements = replacement_objects_refs_prefix(&config.resolved, lenient_config, filter_config_section)? .and_then(|prefix| { let _span = gix_trace::detail!("find replacement objects"); diff --git a/gix/src/pathspec.rs b/gix/src/pathspec.rs index 08ac5e5230a..f501be621f8 100644 --- a/gix/src/pathspec.rs +++ b/gix/src/pathspec.rs @@ -5,6 +5,7 @@ pub use gix_pathspec::*; use crate::{bstr::BStr, AttributeStack, Pathspec, PathspecDetached, Repository}; /// +#[allow(clippy::empty_docs)] pub mod init { /// The error returned by [`Pathspec::new()`](super::Pathspec::new()). #[derive(Debug, thiserror::Error)] @@ -67,6 +68,13 @@ impl<'repo> Pathspec<'repo> { )?, )?; let cache = needs_cache.then(make_attributes).transpose()?; + + gix_trace::debug!( + longest_prefix = ?search.longest_common_directory(), + prefix_dir = ?search.prefix_directory(), + patterns = ?search.patterns().map(gix_pathspec::Pattern::path).collect::>() + ); + Ok(Self { repo, search, diff --git a/gix/src/reference/edits.rs b/gix/src/reference/edits.rs index cba652630ce..4d3d773ce68 100644 --- a/gix/src/reference/edits.rs +++ b/gix/src/reference/edits.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod set_target_id { use gix_macros::momo; use gix_ref::{transaction::PreviousValue, Target}; @@ -53,6 +54,7 @@ pub mod set_target_id { } /// +#[allow(clippy::empty_docs)] pub mod delete { use gix_ref::transaction::{Change, PreviousValue, RefEdit, RefLog}; diff --git a/gix/src/reference/errors.rs b/gix/src/reference/errors.rs index d5b09f78e0b..92714ec790c 100644 --- a/gix/src/reference/errors.rs +++ b/gix/src/reference/errors.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod edit { use crate::config; @@ -21,6 +22,7 @@ pub mod edit { } /// +#[allow(clippy::empty_docs)] pub mod peel { /// The error returned by [`Reference::peel_to_id_in_place(…)`](crate::Reference::peel_to_id_in_place()) and /// [`Reference::into_fully_peeled_id(…)`](crate::Reference::into_fully_peeled_id()). @@ -35,6 +37,7 @@ pub mod peel { } /// +#[allow(clippy::empty_docs)] pub mod head_id { /// The error returned by [`Repository::head_id(…)`](crate::Repository::head_id()). #[derive(Debug, thiserror::Error)] @@ -48,6 +51,7 @@ pub mod head_id { } /// +#[allow(clippy::empty_docs)] pub mod head_commit { /// The error returned by [`Repository::head_commit`(…)](crate::Repository::head_commit()). #[derive(Debug, thiserror::Error)] @@ -61,6 +65,7 @@ pub mod head_commit { } /// +#[allow(clippy::empty_docs)] pub mod head_tree_id { /// The error returned by [`Repository::head_tree_id`(…)](crate::Repository::head_tree_id()). #[derive(Debug, thiserror::Error)] @@ -74,8 +79,10 @@ pub mod head_tree_id { } /// +#[allow(clippy::empty_docs)] pub mod find { /// + #[allow(clippy::empty_docs)] pub mod existing { /// The error returned by [`find_reference(…)`][crate::Repository::find_reference()], and others. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/reference/iter.rs b/gix/src/reference/iter.rs index 604a8ac4b05..995284a4824 100644 --- a/gix/src/reference/iter.rs +++ b/gix/src/reference/iter.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use std::path::Path; use gix_macros::momo; @@ -109,6 +110,7 @@ impl<'r> Iterator for Iter<'r> { } /// +#[allow(clippy::empty_docs)] pub mod init { /// The error returned by [`Platform::all()`][super::Platform::all()] or [`Platform::prefixed()`][super::Platform::prefixed()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/reference/log.rs b/gix/src/reference/log.rs index 2fea1782cc4..275eaf86850 100644 --- a/gix/src/reference/log.rs +++ b/gix/src/reference/log.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use gix_object::commit::MessageRef; use gix_ref::file::ReferenceExt; diff --git a/gix/src/reference/mod.rs b/gix/src/reference/mod.rs index ebdbf66a717..b145509cf92 100644 --- a/gix/src/reference/mod.rs +++ b/gix/src/reference/mod.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] use gix_ref::file::ReferenceExt; @@ -6,6 +7,7 @@ use crate::{Id, Reference}; pub mod iter; /// +#[allow(clippy::empty_docs)] pub mod remote; mod errors; diff --git a/gix/src/remote/build.rs b/gix/src/remote/build.rs index 452da66a006..16243900d9b 100644 --- a/gix/src/remote/build.rs +++ b/gix/src/remote/build.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::{bstr::BStr, remote, Remote}; /// Builder methods diff --git a/gix/src/remote/connection/fetch/mod.rs b/gix/src/remote/connection/fetch/mod.rs index d4afd10234d..213a41bf7e4 100644 --- a/gix/src/remote/connection/fetch/mod.rs +++ b/gix/src/remote/connection/fetch/mod.rs @@ -90,6 +90,7 @@ pub mod outcome { } /// + #[allow(clippy::empty_docs)] pub mod negotiate { /// Key information about each round in the pack-negotiation. #[derive(Debug, Clone)] @@ -138,6 +139,7 @@ impl From for gix_features::progress::Id { pub(crate) mod negotiate; /// +#[allow(clippy::empty_docs)] pub mod prepare { /// The error returned by [`prepare_fetch()`][super::Connection::prepare_fetch()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/remote/connection/fetch/update_refs/mod.rs b/gix/src/remote/connection/fetch/update_refs/mod.rs index c487e7f5c2c..06a74fb3cbc 100644 --- a/gix/src/remote/connection/fetch/update_refs/mod.rs +++ b/gix/src/remote/connection/fetch/update_refs/mod.rs @@ -1,5 +1,5 @@ #![allow(clippy::result_large_err)] -use std::{collections::BTreeMap, convert::TryInto, path::PathBuf}; +use std::{collections::BTreeMap, path::PathBuf}; use gix_object::Exists; use gix_ref::{ @@ -20,6 +20,7 @@ use crate::{ }; /// +#[allow(clippy::empty_docs)] pub mod update; /// Information about the update of a single reference, corresponding the respective entry in [`RefMap::mappings`][crate::remote::fetch::RefMap::mappings]. diff --git a/gix/src/remote/connection/fetch/update_refs/tests.rs b/gix/src/remote/connection/fetch/update_refs/tests.rs index a56e1218043..fbe5e2fcd17 100644 --- a/gix/src/remote/connection/fetch/update_refs/tests.rs +++ b/gix/src/remote/connection/fetch/update_refs/tests.rs @@ -8,8 +8,6 @@ fn hex_to_id(hex: &str) -> gix_hash::ObjectId { } mod update { - use std::convert::TryInto; - use gix_testtools::Result; use super::hex_to_id; diff --git a/gix/src/remote/connection/mod.rs b/gix/src/remote/connection/mod.rs index f9b8aa7e6d4..78c29344bed 100644 --- a/gix/src/remote/connection/mod.rs +++ b/gix/src/remote/connection/mod.rs @@ -23,7 +23,9 @@ pub struct Connection<'a, 'repo, T> { mod access; /// +#[allow(clippy::empty_docs)] pub mod ref_map; /// +#[allow(clippy::empty_docs)] pub mod fetch; diff --git a/gix/src/remote/errors.rs b/gix/src/remote/errors.rs index 34ed8246b24..37b211d28b2 100644 --- a/gix/src/remote/errors.rs +++ b/gix/src/remote/errors.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod find { use crate::{bstr::BString, config, remote}; @@ -27,6 +28,7 @@ pub mod find { } /// + #[allow(clippy::empty_docs)] pub mod existing { use crate::bstr::BString; @@ -44,6 +46,7 @@ pub mod find { } /// + #[allow(clippy::empty_docs)] pub mod for_fetch { /// The error returned by [`Repository::find_fetch_remote(…)`](crate::Repository::find_fetch_remote()). #[derive(Debug, thiserror::Error)] diff --git a/gix/src/remote/fetch.rs b/gix/src/remote/fetch.rs index 4700201de51..ff483c893d7 100644 --- a/gix/src/remote/fetch.rs +++ b/gix/src/remote/fetch.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod negotiate { #[cfg(feature = "credentials")] pub use gix_negotiate::Algorithm; diff --git a/gix/src/remote/init.rs b/gix/src/remote/init.rs index 13b747eda7c..fd81604b257 100644 --- a/gix/src/remote/init.rs +++ b/gix/src/remote/init.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_refspec::RefSpec; use crate::{config, remote, Remote, Repository}; diff --git a/gix/src/remote/mod.rs b/gix/src/remote/mod.rs index 7937f8275ed..38b1ccf27ad 100644 --- a/gix/src/remote/mod.rs +++ b/gix/src/remote/mod.rs @@ -32,6 +32,7 @@ pub enum Name<'repo> { } /// +#[allow(clippy::empty_docs)] pub mod name; mod build; @@ -40,9 +41,11 @@ mod errors; pub use errors::find; /// +#[allow(clippy::empty_docs)] pub mod init; /// +#[allow(clippy::empty_docs)] pub mod fetch; /// @@ -55,8 +58,10 @@ mod connection; pub use connection::{ref_map, AuthenticateFn, Connection}; /// +#[allow(clippy::empty_docs)] pub mod save; mod access; /// +#[allow(clippy::empty_docs)] pub mod url; diff --git a/gix/src/remote/name.rs b/gix/src/remote/name.rs index 6c6afe745f1..f0081cc30df 100644 --- a/gix/src/remote/name.rs +++ b/gix/src/remote/name.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::borrow::Cow; use super::Name; use crate::bstr::{BStr, BString, ByteSlice, ByteVec}; diff --git a/gix/src/remote/save.rs b/gix/src/remote/save.rs index 2a91dfa9c1d..63a2de90d71 100644 --- a/gix/src/remote/save.rs +++ b/gix/src/remote/save.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_macros::momo; use crate::{ diff --git a/gix/src/remote/url/scheme_permission.rs b/gix/src/remote/url/scheme_permission.rs index eed7be6185c..212742de2b6 100644 --- a/gix/src/remote/url/scheme_permission.rs +++ b/gix/src/remote/url/scheme_permission.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::BTreeMap, convert::TryFrom}; +use std::{borrow::Cow, collections::BTreeMap}; use crate::{ bstr::{BStr, BString, ByteSlice}, diff --git a/gix/src/repository/config/branch.rs b/gix/src/repository/config/branch.rs index 334db116786..81f6bc70e54 100644 --- a/gix/src/repository/config/branch.rs +++ b/gix/src/repository/config/branch.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::BTreeSet, convert::TryInto}; +use std::{borrow::Cow, collections::BTreeSet}; use gix_ref::{FullName, FullNameRef}; diff --git a/gix/src/repository/diff.rs b/gix/src/repository/diff.rs index cb1d070a2f4..87e28d0e9c5 100644 --- a/gix/src/repository/diff.rs +++ b/gix/src/repository/diff.rs @@ -1,6 +1,7 @@ use crate::Repository; /// +#[allow(clippy::empty_docs)] pub mod resource_cache { /// The error returned by [Repository::diff_resource_cache()](super::Repository::diff_resource_cache()). #[derive(Debug, thiserror::Error)] @@ -10,6 +11,8 @@ pub mod resource_cache { ResourceCache(#[from] crate::diff::resource_cache::Error), #[error(transparent)] Index(#[from] crate::repository::index_or_load_from_head::Error), + #[error(transparent)] + AttributeStack(#[from] crate::config::attribute_stack::Error), } } @@ -30,15 +33,19 @@ impl Repository { mode: gix_diff::blob::pipeline::Mode, worktree_roots: gix_diff::blob::pipeline::WorktreeRoots, ) -> Result { + let index = self.index_or_load_from_head()?; Ok(crate::diff::resource_cache( self, - &*self.index_or_load_from_head()?, mode, - if worktree_roots.new_root.is_some() || worktree_roots.old_root.is_some() { - gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping - } else { - gix_worktree::stack::state::attributes::Source::IdMapping - }, + self.attributes_only( + &index, + if worktree_roots.new_root.is_some() || worktree_roots.old_root.is_some() { + gix_worktree::stack::state::attributes::Source::WorktreeThenIdMapping + } else { + gix_worktree::stack::state::attributes::Source::IdMapping + }, + )? + .inner, worktree_roots, )?) } diff --git a/gix/src/repository/dirwalk.rs b/gix/src/repository/dirwalk.rs index 2495b544925..e0141613e7f 100644 --- a/gix/src/repository/dirwalk.rs +++ b/gix/src/repository/dirwalk.rs @@ -9,7 +9,7 @@ pub enum Error { #[error(transparent)] Walk(#[from] gix_dir::walk::Error), #[error("A working tree is required to perform a directory walk")] - MissinWorkDir, + MissingWorkDir, #[error(transparent)] Excludes(#[from] config::exclude_stack::Error), #[error(transparent)] @@ -57,7 +57,7 @@ impl Repository { delegate: &mut dyn gix_dir::walk::Delegate, ) -> Result, Error> { let _span = gix_trace::coarse!("gix::dirwalk"); - let workdir = self.work_dir().ok_or(Error::MissinWorkDir)?; + let workdir = self.work_dir().ok_or(Error::MissingWorkDir)?; let mut excludes = self.excludes( index, None, @@ -70,11 +70,6 @@ impl Repository { index, crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping, )?; - gix_trace::debug!( - longest_prefix = ?pathspec.search.longest_common_directory(), - prefix_dir = ?pathspec.search.prefix_directory(), - patterns = ?pathspec.search.patterns().map(gix_pathspec::Pattern::path).collect::>() - ); let git_dir_realpath = crate::path::realpath_opts(self.git_dir(), self.current_dir(), crate::path::realpath::MAX_SYMLINKS)?; diff --git a/gix/src/repository/filter.rs b/gix/src/repository/filter.rs index 0ad7c0ef609..d5dc5690ea2 100644 --- a/gix/src/repository/filter.rs +++ b/gix/src/repository/filter.rs @@ -1,6 +1,7 @@ -use crate::{filter, repository::IndexPersistedOrInMemory, Id, Repository}; +use crate::{filter, worktree::IndexPersistedOrInMemory, Id, Repository}; /// +#[allow(clippy::empty_docs)] pub mod pipeline { /// The error returned by [Repository::filter_pipeline()](super::Repository::filter_pipeline()). #[derive(Debug, thiserror::Error)] diff --git a/gix/src/repository/index.rs b/gix/src/repository/index.rs index 85a1a664bb0..18899d09fba 100644 --- a/gix/src/repository/index.rs +++ b/gix/src/repository/index.rs @@ -1,4 +1,4 @@ -use crate::{config::cache::util::ApplyLeniencyDefault, repository::IndexPersistedOrInMemory, worktree}; +use crate::{config::cache::util::ApplyLeniencyDefault, worktree, worktree::IndexPersistedOrInMemory}; /// Index access impl crate::Repository { diff --git a/gix/src/repository/mod.rs b/gix/src/repository/mod.rs index b60a4bc870c..68b9b491c74 100644 --- a/gix/src/repository/mod.rs +++ b/gix/src/repository/mod.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] /// The kind of repository. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -73,6 +74,7 @@ mod thread_safe; mod worktree; /// +#[allow(clippy::empty_docs)] pub mod branch_remote_ref_name { /// The error returned by [Repository::branch_remote_ref_name()](crate::Repository::branch_remote_ref_name()). @@ -89,6 +91,7 @@ pub mod branch_remote_ref_name { } /// +#[allow(clippy::empty_docs)] pub mod branch_remote_tracking_ref_name { /// The error returned by [Repository::branch_remote_tracking_ref_name()](crate::Repository::branch_remote_tracking_ref_name()). @@ -104,17 +107,6 @@ pub mod branch_remote_tracking_ref_name { } } -/// A type to represent an index which either was loaded from disk as it was persisted there, or created on the fly in memory. -#[cfg(feature = "index")] -pub enum IndexPersistedOrInMemory { - /// The index as loaded from disk, and shared across clones of the owning `Repository`. - Persisted(crate::worktree::Index), - /// A temporary index as created from the `HEAD^{tree}`, with the file path set to the place where it would be stored naturally. - /// - /// Note that unless saved explicitly, it will not persist. - InMemory(gix_index::File), -} - /// #[cfg(feature = "attributes")] pub mod pathspec_defaults_ignore_case { diff --git a/gix/src/repository/object.rs b/gix/src/repository/object.rs index 77f188badf8..742a349e04f 100644 --- a/gix/src/repository/object.rs +++ b/gix/src/repository/object.rs @@ -1,5 +1,5 @@ #![allow(clippy::result_large_err)] -use std::{convert::TryInto, ops::DerefMut}; +use std::ops::DerefMut; use gix_hash::ObjectId; use gix_macros::momo; diff --git a/gix/src/repository/reference.rs b/gix/src/repository/reference.rs index b977c6ea872..9897f008012 100644 --- a/gix/src/repository/reference.rs +++ b/gix/src/repository/reference.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use gix_hash::ObjectId; use gix_macros::momo; use gix_ref::{ diff --git a/gix/src/repository/remote.rs b/gix/src/repository/remote.rs index be0845178b9..76314ea6cf9 100644 --- a/gix/src/repository/remote.rs +++ b/gix/src/repository/remote.rs @@ -1,6 +1,4 @@ #![allow(clippy::result_large_err)] -use std::convert::TryInto; - use crate::{bstr::BStr, config, remote, remote::find, Remote}; impl crate::Repository { diff --git a/gix/src/revision/mod.rs b/gix/src/revision/mod.rs index 9bd23b994bd..0f45590bd9f 100644 --- a/gix/src/revision/mod.rs +++ b/gix/src/revision/mod.rs @@ -6,11 +6,13 @@ pub use gix_revision as plumbing; /// +#[allow(clippy::empty_docs)] pub mod walk; pub use walk::iter::Walk; /// #[cfg(feature = "revision")] +#[allow(clippy::empty_docs)] pub mod spec; /// The specification of a revision as parsed from a revision specification like `HEAD@{1}` or `v1.2.3...main`. diff --git a/gix/src/revision/spec/mod.rs b/gix/src/revision/spec/mod.rs index af58ecdff51..e7c9f244144 100644 --- a/gix/src/revision/spec/mod.rs +++ b/gix/src/revision/spec/mod.rs @@ -2,6 +2,7 @@ use crate::bstr::BStr; use crate::{ext::ReferenceExt, revision::Spec, Id, Reference}; /// +#[allow(clippy::empty_docs)] pub mod parse; mod impls { diff --git a/gix/src/revision/spec/parse/mod.rs b/gix/src/revision/spec/parse/mod.rs index e4584776363..d9c38276a91 100644 --- a/gix/src/revision/spec/parse/mod.rs +++ b/gix/src/revision/spec/parse/mod.rs @@ -11,6 +11,7 @@ use crate::bstr::BString; pub use types::{Error, ObjectKindHint, Options, RefsHint}; /// +#[allow(clippy::empty_docs)] pub mod single { use crate::bstr::BString; @@ -26,6 +27,7 @@ pub mod single { } /// +#[allow(clippy::empty_docs)] pub mod error; impl<'repo> Spec<'repo> { diff --git a/gix/src/shallow.rs b/gix/src/shallow.rs index d49653a6506..dfe376283dd 100644 --- a/gix/src/shallow.rs +++ b/gix/src/shallow.rs @@ -79,6 +79,7 @@ pub mod write { pub use write::function::write; /// +#[allow(clippy::empty_docs)] pub mod open { /// The error returned by [`Repository::shallow_commits()`][crate::Repository::shallow_commits()]. #[derive(Debug, thiserror::Error)] diff --git a/gix/src/status/index_worktree.rs b/gix/src/status/index_worktree.rs new file mode 100644 index 00000000000..0248b3d27db --- /dev/null +++ b/gix/src/status/index_worktree.rs @@ -0,0 +1,836 @@ +use crate::bstr::{BStr, BString}; +use crate::{config, Repository}; +use gix_status::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus}; +use std::sync::atomic::AtomicBool; + +/// The error returned by [Repository::index_worktree_status()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("A working tree is required to perform a directory walk")] + MissingWorkDir, + #[error(transparent)] + AttributesAndExcludes(#[from] crate::repository::attributes::Error), + #[error(transparent)] + Pathspec(#[from] crate::pathspec::init::Error), + #[error(transparent)] + Prefix(#[from] gix_path::realpath::Error), + #[error(transparent)] + FilesystemOptions(#[from] config::boolean::Error), + #[error(transparent)] + IndexAsWorktreeWithRenames(#[from] gix_status::index_as_worktree_with_renames::Error), + #[error(transparent)] + StatOptions(#[from] config::stat_options::Error), + #[error(transparent)] + ResourceCache(#[from] crate::diff::resource_cache::Error), +} + +/// Options for use with [Repository::index_worktree_status()]. +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct Options { + /// The way all output should be sorted. + /// + /// If `None`, and depending on the `rewrites` field, output will be immediate but the output order + /// isn't determined, and may differ between two runs. `rewrites` also depend on the order of entries that + /// are presented to it, hence for deterministic results, sorting needs to be enabled. + /// + /// If `Some(_)`, all entries are collected beforehand, so they can be sorted before outputting any of them + /// to the user. + /// + /// If immediate output of entries in any order is desired, this should be `None`, + /// along with `rewrites` being `None` as well. + pub sorting: Option, + /// If not `None`, the options to configure the directory walk, determining how its results will look like. + /// + /// If `None`, only modification checks are performed. + /// + /// Can be instantiated with [Repository::dirwalk_options()]. + pub dirwalk_options: Option, + /// If `Some(_)`, along with `Some(_)` in `dirwalk_options`, rewrite tracking will be performed between the + /// index and the working tree. + /// Note that there is no git-configuration specific to index-worktree rename tracking. + /// When rewrite tracking is enabled, there will be a delay for some entries as they partake in the rename-analysis. + pub rewrites: Option, + /// If set, don't use more than this amount of threads for the tracked modification check. + /// Otherwise, usually use as many threads as there are logical cores. + /// A value of 0 is interpreted as no-limit + pub thread_limit: Option, +} + +impl Repository { + /// Obtain the status between the index and the worktree, involving modification checks + /// for all tracked files along with information about untracked (and posisbly ignored) files (if configured). + /// + /// * `index` + /// - The index to use for modification checks, and to know which files are tacked when applying the dirwalk. + /// * `patterns` + /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered. + /// * `delegate` + /// - The sink for receiving all status data. + /// * `compare` + /// - The implementations for fine-grained control over what happens if a hash must be recalculated. + /// * `submodule` + /// - Control what kind of information to retrieve when a submodule is encountered while traversing the index. + /// * `progress` + /// - A progress indication for index modification checks. + /// * `should_interrupt` + /// - A flag to stop the whole operation. + /// * `options` + /// - Additional configuration for all parts of the operation. + /// + /// ### Note + /// + /// This is a lower-level method, prefer the [`status`](Repository::status()) method for greater ease of use. + #[allow(clippy::too_many_arguments)] + pub fn index_worktree_status<'index, T, U, E>( + &self, + index: &'index gix_index::State, + patterns: impl IntoIterator>, + delegate: &mut impl gix_status::index_as_worktree_with_renames::VisitEntry< + 'index, + ContentChange = T, + SubmoduleStatus = U, + >, + compare: impl CompareBlobs + Send + Clone, + submodule: impl SubmoduleStatus + Send + Clone, + progress: &mut dyn gix_features::progress::Progress, + should_interrupt: &AtomicBool, + options: Options, + ) -> Result + where + T: Send + Clone, + U: Send + Clone, + E: std::error::Error + Send + Sync + 'static, + { + let _span = gix_trace::coarse!("gix::index_worktree_status"); + let workdir = self.work_dir().ok_or(Error::MissingWorkDir)?; + let attrs_and_excludes = self.attributes( + index, + crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping, + crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped, + None, + )?; + let pathspec = crate::Pathspec::new( + self, + options + .dirwalk_options + .as_ref() + .map_or(false, |opts| opts.empty_patterns_match_prefix), + patterns, + true, /* inherit ignore case */ + || Ok(attrs_and_excludes.clone()), + )?; + + let cwd = self.current_dir(); + let git_dir_realpath = crate::path::realpath_opts(self.git_dir(), cwd, crate::path::realpath::MAX_SYMLINKS)?; + let fs_caps = self.filesystem_options()?; + let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing()); + let resource_cache = crate::diff::resource_cache( + self, + gix_diff::blob::pipeline::Mode::ToGit, + attrs_and_excludes.inner, + gix_diff::blob::pipeline::WorktreeRoots { + old_root: None, + new_root: Some(workdir.to_owned()), + }, + )?; + + let out = gix_status::index_as_worktree_with_renames( + index, + workdir, + delegate, + compare, + submodule, + self.objects.clone().into_arc().expect("arc conversion always works"), + progress, + gix_status::index_as_worktree_with_renames::Context { + pathspec: pathspec.search, + resource_cache, + should_interrupt, + dirwalk: gix_status::index_as_worktree_with_renames::DirwalkContext { + git_dir_realpath: git_dir_realpath.as_path(), + current_dir: cwd, + ignore_case_index_lookup: accelerate_lookup.as_ref(), + }, + }, + gix_status::index_as_worktree_with_renames::Options { + sorting: options.sorting, + object_hash: self.object_hash(), + tracked_file_modifications: gix_status::index_as_worktree::Options { + fs: fs_caps, + thread_limit: options.thread_limit, + stat: self.stat_options()?, + }, + dirwalk: options.dirwalk_options.map(Into::into), + rewrites: options.rewrites, + }, + )?; + Ok(out) + } +} + +/// An implementation of a trait to use with [`Repository::index_worktree_status()`] to compute the submodule status +/// using [Submodule::status()](crate::Submodule::status()). +#[derive(Clone)] +pub struct BuiltinSubmoduleStatus { + mode: crate::status::Submodule, + #[cfg(feature = "parallel")] + repo: crate::ThreadSafeRepository, + #[cfg(not(feature = "parallel"))] + git_dir: std::path::PathBuf, + submodule_paths: Vec, +} + +/// +#[allow(clippy::empty_docs)] +mod submodule_status { + use crate::bstr; + use crate::bstr::BStr; + use crate::status::index_worktree::BuiltinSubmoduleStatus; + use crate::status::Submodule; + use std::borrow::Cow; + + impl BuiltinSubmoduleStatus { + /// Create a new instance from a `repo` and a `mode` to control how the submodule status will be obtained. + pub fn new( + repo: crate::ThreadSafeRepository, + mode: Submodule, + ) -> Result { + let local_repo = repo.to_thread_local(); + let submodule_paths = match local_repo.submodules()? { + Some(sm) => { + let mut v: Vec<_> = sm + .filter(|sm| sm.is_active().unwrap_or_default()) + .filter_map(|sm| sm.path().ok().map(Cow::into_owned)) + .collect(); + v.sort(); + v + } + None => Vec::new(), + }; + Ok(Self { + mode, + #[cfg(feature = "parallel")] + repo, + #[cfg(not(feature = "parallel"))] + git_dir: local_repo.git_dir().to_owned(), + submodule_paths, + }) + } + } + + /// The error returned submodule status checks. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + SubmoduleStatus(#[from] crate::submodule::status::Error), + #[error(transparent)] + IgnoreConfig(#[from] crate::submodule::config::Error), + } + + impl gix_status::index_as_worktree::traits::SubmoduleStatus for BuiltinSubmoduleStatus { + type Output = crate::submodule::Status; + type Error = Error; + + fn status(&mut self, _entry: &gix_index::Entry, rela_path: &BStr) -> Result, Self::Error> { + use bstr::ByteSlice; + if self + .submodule_paths + .binary_search_by(|path| path.as_bstr().cmp(rela_path)) + .is_err() + { + return Ok(None); + } + #[cfg(feature = "parallel")] + let repo = self.repo.to_thread_local(); + #[cfg(not(feature = "parallel"))] + let Ok(repo) = crate::open(&self.git_dir) else { + return Ok(None); + }; + let Ok(Some(mut submodules)) = repo.submodules() else { + return Ok(None); + }; + let Some(sm) = submodules.find(|sm| sm.path().map_or(false, |path| path == rela_path)) else { + return Ok(None); + }; + let (ignore, check_dirty) = match self.mode { + Submodule::AsConfigured { check_dirty } => (sm.ignore()?.unwrap_or_default(), check_dirty), + Submodule::Given { ignore, check_dirty } => (ignore, check_dirty), + }; + let status = sm.status(ignore, check_dirty)?; + Ok(status.is_dirty().and_then(|dirty| dirty.then_some(status))) + } + } +} + +/// An iterator for changes between the index and the worktree. +/// +/// Note that depending on the underlying configuration, there might be a significant delay until the first +/// item is received due to the buffering necessary to perform rename tracking and/or sorting. +/// +/// ### Submodules +/// +/// Note that submodules can be set to 'inactive' which automatically excludes them from the status operation. +/// +/// ### Index Changes +/// +/// Changes to the index are collected and it's possible to write the index back using [iter::Outcome::write_changes()]. +/// Note that these changes are not observable, they will always be kept. +/// +/// ### Parallel Operation +/// +/// Note that without the `parallel` feature, the iterator becomes 'serial', which means all status will be computed in advance +/// and it's non-interruptable, yielding worse performance for is-dirty checks for instance as interruptions won't happen. +/// It's a crutch that is just there to make single-threaded applications possible at all, as it's not really an iterator +/// anymore. If this matters, better run [Repository::index_worktree_status()] by hand as it provides all control one would need, +/// just not as an iterator. +/// +/// Also, even with `parallel` set, the first call to `next()` will block until there is an item available, without a chance +/// to interrupt unless [`status::Platform::should_interrupt_*()`](crate::status::Platform::should_interrupt_shared()) was +/// configured. +pub struct Iter { + #[cfg(feature = "parallel")] + #[allow(clippy::type_complexity)] + rx_and_join: Option<( + std::sync::mpsc::Receiver, + std::thread::JoinHandle>, + )>, + #[cfg(feature = "parallel")] + should_interrupt: crate::status::OwnedOrStaticAtomic, + /// Without parallelization, the iterator has to buffer all changes in advance. + #[cfg(not(feature = "parallel"))] + items: std::vec::IntoIter, + /// The outcome of the operation, only available once the operation has ended. + out: Option, + /// The set of `(entry_index, change)` we extracted in order to potentially write back the index with the changes applied. + changes: Vec<(usize, iter::ApplyChange)>, +} + +/// +#[allow(clippy::empty_docs)] +pub mod iter { + use crate::bstr::{BStr, BString}; + use crate::config::cache::util::ApplyLeniencyDefault; + use crate::status::index_worktree::{iter, BuiltinSubmoduleStatus}; + use crate::status::{index_worktree, Platform}; + use crate::worktree::IndexPersistedOrInMemory; + use gix_status::index_as_worktree::{Change, EntryStatus}; + + pub use gix_status::index_as_worktree_with_renames::Summary; + + pub(super) enum ApplyChange { + SetSizeToZero, + NewStat(crate::index::entry::Stat), + } + + /// The data the thread sends over to the receiving iterator. + pub struct Outcome { + /// The outcome of the index-to-worktree comparison operation. + pub index_worktree: gix_status::index_as_worktree_with_renames::Outcome, + /// The index that was used for the operation. + pub index: crate::worktree::IndexPersistedOrInMemory, + skip_hash: bool, + changes: Option>, + } + + impl Outcome { + /// Returns `true` if the index has received currently unapplied changes that *should* be written back. + /// + /// If they are not written back, subsequent `status` operations will take longer to complete, whereas the + /// additional work can be prevented by writing the changes back to the index. + pub fn has_changes(&self) -> bool { + self.changes.as_ref().map_or(false, |changes| !changes.is_empty()) + } + + /// Write the changes if there are any back to the index file. + /// This can only be done once as the changes are consumed in the process, if there were any. + pub fn write_changes(&mut self) -> Option> { + let _span = gix_features::trace::coarse!("gix::status::index_worktree::iter::Outcome::write_changes()"); + let changes = self.changes.take()?; + let mut index = match &self.index { + IndexPersistedOrInMemory::Persisted(persisted) => (***persisted).clone(), + IndexPersistedOrInMemory::InMemory(index) => index.clone(), + }; + + let entries = index.entries_mut(); + for (entry_index, change) in changes { + let entry = &mut entries[entry_index]; + match change { + ApplyChange::SetSizeToZero => { + entry.stat.size = 0; + } + ApplyChange::NewStat(new_stat) => { + entry.stat = new_stat; + } + } + } + + Some(index.write(crate::index::write::Options { + extensions: Default::default(), + skip_hash: self.skip_hash, + })) + } + } + + /// Either an index entry for renames or another directory entry in case of copies. + #[derive(Clone, PartialEq, Debug)] + pub enum RewriteSource { + /// The source originates in the index and is detected as missing in the working tree. + /// This can also happen for copies. + RewriteFromIndex { + /// The entry that is the source of the rewrite, which means it was removed on disk, + /// equivalent to [Change::Removed]. + /// + /// Note that the [entry-id](gix_index::Entry::id) is the content-id of the source of the rewrite. + source_entry: gix_index::Entry, + /// The index of the `source_entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors. + source_entry_index: usize, + /// The repository-relative path of the `source_entry`. + source_rela_path: BString, + /// The computed status of the `source_entry`. + source_status: gix_status::index_as_worktree::EntryStatus<(), crate::submodule::Status>, + }, + /// This source originates in the directory tree and is always the source of copies. + CopyFromDirectoryEntry { + /// The source of the copy operation, which is also an entry of the directory walk. + /// + /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the source of the rewrite. + source_dirwalk_entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `source_dirwalk_entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `source_dirwalk_entry` and + /// if [gix_dir::walk::Options::emit_collapsed] was [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + source_dirwalk_entry_collapsed_directory_status: Option, + /// The object id as it would appear if the entry was written to the object database. + /// It's the same as [`dirwalk_entry_id`](Item::Rewrite), or `diff` is `Some(_)` to indicate that the copy + /// was determined by similarity, not by content equality. + source_dirwalk_entry_id: gix_hash::ObjectId, + }, + } + + /// Access + impl RewriteSource { + /// The repository-relative path of this source. + pub fn rela_path(&self) -> &BStr { + match self { + RewriteSource::RewriteFromIndex { source_rela_path, .. } => source_rela_path.as_ref(), + RewriteSource::CopyFromDirectoryEntry { + source_dirwalk_entry, .. + } => source_dirwalk_entry.rela_path.as_ref(), + } + } + } + + impl<'index> From> + for RewriteSource + { + fn from(value: gix_status::index_as_worktree_with_renames::RewriteSource<'index, (), SubmoduleStatus>) -> Self { + match value { + gix_status::index_as_worktree_with_renames::RewriteSource::RewriteFromIndex { + index_entries: _, + source_entry, + source_entry_index, + source_rela_path, + source_status, + } => RewriteSource::RewriteFromIndex { + source_entry: source_entry.clone(), + source_entry_index, + source_rela_path: source_rela_path.to_owned(), + source_status, + }, + gix_status::index_as_worktree_with_renames::RewriteSource::CopyFromDirectoryEntry { + source_dirwalk_entry, + source_dirwalk_entry_collapsed_directory_status, + source_dirwalk_entry_id, + } => RewriteSource::CopyFromDirectoryEntry { + source_dirwalk_entry, + source_dirwalk_entry_collapsed_directory_status, + source_dirwalk_entry_id, + }, + } + } + } + + /// The item produced by the iterator + #[derive(Clone, PartialEq, Debug)] + pub enum Item { + /// A tracked file was modified, and index-specific information is passed. + Modification { + /// The entry with modifications. + entry: gix_index::Entry, + /// The index of the `entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors. + entry_index: usize, + /// The repository-relative path of the entry. + rela_path: BString, + /// The computed status of the entry. + status: gix_status::index_as_worktree::EntryStatus<(), SubmoduleStatus>, + }, + /// An entry returned by the directory walk, without any relation to the index. + /// + /// This can happen if ignored files are returned as well, or if rename-tracking is disabled. + DirectoryContents { + /// The entry found during the disk traversal. + entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was + /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + collapsed_directory_status: Option, + }, + /// The rewrite tracking discovered a match between a deleted and added file, and considers them equal enough, + /// depending on the tracker settings. + /// + /// Note that the source of the rewrite is always the index as it detects the absence of entries, something that + /// can't be done during a directory walk. + Rewrite { + /// The source of the rewrite operation. + source: RewriteSource, + /// The untracked entry found during the disk traversal, the destination of the rewrite. + /// + /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the destination of the rewrite, and the current + /// location of the entry. + dirwalk_entry: gix_dir::Entry, + /// `collapsed_directory_status` is `Some(dir_status)` if this `dirwalk_entry` was part of a directory with the given + /// `dir_status` that wasn't the same as the one of `dirwalk_entry` and if [gix_dir::walk::Options::emit_collapsed] was + /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch). + /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All). + dirwalk_entry_collapsed_directory_status: Option, + /// The object id after the rename, specifically hashed in order to determine equality. + dirwalk_entry_id: gix_hash::ObjectId, + /// It's `None` if the 'source.id' is equal to `dirwalk_entry_id`, as identity made an actual diff computation unnecessary. + /// Otherwise, and if enabled, it's `Some(stats)` to indicate how similar both entries were. + diff: Option, + /// If true, this rewrite is created by copy, and 'source.id' is pointing to its source. + /// Otherwise, it's a rename, and 'source.id' points to a deleted object, + /// as renames are tracked as deletions and additions of the same or similar content. + copy: bool, + }, + } + + impl Item { + /// Return a simplified summary of the item as digest of its status, or `None` if this item is + /// created from the directory walk and is *not untracked*, or if it is merely to communicate + /// a needed update to the index entry. + pub fn summary(&self) -> Option { + use gix_status::index_as_worktree_with_renames::Summary::*; + Some(match self { + Item::Modification { status, .. } => match status { + EntryStatus::Conflict(_) => Conflict, + EntryStatus::Change(change) => match change { + Change::Removed => Removed, + Change::Type => TypeChange, + Change::Modification { .. } | Change::SubmoduleModification(_) => Modified, + }, + EntryStatus::NeedsUpdate(_) => return None, + EntryStatus::IntentToAdd => IntentToAdd, + }, + Item::DirectoryContents { entry, .. } => { + if matches!(entry.status, gix_dir::entry::Status::Untracked) { + Added + } else { + return None; + } + } + Item::Rewrite { copy, .. } => { + if *copy { + Copied + } else { + Renamed + } + } + }) + } + } + + impl<'index> From> for Item { + fn from(value: gix_status::index_as_worktree_with_renames::Entry<'index, (), SubmoduleStatus>) -> Self { + match value { + gix_status::index_as_worktree_with_renames::Entry::Modification { + entries: _, + entry, + entry_index, + rela_path, + status, + } => Item::Modification { + entry: entry.clone(), + entry_index, + rela_path: rela_path.to_owned(), + status, + }, + gix_status::index_as_worktree_with_renames::Entry::DirectoryContents { + entry, + collapsed_directory_status, + } => Item::DirectoryContents { + entry, + collapsed_directory_status, + }, + gix_status::index_as_worktree_with_renames::Entry::Rewrite { + source, + dirwalk_entry, + dirwalk_entry_collapsed_directory_status, + dirwalk_entry_id, + diff, + copy, + } => Item::Rewrite { + source: source.into(), + dirwalk_entry, + dirwalk_entry_collapsed_directory_status, + dirwalk_entry_id, + diff, + copy, + }, + } + } + } + + type SubmoduleStatus = crate::submodule::Status; + + /// The error returned by [Platform::into_index_worktree_iter()](crate::status::Platform::into_index_worktree_iter()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Index(#[from] crate::worktree::open_index::Error), + #[error("Failed to spawn producer thread")] + #[cfg(feature = "parallel")] + SpawnThread(#[source] std::io::Error), + #[error(transparent)] + #[cfg(not(feature = "parallel"))] + IndexWorktreeStatus(#[from] crate::status::index_worktree::Error), + #[error(transparent)] + ConfigSkipHash(#[from] crate::config::boolean::Error), + #[error(transparent)] + PrepareSubmodules(#[from] crate::submodule::modules::Error), + } + + /// Lifecycle + impl<'repo, Progress> Platform<'repo, Progress> + where + Progress: gix_features::progress::Progress, + { + /// Turn the platform into an iterator for changes between the index and the working tree. + /// + /// * `patterns` + /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered. + pub fn into_index_worktree_iter(self, patterns: Vec) -> Result { + let index = match self.index { + None => IndexPersistedOrInMemory::Persisted(self.repo.index_or_empty()?), + Some(index) => index, + }; + + let skip_hash = self + .repo + .config + .resolved + .boolean("index", None, "skipHash") + .map(|res| crate::config::tree::Index::SKIP_HASH.enrich_error(res)) + .transpose() + .with_lenient_default(self.repo.config.lenient_config)? + .unwrap_or_default(); + let should_interrupt = self.should_interrupt.clone().unwrap_or_default(); + let submodule = BuiltinSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?; + #[cfg(feature = "parallel")] + { + let (tx, rx) = std::sync::mpsc::channel(); + let mut collect = Collect { tx }; + let join = std::thread::Builder::new() + .name("gix::status::index_worktree::iter::producer".into()) + .spawn({ + let repo = self.repo.clone().into_sync(); + let options = self.index_worktree_options; + let should_interrupt = should_interrupt.clone(); + let mut progress = self.progress; + move || -> Result<_, crate::status::index_worktree::Error> { + let repo = repo.to_thread_local(); + let out = repo.index_worktree_status( + &index, + patterns, + &mut collect, + gix_status::index_as_worktree::traits::FastEq, + submodule, + &mut progress, + &should_interrupt, + options, + )?; + Ok(Outcome { + index_worktree: out, + index, + changes: None, + skip_hash, + }) + } + }) + .map_err(Error::SpawnThread)?; + + Ok(super::Iter { + rx_and_join: Some((rx, join)), + should_interrupt, + changes: Vec::new(), + out: None, + }) + } + #[cfg(not(feature = "parallel"))] + { + let mut collect = Collect { items: Vec::new() }; + + let repo = self.repo.clone().into_sync(); + let options = self.index_worktree_options; + let mut progress = self.progress; + let repo = repo.to_thread_local(); + let out = repo.index_worktree_status( + &index, + patterns, + &mut collect, + gix_status::index_as_worktree::traits::FastEq, + submodule, + &mut progress, + &should_interrupt, + options, + )?; + let mut out = Outcome { + index_worktree: out, + index, + changes: None, + skip_hash, + }; + let mut iter = super::Iter { + items: Vec::new().into_iter(), + changes: Vec::new(), + out: None, + }; + let items = collect + .items + .into_iter() + .filter_map(|item| iter.maybe_keep_index_change(item)) + .collect::>(); + out.changes = (!iter.changes.is_empty()).then(|| std::mem::take(&mut iter.changes)); + iter.items = items.into_iter(); + iter.out = Some(out); + Ok(iter) + } + } + } + + impl Iterator for super::Iter { + type Item = Result; + + fn next(&mut self) -> Option { + #[cfg(feature = "parallel")] + loop { + let (rx, _join) = self.rx_and_join.as_ref()?; + match rx.recv().ok() { + Some(item) => { + if let Some(item) = self.maybe_keep_index_change(item) { + break Some(Ok(item)); + } + continue; + } + None => { + let (_rx, handle) = self.rx_and_join.take()?; + break match handle.join().expect("no panic") { + Ok(mut out) => { + out.changes = Some(std::mem::take(&mut self.changes)); + self.out = Some(out); + None + } + Err(err) => Some(Err(err)), + }; + } + } + } + #[cfg(not(feature = "parallel"))] + self.items.next().map(Ok) + } + } + + impl super::Iter { + /// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed. + pub fn outcome_mut(&mut self) -> Option<&mut Outcome> { + self.out.as_mut() + } + } + + impl super::Iter { + fn maybe_keep_index_change(&mut self, item: Item) -> Option { + let change = match item { + Item::Modification { + status: gix_status::index_as_worktree::EntryStatus::NeedsUpdate(stat), + entry_index, + .. + } => (entry_index, ApplyChange::NewStat(stat)), + Item::Modification { + status: + gix_status::index_as_worktree::EntryStatus::Change( + gix_status::index_as_worktree::Change::Modification { + set_entry_stat_size_zero, + .. + }, + ), + entry_index, + .. + } if set_entry_stat_size_zero => (entry_index, ApplyChange::SetSizeToZero), + _ => return Some(item), + }; + + self.changes.push(change); + None + } + } + + #[cfg(feature = "parallel")] + impl Drop for super::Iter { + fn drop(&mut self) { + use crate::status::OwnedOrStaticAtomic; + let Some((rx, handle)) = self.rx_and_join.take() else { + return; + }; + let prev = self.should_interrupt.swap(true, std::sync::atomic::Ordering::Relaxed); + let undo = match &self.should_interrupt { + OwnedOrStaticAtomic::Shared(flag) => *flag, + OwnedOrStaticAtomic::Owned { flag, private: false } => flag.as_ref(), + OwnedOrStaticAtomic::Owned { private: true, .. } => { + // Leak the handle to let it shut down in the background, so drop returns more quickly. + drop((rx, handle)); + return; + } + }; + // Wait until there is time to respond before we undo the change. + handle.join().ok(); + undo.fetch_update( + std::sync::atomic::Ordering::SeqCst, + std::sync::atomic::Ordering::SeqCst, + |current| current.then_some(prev), + ) + .ok(); + } + } + + struct Collect { + #[cfg(feature = "parallel")] + tx: std::sync::mpsc::Sender, + #[cfg(not(feature = "parallel"))] + items: Vec, + } + + impl<'index> gix_status::index_as_worktree_with_renames::VisitEntry<'index> for Collect { + type ContentChange = ::Output; + type SubmoduleStatus = + ::Output; + + fn visit_entry( + &mut self, + entry: gix_status::index_as_worktree_with_renames::Entry< + 'index, + Self::ContentChange, + Self::SubmoduleStatus, + >, + ) { + // NOTE: we assume that the receiver triggers interruption so the operation will stop if the receiver is down. + #[cfg(feature = "parallel")] + self.tx.send(entry.into()).ok(); + #[cfg(not(feature = "parallel"))] + self.items.push(entry.into()); + } + } +} diff --git a/gix/src/status/mod.rs b/gix/src/status/mod.rs new file mode 100644 index 00000000000..3a565f0ef13 --- /dev/null +++ b/gix/src/status/mod.rs @@ -0,0 +1,212 @@ +use crate::config::cache::util::ApplyLeniencyDefault; +use crate::{config, Repository}; +pub use gix_status as plumbing; +use std::ops::Deref; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +/// A structure to hold options configuring the status request, which can then be turned into an iterator. +pub struct Platform<'repo, Progress> +where + Progress: gix_features::progress::Progress + 'static, +{ + repo: &'repo Repository, + progress: Progress, + index: Option, + submodules: Submodule, + index_worktree_options: index_worktree::Options, + should_interrupt: Option, +} + +#[derive(Clone)] +enum OwnedOrStaticAtomic { + Owned { + flag: Arc, + #[cfg_attr(not(feature = "parallel"), allow(dead_code))] + private: bool, + }, + Shared(&'static AtomicBool), +} + +impl Default for OwnedOrStaticAtomic { + fn default() -> Self { + OwnedOrStaticAtomic::Owned { + flag: Arc::new(AtomicBool::default()), + private: true, + } + } +} + +impl Deref for OwnedOrStaticAtomic { + type Target = std::sync::atomic::AtomicBool; + + fn deref(&self) -> &Self::Target { + match self { + OwnedOrStaticAtomic::Owned { flag, .. } => flag, + OwnedOrStaticAtomic::Shared(flag) => flag, + } + } +} + +/// How to obtain a submodule's status. +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum Submodule { + /// Use the ['ignore' value](crate::Submodule::ignore) to determine which submodules + /// participate in the status query, and to which extent. + AsConfigured { + /// If `true`, default `false`, the computation will stop once the first in a ladder operations + /// ordered from cheap to expensive shows that the submodule is dirty. + /// Thus, submodules that are clean will still impose the complete set of computation, as configured. + check_dirty: bool, + }, + /// Instead of the configuration, use the given ['ignore' value](crate::submodule::config::Ignore). + /// This makes it possible to fine-tune the amount of work invested in this status, while allowing + /// to turn off all submodule status information. + Given { + /// The portion of the submodule status to ignore. + ignore: crate::submodule::config::Ignore, + /// If `true`, default `false`, the computation will stop once the first in a ladder operations + /// ordered from cheap to expensive shows that the submodule is dirty. + /// Thus, submodules that are clean will still impose the complete set of computation, as given. + check_dirty: bool, + }, +} + +/// How untracked files should be handled. +#[derive(Default, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum UntrackedFiles { + /// Do not show any untracked files. + /// + /// This can mean no directory walk is performed. + None, + /// If possible, collapse files into their parent folders to reduce the amount of + /// emitted untracked files. + #[default] + Collapsed, + /// Show each individual untracked file or directory (if empty directories are emitted) that the dirwalk encountered . + Files, +} + +impl Default for Submodule { + fn default() -> Self { + Submodule::AsConfigured { check_dirty: false } + } +} + +/// The error returned by [status()](Repository::status). +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + DirwalkOptions(#[from] config::boolean::Error), + #[error(transparent)] + ConfigureUntrackedFiles(#[from] config::key::GenericErrorWithValue), +} + +/// Status +impl Repository { + /// Obtain a platform for configuring iterators for traversing git repository status information. + /// + /// By default, this is set to the fastest and most immediate way of obtaining a status, + /// which is most similar to + /// + /// `git status --ignored=no` + /// + /// which implies that submodule information is provided by default. + /// + /// Note that `status.showUntrackedFiles` is respected, which leads to untracked files being + /// collapsed by default. If that needs to be controlled, + /// [configure the directory walk explicitly](Platform::dirwalk_options) or more [implicitly](Platform::untracked_files). + /// + /// Pass `progress` to receive progress information on file modifications on this repository. + /// Use [`progress::Discard`](crate::progress::Discard) to discard all progress information. + /// + /// ### Deviation + /// + /// Whereas Git runs the index-modified check before the directory walk to set entries + /// as up-to-date to (potentially) safe some disk-access, we run both in parallel which + /// ultimately is much faster. + pub fn status

(&self, progress: P) -> Result, Error> + where + P: gix_features::progress::Progress + 'static, + { + let platform = Platform { + repo: self, + progress, + index: None, + submodules: Submodule::default(), + should_interrupt: None, + index_worktree_options: index_worktree::Options { + sorting: None, + dirwalk_options: Some(self.dirwalk_options()?), + rewrites: None, + thread_limit: None, + }, + }; + + let untracked = self + .config + .resolved + .string("status", None, "showUntrackedFiles") + .map(|value| { + config::tree::Status::SHOW_UNTRACKED_FILES + .try_into_show_untracked_files(value) + .with_lenient_default(self.config.lenient_config) + }) + .transpose()? + .unwrap_or_default(); + Ok(platform.untracked_files(untracked)) + } +} + +/// +#[allow(clippy::empty_docs)] +pub mod is_dirty { + use crate::Repository; + + /// The error returned by [Repository::is_dirty()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + StatusPlatform(#[from] crate::status::Error), + #[error(transparent)] + CreateStatusIterator(#[from] crate::status::index_worktree::iter::Error), + } + + impl Repository { + /// Returns `true` if the repository is dirty. + /// This means it's changed in one of the following ways: + /// + /// * the index was changed in comparison to its working tree + /// * the working tree was changed in comparison to the index + /// * submodules are taken in consideration, along with their `ignore` and `isActive` configuration + /// + /// Note that *untracked files* do *not* affect this flag. + /// + /// ### Incomplete Implementation Warning + /// + /// Currently, this does not compute changes between the head and the index. + // TODO: use iterator which also tests for head->index changes. + pub fn is_dirty(&self) -> Result { + let is_dirty = self + .status(gix_features::progress::Discard)? + .index_worktree_rewrites(None) + .index_worktree_submodules(crate::status::Submodule::AsConfigured { check_dirty: true }) + .index_worktree_options_mut(|opts| { + opts.dirwalk_options = None; + }) + .into_index_worktree_iter(Vec::new())? + .take_while(Result::is_ok) + .next() + .is_some(); + Ok(is_dirty) + } + } +} + +mod platform; + +/// +#[allow(clippy::empty_docs)] +pub mod index_worktree; diff --git a/gix/src/status/platform.rs b/gix/src/status/platform.rs new file mode 100644 index 00000000000..318db07435b --- /dev/null +++ b/gix/src/status/platform.rs @@ -0,0 +1,110 @@ +use crate::status::{index_worktree, OwnedOrStaticAtomic, Platform, Submodule, UntrackedFiles}; +use std::sync::atomic::AtomicBool; + +/// Builder +impl<'repo, Progress> Platform<'repo, Progress> +where + Progress: gix_features::progress::Progress, +{ + /// Call `cb` on dirwalk options if these are set (which is the default when created through [`Repository::status()`](crate::Repository::status())). + /// The directory walk is used to find untracked files or ignored files. + /// + /// `cb` will be able to run builder-methods on the passed dirwalk options. + pub fn dirwalk_options(mut self, cb: impl FnOnce(crate::dirwalk::Options) -> crate::dirwalk::Options) -> Self { + if let Some(opts) = self.index_worktree_options.dirwalk_options.take() { + self.index_worktree_options.dirwalk_options = Some(cb(opts)); + } + self + } + /// Like [dirwalk_options()](Self::dirwalk_options), but taking a mutable instance instead. + pub fn dirwalk_options_mut(&mut self, cb: impl FnOnce(&mut crate::dirwalk::Options)) -> &mut Self { + if let Some(opts) = self.index_worktree_options.dirwalk_options.as_mut() { + cb(opts); + } + self + } + /// A simple way to explicitly set the desired way of listing `untracked_files`, overriding any value + /// set by the git configuration. + /// + /// Note that if [`None`](UntrackedFiles::None) is used, the directory walk will be disabled entirely + /// after this call. Further, if no dirwalk options are present anymore, this call has no effect. + pub fn untracked_files(mut self, untracked_files: UntrackedFiles) -> Self { + let mode = match untracked_files { + UntrackedFiles::None => { + self.index_worktree_options.dirwalk_options.take(); + return self; + } + UntrackedFiles::Collapsed => gix_dir::walk::EmissionMode::CollapseDirectory, + UntrackedFiles::Files => gix_dir::walk::EmissionMode::Matching, + }; + self.dirwalk_options(|cb| cb.emit_untracked(mode)) + } + + /// Set the interrupt flag to `should_interrupt`, which typically is an application-wide flag + /// that is ultimately controlled by user interrupts. + /// + /// If it is `true`, the iteration will stop immediately. + pub fn should_interrupt_shared(mut self, should_interrupt: &'static AtomicBool) -> Self { + self.should_interrupt = Some(OwnedOrStaticAtomic::Shared(should_interrupt)); + self + } + + /// Set the interrupt flag to `should_interrupt`, as controlled by the caller. + /// + /// If it is `true`, the iteration will stop immediately. + pub fn should_interrupt_owned(mut self, should_interrupt: std::sync::Arc) -> Self { + self.should_interrupt = Some(OwnedOrStaticAtomic::Owned { + flag: should_interrupt, + private: false, + }); + self + } + + /// Configure how the `submodule_status` is obtained when looking at submodules that are still mentioned in the index. + // If `None` is given, no submodule status check is performed. + pub fn index_worktree_submodules(mut self, submodules: impl Into>) -> Self { + let submodules = submodules.into(); + self.submodules = match submodules { + None => Submodule::Given { + ignore: crate::submodule::config::Ignore::All, + check_dirty: false, + }, + Some(status) => status, + }; + self + } + + /// Set the `index` to use when making comparisons to the worktree and the head revision. + /// + /// Defaults to the current index, or an empty one if it doesn't exist (yet). + pub fn index(mut self, index: crate::worktree::IndexPersistedOrInMemory) -> Self { + self.index = Some(index); + self + } + + /// Configure the index-to-worktree rename tracking with `rewrites`, which is `None` by default. + /// + /// Note that Git does not have configuration related to rename tracking of changes between the index + /// and the worktree. The closest there is can be obtained using [`crate::diff::new_rewrites()`], which refers + /// to rename tracking between trees. + /// + /// Also note that if `rewrites` are `Some()`, [`sorting`](index_worktree::Options::sorting) will automatically be + /// configured to assure deterministic outcomes for rewrite solutions. + pub fn index_worktree_rewrites(mut self, rewrites: impl Into>) -> Self { + let rewrites = rewrites.into(); + self.index_worktree_options.rewrites = rewrites; + if rewrites.is_some() && self.index_worktree_options.sorting.is_none() { + self.index_worktree_options.sorting = + Some(gix_status::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive); + } + self + } + + /// Adjust all options related to the index-worktree status. + /// This is a catch-all in case there are no more specific methods that could be used instead to change + /// the respective option. + pub fn index_worktree_options_mut(mut self, cb: impl FnOnce(&mut index_worktree::Options)) -> Self { + cb(&mut self.index_worktree_options); + self + } +} diff --git a/gix/src/submodule/errors.rs b/gix/src/submodule/errors.rs index 4e41337de45..a27b02a7541 100644 --- a/gix/src/submodule/errors.rs +++ b/gix/src/submodule/errors.rs @@ -1,4 +1,5 @@ /// +#[allow(clippy::empty_docs)] pub mod open_modules_file { /// The error returned by [Repository::open_modules_file()](crate::Repository::open_modules_file()). #[derive(Debug, thiserror::Error)] @@ -12,6 +13,7 @@ pub mod open_modules_file { } /// +#[allow(clippy::empty_docs)] pub mod modules { /// The error returned by [Repository::modules()](crate::Repository::modules()). #[derive(Debug, thiserror::Error)] @@ -31,6 +33,7 @@ pub mod modules { } /// +#[allow(clippy::empty_docs)] pub mod is_active { /// The error returned by [Submodule::is_active()](crate::Submodule::is_active()). #[derive(Debug, thiserror::Error)] @@ -50,6 +53,7 @@ pub mod is_active { } /// +#[allow(clippy::empty_docs)] pub mod fetch_recurse { /// The error returned by [Submodule::fetch_recurse()](crate::Submodule::fetch_recurse()). #[derive(Debug, thiserror::Error)] @@ -63,6 +67,7 @@ pub mod fetch_recurse { } /// +#[allow(clippy::empty_docs)] pub mod open { /// The error returned by [Submodule::open()](crate::Submodule::open()). #[derive(Debug, thiserror::Error)] @@ -76,6 +81,7 @@ pub mod open { } /// +#[allow(clippy::empty_docs)] pub mod index_id { /// The error returned by [Submodule::index_id()](crate::Submodule::index_id()). #[derive(Debug, thiserror::Error)] @@ -89,6 +95,7 @@ pub mod index_id { } /// +#[allow(clippy::empty_docs)] pub mod head_id { /// The error returned by [Submodule::head_id()](crate::Submodule::head_id()). #[derive(Debug, thiserror::Error)] diff --git a/gix/src/submodule/mod.rs b/gix/src/submodule/mod.rs index fcfffd26f16..d71a21b9c86 100644 --- a/gix/src/submodule/mod.rs +++ b/gix/src/submodule/mod.rs @@ -9,7 +9,7 @@ use std::{ pub use gix_submodule::*; -use crate::{bstr::BStr, repository::IndexPersistedOrInMemory, Repository, Submodule}; +use crate::{bstr::BStr, worktree::IndexPersistedOrInMemory, Repository, Submodule}; pub(crate) type ModulesFileStorage = gix_features::threading::OwnShared>; /// A lazily loaded and auto-updated worktree index. @@ -96,16 +96,22 @@ impl<'repo> Submodule<'repo> { } /// Return the url from which to clone or update the submodule. + /// + /// This method takes into consideration submodule configuration overrides. pub fn url(&self) -> Result { self.state.modules.url(self.name()) } /// Return the `update` field from this submodule's configuration, if present, or `None`. + /// + /// This method takes into consideration submodule configuration overrides. pub fn update(&self) -> Result, config::update::Error> { self.state.modules.update(self.name()) } /// Return the `branch` field from this submodule's configuration, if present, or `None`. + /// + /// This method takes into consideration submodule configuration overrides. pub fn branch(&self) -> Result, config::branch::Error> { self.state.modules.branch(self.name()) } @@ -126,6 +132,8 @@ impl<'repo> Submodule<'repo> { } /// Return the `ignore` field from this submodule's configuration, if present, or `None`. + /// + /// This method takes into consideration submodule configuration overrides. pub fn ignore(&self) -> Result, config::Error> { self.state.modules.ignore(self.name()) } @@ -267,6 +275,183 @@ impl<'repo> Submodule<'repo> { } } +/// +#[allow(clippy::empty_docs)] +#[cfg(feature = "status")] +pub mod status { + use super::{head_id, index_id, open, Status}; + use crate::Submodule; + use gix_submodule::config; + + /// The error returned by [Submodule::status()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + State(#[from] config::path::Error), + #[error(transparent)] + HeadId(#[from] head_id::Error), + #[error(transparent)] + IndexId(#[from] index_id::Error), + #[error(transparent)] + OpenRepository(#[from] open::Error), + #[error(transparent)] + IgnoreConfiguration(#[from] config::Error), + #[error(transparent)] + StatusPlatform(#[from] crate::status::Error), + #[error(transparent)] + Status(#[from] crate::status::index_worktree::iter::Error), + #[error(transparent)] + IndexWorktreeStatus(#[from] crate::status::index_worktree::Error), + } + + impl<'repo> Submodule<'repo> { + /// Return the status of the submodule. + /// + /// Use `ignore` to control the portion of the submodule status to ignore. It can be obtained from + /// submodule configuration using the [`ignore()`](Submodule::ignore()) method. + /// If `check_dirty` is `true`, the computation will stop once the first in a ladder operations + /// ordered from cheap to expensive shows that the submodule is dirty. + /// Thus, submodules that are clean will still impose the complete set of computation, as given. + #[doc(alias = "submodule_status", alias = "git2")] + pub fn status( + &self, + ignore: config::Ignore, + check_dirty: bool, + ) -> Result { + self.status_opts(ignore, check_dirty, &mut |s| s) + } + /// Return the status of the submodule, just like [`status`](Self::status), but allows to adjust options + /// for more control over how the status is performed. + /// + /// Use `&mut std::convert::identity` for `adjust_options` if no specific options are desired. + /// A reason to change them might be to enable sorting to enjoy deterministic order of changes. + /// + /// The status allows to easily determine if a submodule [has changes](Status::is_dirty). + /// + /// ### Incomplete Implementation Warning + /// + /// Currently, changes between the head and the index aren't computed. + // TODO: Run the full status, including tree->index once available. + #[doc(alias = "submodule_status", alias = "git2")] + pub fn status_opts( + &self, + ignore: config::Ignore, + check_dirty: bool, + adjust_options: &mut dyn for<'a> FnMut( + crate::status::Platform<'a, gix_features::progress::Discard>, + ) + -> crate::status::Platform<'a, gix_features::progress::Discard>, + ) -> Result { + let mut state = self.state()?; + if ignore == config::Ignore::All { + return Ok(Status { + state, + ..Default::default() + }); + } + + let index_id = self.index_id()?; + if !state.repository_exists { + return Ok(Status { + state, + index_id, + ..Default::default() + }); + } + let sm_repo = match self.open()? { + None => { + state.repository_exists = false; + return Ok(Status { + state, + index_id, + ..Default::default() + }); + } + Some(repo) => repo, + }; + + let checked_out_head_id = sm_repo.head_id().ok().map(crate::Id::detach); + let mut status = Status { + state, + index_id, + checked_out_head_id, + ..Default::default() + }; + if ignore == config::Ignore::Dirty || check_dirty && status.is_dirty() == Some(true) { + return Ok(status); + } + + if !state.worktree_checkout { + return Ok(status); + } + let statusses = adjust_options(sm_repo.status(gix_features::progress::Discard)?) + .index_worktree_options_mut(|opts| { + if ignore == config::Ignore::Untracked { + opts.dirwalk_options = None; + } + }) + .into_index_worktree_iter(Vec::new())?; + let mut changes = Vec::new(); + for change in statusses { + changes.push(change?); + } + status.changes = Some(changes); + Ok(status) + } + } + + impl Status { + /// Return `Some(true)` if the submodule status could be determined sufficiently and + /// if there are changes that would render this submodule dirty. + /// + /// Return `Some(false)` if the submodule status could be determined and it has no changes + /// at all. + /// + /// Return `None` if the repository clone or the worktree are missing entirely, which would leave + /// it to the caller to determine if that's considered dirty or not. + pub fn is_dirty(&self) -> Option { + if !self.state.worktree_checkout || !self.state.repository_exists { + return None; + } + let is_dirty = + self.checked_out_head_id != self.index_id || self.changes.as_ref().map_or(false, |c| !c.is_empty()); + Some(is_dirty) + } + } + + pub(super) mod types { + use crate::submodule::State; + + /// A simplified status of the Submodule. + /// + /// As opposed to the similar-sounding [`State`], it is more exhaustive and potentially expensive to compute, + /// particularly for submodules without changes. + /// + /// It's produced by [Submodule::status()](crate::Submodule::status()). + #[derive(Default, Clone, PartialEq, Debug)] + pub struct Status { + /// The cheapest part of the status that is always performed, to learn if the repository is cloned + /// and if there is a worktree checkout. + pub state: State, + /// The commit at which the submodule is supposed to be according to the super-project's index. + /// `None` means the computation wasn't performed, or the submodule didn't exist in the super-project's index anymore. + pub index_id: Option, + /// The commit-id of the `HEAD` at which the submodule is currently checked out. + /// `None` if the computation wasn't performed as it was skipped early, or if no repository was available or + /// if the HEAD could not be obtained or wasn't born. + pub checked_out_head_id: Option, + /// The set of changes obtained from running something akin to `git status` in the submodule working tree. + /// + /// `None` if the computation wasn't performed as the computation was skipped early, or if no working tree was + /// available or repository was available. + pub changes: Option>, + } + } +} +#[cfg(feature = "status")] +pub use status::types::Status; + /// A summary of the state of all parts forming a submodule, which allows to answer various questions about it. /// /// Note that expensive questions about its presence in the `HEAD` or the `index` are left to the caller. diff --git a/gix/src/tag.rs b/gix/src/tag.rs index 84af3b43a7c..b22af2334cf 100644 --- a/gix/src/tag.rs +++ b/gix/src/tag.rs @@ -1,4 +1,5 @@ //! +#![allow(clippy::empty_docs)] mod error { /// The error returned by [`tag(…)`][crate::Repository::tag()]. diff --git a/gix/src/worktree/mod.rs b/gix/src/worktree/mod.rs index f0a0c44a780..87be9ef32b4 100644 --- a/gix/src/worktree/mod.rs +++ b/gix/src/worktree/mod.rs @@ -20,6 +20,17 @@ pub(crate) type IndexStorage = gix_features::threading::OwnShared; +/// A type to represent an index which either was loaded from disk as it was persisted there, or created on the fly in memory. +#[cfg(feature = "index")] +pub enum IndexPersistedOrInMemory { + /// The index as loaded from disk, and shared across clones of the owning `Repository`. + Persisted(crate::worktree::Index), + /// A temporary index as created from the `HEAD^{tree}`, with the file path set to the place where it would be stored naturally. + /// + /// Note that unless saved explicitly, it will not persist. + InMemory(gix_index::File), +} + /// A stand-in to a worktree as result of a worktree iteration. /// /// It provides access to typical worktree state, but may not actually point to a valid checkout as the latter has been moved or @@ -83,6 +94,7 @@ pub(crate) fn id(git_dir: &std::path::Path, has_common_dir: bool) -> Option<&BSt } /// +#[allow(clippy::empty_docs)] pub mod proxy; /// diff --git a/gix/tests/commit/mod.rs b/gix/tests/commit/mod.rs index fd2440bf8ce..3daf74852db 100644 --- a/gix/tests/commit/mod.rs +++ b/gix/tests/commit/mod.rs @@ -4,6 +4,48 @@ mod describe { use crate::named_repo; + #[cfg(feature = "status")] + mod with_dirty_suffix { + use crate::util::named_subrepo_opts; + use gix::commit::describe::SelectRef; + + #[test] + fn dirty_suffix_applies_automatically_if_dirty() -> crate::Result { + let repo = named_subrepo_opts( + "make_submodules.sh", + "submodule-head-changed", + gix::open::Options::isolated(), + )?; + + let actual = repo + .head_commit()? + .describe() + .names(SelectRef::AllRefs) + .try_resolve()? + .expect("resolution") + .format_with_dirty_suffix("dirty".to_owned())? + .to_string(); + assert_eq!(actual, "main-dirty"); + Ok(()) + } + + #[test] + fn dirty_suffix_does_not_apply_if_not_dirty() -> crate::Result { + let repo = named_subrepo_opts("make_submodules.sh", "module1", gix::open::Options::isolated())?; + + let actual = repo + .head_commit()? + .describe() + .names(SelectRef::AllRefs) + .try_resolve()? + .expect("resolution") + .format_with_dirty_suffix("dirty".to_owned())? + .to_string(); + assert_eq!(actual, "main"); + Ok(()) + } + } + #[test] fn tags_are_sorted_by_date_and_lexicographically() -> crate::Result { let repo = named_repo("make_commit_describe_multiple_tags.sh")?; diff --git a/gix/tests/config/tree.rs b/gix/tests/config/tree.rs index e8ab78d3643..8305fb20698 100644 --- a/gix/tests/config/tree.rs +++ b/gix/tests/config/tree.rs @@ -145,6 +145,37 @@ mod ssh { } } +#[cfg(feature = "status")] +mod status { + use crate::config::tree::bcow; + use gix::config::tree::Status; + use gix::status::UntrackedFiles; + + #[test] + fn default() -> crate::Result { + for (actual, expected) in [ + ("no", UntrackedFiles::None), + ("normal", UntrackedFiles::Collapsed), + ("all", UntrackedFiles::Files), + ] { + assert_eq!( + Status::SHOW_UNTRACKED_FILES.try_into_show_untracked_files(bcow(actual))?, + expected + ); + } + + assert_eq!( + Status::SHOW_UNTRACKED_FILES + .try_into_show_untracked_files(bcow("NO")) + .unwrap_err() + .to_string(), + "The key \"status.showUntrackedFiles=NO\" was invalid", + "case-sensitive comparisons" + ); + Ok(()) + } +} + mod push { use crate::config::tree::bcow; use gix::config::tree::Push; diff --git a/gix/tests/diff/mod.rs b/gix/tests/diff/mod.rs index 2fa681ae981..2b9fa79401b 100644 --- a/gix/tests/diff/mod.rs +++ b/gix/tests/diff/mod.rs @@ -5,11 +5,12 @@ use crate::util::named_repo; #[test] fn resource_cache() -> crate::Result { let repo = named_repo("make_diff_repo.sh")?; + let index = repo.index()?; let cache = gix::diff::resource_cache( &repo, - &*repo.index()?, gix::diff::blob::pipeline::Mode::ToWorktreeAndBinaryToText, - gix_worktree::stack::state::attributes::Source::IdMapping, + repo.attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)? + .detach(), Default::default(), )?; assert_eq!( diff --git a/gix/tests/fixtures/generated-archives/make_status_repos.tar.xz b/gix/tests/fixtures/generated-archives/make_status_repos.tar.xz new file mode 100644 index 00000000000..102ad7dcc72 Binary files /dev/null and b/gix/tests/fixtures/generated-archives/make_status_repos.tar.xz differ diff --git a/gix/tests/fixtures/generated-archives/make_submodules.tar.xz b/gix/tests/fixtures/generated-archives/make_submodules.tar.xz index 18cca667581..ac4c0109ac8 100644 Binary files a/gix/tests/fixtures/generated-archives/make_submodules.tar.xz and b/gix/tests/fixtures/generated-archives/make_submodules.tar.xz differ diff --git a/gix/tests/fixtures/make_status_repos.sh b/gix/tests/fixtures/make_status_repos.sh new file mode 100644 index 00000000000..68ff4a72669 --- /dev/null +++ b/gix/tests/fixtures/make_status_repos.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q untracked-only +(cd untracked-only + touch this + mkdir subdir + >subdir/that + + git add . + git commit -q -m init + + mkdir new + touch new/untracked subdir/untracked +) + diff --git a/gix/tests/fixtures/make_submodules.sh b/gix/tests/fixtures/make_submodules.sh index af5b9c96786..4533033e6fb 100755 --- a/gix/tests/fixtures/make_submodules.sh +++ b/gix/tests/fixtures/make_submodules.sh @@ -10,6 +10,62 @@ git init -q module1 git commit -q -m c1 echo hello >> this git commit -q -am c2 + touch untracked +) + +git init submodule-head-changed +(cd submodule-head-changed + git submodule add ../module1 m1 + git commit -m "add submodule" + + cd m1 && git checkout @~1 +) + +git init submodule-head-changed-no-worktree +(cd submodule-head-changed-no-worktree + git submodule add ../module1 m1 + git commit -m "add submodule" + + (cd m1 && git checkout @~1) + rm -Rf m1 && mkdir m1 +) + +git init modified-and-untracked +(cd modified-and-untracked + git submodule add ../module1 m1 + git commit -m "add submodule" + + (cd m1 + echo change >> this + touch new + ) +) + +git init submodule-head-changed-and-modified +(cd submodule-head-changed-and-modified + git submodule add ../module1 m1 + git commit -m "add submodule" + + (cd m1 + git checkout @~1 + echo change >> this + ) +) + +git init modified-untracked-and-submodule-head-changed-and-modified +(cd modified-untracked-and-submodule-head-changed-and-modified + git submodule add ../module1 m1 + git commit -m "add submodule" + + (cd m1 + git checkout @~1 + echo change >> this + ) + + touch this + git add this && git commit -m "this" + echo change >> this + touch untracked ) git init with-submodules diff --git a/gix/tests/gix.rs b/gix/tests/gix.rs index b4b71c9db7f..d47cb2ccf07 100644 --- a/gix/tests/gix.rs +++ b/gix/tests/gix.rs @@ -16,5 +16,7 @@ mod remote; mod repository; #[cfg(feature = "revision")] mod revision; +#[cfg(feature = "status")] +mod status; #[cfg(feature = "attributes")] mod submodule; diff --git a/gix/tests/reference/mod.rs b/gix/tests/reference/mod.rs index d168819eb64..7dd762f097e 100644 --- a/gix/tests/reference/mod.rs +++ b/gix/tests/reference/mod.rs @@ -18,8 +18,6 @@ mod log { } } mod find { - use std::convert::TryInto; - use gix_ref as refs; use gix_ref::{FullName, FullNameRef, Target}; diff --git a/gix/tests/remote/save.rs b/gix/tests/remote/save.rs index 44e5aca8316..69206763894 100644 --- a/gix/tests/remote/save.rs +++ b/gix/tests/remote/save.rs @@ -45,8 +45,6 @@ mod save_to { } mod save_as_to { - use std::convert::TryInto; - use crate::{basic_repo, remote::save::uniformize}; #[test] diff --git a/gix/tests/repository/config/remote.rs b/gix/tests/repository/config/remote.rs index 0bca87da755..ba1e5bdb373 100644 --- a/gix/tests/repository/config/remote.rs +++ b/gix/tests/repository/config/remote.rs @@ -1,6 +1,5 @@ use gix::bstr::BStr; use std::borrow::Cow; -use std::iter::FromIterator; use crate::remote; diff --git a/gix/tests/status/mod.rs b/gix/tests/status/mod.rs new file mode 100644 index 00000000000..51e1da8094c --- /dev/null +++ b/gix/tests/status/mod.rs @@ -0,0 +1,174 @@ +pub fn submodule_repo(name: &str) -> crate::Result { + use crate::util::named_subrepo_opts; + Ok(named_subrepo_opts( + "make_submodules.sh", + name, + gix::open::Options::isolated(), + )?) +} + +pub fn repo(name: &str) -> crate::Result { + use crate::util::named_subrepo_opts; + Ok(named_subrepo_opts( + "make_status_repos.sh", + name, + gix::open::Options::isolated(), + )?) +} + +mod index_worktree { + mod iter { + use crate::status::{repo, submodule_repo}; + use gix::status::index_worktree::iter::Item; + use pretty_assertions::assert_eq; + + #[test] + fn item_size() { + assert_eq!( + std::mem::size_of::(), + 264, + "The size is pretty huge and goes down ideally" + ); + } + + #[test] + fn submodule_modification() -> crate::Result { + let repo = submodule_repo("modified-untracked-and-submodule-head-changed-and-modified")?; + let mut status = repo + .status(gix::progress::Discard)? + .index_worktree_options_mut(|opts| { + opts.sorting = + Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive) + }) + .into_index_worktree_iter(Vec::new())?; + let items: Vec<_> = status.by_ref().filter_map(Result::ok).collect(); + assert_eq!(items.len(), 3, "1 untracked, 1 modified file, 1 submodule modification"); + Ok(()) + } + + #[test] + fn untracked_files_collapse_by_default() -> crate::Result { + let repo = repo("untracked-only")?; + let status = repo + .status(gix::progress::Discard)? + .index_worktree_options_mut(|opts| { + opts.sorting = + Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive) + }) + .into_index_worktree_iter(Vec::new())?; + let items: Vec<_> = status.filter_map(Result::ok).collect(); + assert_eq!( + items, + [ + Item::DirectoryContents { + entry: gix_dir::Entry { + rela_path: "new".into(), + status: gix_dir::entry::Status::Untracked, + property: None, + disk_kind: Some(gix_dir::entry::Kind::Directory), + index_kind: None, + pathspec_match: Some(gix_dir::entry::PathspecMatch::Always), + }, + collapsed_directory_status: None + }, + Item::DirectoryContents { + entry: gix_dir::Entry { + rela_path: "subdir/untracked".into(), + status: gix_dir::entry::Status::Untracked, + property: None, + disk_kind: Some(gix_dir::entry::Kind::File), + index_kind: None, + pathspec_match: Some(gix_dir::entry::PathspecMatch::Always), + }, + collapsed_directory_status: None + } + ], + "'new/untracked' gets collapsed, but the second untracked is in a folder with a tracked file.\ + This collapsing behaviour is the default." + ); + Ok(()) + } + + #[test] + fn untracked_files_settings_none() -> crate::Result { + let mut repo = repo("untracked-only")?; + repo.config_snapshot_mut() + .set_value(&gix::config::tree::Status::SHOW_UNTRACKED_FILES, "no")?; + + let mut status = repo + .status(gix::progress::Discard)? + .index_worktree_options_mut(|opts| { + opts.sorting = + Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive) + }) + .into_index_worktree_iter(Vec::new())?; + let items: Vec<_> = status.by_ref().filter_map(Result::ok).collect(); + assert_eq!(items, [], "no untracked files are found…"); + assert_eq!( + status.outcome_mut().expect("iteration done").index_worktree.dirwalk, + None, + "…as there was no directory walk" + ); + Ok(()) + } + + #[test] + fn early_drop_for_is_dirty_emulation() -> crate::Result { + let repo = submodule_repo("modified-untracked-and-submodule-head-changed-and-modified")?; + let is_dirty = repo + .status(gix::progress::Discard)? + .index_worktree_submodules(gix::status::Submodule::AsConfigured { check_dirty: true }) + .index_worktree_options_mut(|opts| { + opts.sorting = + Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive) + }) + .into_index_worktree_iter(Vec::new())? + .next() + .is_some(); + assert!(is_dirty, "this should abort the work as quickly as possible"); + Ok(()) + } + } +} + +mod is_dirty { + use crate::status::submodule_repo; + + #[test] + fn various_changes_positive() -> crate::Result { + let repo = submodule_repo("modified-untracked-and-submodule-head-changed-and-modified")?; + assert!(repo.is_dirty()?, "The repository has various changes"); + Ok(()) + } + + #[test] + fn submodule_changes_are_picked_up() -> crate::Result { + let repo = submodule_repo("submodule-head-changed")?; + assert!(repo.is_dirty()?, "head-changes are also discoverd"); + Ok(()) + } + + #[test] + fn untracked_files_are_excluded() -> crate::Result { + let repo = submodule_repo("module1")?; + assert_eq!( + repo.status(gix::progress::Discard)? + .into_index_worktree_iter(Vec::new())? + .count(), + 1, + "there is one untracked file" + ); + assert!( + !repo.is_dirty()?, + "untracked files aren't taken into consideration, just like `git describe` which ignores them" + ); + Ok(()) + } + + #[test] + fn no_changes() -> crate::Result { + let repo = submodule_repo("with-submodules")?; + assert!(!repo.is_dirty()?, "there are no changes"); + Ok(()) + } +} diff --git a/gix/tests/submodule/mod.rs b/gix/tests/submodule/mod.rs index 1cdd27a9458..0ff6480d88e 100644 --- a/gix/tests/submodule/mod.rs +++ b/gix/tests/submodule/mod.rs @@ -26,6 +26,7 @@ mod open { worktree_checkout: true, superproject_configuration: true, }, + Some(false), ), ( "dir/m1", @@ -35,6 +36,7 @@ mod open { worktree_checkout: true, superproject_configuration: true, }, + Some(false), ), ] as &[_], ), @@ -48,6 +50,7 @@ mod open { worktree_checkout: false, superproject_configuration: true, }, + None, )], ), ( @@ -60,12 +63,15 @@ mod open { worktree_checkout: false, superproject_configuration: true, }, + None, )], ), ] { let repo = repo(name)?; - for (sm, (name, expected)) in repo.submodules()?.expect("modules present").zip(expected) { - assert_eq!(sm.name(), name); + for (sm, (sm_name, expected, _expected_is_dirty)) in + repo.submodules()?.expect("modules present").zip(expected) + { + assert_eq!(sm.name(), sm_name); let state = sm.state()?; assert_eq!(&state, expected); @@ -87,6 +93,28 @@ mod open { "there is a way to check for indicators that a submodule worktree isn't checked out though" ) } + #[cfg(feature = "status")] + for check_dirty in [false, true] { + let status = match sm.status(gix::submodule::config::Ignore::None, check_dirty) { + Ok(status) => status, + Err(err) => { + assert_eq!( + name, "not-a-submodule", + "{name}: BUG: only one submodule is expected to fail, got '{err:?}'" + ); + continue; + } + }; + assert_eq!( + &status.state, expected, + "no matter what status configuration, the state is always obtained" + ); + assert_eq!( + status.is_dirty(), + *_expected_is_dirty, + "none of these submodules are dirty, but some aren't checked out" + ) + } } assert_eq!( repo.modules()?.expect("present").names().count(), @@ -97,6 +125,179 @@ mod open { Ok(()) } + #[cfg(feature = "status")] + mod status { + use crate::submodule::repo; + use crate::util::hex_to_id; + + #[test] + fn changed_head_compared_to_superproject_index() -> crate::Result { + let repo = repo("submodule-head-changed")?; + let sm = repo.submodules()?.into_iter().flatten().next().expect("one submodule"); + let mut status = sm.status(gix::submodule::config::Ignore::None, false)?; + assert_eq!( + status.is_dirty(), + Some(true), + "we could decide that the submodule is dirty" + ); + assert_eq!( + status.index_id, + Some(hex_to_id("e046f3e51d955840619fc7d01fbd9a469663de22")) + ); + assert_eq!( + status.checked_out_head_id, + Some(hex_to_id("362cb5539acbd3c8ca355471f97c6a68d3db0da7")), + "the checked out head was reset to something else after the superproject commit" + ); + assert_eq!( + status.changes, + Some(Vec::new()), + "the status check ran, but there were no changes" + ); + // make it easier to compare this as baseline + status.changes.take(); + + let status_with_ignore = sm.status(gix::submodule::config::Ignore::Dirty, false)?; + assert_eq!( + status_with_ignore, status, + "The lowest status that makes these changes observable" + ); + + let status_with_ignore_check_only = sm.status(gix::submodule::config::Ignore::Dirty, true)?; + assert_eq!( + status_with_ignore_check_only, status, + "dirty-check has no observable influence here yet as there no 'more expensive' changes" + ); + + let status_with_ignore = sm.status(gix::submodule::config::Ignore::All, false)?; + assert_eq!( + status_with_ignore.is_dirty(), + Some(false), + "no dirty-information is retrieved, it seems clean" + ); + assert_eq!( + status_with_ignore.index_id, None, + "to avoid false-positives, we don't retrieve the value" + ); + assert_eq!( + status_with_ignore.checked_out_head_id, None, + "this check is ignored as it requires opening a repository" + ); + Ok(()) + } + + #[test] + fn modified_and_untracked() -> crate::Result { + let repo = repo("modified-and-untracked")?; + let sm = repo.submodules()?.into_iter().flatten().next().expect("one submodule"); + + let status = sm.status(gix::submodule::config::Ignore::Dirty, false)?; + assert_eq!(status.is_dirty(), Some(false), "Dirty skips worktree changes entirely"); + + let status = sm.status_opts( + gix::submodule::config::Ignore::None, + false, + &mut |status: gix::status::Platform<'_, gix::progress::Discard>| { + status.index_worktree_options_mut(|opts| { + opts.sorting = Some(gix_status::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive); + }) + }, + )?; + assert_eq!( + status.is_dirty(), + Some(true), + "we could decide that the submodule is dirty" + ); + assert_eq!(status.index_id, status.checked_out_head_id, "the head didn't change"); + assert_eq!( + status.changes.as_ref().into_iter().flatten().count(), + 2, + "1 modified, 1 untracked" + ); + + let status_with_dirty_check = sm.status_opts( + gix::submodule::config::Ignore::None, + true, + &mut |status: gix::status::Platform<'_, gix::progress::Discard>| { + status.index_worktree_options_mut(|opts| { + opts.sorting = Some(gix_status::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive); + }) + }, + )?; + assert_eq!( + status_with_dirty_check, status, + "it cannot abort early as the only change it sees is the modification check" + ); + + let status = sm.status(gix::submodule::config::Ignore::Untracked, false)?; + assert_eq!( + status.is_dirty(), + Some(true), + "we could decide that the submodule is dirty, even though untracked files are missing" + ); + assert_eq!(status.index_id, status.checked_out_head_id, "the head didn't change"); + assert_eq!(status.changes.as_ref().into_iter().flatten().count(), 1, "1 modified"); + + Ok(()) + } + + #[test] + fn changed_head_empty_worktree() -> crate::Result { + let repo = repo("submodule-head-changed-no-worktree")?; + let sm = repo.submodules()?.into_iter().flatten().next().expect("one submodule"); + + let status = sm.status(gix::submodule::config::Ignore::None, false)?; + assert_eq!( + status.state, + gix::submodule::State { + repository_exists: true, + is_old_form: false, + worktree_checkout: false, + superproject_configuration: true, + } + ); + assert_eq!( + status.is_dirty(), + None, + "a missing worktree counts as no-dirty, even though the checked out HEAD changed. \ + Git does the same, even though as we express it as 'not determined'" + ); + assert_ne!( + status.index_id, status.checked_out_head_id, + "not considered dirty despite head mismatch" + ); + assert!( + status.changes.is_none(), + "Detailed changes are never done if there is no worktree" + ); + + Ok(()) + } + + #[test] + fn is_dirty_skips_expensive_checks() -> crate::Result { + let repo = repo("submodule-head-changed-and-modified")?; + let sm = repo.submodules()?.into_iter().flatten().next().expect("one submodule"); + + let status = sm.status(gix::submodule::config::Ignore::None, true)?; + assert_eq!( + status.changes, None, + "computation was stopped on the first detected change (the index/head)" + ); + assert_eq!( + status.index_id, + Some(hex_to_id("e046f3e51d955840619fc7d01fbd9a469663de22")), + "the index id was obtained" + ); + assert_eq!( + status.checked_out_head_id, + Some(hex_to_id("362cb5539acbd3c8ca355471f97c6a68d3db0da7")), + "the checked out head was also obtained to be able to se if it's dirty" + ); + Ok(()) + } + } + #[test] fn not_a_submodule() -> crate::Result { let repo = repo("not-a-submodule")?; diff --git a/justfile b/justfile index c43c6c94826..38294a5070f 100755 --- a/justfile +++ b/justfile @@ -92,6 +92,8 @@ check: cargo check -p gix-revision --no-default-features --features describe cargo check -p gix-mailmap --features serde cargo check -p gix-url --all-features + cargo check -p gix-status + cargo check -p gix-status --all-features cargo check -p gix-features --all-features cargo check -p gix-features --features parallel cargo check -p gix-features --features fs-walkdir-parallel diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index aeb589c584f..0f5a146d0ed 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -146,6 +146,24 @@ pub fn main() -> Result<()> { } match cmd { + Subcommands::IsClean | Subcommands::IsChanged => { + let mode = if matches!(cmd, Subcommands::IsClean) { + core::repository::dirty::Mode::IsClean + } else { + core::repository::dirty::Mode::IsDirty + }; + prepare_and_run( + "clean", + trace, + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::repository::dirty::check(repository(Mode::Lenient)?, mode, out, format) + }, + ) + } #[cfg(feature = "gitoxide-core-tools-clean")] Subcommands::Clean(crate::plumbing::options::clean::Command { debug, @@ -188,10 +206,13 @@ pub fn main() -> Result<()> { }, ), Subcommands::Status(crate::plumbing::options::status::Platform { + ignored, + format: status_format, statistics, submodules, no_write, pathspec, + index_worktree_renames, }) => prepare_and_run( "status", trace, @@ -208,31 +229,56 @@ pub fn main() -> Result<()> { err, progress, core::repository::status::Options { - format, + format: match status_format.unwrap_or_default() { + crate::plumbing::options::status::Format::Simplified => { + core::repository::status::Format::Simplified + } + crate::plumbing::options::status::Format::PorcelainV2 => { + core::repository::status::Format::PorcelainV2 + } + }, + ignored: ignored.map(|ignored| match ignored.unwrap_or_default() { + crate::plumbing::options::status::Ignored::Matching => { + core::repository::status::Ignored::Matching + } + crate::plumbing::options::status::Ignored::Collapsed => { + core::repository::status::Ignored::Collapsed + } + }), + output_format: format, statistics, thread_limit: thread_limit.or(cfg!(target_os = "macos").then_some(3)), // TODO: make this a configurable when in `gix`, this seems to be optimal on MacOS, linux scales though! MacOS also scales if reading a lot of files for refresh index allow_write: !no_write, - submodules: match submodules { + index_worktree_renames: index_worktree_renames.map(|percentage| percentage.unwrap_or(0.5)), + submodules: submodules.map(|submodules| match submodules { Submodules::All => core::repository::status::Submodules::All, Submodules::RefChange => core::repository::status::Submodules::RefChange, Submodules::Modifications => core::repository::status::Submodules::Modifications, - }, + Submodules::None => core::repository::status::Submodules::None, + }), }, ) }, ), Subcommands::Submodule(platform) => match platform .cmds - .unwrap_or(crate::plumbing::options::submodule::Subcommands::List) + .unwrap_or(crate::plumbing::options::submodule::Subcommands::List { dirty_suffix: None }) { - crate::plumbing::options::submodule::Subcommands::List => prepare_and_run( + crate::plumbing::options::submodule::Subcommands::List { dirty_suffix } => prepare_and_run( "submodule-list", trace, verbose, progress, progress_keep_open, None, - move |_progress, out, _err| core::repository::submodule::list(repository(Mode::Lenient)?, out, format), + move |_progress, out, _err| { + core::repository::submodule::list( + repository(Mode::Lenient)?, + out, + format, + dirty_suffix.map(|suffix| suffix.unwrap_or_else(|| "dirty".to_string())), + ) + }, ), }, #[cfg(feature = "gitoxide-core-tools-archive")] @@ -1036,6 +1082,7 @@ pub fn main() -> Result<()> { statistics, max_candidates, rev_spec, + dirty_suffix, } => prepare_and_run( "commit-describe", trace, @@ -1057,6 +1104,7 @@ pub fn main() -> Result<()> { statistics, max_candidates, always, + dirty_suffix: dirty_suffix.map(|suffix| suffix.unwrap_or_else(|| "dirty".to_string())), }, ) }, diff --git a/src/plumbing/options/free.rs b/src/plumbing/options/free.rs index 083dd943b34..8da1a6fffa3 100644 --- a/src/plumbing/options/free.rs +++ b/src/plumbing/options/free.rs @@ -19,6 +19,7 @@ pub enum Subcommands { } /// +#[allow(clippy::empty_docs)] pub mod commitgraph { use std::path::PathBuf; @@ -99,6 +100,7 @@ pub mod index { } /// +#[allow(clippy::empty_docs)] pub mod pack { use std::{ffi::OsString, path::PathBuf}; @@ -287,6 +289,7 @@ pub mod pack { } /// + #[allow(clippy::empty_docs)] pub mod multi_index { use std::path::PathBuf; @@ -321,6 +324,7 @@ pub mod pack { } /// + #[allow(clippy::empty_docs)] pub mod index { use std::path::PathBuf; @@ -458,6 +462,7 @@ pub mod pack { } /// +#[allow(clippy::empty_docs)] pub mod mailmap { use std::path::PathBuf; diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index 690ea56ecaa..b5fa3649a68 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -130,6 +130,8 @@ pub enum Subcommands { /// Interact with submodules. #[clap(alias = "submodules")] Submodule(submodule::Platform), + IsClean, + IsChanged, /// Show which git configuration values are used or planned. ConfigTree, Status(status::Platform), @@ -199,7 +201,7 @@ pub mod archive { } pub mod status { - use gitoxide::shared::CheckPathSpec; + use gitoxide::shared::{CheckPathSpec, ParseRenameFraction}; use gix::bstr::BString; #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] @@ -211,20 +213,54 @@ pub mod status { RefChange, /// See if there are worktree modifications compared to the index, but do not check for untracked files. Modifications, + /// Ignore all submodule changes. + None, + } + + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] + pub enum Ignored { + /// display all ignored files and directories, but collapse them if possible to simplify. + #[default] + Collapsed, + /// Show exact matches. Note that this may show directories if these are a match as well. + /// + /// Simplification will not happen in this mode. + Matching, + // TODO: figure out how to implement traditional, which right now can't be done as it requires ignored folders + // to be fully expanded. This should probably be implemented in `gix_dir` which then simply works by not + // allowing to ignore directories, naturally traversing the entire content. + } + + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] + pub enum Format { + /// A basic format that is easy to read, and useful for a first glimpse as flat list. + #[default] + Simplified, + /// Output very similar to `git status --porcelain=2`. + PorcelainV2, } #[derive(Debug, clap::Parser)] #[command(about = "compute repository status similar to `git status`")] pub struct Platform { - /// Define how to display submodule status. - #[clap(long, default_value = "all")] - pub submodules: Submodules, + /// The way status data is displayed. + #[clap(long, short = 'f')] + pub format: Option, + /// If enabled, show ignored files and directories. + #[clap(long)] + pub ignored: Option>, + /// Define how to display the submodule status. Defaults to git configuration if unset. + #[clap(long)] + pub submodules: Option, /// Print additional statistics to help understanding performance. #[clap(long, short = 's')] pub statistics: bool, /// Don't write back a changed index, which forces this operation to always be idempotent. #[clap(long)] pub no_write: bool, + /// Enable rename tracking between the index and the working tree, preventing the collapse of folders as well. + #[clap(long, value_parser = ParseRenameFraction)] + pub index_worktree_renames: Option>, /// The git path specifications to list attributes for, or unset to read from stdin one per line. #[clap(value_parser = CheckPathSpec)] pub pathspec: Vec, @@ -627,6 +663,10 @@ pub mod commit { /// If there was no way to describe the commit, fallback to using the abbreviated input revision. always: bool, + /// Set the suffix to append if the repository is dirty (not counting untracked files). + #[clap(short = 'd', long)] + dirty_suffix: Option>, + /// A specification of the revision to use, or the current `HEAD` if unset. rev_spec: Option, }, @@ -649,6 +689,7 @@ pub mod credential { } /// +#[allow(clippy::empty_docs)] pub mod commitgraph { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { @@ -808,10 +849,10 @@ pub mod index { pub mod entries { #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] pub enum Format { - /// + /// Show only minimal information, useful for first glances. #[default] Simple, - /// Use the `.tar` file format, uncompressed. + /// Show much more information that is still human-readable. Rich, } } @@ -871,9 +912,14 @@ pub mod submodule { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { /// Print all direct submodules to standard output - List, + List { + /// Set the suffix to append if the repository is dirty (not counting untracked files). + #[clap(short = 'd', long)] + dirty_suffix: Option>, + }, } } /// +#[allow(clippy::empty_docs)] pub mod free; diff --git a/src/shared.rs b/src/shared.rs index b3d159c853b..8a51aa75993 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -361,6 +361,28 @@ mod clap { } } + #[derive(Clone)] + pub struct ParseRenameFraction; + + impl TypedValueParser for ParseRenameFraction { + type Value = f32; + + fn parse_ref(&self, cmd: &Command, arg: Option<&Arg>, value: &OsStr) -> Result { + StringValueParser::new() + .try_map(|arg: String| -> Result<_, Box> { + if arg.ends_with('%') { + let val = u32::from_str(&arg[..arg.len() - 1])?; + Ok(val as f32 / 100.0) + } else { + let val = u32::from_str(&arg)?; + let num = format!("0.{val}"); + Ok(f32::from_str(&num)?) + } + }) + .parse_ref(cmd, arg, value) + } + } + #[derive(Clone)] pub struct AsTime; @@ -387,4 +409,36 @@ mod clap { } } } -pub use self::clap::{AsBString, AsHashKind, AsOutputFormat, AsPartialRefName, AsPathSpec, AsTime, CheckPathSpec}; +pub use self::clap::{ + AsBString, AsHashKind, AsOutputFormat, AsPartialRefName, AsPathSpec, AsTime, CheckPathSpec, ParseRenameFraction, +}; + +#[cfg(test)] +mod value_parser_tests { + use super::ParseRenameFraction; + use clap::Parser; + + #[test] + fn rename_fraction() { + #[derive(Debug, clap::Parser)] + pub struct Cmd { + #[clap(long, short='a', value_parser = ParseRenameFraction)] + pub arg: Option>, + } + + let c = Cmd::parse_from(["cmd", "-a"]); + assert_eq!(c.arg, Some(None), "this means we need to fill in the default"); + + let c = Cmd::parse_from(["cmd", "-a=50%"]); + assert_eq!(c.arg, Some(Some(0.5)), "percentages become a fraction"); + + let c = Cmd::parse_from(["cmd", "-a=100%"]); + assert_eq!(c.arg, Some(Some(1.0))); + + let c = Cmd::parse_from(["cmd", "-a=5"]); + assert_eq!(c.arg, Some(Some(0.5)), "another way to specify fractions"); + + let c = Cmd::parse_from(["cmd", "-a=75"]); + assert_eq!(c.arg, Some(Some(0.75))); + } +}