git_index

Generates arguments to pass to stagit-index in descending order by commit date.
git clone https://git.philomathiclife.com/repos/git_index
Log | Files | Refs | README

commit a429b4d66c7de61572254ce104bf7fa636cb030d
parent beb85f468f5edb0c2dbd186ee5ca4a1f4ef204aa
Author: Zack Newman <zack@philomathiclife.com>
Date:   Fri, 20 Mar 2026 15:34:44 -0600

update deps and lints. address new deps and lints

Diffstat:
MCargo.toml | 157++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
MREADME.md | 8++++++--
Msrc/args.rs | 48++++++++++++++++++++++++++++++++++--------------
Msrc/main.rs | 114++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
4 files changed, 210 insertions(+), 117 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,60 +9,109 @@ name = "git_index" publish = false readme = "README.md" repository = "https://git.philomathiclife.com/repos/git_index/" -rust-version = "1.86.0" -version = "0.1.9" +rust-version = "1.93.1" +version = "0.1.10" [lints.rust] -ambiguous_negative_literals = { level = "deny", priority = -1 } -closure_returning_async_block = { level = "deny", priority = -1 } -deprecated_safe = { level = "deny", priority = -1 } -deref_into_dyn_supertrait = { level = "deny", priority = -1 } -ffi_unwind_calls = { level = "deny", priority = -1 } -future_incompatible = { level = "deny", priority = -1 } -#fuzzy_provenance_casts = { level = "deny", priority = -1 } -impl_trait_redundant_captures = { level = "deny", priority = -1 } -keyword_idents = { level = "deny", priority = -1 } -let_underscore = { level = "deny", priority = -1 } -linker_messages = { level = "deny", priority = -1 } -#lossy_provenance_casts = { level = "deny", priority = -1 } -macro_use_extern_crate = { level = "deny", priority = -1 } -meta_variable_misuse = { level = "deny", priority = -1 } -missing_copy_implementations = { level = "deny", priority = -1 } -missing_debug_implementations = { level = "deny", priority = -1 } -missing_docs = { level = "deny", priority = -1 } -#multiple_supertrait_upcastable = { level = "deny", priority = -1 } -#must_not_suspend = { level = "deny", priority = -1 } -non_ascii_idents = { level = "deny", priority = -1 } -#non_exhaustive_omitted_patterns = { level = "deny", priority = -1 } -nonstandard_style = { level = "deny", priority = -1 } -redundant_imports = { level = "deny", priority = -1 } -redundant_lifetimes = { level = "deny", priority = -1 } -refining_impl_trait = { level = "deny", priority = -1 } -rust_2018_compatibility = { level = "deny", priority = -1 } -rust_2018_idioms = { level = "deny", priority = -1 } -rust_2021_compatibility = { level = "deny", priority = -1 } -rust_2024_compatibility = { level = "deny", priority = -1 } -single_use_lifetimes = { level = "deny", priority = -1 } -#supertrait_item_shadowing_definition = { level = "deny", priority = -1 } -trivial_casts = { level = "deny", priority = -1 } -trivial_numeric_casts = { level = "deny", priority = -1 } -unit_bindings = { level = "deny", priority = -1 } -unnameable_types = { level = "deny", priority = -1 } -#unqualified_local_imports = { level = "deny", priority = -1 } -unreachable_pub = { level = "deny", priority = -1 } -unsafe_code = { level = "deny", priority = -1 } -unstable_features = { level = "deny", priority = -1 } +deprecated-safe = { level = "deny", priority = -1 } +future-incompatible = { level = "deny", priority = -1 } +keyword-idents = { level = "deny", priority = -1 } +let-underscore = { level = "deny", priority = -1 } +nonstandard-style = { level = "deny", priority = -1 } +refining-impl-trait = { level = "deny", priority = -1 } +rust-2018-compatibility = { level = "deny", priority = -1 } +rust-2018-idioms = { level = "deny", priority = -1 } +rust-2021-compatibility = { level = "deny", priority = -1 } +rust-2024-compatibility = { level = "deny", priority = -1 } +unknown-or-malformed-diagnostic-attributes = { level = "deny", priority = -1 } unused = { level = "deny", priority = -1 } -unused_crate_dependencies = { level = "deny", priority = -1 } -unused_import_braces = { level = "deny", priority = -1 } -unused_lifetimes = { level = "deny", priority = -1 } -unused_qualifications = { level = "deny", priority = -1 } -unused_results = { level = "deny", priority = -1 } -variant_size_differences = { level = "deny", priority = -1 } warnings = { level = "deny", priority = -1 } +ambiguous-negative-literals = { level = "deny", priority = -1 } +closure-returning-async-block = { level = "deny", priority = -1 } +deprecated-in-future = { level = "deny", priority = -1 } +deref-into-dyn-supertrait = { level = "deny", priority = -1 } +ffi-unwind-calls = { level = "deny", priority = -1 } +#fuzzy-provenance-casts = { level = "deny", priority = -1 } +impl-trait-redundant-captures = { level = "deny", priority = -1 } +linker-messages = { level = "deny", priority = -1 } +#lossy-provenance-casts = { level = "deny", priority = -1 } +macro-use-extern-crate = { level = "deny", priority = -1 } +meta-variable-misuse = { level = "deny", priority = -1 } +missing-copy-implementations = { level = "deny", priority = -1 } +missing-debug-implementations = { level = "deny", priority = -1 } +missing-docs = { level = "deny", priority = -1 } +#multiple-supertrait-upcastable = { level = "deny", priority = -1 } +#must-not-suspend = { level = "deny", priority = -1 } +non-ascii-idents = { level = "deny", priority = -1 } +#non-exhaustive-omitted-patterns = { level = "deny", priority = -1 } +redundant-imports = { level = "deny", priority = -1 } +redundant-lifetimes = { level = "deny", priority = -1 } +#resolving-to-items-shadowing-supertrait-items = { level = "deny", priority = -1 } +#shadowing-supertrait-items = { level = "deny", priority = -1 } +single-use-lifetimes = { level = "deny", priority = -1 } +trivial-casts = { level = "deny", priority = -1 } +trivial-numeric-casts = { level = "deny", priority = -1 } +unit-bindings = { level = "deny", priority = -1 } +unnameable-types = { level = "deny", priority = -1 } +#unqualified-local-imports = { level = "deny", priority = -1 } +unreachable-pub = { level = "deny", priority = -1 } +unsafe-code = { level = "deny", priority = -1 } +unstable-features = { level = "deny", priority = -1 } +unused-crate-dependencies = { level = "deny", priority = -1 } +unused-import-braces = { level = "deny", priority = -1 } +unused-lifetimes = { level = "deny", priority = -1 } +unused-qualifications = { level = "deny", priority = -1 } +unused-results = { level = "deny", priority = -1 } +variant-size-differences = { level = "deny", priority = -1 } +# Before publishing to crates.io, comment above and uncomment below. +#warnings = { level = "allow", priority = -1 } +#ambiguous-associated-items = { level = "allow", priority = -1 } +#arithmetic-overflow = { level = "allow", priority = -1 } +#binary-asm-labels = { level = "allow", priority = -1 } +#bindings-with-variant-name = { level = "allow", priority = -1 } +#conflicting-repr-hints = { level = "allow", priority = -1 } +#dangerous-implicit-autorefs = { level = "allow", priority = -1 } +#default-overrides-default-fields = { level = "allow", priority = -1 } +#dependency-on-unit-never-type-fallback = { level = "allow", priority = -1 } +#deref-nullptr = { level = "allow", priority = -1 } +#elided-lifetimes-in-associated-constant = { level = "allow", priority = -1 } +#enum-intrinsics-non-enums = { level = "allow", priority = -1 } +#explicit-builtin-cfgs-in-flags = { level = "allow", priority = -1 } +#ill-formed-attribute-input = { level = "allow", priority = -1 } +#incomplete-include = { level = "allow", priority = -1 } +#ineffective-unstable-trait-impl = { level = "allow", priority = -1 } +#invalid-atomic-ordering = { level = "allow", priority = -1 } +#invalid-from-utf8-unchecked = { level = "allow", priority = -1 } +#invalid-macro-export-arguments = { level = "allow", priority = -1 } +#invalid-null-arguments = { level = "allow", priority = -1 } +#invalid-reference-casting = { level = "allow", priority = -1 } +#invalid-type-param-default = { level = "allow", priority = -1 } +#legacy-derive-helpers = { level = "allow", priority = -1 } +#let-underscore-lock = { level = "allow", priority = -1 } +#long-running-const-eval = { level = "allow", priority = -1 } +#macro-expanded-macro-exports-accessed-by-absolute-paths = { level = "allow", priority = -1 } +#mutable-transmutes = { level = "allow", priority = -1 } +#named-asm-labels = { level = "allow", priority = -1 } +#never-type-fallback-flowing-into-unsafe = { level = "allow", priority = -1 } +#no-mangle-const-items = { level = "allow", priority = -1 } +#out-of-scope-macro-calls = { level = "allow", priority = -1 } +#overflowing-literals = { level = "allow", priority = -1 } +#patterns-in-fns-without-body = { level = "allow", priority = -1 } +#proc-macro-derive-resolution-fallback = { level = "allow", priority = -1 } +#pub-use-of-private-extern-crate = { level = "allow", priority = -1 } +#repr-transparent-non-zst-fields = { level = "allow", priority = -1 } +#semicolon-in-expressions-from-macros = { level = "allow", priority = -1 } +#soft-unstable = { level = "allow", priority = -1 } +#test-unstable-lint = { level = "allow", priority = -1 } +#text-direction-codepoint-in-comment = { level = "allow", priority = -1 } +#text-direction-codepoint-in-literal = { level = "allow", priority = -1 } +#unconditional-panic = { level = "allow", priority = -1 } +#undropped-manually-drops = { level = "allow", priority = -1 } +#unknown-crate-types = { level = "allow", priority = -1 } +#useless-deprecated = { level = "allow", priority = -1 } +# Before publishing to crates.io, comment below. [lints.clippy] -all = { level = "deny", priority = -1 } cargo = { level = "deny", priority = -1 } complexity = { level = "deny", priority = -1 } correctness = { level = "deny", priority = -1 } @@ -85,11 +134,19 @@ ref_patterns = "allow" return_and_then = "allow" single_call_fn = "allow" +[lints.rustdoc] +# Before publishing to crates.io, comment below and uncomment below that. +all = { level = "deny", priority = -1 } +#all = "allow" + [dependencies] -jiff = { version = "0.2.15", default-features = false } -priv_sep = { version = "3.0.0-alpha.1.3", default-features = false } +jiff = { version = "0.2.23", default-features = false } + +[target.'cfg(target_os = "openbsd")'.dependencies] +priv_sep = { version = "3.0.0-alpha.5.0", default-features = false } [profile.release] +codegen-units = 1 lto = true panic = 'abort' strip = true diff --git a/README.md b/README.md @@ -30,8 +30,12 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -Before any PR is sent, `cargo clippy` and `cargo t` should be run for both. Additionally -`RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure documentation can be built. +Before any PR is sent, `cargo clippy --all-targets` and `cargo test --all-targets` should be run using the stable +and MSRV toolchains. One easy way to achieve this is by invoking [`ci-cargo`](https://crates.io/crates/ci-cargo) +as `ci-cargo clippy --all-targets test --benches --bins --examples --tests --include-ignored` in the `git_index` +directory. + +Last, `cargo +nightly doc` should be run to ensure documentation can be built. ### Status diff --git a/src/args.rs b/src/args.rs @@ -1,44 +1,64 @@ +#[cfg(target_os = "openbsd")] +use alloc::ffi::CString; +#[cfg(target_os = "openbsd")] +use core::ffi::CStr; use core::{ error::Error, fmt::{self, Display, Formatter}, }; use std::{ - env::{self, Args}, - path::{Path, PathBuf}, + env::{self, ArgsOs}, + ffi::OsString, + path::PathBuf, }; /// Wrapper around an absolute [`PathBuf`] to a directory. #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub(crate) struct AbsDirPath { /// The directory. path: PathBuf, + /// The directory. + #[cfg(target_os = "openbsd")] + path_c: CString, } impl AbsDirPath { + /// Returns `self` as a `CStr`. + #[cfg(target_os = "openbsd")] + pub(crate) fn as_cstr(&self) -> &CStr { + &self.path_c + } + /// Returns the `PathBuf` `self` is based on. + pub(crate) fn into_path_buf(self) -> PathBuf { + self.path + } /// Returns an `AbsDirPath` iff `PathBuf::from(val).is_absolute()`. /// /// If `PathBuf::from(val).as_bytes().last().unwrap() != b'/'`, `val` /// will have `/` appended to it. #[expect(clippy::option_if_let_else, reason = "can't without moving val")] - pub(crate) fn from_string(val: String) -> Option<Self> { - match val.as_bytes().last() { + pub(crate) fn from_string(val: OsString) -> Option<Self> { + match val.as_encoded_bytes().last() { Some(byt) => { let last = *byt; let mut path = PathBuf::from(val); - path.is_absolute().then(|| { + if path.is_absolute() { if last != b'/' { path.as_mut_os_string().push("/"); } - Self { path } - }) + #[cfg(not(target_os = "openbsd"))] + let opt = Some(Self { path }); + #[cfg(target_os = "openbsd")] + let opt = CString::new(path.clone().into_os_string().into_encoded_bytes()) + .ok() + .map(|path_c| Self { path, path_c }); + opt + } else { + None + } } None => None, } } } -impl AsRef<Path> for AbsDirPath { - fn as_ref(&self) -> &Path { - self.path.as_path() - } -} /// Error returned when parsing arguments passed to the application. #[derive(Debug)] pub(crate) enum ArgsErr { @@ -62,12 +82,12 @@ impl Error for ArgsErr {} /// Returns `AbsDirPath` based on arguments passed to the application. pub(crate) fn from_env_args() -> Result<AbsDirPath, ArgsErr> { /// Attempts to parse the next `Arg` into an absolute path to a directory. - fn get_path(args: &mut Args) -> Result<AbsDirPath, ArgsErr> { + fn get_path(args: &mut ArgsOs) -> Result<AbsDirPath, ArgsErr> { args.next() .ok_or(ArgsErr::NoArgs) .and_then(|path| AbsDirPath::from_string(path).ok_or(ArgsErr::InvalidDir)) } - let mut args = env::args(); + let mut args = env::args_os(); let Some(_) = args.next() else { return Err(ArgsErr::NoArgs); }; diff --git a/src/main.rs b/src/main.rs @@ -10,34 +10,37 @@ use args::{AbsDirPath, ArgsErr}; use core::convert::Infallible; use core::{ error, + ffi::CStr, fmt::{self, Display, Formatter}, str::{self, Utf8Error}, }; use jiff::{Error as TimeErr, Timestamp, fmt::temporal::DateTimeParser}; -#[cfg(not(target_os = "openbsd"))] -use priv_sep as _; #[cfg(target_os = "openbsd")] -use priv_sep::{NulOrIoErr, Permissions, Promise, Promises}; +use priv_sep::{Errno, Permissions, Promise, Promises}; use std::{ ffi::OsString, fs, io::{self, Error, Write as _}, - path::Path, + path::PathBuf, process::{Command, Stdio}, }; -/// `()` triggers lints when captured by `let`. -/// This only relevant for the no-op functions. -#[cfg(not(target_os = "openbsd"))] -#[derive(Clone, Copy)] -struct Zst; /// Module for reading the options passed to the application. /// Error returned from the program. enum E { /// Variant for errors due to incorrect arguments being passed. Args(ArgsErr), + /// Variant for errors due to calls to `pledge`. #[cfg(target_os = "openbsd")] + Pledge(Errno), + /// Unify with non-OpenBSD. + #[cfg(not(target_os = "openbsd"))] + Pledge(Infallible), /// Variant for errors due to calls to `unveil`. - Unveil(NulOrIoErr), + #[cfg(target_os = "openbsd")] + Unveil(Errno), + /// Unify with non-OpenBSD. + #[cfg(not(target_os = "openbsd"))] + Unveil(Infallible), /// Variant for IO errors. Io(Error), /// Variant when a git repo directory is not valid UTF-8. @@ -63,9 +66,15 @@ impl Display for E { match *self { Self::Args(ref e) => e.fmt(f), #[cfg(target_os = "openbsd")] - Self::Unveil(ref err) => write!(f, "unveil(2) failed with {err}"), + Self::Pledge(err) => write!(f, "pledge(2) failed with {err}"), + #[cfg(not(target_os = "openbsd"))] + Self::Pledge(e) => match e {}, + #[cfg(target_os = "openbsd")] + Self::Unveil(err) => write!(f, "unveil(2) failed with {err}"), + #[cfg(not(target_os = "openbsd"))] + Self::Unveil(e) => match e {}, Self::Io(ref e) => e.fmt(f), - Self::NonUtf8Path(ref e) => write!(f, "{e:?} is not valid UTF-8"), + Self::NonUtf8Path(ref e) => write!(f, "{} is not valid UTF-8", e.display()), Self::GitErr(ref e) => match str::from_utf8(e.as_slice()) { Ok(err) => write!(f, "git errored with: {err}"), Err(err) => write!(f, "git errored with an invalid UTF-8 error: {err}"), @@ -92,18 +101,6 @@ impl From<Error> for E { Self::Io(value) } } -#[cfg(target_os = "openbsd")] -impl From<NulOrIoErr> for E { - fn from(value: NulOrIoErr) -> Self { - Self::Unveil(value) - } -} -#[cfg(not(target_os = "openbsd"))] -impl From<Infallible> for E { - fn from(value: Infallible) -> Self { - match value {} - } -} impl From<Utf8Error> for E { fn from(value: Utf8Error) -> Self { Self::Git(value) @@ -123,7 +120,7 @@ impl From<TimeErr> for E { /// to run. Specifically, the `Promise`s `Exec`, `Proc`, `Rpath`, `Stdio`, and `Unveil` /// are passed. #[cfg(target_os = "openbsd")] -fn pledge_init() -> Result<Promises, Error> { +fn pledge_init() -> Result<Promises, Errno> { let promises = Promises::new([ Promise::Exec, Promise::Proc, @@ -142,12 +139,12 @@ fn pledge_init() -> Result<Promises, Error> { reason = "consistent API as openbsd feature" )] #[cfg(not(target_os = "openbsd"))] -const fn pledge_init() -> Result<Zst, Infallible> { - Ok(Zst) +const fn pledge_init() -> Result<(), Infallible> { + Ok(()) } /// Removes `Promise::Unveil`. #[cfg(target_os = "openbsd")] -fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> { +fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Errno> { promises.remove_then_pledge(Promise::Unveil) } /// No-op that returns `Ok`. @@ -156,12 +153,12 @@ fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> { reason = "consistent API as openbsd feature" )] #[cfg(not(target_os = "openbsd"))] -const fn pledge_away_unveil(_: &mut Zst) -> Result<(), Infallible> { +const fn pledge_away_unveil(_: &mut ()) -> Result<(), Infallible> { Ok(()) } /// Removes all `Promise`s except `Stdio`. #[cfg(target_os = "openbsd")] -fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> { +fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Errno> { promises.retain_then_pledge([Promise::Stdio]) } /// No-op that returns `Ok`. @@ -170,13 +167,13 @@ fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> { reason = "consistent API as openbsd feature" )] #[cfg(not(target_os = "openbsd"))] -const fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), Infallible> { +const fn pledge_away_all_but_stdio(_: &mut ()) -> Result<(), Infallible> { Ok(()) } /// Calls `unveil_none` on `/`. #[cfg(target_os = "openbsd")] -fn veil_all() -> Result<(), NulOrIoErr> { - Permissions::NONE.unveil("/") +fn veil_all() -> Result<(), Errno> { + Permissions::NONE.unveil(c"/") } /// No-op that returns `Ok`. #[expect( @@ -187,10 +184,10 @@ fn veil_all() -> Result<(), NulOrIoErr> { const fn veil_all() -> Result<(), Infallible> { Ok(()) } -/// Calls `unveil`_on `GIT` with `Permissions::EXECUTE`. +/// Calls `unveil`_on `GIT_CSTR` with `Permissions::EXECUTE`. #[cfg(target_os = "openbsd")] -fn unveil_git() -> Result<(), NulOrIoErr> { - Permissions::EXECUTE.unveil(GIT) +fn unveil_git() -> Result<(), Errno> { + Permissions::EXECUTE.unveil(GIT_CSTR) } /// No-op that returns `Ok`. #[expect( @@ -203,8 +200,8 @@ const fn unveil_git() -> Result<(), Infallible> { } /// Calls `unveil`_on `path` with `Permissions::READ`. #[cfg(target_os = "openbsd")] -fn unveil_read<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { - Permissions::READ.unveil(path) +fn unveil_read(path: &AbsDirPath) -> Result<(), Errno> { + Permissions::READ.unveil(path.as_cstr()) } /// No-op that returns `Ok`. #[expect( @@ -212,11 +209,11 @@ fn unveil_read<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { reason = "consistent API as openbsd feature" )] #[cfg(not(target_os = "openbsd"))] -fn unveil_read<P: AsRef<Path>>(_: P) -> Result<(), Infallible> { +const fn unveil_read(_: &AbsDirPath) -> Result<(), Infallible> { Ok(()) } /// For each entry in `dir`, `git` is forked and invoked with `-C <dir><entry_in_dir> log -1 --date=iso-strict --pretty=format:"%cd"`. -fn get_git_data(map: &mut BTreeMap<Timestamp, Vec<String>>, dir: AbsDirPath) -> Result<(), E> { +fn get_git_data(map: &mut BTreeMap<Timestamp, Vec<String>>, dir: PathBuf) -> Result<(), E> { let parser = DateTimeParser::new(); for entry in fs::read_dir(dir)? { let repo = entry?; @@ -260,16 +257,31 @@ fn write_results(map: BTreeMap<Timestamp, Vec<String>>) -> Result<(), Error> { }) } /// The absolute path to `git`. -const GIT: &str = "/usr/local/bin/git"; +const GIT_CSTR: &CStr = c"/usr/local/bin/git"; +/// The absolute path to `git`. +const GIT: &str = match GIT_CSTR.to_str() { + Ok(val) => val, + Err(_) => panic!("/usr/local/bin/git is not a valid str"), +}; fn main() -> Result<(), E> { - let mut promises = pledge_init()?; - veil_all()?; - let git_dir = args::from_env_args()?; - unveil_read(git_dir.as_ref())?; - unveil_git()?; - pledge_away_unveil(&mut promises)?; - let mut map = BTreeMap::new(); - get_git_data(&mut map, git_dir)?; - pledge_away_all_but_stdio(&mut promises)?; - write_results(map).map_err(E::Io) + pledge_init().map_err(E::Pledge).and_then(|mut promises| { + veil_all().map_err(E::Unveil).and_then(|()| { + args::from_env_args().map_err(E::Args).and_then(|git_dir| { + unveil_read(&git_dir).map_err(E::Unveil).and_then(|()| { + unveil_git().map_err(E::Unveil).and_then(|()| { + pledge_away_unveil(&mut promises) + .map_err(E::Pledge) + .and_then(|()| { + let mut map = BTreeMap::new(); + get_git_data(&mut map, git_dir.into_path_buf()).and_then(|()| { + pledge_away_all_but_stdio(&mut promises) + .map_err(E::Pledge) + .and_then(|()| write_results(map).map_err(E::Io)) + }) + }) + }) + }) + }) + }) + }) }