diff --git a/Cargo.lock b/Cargo.lock index 811a8c2cb35..9332cc33cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1737,6 +1737,7 @@ dependencies = [ "once_cell", "pretty_assertions", "serde", + "smallvec", "thiserror 2.0.12", ] diff --git a/Cargo.toml b/Cargo.toml index e868f2b8306..f16f3b46008 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -377,3 +377,7 @@ no_effect_underscore_binding = "allow" # x1 empty_docs = "allow" too_long_first_doc_paragraph = "allow" large_stack_arrays = "allow" + +# Fix one day +result_large_err = "allow" +large_enum_variant = "allow" diff --git a/examples/log.rs b/examples/log.rs index 37e3a9f7b7f..0ca592cb57d 100644 --- a/examples/log.rs +++ b/examples/log.rs @@ -150,7 +150,7 @@ fn run(args: Args) -> anyhow::Result<()> { commit_ref.author.actor().write_to(&mut buf)?; buf.into() }, - time: commit_ref.author.time.format(format::DEFAULT), + time: commit_ref.author.time()?.format(format::DEFAULT), message: commit_ref.message.to_owned(), }) }), diff --git a/gitoxide-core/src/corpus/engine.rs b/gitoxide-core/src/corpus/engine.rs index e48292246d6..6482cea9c78 100644 --- a/gitoxide-core/src/corpus/engine.rs +++ b/gitoxide-core/src/corpus/engine.rs @@ -57,8 +57,9 @@ impl Engine { let repos = self.refresh_repos(&corpus_path, corpus_id)?; self.state.progress.set_name("refresh repos".into()); self.state.progress.info(format!( - "Added or updated {} repositories under {corpus_path:?}", - repos.len() + "Added or updated {} repositories under '{corpus_path}'", + repos.len(), + corpus_path = corpus_path.display(), )); Ok(()) } @@ -316,7 +317,7 @@ impl Engine { out.push(repo); progress.inc(); } - Err(err) => progress.fail(format!("{repo_path:?}: {err:#?}")), + Err(err) => progress.fail(format!("{repo_path}: {err:#?}", repo_path = repo_path.display())), } } statement.finalize()?; diff --git a/gitoxide-core/src/hours/core.rs b/gitoxide-core/src/hours/core.rs index 58221ca638c..110fc24d983 100644 --- a/gitoxide-core/src/hours/core.rs +++ b/gitoxide-core/src/hours/core.rs @@ -17,7 +17,7 @@ const MINUTES_PER_HOUR: f32 = 60.0; pub const HOURS_PER_WORKDAY: f32 = 8.0; pub fn estimate_hours( - commits: &[(u32, gix::actor::SignatureRef<'static>)], + commits: &[(u32, super::SignatureRef<'static>)], stats: &[(u32, FileStats, LineStats)], ) -> WorkByEmail { assert!(!commits.is_empty()); @@ -31,7 +31,7 @@ pub fn estimate_hours( let mut cur = commits.next().expect("at least one commit if we are here"); for next in commits { - let change_in_minutes = (next.time.seconds.saturating_sub(cur.time.seconds)) as f32 / MINUTES_PER_HOUR; + let change_in_minutes = (next.seconds().saturating_sub(cur.seconds())) as f32 / MINUTES_PER_HOUR; if change_in_minutes < MAX_COMMIT_DIFFERENCE_IN_MINUTES { hours += change_in_minutes / MINUTES_PER_HOUR; } else { @@ -166,13 +166,11 @@ pub fn spawn_tree_delta_threads<'scope>( (true, true) => { files.modified += 1; if let Some(cache) = cache.as_mut() { - let mut diff = change.diff(cache).map_err(|err| { - std::io::Error::new(std::io::ErrorKind::Other, err) - })?; + let mut diff = change.diff(cache).map_err(std::io::Error::other)?; let mut nl = 0; - if let Some(counts) = diff.line_counts().map_err(|err| { - std::io::Error::new(std::io::ErrorKind::Other, err) - })? { + if let Some(counts) = + diff.line_counts().map_err(std::io::Error::other)? + { nl += counts.insertions as usize + counts.removals as usize; lines.added += counts.insertions as usize; lines.removed += counts.removals as usize; diff --git a/gitoxide-core/src/hours/mod.rs b/gitoxide-core/src/hours/mod.rs index b233e013d46..f8b16887073 100644 --- a/gitoxide-core/src/hours/mod.rs +++ b/gitoxide-core/src/hours/mod.rs @@ -2,7 +2,6 @@ use std::{collections::BTreeSet, io, path::Path, time::Instant}; use anyhow::bail; use gix::{ - actor, bstr::{BStr, ByteSlice}, prelude::*, progress, Count, NestedProgress, Progress, @@ -27,6 +26,18 @@ pub struct Context { pub out: W, } +pub struct SignatureRef<'a> { + name: &'a BStr, + email: &'a BStr, + time: gix::date::Time, +} + +impl SignatureRef<'_> { + fn seconds(&self) -> gix::date::SecondsSinceUnixEpoch { + self.time.seconds + } +} + /// Estimate the hours it takes to produce the content of the repository in `_working_dir_`, with `_refname_` for /// the start of the commit graph traversal. /// @@ -85,7 +96,7 @@ where out.push(( commit_idx, - actor::SignatureRef { + SignatureRef { name, email, time: author.time, @@ -97,7 +108,7 @@ where out.sort_by(|a, b| { a.1.email .cmp(b.1.email) - .then(a.1.time.seconds.cmp(&b.1.time.seconds).reverse()) + .then(a.1.seconds().cmp(&b.1.seconds()).reverse()) }); Ok(out) }); diff --git a/gitoxide-core/src/lib.rs b/gitoxide-core/src/lib.rs index a33b76a349a..8b24ab2e51e 100644 --- a/gitoxide-core/src/lib.rs +++ b/gitoxide-core/src/lib.rs @@ -30,9 +30,10 @@ #![deny(rust_2018_idioms)] #![forbid(unsafe_code)] -use anyhow::bail; use std::str::FromStr; +use anyhow::bail; + #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum OutputFormat { Human, diff --git a/gitoxide-core/src/organize.rs b/gitoxide-core/src/organize.rs index 4b7000af62d..625e328411f 100644 --- a/gitoxide-core/src/organize.rs +++ b/gitoxide-core/src/organize.rs @@ -1,5 +1,5 @@ -use std::borrow::Cow; use std::{ + borrow::Cow, ffi::OsStr, path::{Path, PathBuf}, }; @@ -138,9 +138,9 @@ fn handle( if let Some(parent_repo_path) = find_parent_repo(git_workdir) { progress.fail(format!( - "Skipping repository at {:?} as it is nested within repository {:?}", + "Skipping repository at '{}' as it is nested within repository '{}'", git_workdir.display(), - parent_repo_path + parent_repo_path.display() )); return Ok(()); } @@ -157,7 +157,7 @@ fn handle( }; if url.path.is_empty() { progress.info(format!( - "Skipping repository at {:?} whose remote does not have a path: {:?}", + "Skipping repository at '{}' whose remote does not have a path: {}", git_workdir.display(), url.to_bstring() )); diff --git a/gitoxide-core/src/pack/receive.rs b/gitoxide-core/src/pack/receive.rs index 5c717ed12db..66b64d95f24 100644 --- a/gitoxide-core/src/pack/receive.rs +++ b/gitoxide-core/src/pack/receive.rs @@ -1,10 +1,10 @@ -use crate::net; -use crate::pack::receive::protocol::fetch::negotiate; -use crate::OutputFormat; -use gix::config::tree::Key; -use gix::protocol::maybe_async; -use gix::remote::fetch::Error; -use gix::DynNestedProgress; +use std::{ + io, + path::PathBuf, + sync::{atomic::AtomicBool, Arc}, +}; + +use gix::{config::tree::Key, protocol::maybe_async, remote::fetch::Error, DynNestedProgress}; pub use gix::{ hash::ObjectId, objs::bstr::{BString, ByteSlice}, @@ -18,11 +18,8 @@ pub use gix::{ }, NestedProgress, Progress, }; -use std::{ - io, - path::PathBuf, - sync::{atomic::AtomicBool, Arc}, -}; + +use crate::{net, pack::receive::protocol::fetch::negotiate, OutputFormat}; pub const PROGRESS_RANGE: std::ops::RangeInclusive = 1..=3; pub struct Context { @@ -294,7 +291,7 @@ fn receive_pack_blocking( None::, options, ) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + .map_err(io::Error::other)?; if let Some(directory) = refs_directory.take() { write_raw_refs(refs, directory)?; diff --git a/gitoxide-core/src/query/db.rs b/gitoxide-core/src/query/db.rs index df1099e50ba..ab483aef036 100644 --- a/gitoxide-core/src/query/db.rs +++ b/gitoxide-core/src/query/db.rs @@ -19,8 +19,12 @@ pub fn create(path: impl AsRef) -> anyhow::Result match con.close() { Ok(()) => { - std::fs::remove_file(path) - .with_context(|| format!("Failed to remove incompatible database file at {path:?}"))?; + std::fs::remove_file(path).with_context(|| { + format!( + "Failed to remove incompatible database file at {path}", + path = path.display() + ) + })?; con = rusqlite::Connection::open(path)?; con.execute_batch(meta_table)?; con.execute("INSERT into meta(version) values(?)", params![VERSION])?; diff --git a/gitoxide-core/src/query/engine/command.rs b/gitoxide-core/src/query/engine/command.rs index 31246b0676d..1b020030821 100644 --- a/gitoxide-core/src/query/engine/command.rs +++ b/gitoxide-core/src/query/engine/command.rs @@ -76,7 +76,7 @@ impl query::Engine { usize, ) = row?; let id = gix::ObjectId::from(hash); - let commit_time = id.attach(&self.repo).object()?.into_commit().committer()?.time; + let commit_time = id.attach(&self.repo).object()?.into_commit().committer()?.time()?; let mode = FileMode::from_usize(mode).context("invalid file mode")?; info.push(trace_path::Info { id, diff --git a/gitoxide-core/src/repository/archive.rs b/gitoxide-core/src/repository/archive.rs index c229fe976b8..0f61b387a71 100644 --- a/gitoxide-core/src/repository/archive.rs +++ b/gitoxide-core/src/repository/archive.rs @@ -84,7 +84,7 @@ fn fetch_rev_info( Ok(match object.kind { gix::object::Kind::Commit => { let commit = object.into_commit(); - (Some(commit.committer()?.time.seconds), commit.tree_id()?.detach()) + (Some(commit.committer()?.seconds()), commit.tree_id()?.detach()) } gix::object::Kind::Tree => (None, object.id), gix::object::Kind::Tag => fetch_rev_info(object.peel_to_kind(gix::object::Kind::Commit)?)?, diff --git a/gitoxide-core/src/repository/attributes/query.rs b/gitoxide-core/src/repository/attributes/query.rs index b12421e77df..6929f4b9bbd 100644 --- a/gitoxide-core/src/repository/attributes/query.rs +++ b/gitoxide-core/src/repository/attributes/query.rs @@ -8,10 +8,10 @@ pub struct Options { } pub(crate) mod function { + use std::{borrow::Cow, io, path::Path}; + use anyhow::bail; use gix::bstr::BStr; - use std::borrow::Cow; - use std::{io, path::Path}; use crate::{ is_dir_to_mode, diff --git a/gitoxide-core/src/repository/attributes/validate_baseline.rs b/gitoxide-core/src/repository/attributes/validate_baseline.rs index 51de5d5382b..763b14a4325 100644 --- a/gitoxide-core/src/repository/attributes/validate_baseline.rs +++ b/gitoxide-core/src/repository/attributes/validate_baseline.rs @@ -44,8 +44,8 @@ pub(crate) mod function { if repo.is_bare() { writeln!( err, - "Repo {:?} is bare - disabling git-ignore baseline as `git check-ignore` needs a worktree", - repo.path() + "Repo at '{repo}' is bare - disabling git-ignore baseline as `git check-ignore` needs a worktree", + repo = repo.path().display() ) .ok(); ignore = false; diff --git a/gitoxide-core/src/repository/blame.rs b/gitoxide-core/src/repository/blame.rs index 04744515343..0d42c75f9e0 100644 --- a/gitoxide-core/src/repository/blame.rs +++ b/gitoxide-core/src/repository/blame.rs @@ -1,7 +1,7 @@ -use gix::bstr::ByteSlice; -use gix::config::tree; use std::ffi::OsStr; +use gix::{bstr::ByteSlice, config::tree}; + pub fn blame_file( mut repo: gix::Repository, file: &OsStr, diff --git a/gitoxide-core/src/repository/cat.rs b/gitoxide-core/src/repository/cat.rs index 2407b4c0a0e..72433ed2d3d 100644 --- a/gitoxide-core/src/repository/cat.rs +++ b/gitoxide-core/src/repository/cat.rs @@ -1,8 +1,7 @@ -use crate::repository::revision::resolve::{BlobFormat, TreeMode}; use anyhow::{anyhow, Context}; -use gix::diff::blob::ResourceKind; -use gix::filter::plumbing::driver::apply::Delay; -use gix::revision::Spec; +use gix::{diff::blob::ResourceKind, filter::plumbing::driver::apply::Delay, revision::Spec}; + +use crate::repository::revision::resolve::{BlobFormat, TreeMode}; pub fn display_object( repo: &gix::Repository, diff --git a/gitoxide-core/src/repository/clean.rs b/gitoxide-core/src/repository/clean.rs index 1146f4b2fb0..5534424479e 100644 --- a/gitoxide-core/src/repository/clean.rs +++ b/gitoxide-core/src/repository/clean.rs @@ -20,17 +20,23 @@ pub struct Options { pub find_untracked_repositories: FindRepository, } pub(crate) mod function { - use crate::repository::clean::{FindRepository, Options}; - use crate::OutputFormat; + use std::{borrow::Cow, path::Path}; + use anyhow::bail; - use gix::bstr::BString; - use gix::bstr::ByteSlice; - use gix::dir::entry::{Kind, Status}; - use gix::dir::walk::EmissionMode::CollapseDirectory; - use gix::dir::walk::ForDeletionMode::*; - use gix::dir::{walk, EntryRef}; - use std::borrow::Cow; - use std::path::Path; + use gix::{ + bstr::{BString, ByteSlice}, + dir::{ + entry::{Kind, Status}, + walk, + walk::{EmissionMode::CollapseDirectory, ForDeletionMode::*}, + EntryRef, + }, + }; + + use crate::{ + repository::clean::{FindRepository, Options}, + OutputFormat, + }; pub fn clean( repo: gix::Repository, diff --git a/gitoxide-core/src/repository/diff.rs b/gitoxide-core/src/repository/diff.rs index 600eaf2efff..a0396599b81 100644 --- a/gitoxide-core/src/repository/diff.rs +++ b/gitoxide-core/src/repository/diff.rs @@ -1,12 +1,16 @@ use anyhow::Context; -use gix::bstr::{BString, ByteSlice}; -use gix::diff::blob::intern::TokenSource; -use gix::diff::blob::unified_diff::{ContextSize, NewlineSeparator}; -use gix::diff::blob::UnifiedDiff; -use gix::objs::tree::EntryMode; -use gix::odb::store::RefreshMode; -use gix::prelude::ObjectIdExt; -use gix::ObjectId; +use gix::{ + bstr::{BString, ByteSlice}, + diff::blob::{ + intern::TokenSource, + unified_diff::{ContextSize, NewlineSeparator}, + UnifiedDiff, + }, + objs::tree::EntryMode, + odb::store::RefreshMode, + prelude::ObjectIdExt, + ObjectId, +}; pub fn tree( mut repo: gix::Repository, diff --git a/gitoxide-core/src/repository/dirty.rs b/gitoxide-core/src/repository/dirty.rs index 8981199d532..44d176b4f05 100644 --- a/gitoxide-core/src/repository/dirty.rs +++ b/gitoxide-core/src/repository/dirty.rs @@ -1,6 +1,7 @@ -use crate::OutputFormat; use anyhow::bail; +use crate::OutputFormat; + pub enum Mode { IsClean, IsDirty, diff --git a/gitoxide-core/src/repository/fetch.rs b/gitoxide-core/src/repository/fetch.rs index 7be5ef9d076..21198349bb3 100644 --- a/gitoxide-core/src/repository/fetch.rs +++ b/gitoxide-core/src/repository/fetch.rs @@ -169,7 +169,7 @@ pub(crate) mod function { let start = std::time::Instant::now(); progress.set_name("layout graph".into()); - progress.info(format!("writing {path:?}…")); + progress.info(format!("writing {}…", path.display())); let mut svg = SVGWriter::new(); vg.do_it(false, false, false, &mut svg); std::fs::write(path, svg.finalize().as_bytes())?; diff --git a/gitoxide-core/src/repository/index/entries.rs b/gitoxide-core/src/repository/index/entries.rs index 470fdd3bd0b..e72a46b483b 100644 --- a/gitoxide-core/src/repository/index/entries.rs +++ b/gitoxide-core/src/repository/index/entries.rs @@ -23,9 +23,9 @@ pub(crate) mod function { io::{BufWriter, Write}, }; - use gix::index::entry::Stage; use gix::{ bstr::{BStr, BString}, + index::entry::Stage, worktree::IndexPersistedOrInMemory, Repository, }; diff --git a/gitoxide-core/src/repository/mailmap.rs b/gitoxide-core/src/repository/mailmap.rs index 44fc0036155..9252bdafde0 100644 --- a/gitoxide-core/src/repository/mailmap.rs +++ b/gitoxide-core/src/repository/mailmap.rs @@ -1,7 +1,7 @@ -use anyhow::bail; -use gix::bstr::{BString, ByteSlice}; use std::io; +use anyhow::bail; +use gix::bstr::{BString, ByteSlice}; #[cfg(feature = "serde")] use gix::mailmap::Entry; diff --git a/gitoxide-core/src/repository/merge/commit.rs b/gitoxide-core/src/repository/merge/commit.rs index 6982cc2b8a7..a745962dff9 100644 --- a/gitoxide-core/src/repository/merge/commit.rs +++ b/gitoxide-core/src/repository/merge/commit.rs @@ -1,11 +1,12 @@ -use crate::OutputFormat; use anyhow::{anyhow, bail, Context}; -use gix::bstr::BString; -use gix::bstr::ByteSlice; -use gix::merge::tree::TreatAsUnresolved; -use gix::prelude::Write; +use gix::{ + bstr::{BString, ByteSlice}, + merge::tree::TreatAsUnresolved, + prelude::Write, +}; use super::tree::Options; +use crate::OutputFormat; #[allow(clippy::too_many_arguments)] pub fn commit( diff --git a/gitoxide-core/src/repository/merge/file.rs b/gitoxide-core/src/repository/merge/file.rs index 7e183c8a9e6..4fde1bbf24d 100644 --- a/gitoxide-core/src/repository/merge/file.rs +++ b/gitoxide-core/src/repository/merge/file.rs @@ -1,15 +1,19 @@ -use crate::OutputFormat; -use anyhow::{anyhow, bail, Context}; -use gix::bstr::BString; -use gix::bstr::ByteSlice; -use gix::merge::blob::builtin_driver::binary; -use gix::merge::blob::builtin_driver::text::Conflict; -use gix::merge::blob::pipeline::WorktreeRoots; -use gix::merge::blob::{Resolution, ResourceKind}; -use gix::object::tree::EntryKind; -use gix::Id; use std::path::Path; +use anyhow::{anyhow, bail, Context}; +use gix::{ + bstr::{BString, ByteSlice}, + merge::blob::{ + builtin_driver::{binary, text::Conflict}, + pipeline::WorktreeRoots, + Resolution, ResourceKind, + }, + object::tree::EntryKind, + Id, +}; + +use crate::OutputFormat; + pub fn file( repo: gix::Repository, out: &mut dyn std::io::Write, diff --git a/gitoxide-core/src/repository/merge/tree.rs b/gitoxide-core/src/repository/merge/tree.rs index 0d6467b09da..1a008208a2d 100644 --- a/gitoxide-core/src/repository/merge/tree.rs +++ b/gitoxide-core/src/repository/merge/tree.rs @@ -10,14 +10,15 @@ pub struct Options { pub(super) mod function { - use crate::OutputFormat; use anyhow::{anyhow, bail, Context}; - use gix::bstr::BString; - use gix::bstr::ByteSlice; - use gix::merge::tree::TreatAsUnresolved; - use gix::prelude::Write; + use gix::{ + bstr::{BString, ByteSlice}, + merge::tree::TreatAsUnresolved, + prelude::Write, + }; use super::Options; + use crate::OutputFormat; #[allow(clippy::too_many_arguments)] pub fn tree( diff --git a/gitoxide-core/src/repository/merge_base.rs b/gitoxide-core/src/repository/merge_base.rs index 8a0b8515c8d..b6140ec9d4b 100644 --- a/gitoxide-core/src/repository/merge_base.rs +++ b/gitoxide-core/src/repository/merge_base.rs @@ -1,6 +1,7 @@ -use crate::OutputFormat; use anyhow::bail; +use crate::OutputFormat; + pub fn merge_base( mut repo: gix::Repository, first: String, diff --git a/gitoxide-core/src/repository/odb.rs b/gitoxide-core/src/repository/odb.rs index c386450c8d6..599e6f7e42e 100644 --- a/gitoxide-core/src/repository/odb.rs +++ b/gitoxide-core/src/repository/odb.rs @@ -1,5 +1,4 @@ -use std::io; -use std::sync::atomic::Ordering; +use std::{io, sync::atomic::Ordering}; use anyhow::bail; diff --git a/gitoxide-core/src/repository/revision/list.rs b/gitoxide-core/src/repository/revision/list.rs index 1698a29d494..c47f1bc3b27 100644 --- a/gitoxide-core/src/repository/revision/list.rs +++ b/gitoxide-core/src/repository/revision/list.rs @@ -117,7 +117,7 @@ pub(crate) mod function { if let Some((mut vg, path, _)) = vg { let start = std::time::Instant::now(); progress.set_name("layout graph".into()); - progress.info(format!("writing {path:?}…")); + progress.info(format!("writing {}…", path.display())); let mut svg = SVGWriter::new(); vg.do_it(false, false, false, &mut svg); std::fs::write(&path, svg.finalize().as_bytes())?; diff --git a/gitoxide-core/src/repository/revision/resolve.rs b/gitoxide-core/src/repository/revision/resolve.rs index 60686d14830..7ef4c5bd9a2 100644 --- a/gitoxide-core/src/repository/revision/resolve.rs +++ b/gitoxide-core/src/repository/revision/resolve.rs @@ -28,9 +28,10 @@ pub(crate) mod function { use gix::revision::Spec; use super::Options; - use crate::repository::cat::display_object; - use crate::repository::revision::resolve::BlobFormat; - use crate::{repository::revision, OutputFormat}; + use crate::{ + repository::{cat::display_object, revision, revision::resolve::BlobFormat}, + OutputFormat, + }; pub fn resolve( mut repo: gix::Repository, diff --git a/gitoxide-core/src/repository/status.rs b/gitoxide-core/src/repository/status.rs index 9804dfa214c..77d94322e35 100644 --- a/gitoxide-core/src/repository/status.rs +++ b/gitoxide-core/src/repository/status.rs @@ -1,8 +1,11 @@ +use std::path::Path; + use anyhow::bail; -use gix::bstr::{BStr, BString, ByteSlice}; -use gix::status::{self, index_worktree}; +use gix::{ + bstr::{BStr, BString, ByteSlice}, + status::{self, index_worktree}, +}; use gix_status::index_as_worktree::{Change, Conflict, EntryStatus}; -use std::path::Path; use crate::OutputFormat; diff --git a/gitoxide-core/src/repository/tree.rs b/gitoxide-core/src/repository/tree.rs index fcde83f8fb1..b57e72022b9 100644 --- a/gitoxide-core/src/repository/tree.rs +++ b/gitoxide-core/src/repository/tree.rs @@ -1,17 +1,18 @@ +use std::{borrow::Cow, io, io::BufWriter}; + use anyhow::bail; use gix::Tree; -use std::io::BufWriter; -use std::{borrow::Cow, io}; use crate::OutputFormat; mod entries { + use std::collections::VecDeque; + use gix::{ bstr::{BStr, BString, ByteSlice, ByteVec}, objs::tree::EntryRef, traverse::tree::visit::Action, }; - use std::collections::VecDeque; use crate::repository::tree::format_entry; diff --git a/gitoxide-core/src/repository/worktree.rs b/gitoxide-core/src/repository/worktree.rs index 9eea9ec662e..e3a373eb80c 100644 --- a/gitoxide-core/src/repository/worktree.rs +++ b/gitoxide-core/src/repository/worktree.rs @@ -1,6 +1,7 @@ -use crate::OutputFormat; use anyhow::bail; +use crate::OutputFormat; + pub fn list(repo: gix::Repository, out: &mut dyn std::io::Write, format: OutputFormat) -> anyhow::Result<()> { if format != OutputFormat::Human { bail!("JSON output isn't implemented yet"); diff --git a/gix-actor/src/lib.rs b/gix-actor/src/lib.rs index ec182e96d1d..e5f7ac87cd0 100644 --- a/gix-actor/src/lib.rs +++ b/gix-actor/src/lib.rs @@ -6,8 +6,7 @@ doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] -#![deny(missing_docs, rust_2018_idioms)] -#![forbid(unsafe_code)] +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// The re-exported `bstr` crate. /// @@ -18,7 +17,6 @@ use bstr::{BStr, BString}; /// /// For convenience to allow using `gix-date` without adding it to own cargo manifest. pub use gix_date as date; -use gix_date::Time; mod identity; /// @@ -53,7 +51,7 @@ pub struct IdentityRef<'a> { pub email: &'a BStr, } -/// A mutable signature is created by an actor at a certain time. +/// A mutable signature that is created by an actor at a certain time. /// /// Note that this is not a cryptographical signature. #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -68,10 +66,10 @@ pub struct Signature { /// Use [SignatureRef::trim()] or trim manually to be able to clean it up. pub email: BString, /// The time stamp at which the signature is performed. - pub time: Time, + pub time: date::Time, } -/// A immutable signature is created by an actor at a certain time. +/// An immutable signature that is created by an actor at a certain time. /// /// Note that this is not a cryptographical signature. #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] @@ -86,6 +84,6 @@ pub struct SignatureRef<'a> { /// /// Use [SignatureRef::trim()] or trim manually to be able to clean it up. pub email: &'a BStr, - /// The time stamp at which the signature was performed. - pub time: gix_date::Time, + /// The timestamp at which the signature was performed. + pub time: &'a str, } diff --git a/gix-actor/src/signature/decode.rs b/gix-actor/src/signature/decode.rs index fecdfaf773b..87f5680c3ba 100644 --- a/gix-actor/src/signature/decode.rs +++ b/gix-actor/src/signature/decode.rs @@ -1,19 +1,14 @@ pub(crate) mod function { - use crate::{IdentityRef, SignatureRef}; use bstr::ByteSlice; - use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time}; - use gix_utils::btoi::to_signed; - use winnow::error::ErrMode; - use winnow::stream::Stream; use winnow::{ - combinator::{alt, opt, separated_pair, terminated}, - error::{AddContext, ParserError, StrContext}, + combinator::{opt, separated_pair}, + error::{AddContext, ErrMode, ParserError, StrContext}, prelude::*, - stream::AsChar, - token::{take, take_until, take_while}, + stream::{AsChar, Stream}, + token::take_while, }; - const SPACE: &[u8] = b" "; + use crate::{IdentityRef, SignatureRef}; /// Parse a signature from the bytes input `i` using `nom`. pub fn decode<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>( @@ -23,36 +18,17 @@ pub(crate) mod function { identity, opt(b" "), opt(( - terminated(take_until(0.., SPACE), take(1usize)) - .verify_map(|v| to_signed::(v).ok()) - .context(StrContext::Expected("".into())), - alt(( - take_while(1.., b'-').map(|_| Sign::Minus), - take_while(1.., b'+').map(|_| Sign::Plus), - )) - .context(StrContext::Expected("+|-".into())), - take_while(2, AsChar::is_dec_digit) - .verify_map(|v| to_signed::(v).ok()) - .context(StrContext::Expected("HH".into())), - take_while(1..=2, AsChar::is_dec_digit) - .verify_map(|v| to_signed::(v).ok()) - .context(StrContext::Expected("MM".into())), - take_while(0.., AsChar::is_dec_digit).map(|v: &[u8]| v), + take_while(0.., |b: u8| b == b'+' || b == b'-' || b.is_space() || b.is_dec_digit()).map(|v: &[u8]| v), )) - .map(|maybe_timestamp| { - if let Some((time, sign, hours, minutes, trailing_digits)) = maybe_timestamp { - let offset = if trailing_digits.is_empty() { - (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 } - } else { - 0 - }; - Time { - seconds: time, - offset, - sign, + .map(|maybe_bytes| { + if let Some((bytes,)) = maybe_bytes { + // SAFETY: The parser validated that there are only ASCII characters. + #[allow(unsafe_code)] + unsafe { + std::str::from_utf8_unchecked(bytes) } } else { - Time::new(0, 0) + "" } }), ) @@ -108,12 +84,10 @@ pub use function::identity; #[cfg(test)] mod tests { mod parse_signature { - use bstr::ByteSlice; - use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch}; use gix_testtools::to_bstr_err; use winnow::prelude::*; - use crate::{signature, SignatureRef, Time}; + use crate::{signature, SignatureRef}; fn decode<'i>( i: &mut &'i [u8], @@ -121,28 +95,31 @@ mod tests { signature::decode.parse_next(i) } - fn signature( - name: &'static str, - email: &'static str, - seconds: SecondsSinceUnixEpoch, - sign: Sign, - offset: OffsetInSeconds, - ) -> SignatureRef<'static> { + fn signature(name: &'static str, email: &'static str, time: &'static str) -> SignatureRef<'static> { SignatureRef { - name: name.as_bytes().as_bstr(), - email: email.as_bytes().as_bstr(), - time: Time { seconds, offset, sign }, + name: name.into(), + email: email.into(), + time, } } #[test] fn tz_minus() { + let actual = decode + .parse_peek(b"Sebastian Thiel 1528473343 -0230") + .expect("parse to work") + .1; assert_eq!( - decode - .parse_peek(b"Sebastian Thiel 1528473343 -0230") - .expect("parse to work") - .1, - signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, -9000) + actual, + signature("Sebastian Thiel", "byronimo@gmail.com", "1528473343 -0230") + ); + assert_eq!(actual.seconds(), 1528473343); + assert_eq!( + actual.time().expect("valid"), + gix_date::Time { + seconds: 1528473343, + offset: -9000, + } ); } @@ -153,7 +130,7 @@ mod tests { .parse_peek(b"Sebastian Thiel 1528473343 +0230") .expect("parse to work") .1, - signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Plus, 9000) + signature("Sebastian Thiel", "byronimo@gmail.com", "1528473343 +0230") ); } @@ -164,7 +141,7 @@ mod tests { .parse_peek(b"Sebastian Thiel <\tbyronimo@gmail.com > 1528473343 +0230") .expect("parse to work") .1, - signature("Sebastian Thiel", "\tbyronimo@gmail.com ", 1528473343, Sign::Plus, 9000) + signature("Sebastian Thiel", "\tbyronimo@gmail.com ", "1528473343 +0230") ); } @@ -175,7 +152,7 @@ mod tests { .parse_peek(b"Sebastian Thiel 1528473343 -0000") .expect("parse to work") .1, - signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, 0) + signature("Sebastian Thiel", "byronimo@gmail.com", "1528473343 -0000") ); } @@ -186,7 +163,7 @@ mod tests { .parse_peek(b"name 1288373970 --700") .expect("parse to work") .1, - signature("name", "name@example.com", 1288373970, Sign::Minus, -252000) + signature("name", "name@example.com", "1288373970 --700") ); } @@ -194,7 +171,7 @@ mod tests { fn empty_name_and_email() { assert_eq!( decode.parse_peek(b" <> 12345 -1215").expect("parse to work").1, - signature("", "", 12345, Sign::Minus, -44100) + signature("", "", "12345 -1215") ); } @@ -213,7 +190,7 @@ mod tests { fn invalid_time() { assert_eq!( decode.parse_peek(b"hello <> abc -1215").expect("parse to work").1, - signature("hello", "", 0, Sign::Plus, 0) + signature("hello", "", "") ); } } diff --git a/gix-actor/src/signature/mod.rs b/gix-actor/src/signature/mod.rs index 9a9a0510a6c..f7f7b331e43 100644 --- a/gix-actor/src/signature/mod.rs +++ b/gix-actor/src/signature/mod.rs @@ -4,6 +4,7 @@ mod _ref { use crate::{signature::decode, IdentityRef, Signature, SignatureRef}; + /// Lifecycle impl<'a> SignatureRef<'a> { /// Deserialize a signature from the given `data`. pub fn from_bytes(mut data: &'a [u8]) -> Result, winnow::error::ErrMode> @@ -14,67 +15,85 @@ mod _ref { } /// Create an owned instance from this shared one. - pub fn to_owned(&self) -> Signature { - Signature { + pub fn to_owned(&self) -> Result { + Ok(Signature { name: self.name.to_owned(), email: self.email.to_owned(), - time: self.time, - } + time: self.time()?, + }) } + } - /// Trim whitespace surrounding the name and email and return a new signature. + /// Access + impl<'a> SignatureRef<'a> { + /// Trim the whitespace surrounding the `name`, `email` and `time` and return a new signature. pub fn trim(&self) -> SignatureRef<'a> { SignatureRef { name: self.name.trim().as_bstr(), email: self.email.trim().as_bstr(), - time: self.time, + time: self.time.trim(), } } - /// Return the actor's name and email, effectively excluding the time stamp of this signature. + /// Return the actor's name and email, effectively excluding the timestamp of this signature. pub fn actor(&self) -> IdentityRef<'a> { IdentityRef { name: self.name, email: self.email, } } + + /// Parse only the seconds since unix epoch from the `time` field, or silently default to 0 + /// if parsing fails. Note that this ignores the timezone, so it can parse otherwise broken dates. + /// + /// For a fallible and more complete, but slower version, use [`time()`](Self::time). + pub fn seconds(&self) -> gix_date::SecondsSinceUnixEpoch { + self.time + .trim() + .split(' ') + .next() + .and_then(|i| i.parse().ok()) + .unwrap_or_default() + } + + /// Parse the `time` field for access to the passed time since unix epoch, and the time offset. + /// The format is expected to be [raw](gix_date::parse_header()). + pub fn time(&self) -> Result { + self.time.parse() + } } } mod convert { + use gix_date::parse::TimeBuf; + use crate::{Signature, SignatureRef}; impl Signature { - /// Borrow this instance as immutable - pub fn to_ref(&self) -> SignatureRef<'_> { + /// Borrow this instance as immutable, serializing the `time` field into `buf`. + pub fn to_ref<'a>(&'a self, time_buf: &'a mut TimeBuf) -> SignatureRef<'a> { SignatureRef { name: self.name.as_ref(), email: self.email.as_ref(), - time: self.time, + time: self.time.to_str(time_buf), } } } impl From> for Signature { fn from(other: SignatureRef<'_>) -> Signature { - let SignatureRef { name, email, time } = other; Signature { - name: name.to_owned(), - email: email.to_owned(), - time, + name: other.name.to_owned(), + email: other.email.to_owned(), + time: other.time().unwrap_or_default(), } } } - - impl<'a> From<&'a Signature> for SignatureRef<'a> { - fn from(other: &'a Signature) -> SignatureRef<'a> { - other.to_ref() - } - } } pub(crate) mod write { use bstr::{BStr, ByteSlice}; + use gix_date::parse::TimeBuf; use crate::{Signature, SignatureRef}; @@ -96,11 +115,12 @@ pub(crate) mod write { impl Signature { /// Serialize this instance to `out` in the git serialization format for actors. pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> { - self.to_ref().write_to(out) + let mut buf = TimeBuf::default(); + self.to_ref(&mut buf).write_to(out) } /// Computes the number of bytes necessary to serialize this signature pub fn size(&self) -> usize { - self.to_ref().size() + self.name.len() + 2 /* space <*/ + self.email.len() + 2 /* > space */ + self.time.size() } } @@ -112,11 +132,11 @@ pub(crate) mod write { out.write_all(b"<")?; out.write_all(validated_token(self.email)?)?; out.write_all(b"> ")?; - self.time.write_to(out) + out.write_all(validated_token(self.time.into())?) } /// Computes the number of bytes necessary to serialize this signature pub fn size(&self) -> usize { - self.name.len() + 2 /* space <*/ + self.email.len() + 2 /* > space */ + self.time.size() + self.name.len() + 2 /* space <*/ + self.email.len() + 2 /* > space */ + self.time.len() } } diff --git a/gix-actor/tests/signature/mod.rs b/gix-actor/tests/signature/mod.rs index 15a04f6a6c9..a007ad871a7 100644 --- a/gix-actor/tests/signature/mod.rs +++ b/gix-actor/tests/signature/mod.rs @@ -1,14 +1,14 @@ mod write_to { mod invalid { use gix_actor::Signature; - use gix_date::{time::Sign, Time}; + use gix_date::Time; #[test] fn name() { let signature = Signature { name: "invalid < middlename".into(), email: "ok".into(), - time: default_time(), + time: Time::default(), }; assert_eq!( format!("{:?}", signature.write_to(&mut Vec::new())), @@ -21,7 +21,7 @@ mod write_to { let signature = Signature { name: "ok".into(), email: "server>.example.com".into(), - time: default_time(), + time: Time::default(), }; assert_eq!( format!("{:?}", signature.write_to(&mut Vec::new())), @@ -34,21 +34,13 @@ mod write_to { let signature = Signature { name: "hello\nnewline".into(), email: "name@example.com".into(), - time: default_time(), + time: Time::default(), }; assert_eq!( format!("{:?}", signature.write_to(&mut Vec::new())), "Err(Custom { kind: Other, error: IllegalCharacter })" ); } - - fn default_time() -> Time { - Time { - seconds: 0, - offset: 0, - sign: Sign::Plus, - } - } } } @@ -81,6 +73,16 @@ fn round_trip() -> Result<(), Box> { Ok(()) } +#[test] +fn signature_ref_round_trips_with_seconds_in_offset() -> Result<(), Box> { + let input = b"Sebastian Thiel 1313584730 +051800"; // Seen in the wild + let signature: SignatureRef = gix_actor::SignatureRef::from_bytes::<()>(input).unwrap(); + let mut output = Vec::new(); + signature.write_to(&mut output)?; + assert_eq!(output.as_bstr(), input.as_bstr()); + Ok(()) +} + #[test] fn parse_timestamp_with_trailing_digits() { let signature = gix_actor::SignatureRef::from_bytes::<()>(b"first last 1312735823 +051800") @@ -90,7 +92,7 @@ fn parse_timestamp_with_trailing_digits() { SignatureRef { name: "first last".into(), email: "name@example.com".into(), - time: gix_actor::date::Time::new(1312735823, 0), + time: "1312735823 +051800", } ); @@ -101,7 +103,7 @@ fn parse_timestamp_with_trailing_digits() { SignatureRef { name: "first last".into(), email: "name@example.com".into(), - time: gix_actor::date::Time::new(1312735823, 19080), + time: "1312735823 +0518", } ); } @@ -115,7 +117,7 @@ fn parse_missing_timestamp() { SignatureRef { name: "first last".into(), email: "name@example.com".into(), - time: gix_actor::date::Time::new(0, 0), + time: "" } ); } diff --git a/gix-attributes/src/name.rs b/gix-attributes/src/name.rs index c8d50743a1e..6ca70248a1c 100644 --- a/gix-attributes/src/name.rs +++ b/gix-attributes/src/name.rs @@ -1,7 +1,8 @@ -use crate::{Name, NameRef}; use bstr::{BStr, BString, ByteSlice}; use kstring::KStringRef; +use crate::{Name, NameRef}; + impl NameRef<'_> { /// Turn this ref into its owned counterpart. pub fn to_owned(self) -> Name { diff --git a/gix-attributes/src/parse.rs b/gix-attributes/src/parse.rs index 600c7c1191e..6bbf9bf23c4 100644 --- a/gix-attributes/src/parse.rs +++ b/gix-attributes/src/parse.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; -use crate::{name, AssignmentRef, Name, NameRef, StateRef}; use bstr::{BStr, ByteSlice}; use kstring::KStringRef; +use crate::{name, AssignmentRef, Name, NameRef, StateRef}; + /// The kind of attribute that was parsed. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/gix-attributes/src/search/mod.rs b/gix-attributes/src/search/mod.rs index db17f01fe36..83c6d41929c 100644 --- a/gix-attributes/src/search/mod.rs +++ b/gix-attributes/src/search/mod.rs @@ -1,6 +1,7 @@ +use std::collections::HashMap; + use kstring::KString; use smallvec::SmallVec; -use std::collections::HashMap; use crate::{Assignment, AssignmentRef}; diff --git a/gix-attributes/src/state.rs b/gix-attributes/src/state.rs index 21e51faedf1..c3ff5e35a0b 100644 --- a/gix-attributes/src/state.rs +++ b/gix-attributes/src/state.rs @@ -1,6 +1,7 @@ -use crate::{State, StateRef}; use bstr::{BStr, BString, ByteSlice}; +use crate::{State, StateRef}; + /// A container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/gix-attributes/tests/parse/mod.rs b/gix-attributes/tests/parse/mod.rs index 75000826327..0092b38928c 100644 --- a/gix-attributes/tests/parse/mod.rs +++ b/gix-attributes/tests/parse/mod.rs @@ -1,6 +1,5 @@ use bstr::BString; -use gix_attributes::state::ValueRef; -use gix_attributes::{parse, StateRef}; +use gix_attributes::{parse, state::ValueRef, StateRef}; use gix_glob::pattern::Mode; use gix_testtools::fixture_bytes; diff --git a/gix-blame/src/file/function.rs b/gix-blame/src/file/function.rs index 08d90b0127d..12ef57d374d 100644 --- a/gix-blame/src/file/function.rs +++ b/gix-blame/src/file/function.rs @@ -1,7 +1,6 @@ -use super::{process_changes, Change, UnblamedHunk}; -use crate::{BlameEntry, Error, Options, Outcome, Statistics}; -use gix_diff::blob::intern::TokenSource; -use gix_diff::tree::Visit; +use std::{num::NonZeroU32, ops::Range}; + +use gix_diff::{blob::intern::TokenSource, tree::Visit}; use gix_hash::ObjectId; use gix_object::{ bstr::{BStr, BString}, @@ -9,8 +8,9 @@ use gix_object::{ }; use gix_traverse::commit::find as find_commit; use smallvec::SmallVec; -use std::num::NonZeroU32; -use std::ops::Range; + +use super::{process_changes, Change, UnblamedHunk}; +use crate::{BlameEntry, Error, Options, Outcome, Statistics}; /// Produce a list of consecutive [`BlameEntry`] instances to indicate in which commits the ranges of the file /// at `suspect:` originated in. @@ -499,8 +499,7 @@ fn tree_diff_at_file_path( } fn visit(&mut self, change: gix_diff::tree::visit::Change) -> gix_diff::tree::visit::Action { - use gix_diff::tree::visit; - use gix_diff::tree::visit::Change::*; + use gix_diff::tree::{visit, visit::Change::*}; if self.inner.path() == self.interesting_path { self.change = Some(match change { @@ -672,7 +671,7 @@ type CommitTime = i64; fn commit_time(commit: gix_traverse::commit::Either<'_, '_>) -> Result { match commit { gix_traverse::commit::Either::CommitRefIter(commit_ref_iter) => { - commit_ref_iter.committer().map(|c| c.time.seconds) + commit_ref_iter.committer().map(|c| c.seconds()) } gix_traverse::commit::Either::CachedCommit(commit) => Ok(commit.committer_timestamp() as i64), } @@ -701,7 +700,7 @@ fn collect_parents( for id in commit_ref_iter.parent_ids() { let parent = odb.find_commit_iter(id.as_ref(), buf).ok(); let parent_commit_time = parent - .and_then(|parent| parent.committer().ok().map(|committer| committer.time.seconds)) + .and_then(|parent| parent.committer().ok().map(|committer| committer.seconds())) .unwrap_or_default(); parent_ids.push((id, parent_commit_time)); } diff --git a/gix-blame/src/file/mod.rs b/gix-blame/src/file/mod.rs index 4783ab30b19..bbca9681837 100644 --- a/gix-blame/src/file/mod.rs +++ b/gix-blame/src/file/mod.rs @@ -1,12 +1,10 @@ //! A module with low-level types and functions. -use std::num::NonZeroU32; -use std::ops::Range; +use std::{num::NonZeroU32, ops::Range}; use gix_hash::ObjectId; -use crate::types::{BlameEntry, Either, LineRange}; -use crate::types::{Change, Offset, UnblamedHunk}; +use crate::types::{BlameEntry, Change, Either, LineRange, Offset, UnblamedHunk}; pub(super) mod function; diff --git a/gix-blame/src/file/tests.rs b/gix-blame/src/file/tests.rs index 1366fcfea9c..02e7d89ebd1 100644 --- a/gix-blame/src/file/tests.rs +++ b/gix-blame/src/file/tests.rs @@ -1,7 +1,9 @@ -use crate::file::{Offset, UnblamedHunk}; -use gix_hash::ObjectId; use std::ops::Range; +use gix_hash::ObjectId; + +use crate::file::{Offset, UnblamedHunk}; + fn new_unblamed_hunk(range_in_blamed_file: Range, suspect: ObjectId, offset: Offset) -> UnblamedHunk { assert!( range_in_blamed_file.end > range_in_blamed_file.start, @@ -957,8 +959,11 @@ mod process_change { } mod process_changes { - use crate::file::tests::{new_unblamed_hunk, one_sha, zero_sha}; - use crate::file::{process_changes, Change, Offset, UnblamedHunk}; + use crate::file::{ + process_changes, + tests::{new_unblamed_hunk, one_sha, zero_sha}, + Change, Offset, UnblamedHunk, + }; #[test] fn nothing() { diff --git a/gix-blame/src/types.rs b/gix-blame/src/types.rs index f7fdb6608d1..bc01e4d8bec 100644 --- a/gix-blame/src/types.rs +++ b/gix-blame/src/types.rs @@ -1,9 +1,13 @@ -use crate::file::function::tokens_for_diffing; +use std::{ + num::NonZeroU32, + ops::{AddAssign, Range, SubAssign}, +}; + use gix_hash::ObjectId; use gix_object::bstr::BString; use smallvec::SmallVec; -use std::num::NonZeroU32; -use std::ops::{AddAssign, Range, SubAssign}; + +use crate::file::function::tokens_for_diffing; /// Options to be passed to [`file()`](crate::file()). #[derive(Default, Debug, Clone)] diff --git a/gix-command/src/lib.rs b/gix-command/src/lib.rs index 8199d0c186a..a3c428edeba 100644 --- a/gix-command/src/lib.rs +++ b/gix-command/src/lib.rs @@ -2,9 +2,9 @@ #![deny(rust_2018_idioms, missing_docs)] #![forbid(unsafe_code)] -use std::io::Read; use std::{ ffi::OsString, + io::Read, path::{Path, PathBuf}, }; @@ -85,8 +85,8 @@ pub struct Context { } mod prepare { - use std::borrow::Cow; use std::{ + borrow::Cow, ffi::OsString, process::{Command, Stdio}, }; @@ -425,9 +425,9 @@ pub fn extract_interpreter(executable: &Path) -> Option { /// pub mod shebang { + use std::{ffi::OsString, path::PathBuf}; + use bstr::{BStr, ByteSlice}; - use std::ffi::OsString; - use std::path::PathBuf; /// Parse `buf` to extract all shebang information. pub fn parse(buf: &BStr) -> Option { diff --git a/gix-command/tests/command.rs b/gix-command/tests/command.rs index 30b29977563..6ab8775afe9 100644 --- a/gix-command/tests/command.rs +++ b/gix-command/tests/command.rs @@ -1,6 +1,7 @@ -use gix_testtools::Result; use std::path::Path; +use gix_testtools::Result; + #[test] fn extract_interpreter() -> gix_testtools::Result { let root = gix_testtools::scripted_fixture_read_only("win_path_lookup.sh")?; @@ -79,8 +80,9 @@ mod shebang { #[cfg(unix)] { - use bstr::ByteSlice; use std::os::unix::ffi::OsStrExt; + + use bstr::ByteSlice; assert_eq!( shebang::parse(b"#!/bin/sh -x \xC3\x28\x41 -y ".as_bstr()), Some(shebang::Data { diff --git a/gix-commitgraph/src/file/init.rs b/gix-commitgraph/src/file/init.rs index 022cc7b7ea2..07bfdee3642 100644 --- a/gix-commitgraph/src/file/init.rs +++ b/gix-commitgraph/src/file/init.rs @@ -1,5 +1,6 @@ -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; + +use bstr::ByteSlice; use crate::{ file::{ @@ -8,7 +9,6 @@ use crate::{ }, File, }; -use bstr::ByteSlice; /// The error used in [`File::at()`]. #[derive(thiserror::Error, Debug)] diff --git a/gix-config/src/file/access/comfort.rs b/gix-config/src/file/access/comfort.rs index 6a75188d016..44b69cda6f5 100644 --- a/gix-config/src/file/access/comfort.rs +++ b/gix-config/src/file/access/comfort.rs @@ -2,8 +2,7 @@ use std::borrow::Cow; use bstr::BStr; -use crate::file::Metadata; -use crate::{value, AsKey, File}; +use crate::{file::Metadata, value, AsKey, File}; /// Comfortable API for accessing values impl File<'_> { diff --git a/gix-config/src/file/access/mutate.rs b/gix-config/src/file/access/mutate.rs index 3da5f178bf4..167d7c6a2f3 100644 --- a/gix-config/src/file/access/mutate.rs +++ b/gix-config/src/file/access/mutate.rs @@ -3,9 +3,8 @@ use std::borrow::Cow; use bstr::BStr; use gix_features::threading::OwnShared; -use crate::file::Metadata; use crate::{ - file::{self, rename_section, write::ends_with_newline, SectionBodyIdsLut, SectionId, SectionMut}, + file::{self, rename_section, write::ends_with_newline, Metadata, SectionBodyIdsLut, SectionId, SectionMut}, lookup, parse::{section, Event, FrontMatterEvents}, File, diff --git a/gix-config/src/file/access/raw.rs b/gix-config/src/file/access/raw.rs index c2b8c967f7f..4c8f73d4c6d 100644 --- a/gix-config/src/file/access/raw.rs +++ b/gix-config/src/file/access/raw.rs @@ -3,9 +3,8 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; use smallvec::ToSmallVec; -use crate::file::Metadata; use crate::{ - file::{mutable::multi_value::EntryData, Index, MultiValueMut, Size, ValueMut}, + file::{mutable::multi_value::EntryData, Index, Metadata, MultiValueMut, Size, ValueMut}, lookup, parse::{section, Event}, AsKey, File, diff --git a/gix-config/src/file/includes/types.rs b/gix-config/src/file/includes/types.rs index ed55ffe47c3..340d4fdaaf1 100644 --- a/gix-config/src/file/includes/types.rs +++ b/gix-config/src/file/includes/types.rs @@ -1,6 +1,7 @@ -use crate::{parse, path::interpolate}; use std::path::PathBuf; +use crate::{parse, path::interpolate}; + /// The error returned when following includes. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] diff --git a/gix-config/src/file/mutable/section.rs b/gix-config/src/file/mutable/section.rs index d41f2f6e8f5..ec38ab85e2c 100644 --- a/gix-config/src/file/mutable/section.rs +++ b/gix-config/src/file/mutable/section.rs @@ -3,6 +3,10 @@ use std::{ ops::{Deref, Range}, }; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use gix_sec::Trust; +use smallvec::SmallVec; + use crate::{ file::{ self, @@ -13,9 +17,6 @@ use crate::{ parse::{section::ValueName, Event}, value::{normalize, normalize_bstr, normalize_bstring}, }; -use bstr::{BStr, BString, ByteSlice, ByteVec}; -use gix_sec::Trust; -use smallvec::SmallVec; /// A opaque type that represents a mutable reference to a section. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] diff --git a/gix-config/src/key.rs b/gix-config/src/key.rs index cba7d92dce9..cdbae4e6dc8 100644 --- a/gix-config/src/key.rs +++ b/gix-config/src/key.rs @@ -1,5 +1,4 @@ -use bstr::BStr; -use bstr::ByteSlice; +use bstr::{BStr, ByteSlice}; /// Parse parts of a Git configuration key, like `remote.origin.url` or `core.bare`. pub trait AsKey { diff --git a/gix-config/src/parse/tests.rs b/gix-config/src/parse/tests.rs index 75c53fb677e..703f8a2167a 100644 --- a/gix-config/src/parse/tests.rs +++ b/gix-config/src/parse/tests.rs @@ -1,9 +1,10 @@ mod section { - use crate::parse::section::Header; - use crate::parse::{section, Comment, Event, Events, Section}; - use bstr::BStr; use std::borrow::Cow; + use bstr::BStr; + + use crate::parse::{section, section::Header, Comment, Event, Events, Section}; + #[test] #[cfg(target_pointer_width = "64")] fn size_of_events() { diff --git a/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs index 9e6dbbf4697..b7057d8351b 100644 --- a/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/gix-config/tests/config/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -142,8 +142,8 @@ pub fn assert_section_value( Some(Value::Override) => Some(cow_str("override-value")), None => None, }, - "gix-config disagrees with the expected value, {:?} for debugging", - env.tempdir.into_path() + "gix-config disagrees with the expected value, {} for debugging", + env.tempdir.into_path().display() ); assure_git_agrees(expected, env) } @@ -171,9 +171,9 @@ fn assure_git_agrees(expected: Option, env: GitEnv) -> crate::Result { assert_eq!( output.status.success(), expected.is_some(), - "{:?}, {:?} for debugging", + "{:?}, {} for debugging", output, - env.tempdir.into_path() + env.tempdir.into_path().display() ); let git_output: BString = output.stdout.trim_end().into(); assert_eq!( diff --git a/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs b/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs index 5ed6de7a030..27ab7a7b7c0 100644 --- a/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs +++ b/gix-config/tests/config/file/init/from_paths/includes/conditional/hasconfig.rs @@ -1,6 +1,7 @@ -use gix_config::file::{includes, init}; use std::path::{Path, PathBuf}; +use gix_config::file::{includes, init}; + #[test] fn simple() -> crate::Result { let (config, root) = config_with_includes("basic")?; diff --git a/gix-config/tests/config/mod.rs b/gix-config/tests/config/mod.rs index 2782af4095a..93a04387fbf 100644 --- a/gix-config/tests/config/mod.rs +++ b/gix-config/tests/config/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unnecessary_debug_formatting)] pub use gix_testtools::Result; mod file; diff --git a/gix-config/tests/config/source/mod.rs b/gix-config/tests/config/source/mod.rs index 26dc4e093bc..9bc63d14135 100644 --- a/gix-config/tests/config/source/mod.rs +++ b/gix-config/tests/config/source/mod.rs @@ -1,6 +1,7 @@ -use gix_config::Source; use std::path::Path; +use gix_config::Source; + #[test] fn git_config_no_system() { assert_eq!( diff --git a/gix-config/tests/mem.rs b/gix-config/tests/mem.rs index 5c65cb26d6c..7bbde99e22f 100644 --- a/gix-config/tests/mem.rs +++ b/gix-config/tests/mem.rs @@ -1,7 +1,7 @@ +use std::{alloc, time::Instant}; + use bytesize::ByteSize; use cap::Cap; -use std::alloc; -use std::time::Instant; #[global_allocator] static ALLOCATOR: Cap = Cap::new(alloc::System, usize::MAX); diff --git a/gix-date/Cargo.toml b/gix-date/Cargo.toml index 1c6acca213b..b0c67c1b744 100644 --- a/gix-date/Cargo.toml +++ b/gix-date/Cargo.toml @@ -24,6 +24,9 @@ serde = { version = "1.0.114", optional = true, default-features = false, featur itoa = "1.0.1" jiff = "0.2.10" thiserror = "2.0.0" +# TODO: used for quick and easy `TimeBacking: std::io::Write` implementation, but could make that `Copy` +# and remove this dep with custom impl +smallvec = { version = "1.15.0", features = ["write"] } document-features = { version = "0.2.0", optional = true } diff --git a/gix-date/src/lib.rs b/gix-date/src/lib.rs index 20893a7cab8..68d39dfda84 100644 --- a/gix-date/src/lib.rs +++ b/gix-date/src/lib.rs @@ -7,29 +7,25 @@ doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] -#![deny(missing_docs, rust_2018_idioms)] -#![forbid(unsafe_code)] - +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] /// pub mod time; /// pub mod parse; -pub use parse::function::parse; +pub use parse::function::{parse, parse_header}; /// A timestamp with timezone. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Time { - /// The seconds that passed since UNIX epoch. This makes it UTC, or `+0000`. + /// The seconds that have passed since UNIX epoch. This makes it UTC, or `+0000`. pub seconds: SecondsSinceUnixEpoch, /// The time's offset in seconds, which may be negative to match the `sign` field. pub offset: OffsetInSeconds, - /// the sign of `offset`, used to encode `-0000` which would otherwise lose sign information. - pub sign: time::Sign, } -/// The amount of seconds since unix epoch. +/// The number of seconds since unix epoch. /// /// Note that negative dates represent times before the unix epoch. /// diff --git a/gix-date/src/parse.rs b/gix-date/src/parse.rs index afd3470ac60..2dfba0f0532 100644 --- a/gix-date/src/parse.rs +++ b/gix-date/src/parse.rs @@ -1,3 +1,9 @@ +use std::str::FromStr; + +use smallvec::SmallVec; + +use crate::Time; + #[derive(thiserror::Error, Debug, Clone)] #[allow(missing_docs)] pub enum Error { @@ -11,6 +17,61 @@ pub enum Error { MissingCurrentTime, } +/// A container for just enough bytes to hold the largest-possible [`time`](Time) instance. +/// It's used in conjunction with +#[derive(Default, Clone)] +pub struct TimeBuf { + buf: SmallVec<[u8; Time::MAX.size()]>, +} + +impl TimeBuf { + /// Represent this instance as standard string, serialized in a format compatible with + /// signature fields in Git commits, also known as anything parseable as [raw format](function::parse_header()). + pub fn as_str(&self) -> &str { + // SAFETY: We know that serialized times are pure ASCII, a subset of UTF-8. + // `buf` and `len` are written only by time-serialization code. + let time_bytes = self.buf.as_slice(); + #[allow(unsafe_code)] + unsafe { + std::str::from_utf8_unchecked(time_bytes) + } + } + + /// Clear the previous content. + fn clear(&mut self) { + self.buf.clear(); + } +} + +impl std::io::Write for TimeBuf { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.buf.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.buf.flush() + } +} + +impl Time { + /// Serialize this instance into `buf`, exactly as it would appear in the header of a Git commit, + /// and return `buf` as `&str` for easy consumption. + pub fn to_str<'a>(&self, buf: &'a mut TimeBuf) -> &'a str { + buf.clear(); + self.write_to(buf) + .expect("write to memory of just the right size cannot fail"); + buf.as_str() + } +} + +impl FromStr for Time { + type Err = Error; + + fn from_str(s: &str) -> Result { + crate::parse_header(s).ok_or_else(|| Error::InvalidDateString { input: s.into() }) + } +} + pub(crate) mod function { use std::{str::FromStr, time::SystemTime}; @@ -18,14 +79,71 @@ pub(crate) mod function { use crate::{ parse::{relative, Error}, - time::{ - format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT}, - Sign, - }, - SecondsSinceUnixEpoch, Time, + time::format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT}, + OffsetInSeconds, SecondsSinceUnixEpoch, Time, }; - #[allow(missing_docs)] + /// Parse `input` as any time that Git can parse when inputting a date. + /// + /// ## Examples + /// + /// ### 1. SHORT Format + /// + /// * `2018-12-24` + /// * `1970-01-01` + /// * `1950-12-31` + /// * `2024-12-31` + /// + /// ### 2. RFC2822 Format + /// + /// * `Thu, 18 Aug 2022 12:45:06 +0800` + /// * `Mon Oct 27 10:30:00 2023 -0800` + /// + /// ### 3. GIT_RFC2822 Format + /// + /// * `Thu, 8 Aug 2022 12:45:06 +0800` + /// * `Mon Oct 27 10:30:00 2023 -0800` (Note the single-digit day) + /// + /// ### 4. ISO8601 Format + /// + /// * `2022-08-17 22:04:58 +0200` + /// * `1970-01-01 00:00:00 -0500` + /// + /// ### 5. ISO8601_STRICT Format + /// + /// * `2022-08-17T21:43:13+08:00` + /// + /// ### 6. UNIX Timestamp (Seconds Since Epoch) + /// + /// * `123456789` + /// * `0` (January 1, 1970 UTC) + /// * `-1000` + /// * `1700000000` + /// + /// ### 7. Commit Header Format + /// + /// * `1745582210 +0200` + /// * `1660874655 +0800` + /// * `-1660874655 +0800` + /// + /// See also the [`parse_header()`]. + /// + /// ### 8. GITOXIDE Format + /// + /// * `Thu Sep 04 2022 10:45:06 -0400` + /// * `Mon Oct 27 2023 10:30:00 +0000` + /// + /// ### 9. DEFAULT Format + /// + /// * `Thu Sep 4 10:45:06 2022 -0400` + /// * `Mon Oct 27 10:30:00 2023 +0000` + /// + /// ### 10. Relative Dates (e.g., "2 minutes ago", "1 hour from now") + /// + /// These dates are parsed *relative to a `now` timestamp*. The examples depend entirely on the value of `now`. + /// If `now` is October 27, 2023 at 10:00:00 UTC: + /// * `2 minutes ago` (October 27, 2023 at 09:58:00 UTC) + /// * `3 hours ago` (October 27, 2023 at 07:00:00 UTC) pub fn parse(input: &str, now: Option) -> Result { // TODO: actual implementation, this is just to not constantly fail if input == "1979-02-26 18:30:00" { @@ -50,39 +168,73 @@ pub(crate) mod function { } else if let Ok(val) = SecondsSinceUnixEpoch::from_str(input) { // Format::Unix Time::new(val, 0) - } else if let Some(val) = parse_raw(input) { - // Format::Raw - val } else if let Some(val) = relative::parse(input, now).transpose()? { Time::new(val.timestamp().as_second(), val.offset().seconds()) + } else if let Some(val) = parse_header(input) { + // Format::Raw + val } else { return Err(Error::InvalidDateString { input: input.into() }); }) } - fn parse_raw(input: &str) -> Option