Unified · Split
| @@ -1234,7 +1234,9 @@ dependencies = [ · 1233 unmodified lines | |||
| 1234 | "document-features", | 1234 | "document-features", |
| 1235 | "git-testtools", | 1235 | "git-testtools", |
| 1236 | "itoa 1.0.3", | 1236 | "itoa 1.0.3", |
| 1237 | "once_cell", | ||
| 1237 | "serde", | 1238 | "serde", |
| 1239 | "thiserror", | ||
| 1238 | "time", | 1240 | "time", |
| 1239 | ] | 1241 | ] |
| 1240 | 1242 | ||
| @@ -1307,13 +1309,13 @@ dependencies = [ · 66 unmodified lines | |||
| 1307 | 1309 | ||
| 1308 | [[package]] | 1310 | [[package]] |
| 1309 | name = "git-hash" | 1311 | name = "git-hash" |
| 1310 | version = "0.9.8" | 1312 | version = "0.9.9" |
| 1311 | dependencies = [ | 1313 | dependencies = [ |
| 1312 | "document-features", | 1314 | "document-features", |
| 1313 | "git-testtools", | 1315 | "git-testtools", |
| 1314 | "hex", | 1316 | "hex", |
| 1315 | "quick-error", | ||
| 1316 | "serde", | 1317 | "serde", |
| 1318 | "thiserror", | ||
| 1317 | ] | 1319 | ] |
| 1318 | 1320 | ||
| 1319 | [[package]] | 1321 | [[package]] |
| @@ -3026,18 +3028,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" · 1706 unmodified lines | |||
| 3026 | 3028 | ||
| 3027 | [[package]] | 3029 | [[package]] |
| 3028 | name = "thiserror" | 3030 | name = "thiserror" |
| 3029 | version = "1.0.32" | 3031 | version = "1.0.33" |
| 3030 | source = "registry+https://github.com/rust-lang/crates.io-index" | 3032 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3031 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" | 3033 | checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" |
| 3032 | dependencies = [ | 3034 | dependencies = [ |
| 3033 | "thiserror-impl", | 3035 | "thiserror-impl", |
| 3034 | ] | 3036 | ] |
| 3035 | 3037 | ||
| 3036 | [[package]] | 3038 | [[package]] |
| 3037 | name = "thiserror-impl" | 3039 | name = "thiserror-impl" |
| 3038 | version = "1.0.32" | 3040 | version = "1.0.33" |
| 3039 | source = "registry+https://github.com/rust-lang/crates.io-index" | 3041 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3040 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" | 3042 | checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" |
| 3041 | dependencies = [ | 3043 | dependencies = [ |
| 3042 | "proc-macro2", | 3044 | "proc-macro2", |
| 3043 | "quote", | 3045 | "quote", |
| @@ -42,7 +42,7 @@ echo "in root: gitoxide CLI" · 41 unmodified lines | |||
| 42 | (enter git-traverse && indent cargo diet -n --package-size-limit 10KB) | 42 | (enter git-traverse && indent cargo diet -n --package-size-limit 10KB) |
| 43 | (enter git-url && indent cargo diet -n --package-size-limit 20KB) | 43 | (enter git-url && indent cargo diet -n --package-size-limit 20KB) |
| 44 | (enter git-validate && indent cargo diet -n --package-size-limit 5KB) | 44 | (enter git-validate && indent cargo diet -n --package-size-limit 5KB) |
| 45 | (enter git-date && indent cargo diet -n --package-size-limit 10KB) | 45 | (enter git-date && indent cargo diet -n --package-size-limit 15KB) |
| 46 | (enter git-filter && indent cargo diet -n --package-size-limit 5KB) | 46 | (enter git-filter && indent cargo diet -n --package-size-limit 5KB) |
| 47 | (enter git-lfs && indent cargo diet -n --package-size-limit 5KB) | 47 | (enter git-lfs && indent cargo diet -n --package-size-limit 5KB) |
| 48 | (enter git-note && indent cargo diet -n --package-size-limit 5KB) | 48 | (enter git-note && indent cargo diet -n --package-size-limit 5KB) |
| @@ -18,7 +18,7 @@ serde1 = ["serde", "git-hash/serde1", "bstr/serde1"] · 17 unmodified lines | |||
| 18 | 18 | ||
| 19 | [dependencies] | 19 | [dependencies] |
| 20 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1"] } | 20 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1"] } |
| 21 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 21 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 22 | git-chunk = { version = "^0.3.1", path = "../git-chunk" } | 22 | git-chunk = { version = "^0.3.1", path = "../git-chunk" } |
| 23 | 23 | ||
| 24 | bstr = { version = "0.2.13", default-features = false, features = ["std"] } | 24 | bstr = { version = "0.2.13", default-features = false, features = ["std"] } |
| @@ -6,6 +6,7 @@ license = "MIT/Apache-2.0" · 5 unmodified lines | |||
| 6 | description = "A WIP crate of the gitoxide project parsing dates the way git does" | 6 | description = "A WIP crate of the gitoxide project parsing dates the way git does" |
| 7 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] | 7 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] |
| 8 | edition = "2018" | 8 | edition = "2018" |
| 9 | include = ["src/**/*", "CHANGELOG.md"] | ||
| 9 | 10 | ||
| 10 | [lib] | 11 | [lib] |
| 11 | doctest = false | 12 | doctest = false |
| @@ -18,12 +19,14 @@ serde1 = ["serde", "bstr/serde1"] · 6 unmodified lines | |||
| 18 | bstr = { version = "0.2.13", default-features = false, features = ["std"]} | 19 | bstr = { version = "0.2.13", default-features = false, features = ["std"]} |
| 19 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} | 20 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} |
| 20 | itoa = "1.0.1" | 21 | itoa = "1.0.1" |
| 21 | time = { version = "0.3.2", default-features = false, features = ["local-offset", "formatting", "macros"] } | 22 | time = { version = "0.3.2", default-features = false, features = ["local-offset", "formatting", "macros", "parsing"] } |
| 23 | thiserror = "1.0.32" | ||
| 22 | 24 | ||
| 23 | document-features = { version = "0.2.0", optional = true } | 25 | document-features = { version = "0.2.0", optional = true } |
| 24 | 26 | ||
| 25 | [dev-dependencies] | 27 | [dev-dependencies] |
| 26 | git-testtools = { path = "../tests/tools"} | 28 | git-testtools = { path = "../tests/tools"} |
| 29 | once_cell = "1.12.0" | ||
| 27 | 30 | ||
| 28 | [package.metadata.docs.rs] | 31 | [package.metadata.docs.rs] |
| 29 | all-features = true | 32 | all-features = true |
| @@ -13,8 +13,9 @@ · 12 unmodified lines | |||
| 13 | /// | 13 | /// |
| 14 | pub mod time; | 14 | pub mod time; |
| 15 | 15 | ||
| 16 | mod parse; | 16 | /// |
| 17 | pub use parse::parse; | 17 | pub mod parse; |
| 18 | pub use parse::function::parse; | ||
| 18 | 19 | ||
| 19 | /// A timestamp with timezone. | 20 | /// A timestamp with timezone. |
| 20 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] | 21 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] |
| @@ -1,11 +1,134 @@ | |||
| 1 | use crate::Time; | 1 | #[derive(thiserror::Error, Debug)] |
| 2 | |||
| 3 | #[allow(missing_docs)] | 2 | #[allow(missing_docs)] |
| 4 | pub fn parse(input: &str) -> Option<Time> { | 3 | pub enum Error { |
| 5 | // TODO: actual implementation, this is just to not constantly fail | 4 | #[error("Cannot represent times before UNIX epoch at timestamp {timestamp}")] |
| 6 | if input == "1979-02-26 18:30:00" { | 5 | TooEarly { timestamp: i64 }, |
| 7 | Some(Time::new(42, 1800)) | 6 | #[error("Date string can not be parsed")] |
| 8 | } else { | 7 | InvalidDateString, |
| 9 | None | 8 | #[error("Dates past 2038 can not be represented.")] |
| 9 | InvalidDate(#[from] std::num::TryFromIntError), | ||
| 10 | #[error("Current time is missing.")] | ||
| 11 | MissingCurrentTime, | ||
| 12 | } | ||
| 13 | |||
| 14 | pub(crate) mod function { | ||
| 15 | use crate::parse::{relative, Error}; | ||
| 16 | use crate::time::format::{DEFAULT, ISO8601, ISO8601_STRICT, RFC2822, SHORT}; | ||
| 17 | use crate::time::Sign; | ||
| 18 | use crate::Time; | ||
| 19 | use std::convert::TryInto; | ||
| 20 | use std::str::FromStr; | ||
| 21 | use std::time::SystemTime; | ||
| 22 | use time::{Date, OffsetDateTime}; | ||
| 23 | |||
| 24 | #[allow(missing_docs)] | ||
| 25 | pub fn parse(input: &str, now: Option<SystemTime>) -> Result<Time, Error> { | ||
| 26 | // TODO: actual implementation, this is just to not constantly fail | ||
| 27 | if input == "1979-02-26 18:30:00" { | ||
| 28 | return Ok(Time::new(42, 1800)); | ||
| 29 | } | ||
| 30 | |||
| 31 | Ok(if let Ok(val) = Date::parse(input, SHORT) { | ||
| 32 | let val = val.with_hms(0, 0, 0).expect("date is in range").assume_utc(); | ||
| 33 | Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds()) | ||
| 34 | } else if let Ok(val) = OffsetDateTime::parse(input, RFC2822) { | ||
| 35 | Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds()) | ||
| 36 | } else if let Ok(val) = OffsetDateTime::parse(input, ISO8601) { | ||
| 37 | Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds()) | ||
| 38 | } else if let Ok(val) = OffsetDateTime::parse(input, ISO8601_STRICT) { | ||
| 39 | Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds()) | ||
| 40 | } else if let Ok(val) = OffsetDateTime::parse(input, DEFAULT) { | ||
| 41 | Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds()) | ||
| 42 | } else if let Ok(val) = u32::from_str(input) { | ||
| 43 | // Format::Unix | ||
| 44 | Time::new(val, 0) | ||
| 45 | } else if let Some(val) = parse_raw(input) { | ||
| 46 | // Format::Raw | ||
| 47 | val | ||
| 48 | } else if let Some(time) = relative::parse(input, now).transpose()? { | ||
| 49 | Time::new(timestamp(time)?, time.offset().whole_seconds()) | ||
| 50 | } else { | ||
| 51 | return Err(Error::InvalidDateString); | ||
| 52 | }) | ||
| 53 | } | ||
| 54 | |||
| 55 | fn timestamp(date: OffsetDateTime) -> Result<u32, Error> { | ||
| 56 | let timestamp = date.unix_timestamp(); | ||
| 57 | if timestamp < 0 { | ||
| 58 | Err(Error::TooEarly { timestamp }) | ||
| 59 | } else { | ||
| 60 | Ok(timestamp.try_into()?) | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | fn parse_raw(input: &str) -> Option<Time> { | ||
| 65 | let mut split = input.split_whitespace(); | ||
| 66 | let seconds_since_unix_epoch: u32 = split.next()?.parse().ok()?; | ||
| 67 | let offset = split.next()?; | ||
| 68 | if offset.len() != 5 { | ||
| 69 | return None; | ||
| 70 | } | ||
| 71 | let sign = if &offset[..1] == "-" { Sign::Plus } else { Sign::Minus }; | ||
| 72 | let hours: i32 = offset[1..3].parse().ok()?; | ||
| 73 | let minutes: i32 = offset[3..5].parse().ok()?; | ||
| 74 | let offset_in_seconds = hours * 3600 + minutes * 60; | ||
| 75 | let time = Time { | ||
| 76 | seconds_since_unix_epoch, | ||
| 77 | offset_in_seconds, | ||
| 78 | sign, | ||
| 79 | }; | ||
| 80 | Some(time) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | mod relative { | ||
| 85 | use crate::parse::Error; | ||
| 86 | use std::convert::TryInto; | ||
| 87 | use std::str::FromStr; | ||
| 88 | use std::time::SystemTime; | ||
| 89 | use time::{Duration, OffsetDateTime}; | ||
| 90 | |||
| 91 | fn parse_inner(input: &str) -> Option<Duration> { | ||
| 92 | let mut split = input.split_whitespace(); | ||
| 93 | let multiplier = i64::from_str(split.next()?).ok()?; | ||
| 94 | let period = split.next()?; | ||
| 95 | if split.next()? != "ago" { | ||
| 96 | return None; | ||
| 97 | } | ||
| 98 | duration(period, multiplier) | ||
| 99 | } | ||
| 100 | |||
| 101 | pub(crate) fn parse(input: &str, now: Option<SystemTime>) -> Option<Result<OffsetDateTime, Error>> { | ||
| 102 | parse_inner(input).map(|offset| { | ||
| 103 | let offset = std::time::Duration::from_secs(offset.whole_seconds().try_into().expect("positive value")); | ||
| 104 | now.ok_or(Error::MissingCurrentTime).map(|now| { | ||
| 105 | now.checked_sub(offset) | ||
| 106 | .expect("BUG: values can't be large enough to cause underflow") | ||
| 107 | .into() | ||
| 108 | }) | ||
| 109 | }) | ||
| 110 | } | ||
| 111 | |||
| 112 | fn duration(period: &str, multiplier: i64) -> Option<Duration> { | ||
| 113 | let period = period.strip_suffix('s').unwrap_or(period); | ||
| 114 | Some(match period { | ||
| 115 | "second" => Duration::seconds(multiplier), | ||
| 116 | "minute" => Duration::minutes(multiplier), | ||
| 117 | "hour" => Duration::hours(multiplier), | ||
| 118 | "day" => Duration::days(multiplier), | ||
| 119 | "week" => Duration::weeks(multiplier), | ||
| 120 | // TODO months & years | ||
| 121 | _ => return None, | ||
| 122 | }) | ||
| 123 | } | ||
| 124 | |||
| 125 | #[cfg(test)] | ||
| 126 | mod tests { | ||
| 127 | use super::*; | ||
| 128 | |||
| 129 | #[test] | ||
| 130 | fn two_weeks_ago() { | ||
| 131 | assert_eq!(parse_inner("2 weeks ago"), Some(Duration::weeks(2))); | ||
| 132 | } | ||
| 10 | } | 133 | } |
| 11 | } | 134 | } |
| @@ -0,0 +1,46 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | set -eu -o pipefail | ||
| 3 | |||
| 4 | git init; | ||
| 5 | |||
| 6 | function baseline() { | ||
| 7 | local test_date=$1 # first argument is the date to test | ||
| 8 | |||
| 9 | git -c section.key="$test_date" config --type=expiry-date section.key && status=0 || status=$? | ||
| 10 | { | ||
| 11 | echo "$test_date" | ||
| 12 | echo "$status" | ||
| 13 | if [ $status == 0 ] | ||
| 14 | then | ||
| 15 | git -c section.key="$test_date" config --type=expiry-date section.key | ||
| 16 | else | ||
| 17 | echo "-1" | ||
| 18 | fi | ||
| 19 | } >> baseline.git | ||
| 20 | } | ||
| 21 | |||
| 22 | # success | ||
| 23 | |||
| 24 | # date formats following to https://git-scm.com/docs/git-log#Documentation/git-log.txt---dateltformatgt | ||
| 25 | |||
| 26 | # short | ||
| 27 | # ODO | ||
| 28 | #baseline '2022-08-22' | ||
| 29 | # rfc2822 | ||
| 30 | baseline 'Thu, 18 Aug 2022 12:45:06 +0800' | ||
| 31 | # iso8601 | ||
| 32 | baseline '2022-08-17 22:04:58 +0200' | ||
| 33 | # iso8601_strict | ||
| 34 | baseline '2022-08-17T21:43:13+08:00' | ||
| 35 | # default | ||
| 36 | baseline 'Thu Sep 04 2022 10:45:06 -0400' | ||
| 37 | # unix | ||
| 38 | baseline '123456789' | ||
| 39 | # raw | ||
| 40 | baseline '1660874655 +0800' | ||
| 41 | |||
| 42 | # failing | ||
| 43 | |||
| 44 | # empty_input | ||
| 45 | baseline "" | ||
| 46 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | version https://git-lfs.github.com/spec/v1 | ||
| 2 | oid sha256:7495815a04eea1a5b657d944b85f2dac5bb73bbda43a409ba70a2094c23261cb | ||
| 3 | size 9188 | ||
| @@ -1,10 +1,51 @@ | |||
| 1 | use bstr::{BString, ByteSlice}; | ||
| 1 | use git_date::time::Sign; | 2 | use git_date::time::Sign; |
| 2 | use git_date::Time; | 3 | use git_date::Time; |
| 4 | use once_cell::sync::Lazy; | ||
| 5 | use std::collections::HashMap; | ||
| 6 | use std::str::FromStr; | ||
| 7 | use std::time::SystemTime; | ||
| 8 | |||
| 9 | type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>; | ||
| 10 | |||
| 11 | static BASELINE: Lazy<HashMap<BString, (usize, BString)>> = Lazy::new(|| { | ||
| 12 | let base = git_testtools::scripted_fixture_repo_read_only("generate_git_date_baseline.sh").unwrap(); | ||
| 13 | |||
| 14 | (|| -> Result<_> { | ||
| 15 | let mut map = HashMap::new(); | ||
| 16 | let baseline = std::fs::read(base.join("baseline.git"))?; | ||
| 17 | let mut lines = baseline.lines(); | ||
| 18 | while let Some(date_str) = lines.next() { | ||
| 19 | let exit_code = lines.next().expect("three lines per baseline").to_str()?.parse()?; | ||
| 20 | let output = lines.next().expect("three lines per baseline").into(); | ||
| 21 | map.insert(date_str.into(), (exit_code, output)); | ||
| 22 | } | ||
| 23 | Ok(map) | ||
| 24 | })() | ||
| 25 | .unwrap() | ||
| 26 | }); | ||
| 27 | |||
| 28 | #[test] | ||
| 29 | fn baseline() { | ||
| 30 | for (pattern, (exit_code, output)) in BASELINE.iter() { | ||
| 31 | let res = git_date::parse(pattern.to_str().expect("valid pattern"), Some(SystemTime::now())); | ||
| 32 | assert_eq!( | ||
| 33 | res.is_ok(), | ||
| 34 | *exit_code == 0, | ||
| 35 | "{pattern:?} disagrees with baseline: {res:?}" | ||
| 36 | ); | ||
| 37 | if *exit_code == 0 { | ||
| 38 | let actual = res.unwrap().seconds_since_unix_epoch; | ||
| 39 | let expected = u32::from_str(output.to_str().expect("valid utf")).expect("valid epoch value"); | ||
| 40 | assert_eq!(actual, expected, "{pattern:?} disagrees with baseline: {actual:?}") | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 3 | 44 | ||
| 4 | #[test] | 45 | #[test] |
| 5 | fn special_time_is_ok_for_now() { | 46 | fn special_time_is_ok_for_now() { |
| 6 | assert_eq!( | 47 | assert_eq!( |
| 7 | git_date::parse("1979-02-26 18:30:00").unwrap(), | 48 | git_date::parse("1979-02-26 18:30:00", Some(SystemTime::now())).unwrap(), |
| 8 | Time { | 49 | Time { |
| 9 | seconds_since_unix_epoch: 42, | 50 | seconds_since_unix_epoch: 42, |
| 10 | offset_in_seconds: 1800, | 51 | offset_in_seconds: 1800, |
| @@ -12,3 +53,78 @@ fn special_time_is_ok_for_now() { · 1 unmodified line | |||
| 12 | } | 53 | } |
| 13 | ); | 54 | ); |
| 14 | } | 55 | } |
| 56 | |||
| 57 | #[test] | ||
| 58 | fn short() { | ||
| 59 | assert_eq!( | ||
| 60 | git_date::parse("1979-02-26", Some(SystemTime::now())).expect("parsed date"), | ||
| 61 | Time { | ||
| 62 | seconds_since_unix_epoch: 288835200, | ||
| 63 | offset_in_seconds: 0, | ||
| 64 | sign: Sign::Plus, | ||
| 65 | }, | ||
| 66 | "could not parse with SHORT format" | ||
| 67 | ); | ||
| 68 | } | ||
| 69 | |||
| 70 | #[test] | ||
| 71 | fn rfc2822() { | ||
| 72 | assert_eq!( | ||
| 73 | git_date::parse("Thu, 18 Aug 2022 12:45:06 +0800", None).expect("parsed rfc2822 string"), | ||
| 74 | Time { | ||
| 75 | seconds_since_unix_epoch: 1660797906, | ||
| 76 | offset_in_seconds: 28800, | ||
| 77 | sign: Sign::Plus, | ||
| 78 | }, | ||
| 79 | "could not parse with RFC2822 format" | ||
| 80 | ); | ||
| 81 | } | ||
| 82 | |||
| 83 | #[test] | ||
| 84 | fn invalid_dates_can_be_produced_without_current_time() { | ||
| 85 | assert!(matches!( | ||
| 86 | git_date::parse("foobar", None).unwrap_err(), | ||
| 87 | git_date::parse::Error::InvalidDateString | ||
| 88 | )); | ||
| 89 | } | ||
| 90 | |||
| 91 | mod relative { | ||
| 92 | use git_date::parse::Error; | ||
| 93 | use git_date::time::Sign; | ||
| 94 | use std::time::SystemTime; | ||
| 95 | use time::{Duration, OffsetDateTime}; | ||
| 96 | |||
| 97 | #[test] | ||
| 98 | #[should_panic] // TODO: fix | ||
| 99 | fn large_offsets_can_panic() { | ||
| 100 | git_date::parse("999999999999999 weeks ago", Some(std::time::UNIX_EPOCH)).ok(); | ||
| 101 | } | ||
| 102 | |||
| 103 | #[test] | ||
| 104 | #[should_panic] // TODO: fix | ||
| 105 | fn large_offsets_can_panic_elsewhere() { | ||
| 106 | git_date::parse("9999999999 weeks ago", Some(std::time::UNIX_EPOCH)).ok(); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[test] | ||
| 110 | fn offset_leading_to_before_unix_epoch_cannot_be_represented() { | ||
| 111 | let err = git_date::parse("1 second ago", Some(std::time::UNIX_EPOCH)).unwrap_err(); | ||
| 112 | assert!(matches!(err, Error::TooEarly{timestamp} if timestamp == -1)); | ||
| 113 | } | ||
| 114 | |||
| 115 | #[test] | ||
| 116 | fn various() { | ||
| 117 | let now = Some(SystemTime::now()); | ||
| 118 | let two_weeks_ago = git_date::parse("2 weeks ago", now).expect("valid time"); | ||
| 119 | assert_eq!(Sign::Plus, two_weeks_ago.sign); | ||
| 120 | assert_eq!(0, two_weeks_ago.offset_in_seconds); | ||
| 121 | let expected = OffsetDateTime::from(now.unwrap()).saturating_sub(Duration::weeks(2)); | ||
| 122 | // account for the loss of precision when creating `Time` with seconds | ||
| 123 | let expected = expected.replace_nanosecond(0).unwrap(); | ||
| 124 | assert_eq!( | ||
| 125 | OffsetDateTime::from_unix_timestamp(two_weeks_ago.seconds_since_unix_epoch as i64).expect("valid datetime"), | ||
| 126 | expected, | ||
| 127 | "relative times differ" | ||
| 128 | ); | ||
| 129 | } | ||
| 130 | } | ||
| @@ -12,7 +12,7 @@ include = ["src/**/*"] · 11 unmodified lines | |||
| 12 | doctest = false | 12 | doctest = false |
| 13 | 13 | ||
| 14 | [dependencies] | 14 | [dependencies] |
| 15 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 15 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 16 | git-object = { version = "^0.20.3", path = "../git-object" } | 16 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 17 | thiserror = "1.0.32" | 17 | thiserror = "1.0.32" |
| 18 | 18 | ||
| @@ -15,7 +15,7 @@ doctest = false · 14 unmodified lines | |||
| 15 | git-sec = { version = "^0.3.1", path = "../git-sec", features = ["thiserror"] } | 15 | git-sec = { version = "^0.3.1", path = "../git-sec", features = ["thiserror"] } |
| 16 | git-path = { version = "^0.4.2", path = "../git-path" } | 16 | git-path = { version = "^0.4.2", path = "../git-path" } |
| 17 | git-ref = { version = "^0.15.2", path = "../git-ref" } | 17 | git-ref = { version = "^0.15.2", path = "../git-ref" } |
| 18 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 18 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 19 | 19 | ||
| 20 | bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] } | 20 | bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] } |
| 21 | thiserror = "1.0.26" | 21 | thiserror = "1.0.26" |
| @@ -92,7 +92,7 @@ required-features = ["io-pipe"] · 91 unmodified lines | |||
| 92 | [dependencies] | 92 | [dependencies] |
| 93 | #! ### Optional Dependencies | 93 | #! ### Optional Dependencies |
| 94 | 94 | ||
| 95 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 95 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 96 | 96 | ||
| 97 | 97 | ||
| 98 | 98 | ||
| @@ -5,8 +5,46 @@ All notable changes to this project will be documented in this file. · 4 unmodified lines | |||
| 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | 7 | ||
| 8 | ## 0.9.9 (2022-09-02) | ||
| 9 | |||
| 10 | ### Bug Fixes | ||
| 11 | |||
| 12 | - <csr-id-aaed7eaf4887d5e499437d45c8284bc8941da2ac/> don't assume hex-only characters in `ObjectId::from_hex(…)`. | ||
| 13 | |||
| 14 | |||
| 15 | ### Refactor | ||
| 16 | |||
| 17 | - <csr-id-7926f47ebc34e11c769acfd3441ab391fc1b9b36/> replace `quickerror` with `thiserror` | ||
| 18 | |||
| 19 | ### Commit Statistics | ||
| 20 | |||
| 21 | <csr-read-only-do-not-edit/> | ||
| 22 | |||
| 23 | - 6 commits contributed to the release. | ||
| 24 | - 9 days passed between releases. | ||
| 25 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). | ||
| 26 | - 1 unique issue was worked on: [#522](https://github.com/Byron/gitoxide/issues/522) | ||
| 27 | |||
| 28 | ### Commit Details | ||
| 29 | |||
| 30 | <csr-read-only-do-not-edit/> | ||
| 31 | |||
| 32 | <details><summary>view details</summary> | ||
| 33 | |||
| 34 | * **[#522](https://github.com/Byron/gitoxide/issues/522)** | ||
| 35 | - don't assume hex-only characters in `ObjectId::from_hex(…)`. ([`aaed7ea`](https://github.com/Byron/gitoxide/commit/aaed7eaf4887d5e499437d45c8284bc8941da2ac)) | ||
| 36 | - refactor ([`f3bcddf`](https://github.com/Byron/gitoxide/commit/f3bcddff931e50472e7a3e8c2c60f3cd565bfa56)) | ||
| 37 | - refactor ([`0f0de2b`](https://github.com/Byron/gitoxide/commit/0f0de2ba7837e2044e22a16b6d5e20b3137e9691)) | ||
| 38 | * **Uncategorized** | ||
| 39 | - fix docs ([`71cb9ea`](https://github.com/Byron/gitoxide/commit/71cb9eaefb792656b6380fdcc760c2234f9b9fa7)) | ||
| 40 | - replace `quickerror` with `thiserror` ([`7926f47`](https://github.com/Byron/gitoxide/commit/7926f47ebc34e11c769acfd3441ab391fc1b9b36)) | ||
| 41 | - some more tests ([`400c6cb`](https://github.com/Byron/gitoxide/commit/400c6cb14fe24b5e947f0c7f3a105deecd3b2f84)) | ||
| 42 | </details> | ||
| 43 | |||
| 8 | ## 0.9.8 (2022-08-24) | 44 | ## 0.9.8 (2022-08-24) |
| 9 | 45 | ||
| 46 | <csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> | ||
| 47 | |||
| 10 | ### Chore | 48 | ### Chore |
| 11 | 49 | ||
| 12 | - <csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> uniformize deny attributes | 50 | - <csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> uniformize deny attributes |
| @@ -20,9 +58,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 7 unmodified lines | |||
| 20 | 58 | ||
| 21 | <csr-read-only-do-not-edit/> | 59 | <csr-read-only-do-not-edit/> |
| 22 | 60 | ||
| 23 | - 4 commits contributed to the release over the course of 3 calendar days. | 61 | - 5 commits contributed to the release over the course of 3 calendar days. |
| 24 | - 8 days passed between releases. | 62 | - 8 days passed between releases. |
| 25 | - 3 commits where understood as [conventional](https://www.conventionalcommits.org). | 63 | - 3 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 26 | - 0 issues like '(#ID)' where seen in commit messages | 64 | - 0 issues like '(#ID)' where seen in commit messages |
| 27 | 65 | ||
| 28 | ### Commit Details | 66 | ### Commit Details |
| @@ -32,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 3 unmodified lines | |||
| 32 | <details><summary>view details</summary> | 70 | <details><summary>view details</summary> |
| 33 | 71 | ||
| 34 | * **Uncategorized** | 72 | * **Uncategorized** |
| 73 | - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d)) | ||
| 35 | - Merge branch 'example-new-repo' ([`946dd3a`](https://github.com/Byron/gitoxide/commit/946dd3a80522ef437e09528a93aa1433f01b0ee8)) | 74 | - Merge branch 'example-new-repo' ([`946dd3a`](https://github.com/Byron/gitoxide/commit/946dd3a80522ef437e09528a93aa1433f01b0ee8)) |
| 36 | - use docsrs feature in code to show what is feature-gated automatically on docs.rs ([`b1c40b0`](https://github.com/Byron/gitoxide/commit/b1c40b0364ef092cd52d03b34f491b254816b18d)) | 75 | - use docsrs feature in code to show what is feature-gated automatically on docs.rs ([`b1c40b0`](https://github.com/Byron/gitoxide/commit/b1c40b0364ef092cd52d03b34f491b254816b18d)) |
| 37 | - uniformize deny attributes ([`f7f136d`](https://github.com/Byron/gitoxide/commit/f7f136dbe4f86e7dee1d54835c420ec07c96cd78)) | 76 | - uniformize deny attributes ([`f7f136d`](https://github.com/Byron/gitoxide/commit/f7f136dbe4f86e7dee1d54835c420ec07c96cd78)) |
| @@ -53,7 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 15 unmodified lines | |||
| 53 | 92 | ||
| 54 | - 9 commits contributed to the release over the course of 19 calendar days. | 93 | - 9 commits contributed to the release over the course of 19 calendar days. |
| 55 | - 24 days passed between releases. | 94 | - 24 days passed between releases. |
| 56 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | 95 | - 1 commit was understood as [conventional](https://www.conventionalcommits.org). |
| 57 | - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) | 96 | - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) |
| 58 | 97 | ||
| 59 | ### Thanks Clippy | 98 | ### Thanks Clippy |
| @@ -91,7 +130,7 @@ This is a maintenance release with no functional changes. · 31 unmodified lines | |||
| 91 | 130 | ||
| 92 | - 3 commits contributed to the release over the course of 12 calendar days. | 131 | - 3 commits contributed to the release over the course of 12 calendar days. |
| 93 | - 39 days passed between releases. | 132 | - 39 days passed between releases. |
| 94 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 133 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 95 | - 0 issues like '(#ID)' where seen in commit messages | 134 | - 0 issues like '(#ID)' where seen in commit messages |
| 96 | 135 | ||
| 97 | ### Commit Details | 136 | ### Commit Details |
| @@ -120,7 +159,7 @@ This is a maintenance release with no functional changes. · 22 unmodified lines | |||
| 120 | 159 | ||
| 121 | - 3 commits contributed to the release over the course of 5 calendar days. | 160 | - 3 commits contributed to the release over the course of 5 calendar days. |
| 122 | - 25 days passed between releases. | 161 | - 25 days passed between releases. |
| 123 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | 162 | - 1 commit was understood as [conventional](https://www.conventionalcommits.org). |
| 124 | - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) | 163 | - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) |
| 125 | 164 | ||
| 126 | ### Commit Details | 165 | ### Commit Details |
| @@ -151,7 +190,7 @@ This is a maintenance release with no functional changes. · 24 unmodified lines | |||
| 151 | 190 | ||
| 152 | - 10 commits contributed to the release over the course of 46 calendar days. | 191 | - 10 commits contributed to the release over the course of 46 calendar days. |
| 153 | - 46 days passed between releases. | 192 | - 46 days passed between releases. |
| 154 | - 2 commits where understood as [conventional](https://www.conventionalcommits.org). | 193 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 155 | - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#413](https://github.com/Byron/gitoxide/issues/413) | 194 | - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#413](https://github.com/Byron/gitoxide/issues/413) |
| 156 | 195 | ||
| 157 | ### Commit Details | 196 | ### Commit Details |
| @@ -194,7 +233,7 @@ This is a maintenance release with no functional changes. · 36 unmodified lines | |||
| 194 | 233 | ||
| 195 | - 17 commits contributed to the release over the course of 54 calendar days. | 234 | - 17 commits contributed to the release over the course of 54 calendar days. |
| 196 | - 59 days passed between releases. | 235 | - 59 days passed between releases. |
| 197 | - 2 commits where understood as [conventional](https://www.conventionalcommits.org). | 236 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 198 | - 4 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#331](https://github.com/Byron/gitoxide/issues/331) | 237 | - 4 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#331](https://github.com/Byron/gitoxide/issues/331) |
| 199 | 238 | ||
| 200 | ### Commit Details | 239 | ### Commit Details |
| @@ -243,7 +282,7 @@ A automated maintenance release without impact to the public API. · 42 unmodified lines | |||
| 243 | 282 | ||
| 244 | - 4 commits contributed to the release over the course of 8 calendar days. | 283 | - 4 commits contributed to the release over the course of 8 calendar days. |
| 245 | - 8 days passed between releases. | 284 | - 8 days passed between releases. |
| 246 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | 285 | - 1 commit was understood as [conventional](https://www.conventionalcommits.org). |
| 247 | - 1 unique issue was worked on: [#298](https://github.com/Byron/gitoxide/issues/298) | 286 | - 1 unique issue was worked on: [#298](https://github.com/Byron/gitoxide/issues/298) |
| 248 | 287 | ||
| 249 | ### Commit Details | 288 | ### Commit Details |
| @@ -315,7 +354,7 @@ A automated maintenance release without impact to the public API. · 65 unmodified lines | |||
| 315 | 354 | ||
| 316 | - 3 commits contributed to the release over the course of 3 calendar days. | 355 | - 3 commits contributed to the release over the course of 3 calendar days. |
| 317 | - 4 days passed between releases. | 356 | - 4 days passed between releases. |
| 318 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | 357 | - 1 commit was understood as [conventional](https://www.conventionalcommits.org). |
| 319 | - 0 issues like '(#ID)' where seen in commit messages | 358 | - 0 issues like '(#ID)' where seen in commit messages |
| 320 | 359 | ||
| 321 | ### Commit Details | 360 | ### Commit Details |
| @@ -381,7 +420,7 @@ A automated maintenance release without impact to the public API. · 59 unmodified lines | |||
| 381 | 420 | ||
| 382 | - 25 commits contributed to the release over the course of 30 calendar days. | 421 | - 25 commits contributed to the release over the course of 30 calendar days. |
| 383 | - 92 days passed between releases. | 422 | - 92 days passed between releases. |
| 384 | - 16 commits where understood as [conventional](https://www.conventionalcommits.org). | 423 | - 16 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 385 | - 3 unique issues were worked on: [#279](https://github.com/Byron/gitoxide/issues/279), [#287](https://github.com/Byron/gitoxide/issues/287), [#293](https://github.com/Byron/gitoxide/issues/293) | 424 | - 3 unique issues were worked on: [#279](https://github.com/Byron/gitoxide/issues/279), [#287](https://github.com/Byron/gitoxide/issues/287), [#293](https://github.com/Byron/gitoxide/issues/293) |
| 386 | 425 | ||
| 387 | ### Commit Details | 426 | ### Commit Details |
| @@ -433,7 +472,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 45 unmodified lines | |||
| 433 | 472 | ||
| 434 | - 5 commits contributed to the release over the course of 3 calendar days. | 473 | - 5 commits contributed to the release over the course of 3 calendar days. |
| 435 | - 3 days passed between releases. | 474 | - 3 days passed between releases. |
| 436 | - 1 commit where understood as [conventional](https://www.conventionalcommits.org). | 475 | - 1 commit was understood as [conventional](https://www.conventionalcommits.org). |
| 437 | - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222) | 476 | - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222) |
| 438 | 477 | ||
| 439 | ## v0.7.0 (2021-10-15) | 478 | ## v0.7.0 (2021-10-15) |
| @@ -452,7 +491,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 12 unmodified lines | |||
| 452 | 491 | ||
| 453 | - 16 commits contributed to the release over the course of 11 calendar days. | 492 | - 16 commits contributed to the release over the course of 11 calendar days. |
| 454 | - 38 days passed between releases. | 493 | - 38 days passed between releases. |
| 455 | - 2 commits where understood as [conventional](https://www.conventionalcommits.org). | 494 | - 2 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 456 | - 1 unique issue was worked on: [#198](https://github.com/Byron/gitoxide/issues/198) | 495 | - 1 unique issue was worked on: [#198](https://github.com/Byron/gitoxide/issues/198) |
| 457 | 496 | ||
| 458 | ### Commit Details | 497 | ### Commit Details |
| @@ -494,7 +533,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 35 unmodified lines | |||
| 494 | 533 | ||
| 495 | - 2 commits contributed to the release over the course of 1 calendar day. | 534 | - 2 commits contributed to the release over the course of 1 calendar day. |
| 496 | - 20 days passed between releases. | 535 | - 20 days passed between releases. |
| 497 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 536 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 498 | - 0 issues like '(#ID)' where seen in commit messages | 537 | - 0 issues like '(#ID)' where seen in commit messages |
| 499 | 538 | ||
| 500 | ### Commit Details | 539 | ### Commit Details |
| @@ -516,7 +555,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines | |||
| 516 | 555 | ||
| 517 | - 2 commits contributed to the release over the course of 1 calendar day. | 556 | - 2 commits contributed to the release over the course of 1 calendar day. |
| 518 | - 6 days passed between releases. | 557 | - 6 days passed between releases. |
| 519 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 558 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 520 | - 0 issues like '(#ID)' where seen in commit messages | 559 | - 0 issues like '(#ID)' where seen in commit messages |
| 521 | 560 | ||
| 522 | ### Commit Details | 561 | ### Commit Details |
| @@ -538,7 +577,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines | |||
| 538 | 577 | ||
| 539 | - 10 commits contributed to the release over the course of 74 calendar days. | 578 | - 10 commits contributed to the release over the course of 74 calendar days. |
| 540 | - 102 days passed between releases. | 579 | - 102 days passed between releases. |
| 541 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 580 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 542 | - 0 issues like '(#ID)' where seen in commit messages | 581 | - 0 issues like '(#ID)' where seen in commit messages |
| 543 | 582 | ||
| 544 | ### Commit Details | 583 | ### Commit Details |
| @@ -574,7 +613,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 29 unmodified lines | |||
| 574 | 613 | ||
| 575 | - 5 commits contributed to the release over the course of 16 calendar days. | 614 | - 5 commits contributed to the release over the course of 16 calendar days. |
| 576 | - 21 days passed between releases. | 615 | - 21 days passed between releases. |
| 577 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 616 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 578 | - 0 issues like '(#ID)' where seen in commit messages | 617 | - 0 issues like '(#ID)' where seen in commit messages |
| 579 | 618 | ||
| 580 | ### Commit Details | 619 | ### Commit Details |
| @@ -599,7 +638,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 18 unmodified lines | |||
| 599 | 638 | ||
| 600 | - 20 commits contributed to the release over the course of 1 calendar day. | 639 | - 20 commits contributed to the release over the course of 1 calendar day. |
| 601 | - 86 days passed between releases. | 640 | - 86 days passed between releases. |
| 602 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 641 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 603 | - 1 unique issue was worked on: [#63](https://github.com/Byron/gitoxide/issues/63) | 642 | - 1 unique issue was worked on: [#63](https://github.com/Byron/gitoxide/issues/63) |
| 604 | 643 | ||
| 605 | ### Commit Details | 644 | ### Commit Details |
| @@ -640,7 +679,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 34 unmodified lines | |||
| 640 | 679 | ||
| 641 | - 3 commits contributed to the release over the course of 26 calendar days. | 680 | - 3 commits contributed to the release over the course of 26 calendar days. |
| 642 | - 27 days passed between releases. | 681 | - 27 days passed between releases. |
| 643 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 682 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 644 | - 0 issues like '(#ID)' where seen in commit messages | 683 | - 0 issues like '(#ID)' where seen in commit messages |
| 645 | 684 | ||
| 646 | ### Commit Details | 685 | ### Commit Details |
| @@ -662,7 +701,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines | |||
| 662 | <csr-read-only-do-not-edit/> | 701 | <csr-read-only-do-not-edit/> |
| 663 | 702 | ||
| 664 | - 2 commits contributed to the release. | 703 | - 2 commits contributed to the release. |
| 665 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 704 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 666 | - 0 issues like '(#ID)' where seen in commit messages | 705 | - 0 issues like '(#ID)' where seen in commit messages |
| 667 | 706 | ||
| 668 | ### Commit Details | 707 | ### Commit Details |
| @@ -683,7 +722,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 14 unmodified lines | |||
| 683 | <csr-read-only-do-not-edit/> | 722 | <csr-read-only-do-not-edit/> |
| 684 | 723 | ||
| 685 | - 1 commit contributed to the release. | 724 | - 1 commit contributed to the release. |
| 686 | - 0 commits where understood as [conventional](https://www.conventionalcommits.org). | 725 | - 0 commits were understood as [conventional](https://www.conventionalcommits.org). |
| 687 | - 0 issues like '(#ID)' where seen in commit messages | 726 | - 0 issues like '(#ID)' where seen in commit messages |
| 688 | 727 | ||
| 689 | ### Commit Details | 728 | ### Commit Details |
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "git-hash" | 2 | name = "git-hash" |
| 3 | version = "0.9.8" | 3 | version = "0.9.9" |
| 4 | description = "Borrowed and owned git hash digests used to identify git objects" | 4 | description = "Borrowed and owned git hash digests used to identify git objects" |
| 5 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] | 5 | authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"] |
| 6 | repository = "https://github.com/Byron/gitoxide" | 6 | repository = "https://github.com/Byron/gitoxide" |
| @@ -17,7 +17,7 @@ test = false · 10 unmodified lines | |||
| 17 | serde1 = ["serde"] | 17 | serde1 = ["serde"] |
| 18 | 18 | ||
| 19 | [dependencies] | 19 | [dependencies] |
| 20 | quick-error = "2.0.0" | 20 | thiserror = "1.0.33" |
| 21 | hex = "0.4.2" | 21 | hex = "0.4.2" |
| 22 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } | 22 | serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } |
| 23 | 23 | ||
| @@ -0,0 +1,122 @@ | |||
| 1 | use crate::{oid, Kind, ObjectId}; | ||
| 2 | use std::convert::TryFrom; | ||
| 3 | use std::str::FromStr; | ||
| 4 | |||
| 5 | impl Default for Kind { | ||
| 6 | fn default() -> Self { | ||
| 7 | Kind::Sha1 | ||
| 8 | } | ||
| 9 | } | ||
| 10 | |||
| 11 | impl TryFrom<u8> for Kind { | ||
| 12 | type Error = u8; | ||
| 13 | |||
| 14 | fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
| 15 | Ok(match value { | ||
| 16 | 1 => Kind::Sha1, | ||
| 17 | unknown => return Err(unknown), | ||
| 18 | }) | ||
| 19 | } | ||
| 20 | } | ||
| 21 | |||
| 22 | impl FromStr for Kind { | ||
| 23 | type Err = String; | ||
| 24 | |||
| 25 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| 26 | Ok(match s { | ||
| 27 | "sha1" | "SHA1" => Kind::Sha1, | ||
| 28 | other => return Err(other.into()), | ||
| 29 | }) | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | impl std::fmt::Display for Kind { | ||
| 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 35 | match self { | ||
| 36 | Kind::Sha1 => f.write_str("SHA1"), | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl Kind { | ||
| 42 | /// Returns the shortest hash we support | ||
| 43 | #[inline] | ||
| 44 | pub const fn shortest() -> Self { | ||
| 45 | Self::Sha1 | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Returns the longest hash we support | ||
| 49 | #[inline] | ||
| 50 | pub const fn longest() -> Self { | ||
| 51 | Self::Sha1 | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Returns a buffer suitable to hold the longest possible hash in hex. | ||
| 55 | #[inline] | ||
| 56 | pub const fn hex_buf() -> [u8; Kind::longest().len_in_hex()] { | ||
| 57 | [0u8; Kind::longest().len_in_hex()] | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Returns a buffer suitable to hold the longest possible hash as raw bytes. | ||
| 61 | #[inline] | ||
| 62 | pub const fn buf() -> [u8; Kind::longest().len_in_bytes()] { | ||
| 63 | [0u8; Kind::longest().len_in_bytes()] | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Returns the amount of ascii-characters needed to encode this has in hex | ||
| 67 | #[inline] | ||
| 68 | pub const fn len_in_hex(&self) -> usize { | ||
| 69 | match self { | ||
| 70 | Kind::Sha1 => 40, | ||
| 71 | } | ||
| 72 | } | ||
| 73 | /// Returns the amount of bytes taken up by the hash of the current kind | ||
| 74 | #[inline] | ||
| 75 | pub const fn len_in_bytes(&self) -> usize { | ||
| 76 | match self { | ||
| 77 | Kind::Sha1 => 20, | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Returns the kind of hash that would fit the given `hex_len`, or `None` if there is no fitting hash. | ||
| 82 | /// Note that 0 as `hex_len` fits always yields Sha1. | ||
| 83 | #[inline] | ||
| 84 | pub const fn from_hex_len(hex_len: usize) -> Option<Self> { | ||
| 85 | Some(match hex_len { | ||
| 86 | 0..=40 => Kind::Sha1, | ||
| 87 | _ => return None, | ||
| 88 | }) | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Converts a size in bytes as obtained by `Kind::len_in_bytes()` into the corresponding hash kind, if possible. | ||
| 92 | /// | ||
| 93 | /// **Panics** if the hash length doesn't match a known hash. | ||
| 94 | /// | ||
| 95 | /// NOTE that this method isn't public as it shouldn't be encouraged to assume all hashes have the same length. | ||
| 96 | /// However, if there should be such a thing, our `oid` implementation will have to become an enum and it's pretty breaking | ||
| 97 | /// to the way it's currently being used as auto-dereffing doesn't work anymore. Let's hope it won't happen. | ||
| 98 | // TODO: make 'const' once Rust 1.57 is more readily available in projects using 'gitoxide'. | ||
| 99 | #[inline] | ||
| 100 | pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { | ||
| 101 | match bytes { | ||
| 102 | 20 => Kind::Sha1, | ||
| 103 | _ => panic!("BUG: must be called only with valid hash lengths produced by len_in_bytes()"), | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Create a null-id of our hash kind. | ||
| 108 | #[inline] | ||
| 109 | pub fn null_ref(&self) -> &'static oid { | ||
| 110 | match self { | ||
| 111 | Kind::Sha1 => oid::null_sha1(), | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /// Create a null-id of our hash kind. | ||
| 116 | #[inline] | ||
| 117 | pub const fn null(&self) -> ObjectId { | ||
| 118 | match self { | ||
| 119 | Kind::Sha1 => ObjectId::null_sha1(), | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| @@ -9,57 +9,23 @@ · 8 unmodified lines | |||
| 9 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] | 9 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] |
| 10 | #![deny(missing_docs, rust_2018_idioms, unsafe_code)] | 10 | #![deny(missing_docs, rust_2018_idioms, unsafe_code)] |
| 11 | 11 | ||
| 12 | #[path = "oid.rs"] | ||
| 12 | mod borrowed; | 13 | mod borrowed; |
| 13 | |||
| 14 | use std::{convert::TryFrom, str::FromStr}; | ||
| 15 | |||
| 16 | pub use borrowed::oid; | 14 | pub use borrowed::oid; |
| 17 | 15 | ||
| 18 | mod owned; | 16 | mod object_id; |
| 19 | pub use owned::{prefix, ObjectId, Prefix}; | 17 | pub use object_id::{decode, ObjectId}; |
| 20 | |||
| 21 | #[allow(missing_docs)] | ||
| 22 | pub mod decode { | ||
| 23 | use std::str::FromStr; | ||
| 24 | |||
| 25 | use quick_error::quick_error; | ||
| 26 | |||
| 27 | use crate::owned::ObjectId; | ||
| 28 | |||
| 29 | quick_error! { | ||
| 30 | /// An error returned by [`ObjectId::from_40_bytes_in_hex()`] | ||
| 31 | #[derive(Debug)] | ||
| 32 | #[allow(missing_docs)] | ||
| 33 | pub enum Error { | ||
| 34 | InvalidHexEncodingLength(length: usize) { | ||
| 35 | display("A hash sized {} hexadecimal characters is invalid", length) | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Hash decoding | ||
| 41 | impl ObjectId { | ||
| 42 | /// Create an instance from a `buffer` of 40 bytes encoded with hexadecimal notation. | ||
| 43 | /// | ||
| 44 | /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] | ||
| 45 | pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> { | ||
| 46 | use hex::FromHex; | ||
| 47 | match buffer.len() { | ||
| 48 | 40 => Ok(ObjectId::Sha1( | ||
| 49 | <[u8; 20]>::from_hex(buffer).expect("our length check is correct thus we can decode hex"), | ||
| 50 | )), | ||
| 51 | len => Err(Error::InvalidHexEncodingLength(len)), | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | 18 | ||
| 56 | impl FromStr for ObjectId { | 19 | /// |
| 57 | type Err = Error; | 20 | pub mod prefix; |
| 58 | 21 | ||
| 59 | fn from_str(s: &str) -> Result<Self, Self::Err> { | 22 | /// An partial owned hash possibly identifying an object uniquely, |
| 60 | Self::from_hex(s.as_bytes()) | 23 | /// whose non-prefix bytes are zeroed. |
| 61 | } | 24 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)] |
| 62 | } | 25 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] |
| 26 | pub struct Prefix { | ||
| 27 | bytes: ObjectId, | ||
| 28 | hex_len: usize, | ||
| 63 | } | 29 | } |
| 64 | 30 | ||
| 65 | /// The size of a SHA1 hash digest in bytes | 31 | /// The size of a SHA1 hash digest in bytes |
| @@ -73,121 +39,4 @@ pub enum Kind { · 7 unmodified lines | |||
| 73 | Sha1 = 1, | 39 | Sha1 = 1, |
| 74 | } | 40 | } |
| 75 | 41 | ||
| 76 | impl Default for Kind { | 42 | mod kind; |
| 77 | fn default() -> Self { | ||
| 78 | Kind::Sha1 | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | impl TryFrom<u8> for Kind { | ||
| 83 | type Error = u8; | ||
| 84 | |||
| 85 | fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
| 86 | Ok(match value { | ||
| 87 | 1 => Kind::Sha1, | ||
| 88 | unknown => return Err(unknown), | ||
| 89 | }) | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | impl FromStr for Kind { | ||
| 94 | type Err = String; | ||
| 95 | |||
| 96 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| 97 | Ok(match s { | ||
| 98 | "sha1" | "SHA1" => Kind::Sha1, | ||
| 99 | other => return Err(other.into()), | ||
| 100 | }) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | impl std::fmt::Display for Kind { | ||
| 105 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 106 | match self { | ||
| 107 | Kind::Sha1 => f.write_str("SHA1"), | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | impl Kind { | ||
| 113 | /// Returns the shortest hash we support | ||
| 114 | #[inline] | ||
| 115 | pub const fn shortest() -> Self { | ||
| 116 | Self::Sha1 | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Returns the longest hash we support | ||
| 120 | #[inline] | ||
| 121 | pub const fn longest() -> Self { | ||
| 122 | Self::Sha1 | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Returns a buffer suitable to hold the longest possible hash in hex. | ||
| 126 | #[inline] | ||
| 127 | pub const fn hex_buf() -> [u8; Kind::longest().len_in_hex()] { | ||
| 128 | [0u8; Kind::longest().len_in_hex()] | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Returns a buffer suitable to hold the longest possible hash as raw bytes. | ||
| 132 | #[inline] | ||
| 133 | pub const fn buf() -> [u8; Kind::longest().len_in_bytes()] { | ||
| 134 | [0u8; Kind::longest().len_in_bytes()] | ||
| 135 | } | ||
| 136 | |||
| 137 | /// Returns the amount of ascii-characters needed to encode this has in hex | ||
| 138 | #[inline] | ||
| 139 | pub const fn len_in_hex(&self) -> usize { | ||
| 140 | match self { | ||
| 141 | Kind::Sha1 => 40, | ||
| 142 | } | ||
| 143 | } | ||
| 144 | /// Returns the amount of bytes taken up by the hash of the current kind | ||
| 145 | #[inline] | ||
| 146 | pub const fn len_in_bytes(&self) -> usize { | ||
| 147 | match self { | ||
| 148 | Kind::Sha1 => 20, | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Returns the kind of hash that would fit the given `hex_len`, or `None` if there is no fitting hash. | ||
| 153 | /// Note that 0 as `hex_len` fits always yields Sha1. | ||
| 154 | #[inline] | ||
| 155 | pub const fn from_hex_len(hex_len: usize) -> Option<Self> { | ||
| 156 | Some(match hex_len { | ||
| 157 | 0..=40 => Kind::Sha1, | ||
| 158 | _ => return None, | ||
| 159 | }) | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Converts a size in bytes as obtained by `Kind::len_in_bytes()` into the corresponding hash kind, if possible. | ||
| 163 | /// | ||
| 164 | /// **Panics** if the hash length doesn't match a known hash. | ||
| 165 | /// | ||
| 166 | /// NOTE that this method isn't public as it shouldn't be encouraged to assume all hashes have the same length. | ||
| 167 | /// However, if there should be such a thing, our `oid` implementation will have to become an enum and it's pretty breaking | ||
| 168 | /// to the way it's currently being used as auto-dereffing doesn't work anymore. Let's hope it won't happen. | ||
| 169 | // TODO: make 'const' once Rust 1.57 is more readily available in projects using 'gitoxide'. | ||
| 170 | #[inline] | ||
| 171 | pub(crate) fn from_len_in_bytes(bytes: usize) -> Self { | ||
| 172 | match bytes { | ||
| 173 | 20 => Kind::Sha1, | ||
| 174 | _ => panic!("BUG: must be called only with valid hash lengths produced by len_in_bytes()"), | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | /// Create a null-id of our hash kind. | ||
| 179 | #[inline] | ||
| 180 | pub fn null_ref(&self) -> &'static oid { | ||
| 181 | match self { | ||
| 182 | Kind::Sha1 => oid::null_sha1(), | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | /// Create a null-id of our hash kind. | ||
| 187 | #[inline] | ||
| 188 | pub const fn null(&self) -> ObjectId { | ||
| 189 | match self { | ||
| 190 | Kind::Sha1 => ObjectId::null_sha1(), | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| @@ -2,18 +2,6 @@ use std::{borrow::Borrow, convert::TryInto, fmt, ops::Deref}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; | 3 | use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; |
| 4 | 4 | ||
| 5 | /// An partial owned hash possibly identifying an object uniquely, | ||
| 6 | /// whose non-prefix bytes are zeroed. | ||
| 7 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)] | ||
| 8 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 9 | pub struct Prefix { | ||
| 10 | bytes: ObjectId, | ||
| 11 | hex_len: usize, | ||
| 12 | } | ||
| 13 | |||
| 14 | /// | ||
| 15 | pub mod prefix; | ||
| 16 | |||
| 17 | /// An owned hash identifying objects, most commonly Sha1 | 5 | /// An owned hash identifying objects, most commonly Sha1 |
| 18 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] | 6 | #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] |
| 19 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | 7 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] |
| @@ -22,15 +10,49 @@ pub enum ObjectId { · 2 unmodified lines | |||
| 22 | Sha1([u8; SIZE_OF_SHA1_DIGEST]), | 10 | Sha1([u8; SIZE_OF_SHA1_DIGEST]), |
| 23 | } | 11 | } |
| 24 | 12 | ||
| 25 | impl std::fmt::Debug for ObjectId { | 13 | #[allow(missing_docs)] |
| 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 14 | pub mod decode { |
| 27 | match self { | 15 | use std::str::FromStr; |
| 28 | ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, | 16 | |
| 17 | use crate::object_id::ObjectId; | ||
| 18 | |||
| 19 | /// An error returned by [`ObjectId::from_40_bytes_in_hex()`][crate::ObjectId::from_40_bytes_in_hex()] | ||
| 20 | #[derive(Debug, thiserror::Error)] | ||
| 21 | #[allow(missing_docs)] | ||
| 22 | pub enum Error { | ||
| 23 | #[error("A hash sized {0} hexadecimal characters is invalid")] | ||
| 24 | InvalidHexEncodingLength(usize), | ||
| 25 | #[error("Invalid character {c} at position {index}")] | ||
| 26 | Invalid { c: char, index: usize }, | ||
| 27 | } | ||
| 28 | |||
| 29 | /// Hash decoding | ||
| 30 | impl ObjectId { | ||
| 31 | /// Create an instance from a `buffer` of 40 bytes encoded with hexadecimal notation. | ||
| 32 | /// | ||
| 33 | /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()] | ||
| 34 | pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> { | ||
| 35 | use hex::FromHex; | ||
| 36 | match buffer.len() { | ||
| 37 | 40 => Ok(ObjectId::Sha1(<[u8; 20]>::from_hex(buffer).map_err( | ||
| 38 | |err| match err { | ||
| 39 | hex::FromHexError::InvalidHexCharacter { c, index } => Error::Invalid { c, index }, | ||
| 40 | hex::FromHexError::OddLength | hex::FromHexError::InvalidStringLength => { | ||
| 41 | unreachable!("BUG: This is already checked") | ||
| 42 | } | ||
| 43 | }, | ||
| 44 | )?)), | ||
| 45 | len => Err(Error::InvalidHexEncodingLength(len)), | ||
| 46 | } | ||
| 29 | } | 47 | } |
| 30 | for b in self.as_bytes() { | 48 | } |
| 31 | write!(f, "{:02x}", b)?; | 49 | |
| 50 | impl FromStr for ObjectId { | ||
| 51 | type Err = Error; | ||
| 52 | |||
| 53 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
| 54 | Self::from_hex(s.as_bytes()) | ||
| 32 | } | 55 | } |
| 33 | f.write_str(")") | ||
| 34 | } | 56 | } |
| 35 | } | 57 | } |
| 36 | 58 | ||
| @@ -110,6 +132,18 @@ impl ObjectId { · 73 unmodified lines | |||
| 110 | } | 132 | } |
| 111 | } | 133 | } |
| 112 | 134 | ||
| 135 | impl std::fmt::Debug for ObjectId { | ||
| 136 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 137 | match self { | ||
| 138 | ObjectId::Sha1(_hash) => f.write_str("Sha1(")?, | ||
| 139 | } | ||
| 140 | for b in self.as_bytes() { | ||
| 141 | write!(f, "{:02x}", b)?; | ||
| 142 | } | ||
| 143 | f.write_str(")") | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 113 | impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { | 147 | impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { |
| 114 | fn from(v: [u8; 20]) -> Self { | 148 | fn from(v: [u8; 20]) -> Self { |
| 115 | Self::new_sha1(v) | 149 | Self::new_sha1(v) |
| @@ -51,15 +51,10 @@ impl fmt::Debug for oid { · 50 unmodified lines | |||
| 51 | } | 51 | } |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | use quick_error::quick_error; | 54 | #[derive(Debug, thiserror::Error)] |
| 55 | 55 | pub enum Error { | |
| 56 | quick_error! { | 56 | #[error("Cannot instantiate git hash from a digest of length {0}")] |
| 57 | #[derive(Debug)] | 57 | InvalidByteSliceLength(usize), |
| 58 | pub enum Error { | ||
| 59 | InvalidByteSliceLength(length: usize) { | ||
| 60 | display("Cannot instantiate git hash from a digest of length {}", length) | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | 58 | } |
| 64 | 59 | ||
| 65 | /// Conversion | 60 | /// Conversion |
| @@ -1,41 +1,35 @@ | |||
| 1 | use std::{cmp::Ordering, convert::TryFrom}; | 1 | use std::{cmp::Ordering, convert::TryFrom}; |
| 2 | 2 | ||
| 3 | use quick_error::quick_error; | ||
| 4 | |||
| 5 | use crate::{oid, ObjectId, Prefix}; | 3 | use crate::{oid, ObjectId, Prefix}; |
| 6 | 4 | ||
| 7 | quick_error! { | 5 | /// The error returned by [Prefix::try_from_id()]. |
| 8 | /// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()]. | 6 | #[derive(Debug, thiserror::Error)] |
| 9 | #[derive(Debug)] | 7 | #[allow(missing_docs)] |
| 10 | #[allow(missing_docs)] | 8 | pub enum Error { |
| 11 | pub enum Error { | 9 | #[error( |
| 12 | TooShort { hex_len: usize } { | 10 | "The minimum hex length of a short object id is {}, got {hex_len}", |
| 13 | display("The minimum hex length of a short object id is {}, got {}", Prefix::MIN_HEX_LEN, hex_len) | 11 | Prefix::MIN_HEX_LEN |
| 14 | } | 12 | )] |
| 15 | TooLong { object_kind: crate::Kind, hex_len: usize } { | 13 | TooShort { hex_len: usize }, |
| 16 | display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len) | 14 | #[error("An object of kind {} cannot be larger than {object_kind} in hex, but {hex_len} was requested")] |
| 17 | } | 15 | TooLong { object_kind: crate::Kind, hex_len: usize }, |
| 18 | } | ||
| 19 | } | 16 | } |
| 20 | 17 | ||
| 21 | /// | 18 | /// |
| 22 | pub mod from_hex { | 19 | pub mod from_hex { |
| 23 | use quick_error::quick_error; | 20 | /// The error returned by [Prefix::from_hex][super::Prefix::from_hex()]. |
| 24 | quick_error! { | 21 | #[derive(Debug, Eq, PartialEq, thiserror::Error)] |
| 25 | /// The error returned by [Prefix::from_hex][super::Prefix::from_hex()]. | 22 | #[allow(missing_docs)] |
| 26 | #[derive(Debug, Eq, PartialEq)] | 23 | pub enum Error { |
| 27 | #[allow(missing_docs)] | 24 | #[error( |
| 28 | pub enum Error { | 25 | "The minimum hex length of a short object id is {}, got {hex_len}", |
| 29 | TooShort { hex_len: usize } { | 26 | super::Prefix::MIN_HEX_LEN |
| 30 | display("The minimum hex length of a short object id is {}, got {}", super::Prefix::MIN_HEX_LEN, hex_len) | 27 | )] |
| 31 | } | 28 | TooShort { hex_len: usize }, |
| 32 | TooLong { hex_len: usize } { | 29 | #[error("An id cannot be larger than {} chars in hex, but {hex_len} was requested", crate::Kind::longest().len_in_hex())] |
| 33 | display("An id cannot be larger than {} chars in hex, but {} was requested", crate::Kind::longest().len_in_hex(), hex_len) | 30 | TooLong { hex_len: usize }, |
| 34 | } | 31 | #[error("Invalid character {c} at position {index}")] |
| 35 | Invalid { c: char, index: usize } { | 32 | Invalid { c: char, index: usize }, |
| 36 | display("Invalid character {} at position {}", c, index) | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | 33 | } |
| 40 | } | 34 | } |
| 41 | 35 | ||
| @@ -0,0 +1,4 @@ | |||
| 1 | mod kind; | ||
| 2 | mod object_id; | ||
| 3 | mod oid; | ||
| 4 | mod prefix; | ||
| @@ -1,2 +0,0 @@ | |||
| 1 | mod kind; | ||
| 2 | mod oid; | ||
| @@ -0,0 +1,43 @@ | |||
| 1 | mod from_hex { | ||
| 2 | |||
| 3 | mod valid { | ||
| 4 | use git_hash::ObjectId; | ||
| 5 | |||
| 6 | #[test] | ||
| 7 | fn twenty_hex_chars_lowercase() { | ||
| 8 | assert!(ObjectId::from_hex(b"1234567890abcdefaaaaaaaaaaaaaaaaaaaaaaaa").is_ok()); | ||
| 9 | } | ||
| 10 | |||
| 11 | #[test] | ||
| 12 | fn twenty_hex_chars_uppercase() { | ||
| 13 | assert!(ObjectId::from_hex(b"1234567890ABCDEFAAAAAAAAAAAAAAAAAAAAAAAA").is_ok()); | ||
| 14 | } | ||
| 15 | } | ||
| 16 | |||
| 17 | mod invalid { | ||
| 18 | use git_hash::{decode, ObjectId}; | ||
| 19 | |||
| 20 | #[test] | ||
| 21 | fn non_hex_characters() { | ||
| 22 | assert!(matches!( | ||
| 23 | ObjectId::from_hex(b"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz").unwrap_err(), | ||
| 24 | decode::Error::Invalid { index: 0, c: 'z' } | ||
| 25 | )); | ||
| 26 | } | ||
| 27 | |||
| 28 | #[test] | ||
| 29 | fn too_short() { | ||
| 30 | assert!(matches!( | ||
| 31 | ObjectId::from_hex(b"abcd").unwrap_err(), | ||
| 32 | decode::Error::InvalidHexEncodingLength(4) | ||
| 33 | )); | ||
| 34 | } | ||
| 35 | #[test] | ||
| 36 | fn too_long() { | ||
| 37 | assert!(matches!( | ||
| 38 | ObjectId::from_hex(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaf").unwrap_err(), | ||
| 39 | decode::Error::InvalidHexEncodingLength(41) | ||
| 40 | )); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| @@ -1,128 +1,4 @@ | |||
| 1 | mod prefix { | 1 | mod to_hex_with_len { |
| 2 | mod cmp_oid { | ||
| 3 | use std::cmp::Ordering; | ||
| 4 | |||
| 5 | use git_testtools::hex_to_id; | ||
| 6 | |||
| 7 | #[test] | ||
| 8 | fn it_detects_inequality() { | ||
| 9 | let prefix = git_hash::Prefix::new(hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb3"), 7).unwrap(); | ||
| 10 | assert_eq!( | ||
| 11 | prefix.cmp_oid(&hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3")), | ||
| 12 | Ordering::Greater | ||
| 13 | ); | ||
| 14 | assert_eq!( | ||
| 15 | prefix.cmp_oid(&hex_to_id("b920bbf055e1efb9080592a409d3975738b6efb3")), | ||
| 16 | Ordering::Less | ||
| 17 | ); | ||
| 18 | assert_eq!(prefix.to_string(), "b920bbb"); | ||
| 19 | } | ||
| 20 | |||
| 21 | #[test] | ||
| 22 | fn it_detects_equality() { | ||
| 23 | let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3"); | ||
| 24 | let prefix = git_hash::Prefix::new(id, 6).unwrap(); | ||
| 25 | assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); | ||
| 26 | assert_eq!( | ||
| 27 | prefix.cmp_oid(&hex_to_id("a920bbffffffffffffffffffffffffffffffffff")), | ||
| 28 | Ordering::Equal | ||
| 29 | ); | ||
| 30 | assert_eq!(prefix.to_string(), "a920bb"); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | mod new { | ||
| 34 | use std::cmp::Ordering; | ||
| 35 | |||
| 36 | use git_hash::{Kind, ObjectId}; | ||
| 37 | use git_testtools::hex_to_id; | ||
| 38 | |||
| 39 | #[test] | ||
| 40 | fn various_valid_inputs() { | ||
| 41 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 42 | let oid = hex_to_id(oid_hex); | ||
| 43 | |||
| 44 | for hex_len in 4..oid.kind().len_in_hex() { | ||
| 45 | let mut expected = String::from(&oid_hex[..hex_len]); | ||
| 46 | let num_of_zeros = oid.kind().len_in_hex() - hex_len; | ||
| 47 | expected.extend(std::iter::repeat('0').take(num_of_zeros)); | ||
| 48 | let prefix = git_hash::Prefix::new(oid, hex_len).unwrap(); | ||
| 49 | assert_eq!(prefix.as_oid().to_hex().to_string(), expected, "{}", hex_len); | ||
| 50 | assert_eq!(prefix.hex_len(), hex_len); | ||
| 51 | assert_eq!(prefix.cmp_oid(&oid), Ordering::Equal); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | #[test] | ||
| 56 | fn errors_if_hex_len_is_longer_than_oid_len_in_hex() { | ||
| 57 | let kind = Kind::Sha1; | ||
| 58 | assert!(matches!( | ||
| 59 | git_hash::Prefix::new(ObjectId::null(kind), kind.len_in_hex() + 1), | ||
| 60 | Err(git_hash::prefix::Error::TooLong { .. }) | ||
| 61 | )); | ||
| 62 | } | ||
| 63 | |||
| 64 | #[test] | ||
| 65 | fn errors_if_hex_len_is_too_short() { | ||
| 66 | let kind = Kind::Sha1; | ||
| 67 | assert!(matches!( | ||
| 68 | git_hash::Prefix::new(ObjectId::null(kind), 3), | ||
| 69 | Err(git_hash::prefix::Error::TooShort { .. }) | ||
| 70 | )); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | mod try_from { | ||
| 75 | use std::{cmp::Ordering, convert::TryFrom}; | ||
| 76 | |||
| 77 | use git_hash::{prefix::from_hex::Error, Prefix}; | ||
| 78 | use git_testtools::hex_to_id; | ||
| 79 | |||
| 80 | #[test] | ||
| 81 | fn id_8_chars() { | ||
| 82 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 83 | let input = "abcdef"; | ||
| 84 | |||
| 85 | let expected = hex_to_id(oid_hex); | ||
| 86 | let actual = Prefix::try_from(input).expect("No errors"); | ||
| 87 | assert_eq!(actual.cmp_oid(&expected), Ordering::Equal); | ||
| 88 | } | ||
| 89 | |||
| 90 | #[test] | ||
| 91 | fn id_9_chars() { | ||
| 92 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 93 | let input = "abcdefa"; | ||
| 94 | |||
| 95 | let expected = hex_to_id(oid_hex); | ||
| 96 | let actual = Prefix::try_from(input).expect("No errors"); | ||
| 97 | assert_eq!(actual.cmp_oid(&expected), Ordering::Equal); | ||
| 98 | } | ||
| 99 | #[test] | ||
| 100 | fn id_to_short() { | ||
| 101 | let input = "ab"; | ||
| 102 | let expected = Error::TooShort { hex_len: 2 }; | ||
| 103 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 104 | assert_eq!(actual, expected); | ||
| 105 | } | ||
| 106 | |||
| 107 | #[test] | ||
| 108 | fn id_to_long() { | ||
| 109 | let input = "abcdefabcdefabcdefabcdefabcdefabcdefabcd123123123123123123"; | ||
| 110 | let expected = Error::TooLong { hex_len: 58 }; | ||
| 111 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 112 | assert_eq!(actual, expected); | ||
| 113 | } | ||
| 114 | |||
| 115 | #[test] | ||
| 116 | fn invalid_chars() { | ||
| 117 | let input = "abcdfOsd"; | ||
| 118 | let expected = Error::Invalid { c: 'O', index: 5 }; | ||
| 119 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 120 | assert_eq!(actual, expected); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | mod short_hex { | ||
| 126 | #[test] | 2 | #[test] |
| 127 | fn display_entire_range_sha1() { | 3 | fn display_entire_range_sha1() { |
| 128 | let id_hex = "0123456789abcdef123456789abcdef123456789"; | 4 | let id_hex = "0123456789abcdef123456789abcdef123456789"; |
| @@ -0,0 +1,123 @@ | |||
| 1 | mod cmp_oid { | ||
| 2 | use std::cmp::Ordering; | ||
| 3 | |||
| 4 | use git_testtools::hex_to_id; | ||
| 5 | |||
| 6 | #[test] | ||
| 7 | fn it_detects_inequality() { | ||
| 8 | let prefix = git_hash::Prefix::new(hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb3"), 7).unwrap(); | ||
| 9 | assert_eq!( | ||
| 10 | prefix.cmp_oid(&hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3")), | ||
| 11 | Ordering::Greater | ||
| 12 | ); | ||
| 13 | assert_eq!( | ||
| 14 | prefix.cmp_oid(&hex_to_id("b920bbf055e1efb9080592a409d3975738b6efb3")), | ||
| 15 | Ordering::Less | ||
| 16 | ); | ||
| 17 | assert_eq!(prefix.to_string(), "b920bbb"); | ||
| 18 | } | ||
| 19 | |||
| 20 | #[test] | ||
| 21 | fn it_detects_equality() { | ||
| 22 | let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3"); | ||
| 23 | let prefix = git_hash::Prefix::new(id, 6).unwrap(); | ||
| 24 | assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); | ||
| 25 | assert_eq!( | ||
| 26 | prefix.cmp_oid(&hex_to_id("a920bbffffffffffffffffffffffffffffffffff")), | ||
| 27 | Ordering::Equal | ||
| 28 | ); | ||
| 29 | assert_eq!(prefix.to_string(), "a920bb"); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | mod new { | ||
| 34 | use std::cmp::Ordering; | ||
| 35 | |||
| 36 | use git_hash::{Kind, ObjectId}; | ||
| 37 | use git_testtools::hex_to_id; | ||
| 38 | |||
| 39 | #[test] | ||
| 40 | fn various_valid_inputs() { | ||
| 41 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 42 | let oid = hex_to_id(oid_hex); | ||
| 43 | |||
| 44 | for hex_len in 4..oid.kind().len_in_hex() { | ||
| 45 | let mut expected = String::from(&oid_hex[..hex_len]); | ||
| 46 | let num_of_zeros = oid.kind().len_in_hex() - hex_len; | ||
| 47 | expected.extend(std::iter::repeat('0').take(num_of_zeros)); | ||
| 48 | let prefix = git_hash::Prefix::new(oid, hex_len).unwrap(); | ||
| 49 | assert_eq!(prefix.as_oid().to_hex().to_string(), expected, "{}", hex_len); | ||
| 50 | assert_eq!(prefix.hex_len(), hex_len); | ||
| 51 | assert_eq!(prefix.cmp_oid(&oid), Ordering::Equal); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | #[test] | ||
| 56 | fn errors_if_hex_len_is_longer_than_oid_len_in_hex() { | ||
| 57 | let kind = Kind::Sha1; | ||
| 58 | assert!(matches!( | ||
| 59 | git_hash::Prefix::new(ObjectId::null(kind), kind.len_in_hex() + 1), | ||
| 60 | Err(git_hash::prefix::Error::TooLong { .. }) | ||
| 61 | )); | ||
| 62 | } | ||
| 63 | |||
| 64 | #[test] | ||
| 65 | fn errors_if_hex_len_is_too_short() { | ||
| 66 | let kind = Kind::Sha1; | ||
| 67 | assert!(matches!( | ||
| 68 | git_hash::Prefix::new(ObjectId::null(kind), 3), | ||
| 69 | Err(git_hash::prefix::Error::TooShort { .. }) | ||
| 70 | )); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | mod try_from { | ||
| 75 | use std::{cmp::Ordering, convert::TryFrom}; | ||
| 76 | |||
| 77 | use git_hash::prefix::from_hex::Error; | ||
| 78 | use git_hash::Prefix; | ||
| 79 | use git_testtools::hex_to_id; | ||
| 80 | |||
| 81 | #[test] | ||
| 82 | fn id_8_chars() { | ||
| 83 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 84 | let input = "abcdef"; | ||
| 85 | |||
| 86 | let expected = hex_to_id(oid_hex); | ||
| 87 | let actual = Prefix::try_from(input).expect("No errors"); | ||
| 88 | assert_eq!(actual.cmp_oid(&expected), Ordering::Equal); | ||
| 89 | } | ||
| 90 | |||
| 91 | #[test] | ||
| 92 | fn id_9_chars() { | ||
| 93 | let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; | ||
| 94 | let input = "abcdefa"; | ||
| 95 | |||
| 96 | let expected = hex_to_id(oid_hex); | ||
| 97 | let actual = Prefix::try_from(input).expect("No errors"); | ||
| 98 | assert_eq!(actual.cmp_oid(&expected), Ordering::Equal); | ||
| 99 | } | ||
| 100 | #[test] | ||
| 101 | fn id_to_short() { | ||
| 102 | let input = "ab"; | ||
| 103 | let expected = Error::TooShort { hex_len: 2 }; | ||
| 104 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 105 | assert_eq!(actual, expected); | ||
| 106 | } | ||
| 107 | |||
| 108 | #[test] | ||
| 109 | fn id_to_long() { | ||
| 110 | let input = "abcdefabcdefabcdefabcdefabcdefabcdefabcd123123123123123123"; | ||
| 111 | let expected = Error::TooLong { hex_len: 58 }; | ||
| 112 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 113 | assert_eq!(actual, expected); | ||
| 114 | } | ||
| 115 | |||
| 116 | #[test] | ||
| 117 | fn invalid_chars() { | ||
| 118 | let input = "abcdfOsd"; | ||
| 119 | let expected = Error::Invalid { c: 'O', index: 5 }; | ||
| 120 | let actual = Prefix::try_from(input).unwrap_err(); | ||
| 121 | assert_eq!(actual, expected); | ||
| 122 | } | ||
| 123 | } | ||
| @@ -31,7 +31,7 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] · 30 unmodified lines | |||
| 31 | 31 | ||
| 32 | [dependencies] | 32 | [dependencies] |
| 33 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1", "progress"] } | 33 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1", "progress"] } |
| 34 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 34 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 35 | git-bitmap = { version = "^0.1.2", path = "../git-bitmap" } | 35 | git-bitmap = { version = "^0.1.2", path = "../git-bitmap" } |
| 36 | git-object = { version = "^0.20.2", path = "../git-object" } | 36 | git-object = { version = "^0.20.2", path = "../git-object" } |
| 37 | 37 | ||
| @@ -22,7 +22,7 @@ verbose-object-parsing-errors = ["nom/std"] · 21 unmodified lines | |||
| 22 | 22 | ||
| 23 | [dependencies] | 23 | [dependencies] |
| 24 | git-features = { version = "^0.22.2", path = "../git-features", features = ["rustsha1"] } | 24 | git-features = { version = "^0.22.2", path = "../git-features", features = ["rustsha1"] } |
| 25 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 25 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 26 | git-validate = { version = "^0.5.5", path = "../git-validate" } | 26 | git-validate = { version = "^0.5.5", path = "../git-validate" } |
| 27 | git-actor = { version = "^0.11.3", path = "../git-actor" } | 27 | git-actor = { version = "^0.11.3", path = "../git-actor" } |
| 28 | 28 | ||
| @@ -28,8 +28,8 @@ required-features = [] · 27 unmodified lines | |||
| 28 | 28 | ||
| 29 | [dependencies] | 29 | [dependencies] |
| 30 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } | 30 | git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } |
| 31 | git-hash = { version = "^0.9.9", path = "../git-hash" } | ||
| 31 | git-path = { version = "^0.4.2", path = "../git-path" } | 32 | git-path = { version = "^0.4.2", path = "../git-path" } |
| 32 | git-hash = { version = "^0.9.8", path = "../git-hash" } | ||
| 33 | git-quote = { version = "^0.2.1", path = "../git-quote" } | 33 | git-quote = { version = "^0.2.1", path = "../git-quote" } |
| 34 | git-object = { version = "^0.20.3", path = "../git-object" } | 34 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 35 | git-pack = { version = "^0.22.0", path = "../git-pack" } | 35 | git-pack = { version = "^0.22.0", path = "../git-pack" } |
| @@ -38,8 +38,8 @@ required-features = ["internal-testing-to-avoid-being-run-by-cargo-test-all"] · 37 unmodified lines | |||
| 38 | 38 | ||
| 39 | [dependencies] | 39 | [dependencies] |
| 40 | git-features = { version = "^0.22.3", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } | 40 | git-features = { version = "^0.22.3", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } |
| 41 | git-hash = { version = "^0.9.8", path = "../git-hash" } | ||
| 42 | git-path = { version = "^0.4.2", path = "../git-path" } | 41 | git-path = { version = "^0.4.2", path = "../git-path" } |
| 42 | git-hash = { version = "^0.9.9", path = "../git-hash" } | ||
| 43 | git-chunk = { version = "^0.3.1", path = "../git-chunk" } | 43 | git-chunk = { version = "^0.3.1", path = "../git-chunk" } |
| 44 | git-object = { version = "^0.20.3", path = "../git-object" } | 44 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 45 | git-traverse = { version = "^0.16.3", path = "../git-traverse" } | 45 | git-traverse = { version = "^0.16.3", path = "../git-traverse" } |
| @@ -41,7 +41,7 @@ required-features = ["async-client"] · 40 unmodified lines | |||
| 41 | [dependencies] | 41 | [dependencies] |
| 42 | git-features = { version = "^0.22.3", path = "../git-features", features = ["progress"] } | 42 | git-features = { version = "^0.22.3", path = "../git-features", features = ["progress"] } |
| 43 | git-transport = { version = "^0.19.3", path = "../git-transport" } | 43 | git-transport = { version = "^0.19.3", path = "../git-transport" } |
| 44 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 44 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 45 | git-credentials = { version = "^0.4.0", path = "../git-credentials" } | 45 | git-credentials = { version = "^0.4.0", path = "../git-credentials" } |
| 46 | 46 | ||
| 47 | thiserror = "1.0.32" | 47 | thiserror = "1.0.32" |
| @@ -25,7 +25,7 @@ required-features = ["internal-testing-git-features-parallel"] · 24 unmodified lines | |||
| 25 | [dependencies] | 25 | [dependencies] |
| 26 | git-features = { version = "^0.22.3", path = "../git-features", features = ["walkdir"]} | 26 | git-features = { version = "^0.22.3", path = "../git-features", features = ["walkdir"]} |
| 27 | git-path = { version = "^0.4.2", path = "../git-path" } | 27 | git-path = { version = "^0.4.2", path = "../git-path" } |
| 28 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 28 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 29 | git-object = { version = "^0.20.3", path = "../git-object" } | 29 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 30 | git-validate = { version = "^0.5.5", path = "../git-validate" } | 30 | git-validate = { version = "^0.5.5", path = "../git-validate" } |
| 31 | git-actor = { version = "^0.11.3", path = "../git-actor" } | 31 | git-actor = { version = "^0.11.3", path = "../git-actor" } |
| @@ -14,7 +14,7 @@ doctest = false · 13 unmodified lines | |||
| 14 | [dependencies] | 14 | [dependencies] |
| 15 | git-revision = { version = "^0.4.4", path = "../git-revision" } | 15 | git-revision = { version = "^0.4.4", path = "../git-revision" } |
| 16 | git-validate = { version = "^0.5.5", path = "../git-validate" } | 16 | git-validate = { version = "^0.5.5", path = "../git-validate" } |
| 17 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 17 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 18 | 18 | ||
| 19 | bstr = { version = "0.2.13", default-features = false, features = ["std"]} | 19 | bstr = { version = "0.2.13", default-features = false, features = ["std"]} |
| 20 | thiserror = "1.0.26" | 20 | thiserror = "1.0.26" |
| @@ -87,7 +87,7 @@ git-refspec = { version = "^0.1.1", path = "../git-refspec" } · 86 unmodified lines | |||
| 87 | 87 | ||
| 88 | git-config = { version = "^0.7.1", path = "../git-config" } | 88 | git-config = { version = "^0.7.1", path = "../git-config" } |
| 89 | git-odb = { version = "^0.32.0", path = "../git-odb" } | 89 | git-odb = { version = "^0.32.0", path = "../git-odb" } |
| 90 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 90 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 91 | git-object = { version = "^0.20.3", path = "../git-object" } | 91 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 92 | git-actor = { version = "^0.11.4", path = "../git-actor" } | 92 | git-actor = { version = "^0.11.4", path = "../git-actor" } |
| 93 | git-pack = { version = "^0.22.0", path = "../git-pack", features = ["object-cache-dynamic"] } | 93 | git-pack = { version = "^0.22.0", path = "../git-pack", features = ["object-cache-dynamic"] } |
| @@ -1,4 +1,5 @@ | |||
| 1 | use std::borrow::Cow; | 1 | use std::borrow::Cow; |
| 2 | use std::time::SystemTime; | ||
| 2 | 3 | ||
| 3 | use crate::{bstr::BString, permission}; | 4 | use crate::{bstr::BString, permission}; |
| 4 | 5 | ||
| @@ -125,13 +126,13 @@ impl Personas { · 120 unmodified lines | |||
| 125 | committer_email = committer_email.or_else(|| env_var("GIT_COMMITTER_EMAIL")); | 126 | committer_email = committer_email.or_else(|| env_var("GIT_COMMITTER_EMAIL")); |
| 126 | committer_date = std::env::var("GIT_COMMITTER_DATE") | 127 | committer_date = std::env::var("GIT_COMMITTER_DATE") |
| 127 | .ok() | 128 | .ok() |
| 128 | .and_then(|date| git_date::parse(&date)); | 129 | .and_then(|date| git_date::parse(&date, Some(SystemTime::now())).ok()); |
| 129 | 130 | ||
| 130 | author_name = author_name.or_else(|| env_var("GIT_AUTHOR_NAME")); | 131 | author_name = author_name.or_else(|| env_var("GIT_AUTHOR_NAME")); |
| 131 | author_email = author_email.or_else(|| env_var("GIT_AUTHOR_EMAIL")); | 132 | author_email = author_email.or_else(|| env_var("GIT_AUTHOR_EMAIL")); |
| 132 | author_date = std::env::var("GIT_AUTHOR_DATE") | 133 | author_date = std::env::var("GIT_AUTHOR_DATE") |
| 133 | .ok() | 134 | .ok() |
| 134 | .and_then(|date| git_date::parse(&date)); | 135 | .and_then(|date| git_date::parse(&date, Some(SystemTime::now())).ok()); |
| 135 | 136 | ||
| 136 | user_email = user_email.or_else(|| env_var("EMAIL")); // NOTE: we don't have permission for this specific one… | 137 | user_email = user_email.or_else(|| env_var("EMAIL")); // NOTE: we don't have permission for this specific one… |
| 137 | } | 138 | } |
| @@ -16,7 +16,7 @@ doctest = false · 15 unmodified lines | |||
| 16 | serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ] | 16 | serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ] |
| 17 | 17 | ||
| 18 | [dependencies] | 18 | [dependencies] |
| 19 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 19 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 20 | git-object = { version = "^0.20.2", path = "../git-object" } | 20 | git-object = { version = "^0.20.2", path = "../git-object" } |
| 21 | git-date = { version = "^0.1.0", path = "../git-date" } | 21 | git-date = { version = "^0.1.0", path = "../git-date" } |
| 22 | 22 | ||
| @@ -1,3 +1,4 @@ | |||
| 1 | use std::time::SystemTime; | ||
| 1 | use std::{convert::TryInto, str::FromStr}; | 2 | use std::{convert::TryInto, str::FromStr}; |
| 2 | 3 | ||
| 3 | use bstr::{BStr, BString, ByteSlice, ByteVec}; | 4 | use bstr::{BStr, BString, ByteSlice, ByteVec}; |
| @@ -434,9 +435,16 @@ where · 430 unmodified lines | |||
| 434 | } else if has_ref_or_implied_name { | 435 | } else if has_ref_or_implied_name { |
| 435 | let time = nav | 436 | let time = nav |
| 436 | .to_str() | 437 | .to_str() |
| 437 | .ok() | 438 | .map_err(|_| Error::Time { |
| 438 | .and_then(git_date::parse) | 439 | input: nav.into(), |
| 439 | .ok_or_else(|| Error::Time { input: nav.into() })?; | 440 | source: None, |
| 441 | }) | ||
| 442 | .and_then(|date| { | ||
| 443 | git_date::parse(date, Some(SystemTime::now())).map_err(|err| Error::Time { | ||
| 444 | input: nav.into(), | ||
| 445 | source: err.into(), | ||
| 446 | }) | ||
| 447 | })?; | ||
| 440 | delegate | 448 | delegate |
| 441 | .reflog(delegate::ReflogLookup::Date(time)) | 449 | .reflog(delegate::ReflogLookup::Date(time)) |
| 442 | .ok_or(Error::Delegate)?; | 450 | .ok_or(Error::Delegate)?; |
| @@ -17,7 +17,10 @@ pub enum Error { · 16 unmodified lines | |||
| 17 | #[error("Cannot peel to {:?} - unknown target.", .input)] | 17 | #[error("Cannot peel to {:?} - unknown target.", .input)] |
| 18 | InvalidObject { input: BString }, | 18 | InvalidObject { input: BString }, |
| 19 | #[error("Could not parse time {:?} for revlog lookup.", .input)] | 19 | #[error("Could not parse time {:?} for revlog lookup.", .input)] |
| 20 | Time { input: BString }, | 20 | Time { |
| 21 | input: BString, | ||
| 22 | source: Option<git_date::parse::Error>, | ||
| 23 | }, | ||
| 21 | #[error("Sibling branches like 'upstream' or 'push' require a branch name with remote configuration, got {:?}", .name)] | 24 | #[error("Sibling branches like 'upstream' or 'push' require a branch name with remote configuration, got {:?}", .name)] |
| 22 | SiblingBranchNeedsBranchName { name: BString }, | 25 | SiblingBranchNeedsBranchName { name: BString }, |
| 23 | #[error("Reflog entries require a ref name, got {:?}", .name)] | 26 | #[error("Reflog entries require a ref name, got {:?}", .name)] |
| @@ -43,7 +43,7 @@ fn reflog_by_date_for_current_branch() { · 42 unmodified lines | |||
| 43 | #[test] | 43 | #[test] |
| 44 | fn reflog_by_date_with_date_parse_failure() { | 44 | fn reflog_by_date_with_date_parse_failure() { |
| 45 | let err = try_parse("@{foo}").unwrap_err(); | 45 | let err = try_parse("@{foo}").unwrap_err(); |
| 46 | assert!(matches!(err, spec::parse::Error::Time {input} if input == "foo")); | 46 | assert!(matches!(err, spec::parse::Error::Time {input, source} if input == "foo" && source.is_some())); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | #[test] | 49 | #[test] |
| @@ -12,7 +12,7 @@ include = ["src/**/*"] · 11 unmodified lines | |||
| 12 | doctest = false | 12 | doctest = false |
| 13 | 13 | ||
| 14 | [dependencies] | 14 | [dependencies] |
| 15 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 15 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 16 | git-object = { version = "^0.20.3", path = "../git-object" } | 16 | git-object = { version = "^0.20.3", path = "../git-object" } |
| 17 | thiserror = "1.0.32" | 17 | thiserror = "1.0.32" |
| 18 | hash_hasher = "2.0.3" | 18 | hash_hasher = "2.0.3" |
| @@ -30,7 +30,7 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] · 29 unmodified lines | |||
| 30 | 30 | ||
| 31 | [dependencies] | 31 | [dependencies] |
| 32 | git-index = { version = "^0.4.3", path = "../git-index" } | 32 | git-index = { version = "^0.4.3", path = "../git-index" } |
| 33 | git-hash = { version = "^0.9.8", path = "../git-hash" } | 33 | git-hash = { version = "^0.9.9", path = "../git-hash" } |
| 34 | git-object = { version = "^0.20.2", path = "../git-object" } | 34 | git-object = { version = "^0.20.2", path = "../git-object" } |
| 35 | git-glob = { version = "^0.3.2", path = "../git-glob" } | 35 | git-glob = { version = "^0.3.2", path = "../git-glob" } |
| 36 | git-path = { version = "^0.4.2", path = "../git-path" } | 36 | git-path = { version = "^0.4.2", path = "../git-path" } |
| @@ -14,7 +14,7 @@ path = "src/main.rs" · 13 unmodified lines | |||
| 14 | doctest = false | 14 | doctest = false |
| 15 | 15 | ||
| 16 | [dependencies] | 16 | [dependencies] |
| 17 | git-hash = { version = "^0.9.8", path = "../../git-hash" } | 17 | git-hash = { version = "^0.9.9", path = "../../git-hash" } |
| 18 | git-lock = { version = "^2.0.0", path = "../../git-lock" } | 18 | git-lock = { version = "^2.0.0", path = "../../git-lock" } |
| 19 | git-discover = { version = "^0.4.2", path = "../../git-discover" } | 19 | git-discover = { version = "^0.4.2", path = "../../git-discover" } |
| 20 | git-attributes = { version = "^0.3.3", path = "../../git-attributes" } | 20 | git-attributes = { version = "^0.3.3", path = "../../git-attributes" } |