History

Merge branch 'main' into filter-refs-by-spec

Sebastian Thiel committed 4 years ago

Commit 1f6e5ab2 parents 82ee79e 8c4608840 files · +796 −413Browse files

Unified · Split

40 files
  1. Cargo.lock +8 −6
  2. etc/check-package-size.sh +1 −1
  3. git-commitgraph/Cargo.toml +1 −1
  4. git-date/Cargo.toml +4 −1
  5. git-date/src/lib.rs +3 −2
  6. git-date/src/parse.rs +131 −8
  7. git-date/tests/fixtures/generate_git_date_baseline.sh +46 −0
  8. git-date/tests/fixtures/generated-archives/generate_git_date_baseline.tar.xz +3 −0
  9. git-date/tests/time/parse.rs +117 −1
  10. git-diff/Cargo.toml +1 −1
  11. git-discover/Cargo.toml +1 −1
  12. git-features/Cargo.toml +1 −1
  13. git-hash/CHANGELOG.md +59 −20
  14. git-hash/Cargo.toml +2 −2
  15. git-hash/src/kind.rs +122 −0
  16. git-hash/src/lib.rs +13 −164
  17. git-hash/src/object_id.rs +53 −19
  18. git-hash/src/oid.rs +4 −9
  19. git-hash/src/prefix.rs +24 −30
  20. git-hash/tests/hash.rs +4 −0
  21. git-hash/tests/integration.rs +0 −2
  22. git-hash/tests/object_id/mod.rs +43 −0
  23. git-hash/tests/oid/mod.rs +1 −125
  24. git-hash/tests/prefix/mod.rs +123 −0
  25. git-index/Cargo.toml +1 −1
  26. git-object/Cargo.toml +1 −1
  27. git-odb/Cargo.toml +1 −1
  28. git-pack/Cargo.toml +1 −1
  29. git-protocol/Cargo.toml +1 −1
  30. git-ref/Cargo.toml +1 −1
  31. git-refspec/Cargo.toml +1 −1
  32. git-repository/Cargo.toml +1 −1
  33. git-repository/src/repository/identity.rs +3 −2
  34. git-revision/Cargo.toml +1 −1
  35. git-revision/src/spec/parse/function.rs +11 −3
  36. git-revision/src/spec/parse/mod.rs +4 −1
  37. git-revision/tests/spec/parse/anchor/at_symbol.rs +1 −1
  38. git-traverse/Cargo.toml +1 −1
  39. git-worktree/Cargo.toml +1 −1
  40. tests/tools/Cargo.toml +1 −1
Cargo.lock+8−6
@@ -1234,7 +1234,9 @@ dependencies = [ · 1233 unmodified lines
12341234 "document-features",
12351235 "git-testtools",
12361236 "itoa 1.0.3",
1237+ "once_cell",
12371238 "serde",
1239+ "thiserror",
12381240 "time",
12391241 ]
12401242
@@ -1307,13 +1309,13 @@ dependencies = [ · 66 unmodified lines
13071309
13081310 [[package]]
13091311 name = "git-hash"
1310−version = "0.9.8"
1312+version = "0.9.9"
13111313 dependencies = [
13121314 "document-features",
13131315 "git-testtools",
13141316 "hex",
1315− "quick-error",
13161317 "serde",
1318+ "thiserror",
13171319 ]
13181320
13191321 [[package]]
@@ -3026,18 +3028,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" · 1706 unmodified lines
30263028
30273029 [[package]]
30283030 name = "thiserror"
3029−version = "1.0.32"
3031+version = "1.0.33"
30303032 source = "registry+https://github.com/rust-lang/crates.io-index"
3031−checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
3033+checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57"
30323034 dependencies = [
30333035 "thiserror-impl",
30343036 ]
30353037
30363038 [[package]]
30373039 name = "thiserror-impl"
3038−version = "1.0.32"
3040+version = "1.0.33"
30393041 source = "registry+https://github.com/rust-lang/crates.io-index"
3040−checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
3042+checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09"
30413043 dependencies = [
30423044 "proc-macro2",
30433045 "quote",
etc/check-package-size.sh+1−1
@@ -42,7 +42,7 @@ echo "in root: gitoxide CLI" · 41 unmodified lines
4242 (enter git-traverse && indent cargo diet -n --package-size-limit 10KB)
4343 (enter git-url && indent cargo diet -n --package-size-limit 20KB)
4444 (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)
4646 (enter git-filter && indent cargo diet -n --package-size-limit 5KB)
4747 (enter git-lfs && indent cargo diet -n --package-size-limit 5KB)
4848 (enter git-note && indent cargo diet -n --package-size-limit 5KB)
git-commitgraph/Cargo.toml+1−1
@@ -18,7 +18,7 @@ serde1 = ["serde", "git-hash/serde1", "bstr/serde1"] · 17 unmodified lines
1818
1919 [dependencies]
2020 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" }
2222 git-chunk = { version = "^0.3.1", path = "../git-chunk" }
2323
2424 bstr = { version = "0.2.13", default-features = false, features = ["std"] }
git-date/Cargo.toml+4−1
@@ -6,6 +6,7 @@ license = "MIT/Apache-2.0" · 5 unmodified lines
66 description = "A WIP crate of the gitoxide project parsing dates the way git does"
77 authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
88 edition = "2018"
9+include = ["src/**/*", "CHANGELOG.md"]
910
1011 [lib]
1112 doctest = false
@@ -18,12 +19,14 @@ serde1 = ["serde", "bstr/serde1"] · 6 unmodified lines
1819 bstr = { version = "0.2.13", default-features = false, features = ["std"]}
1920 serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
2021 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"
2224
2325 document-features = { version = "0.2.0", optional = true }
2426
2527 [dev-dependencies]
2628 git-testtools = { path = "../tests/tools"}
29+once_cell = "1.12.0"
2730
2831 [package.metadata.docs.rs]
2932 all-features = true
git-date/src/lib.rs+3−2
@@ -13,8 +13,9 @@ · 12 unmodified lines
1313 ///
1414 pub mod time;
1515
16−mod parse;
17−pub use parse::parse;
16+///
17+pub mod parse;
18+pub use parse::function::parse;
1819
1920 /// A timestamp with timezone.
2021 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
git-date/src/parse.rs+131−8
@@ -1,11 +1,134 @@
1−use crate::Time;
2−
1+#[derive(thiserror::Error, Debug)]
32 #[allow(missing_docs)]
4−pub fn parse(input: &str) -> Option<Time> {
5− // TODO: actual implementation, this is just to not constantly fail
6− if input == "1979-02-26 18:30:00" {
7− Some(Time::new(42, 1800))
8− } else {
9− None
3+pub enum Error {
4+ #[error("Cannot represent times before UNIX epoch at timestamp {timestamp}")]
5+ TooEarly { timestamp: i64 },
6+ #[error("Date string can not be parsed")]
7+ InvalidDateString,
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+ }
10133 }
11134 }
git-date/tests/fixtures/generate_git_date_baseline.sh+46−0
@@ -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+
git-date/tests/fixtures/generated-archives/generate_git_date_baseline.tar.xz+3−0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:7495815a04eea1a5b657d944b85f2dac5bb73bbda43a409ba70a2094c23261cb
3+size 9188
git-date/tests/time/parse.rs+117−1
@@ -1,10 +1,51 @@
1+use bstr::{BString, ByteSlice};
12 use git_date::time::Sign;
23 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+}
344
445 #[test]
546 fn special_time_is_ok_for_now() {
647 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(),
849 Time {
950 seconds_since_unix_epoch: 42,
1051 offset_in_seconds: 1800,
@@ -12,3 +53,78 @@ fn special_time_is_ok_for_now() { · 1 unmodified line
1253 }
1354 );
1455 }
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+}
git-diff/Cargo.toml+1−1
@@ -12,7 +12,7 @@ include = ["src/**/*"] · 11 unmodified lines
1212 doctest = false
1313
1414 [dependencies]
15−git-hash = { version = "^0.9.8", path = "../git-hash" }
15+git-hash = { version = "^0.9.9", path = "../git-hash" }
1616 git-object = { version = "^0.20.3", path = "../git-object" }
1717 thiserror = "1.0.32"
1818
git-discover/Cargo.toml+1−1
@@ -15,7 +15,7 @@ doctest = false · 14 unmodified lines
1515 git-sec = { version = "^0.3.1", path = "../git-sec", features = ["thiserror"] }
1616 git-path = { version = "^0.4.2", path = "../git-path" }
1717 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" }
1919
2020 bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] }
2121 thiserror = "1.0.26"
git-features/Cargo.toml+1−1
@@ -92,7 +92,7 @@ required-features = ["io-pipe"] · 91 unmodified lines
9292 [dependencies]
9393 #! ### Optional Dependencies
9494
95−git-hash = { version = "^0.9.8", path = "../git-hash" }
95+git-hash = { version = "^0.9.9", path = "../git-hash" }
9696
9797
9898
git-hash/CHANGELOG.md+59−20
@@ -5,8 +5,46 @@ All notable changes to this project will be documented in this file. · 4 unmodified lines
55 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77
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+
844 ## 0.9.8 (2022-08-24)
945
46+<csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/>
47+
1048 ### Chore
1149
1250 - <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
2058
2159 <csr-read-only-do-not-edit/>
2260
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.
2462 - 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).
2664 - 0 issues like '(#ID)' where seen in commit messages
2765
2866 ### Commit Details
@@ -32,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 · 3 unmodified lines
3270 <details><summary>view details</summary>
3371
3472 * **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))
3574 - Merge branch 'example-new-repo' ([`946dd3a`](https://github.com/Byron/gitoxide/commit/946dd3a80522ef437e09528a93aa1433f01b0ee8))
3675 - use docsrs feature in code to show what is feature-gated automatically on docs.rs ([`b1c40b0`](https://github.com/Byron/gitoxide/commit/b1c40b0364ef092cd52d03b34f491b254816b18d))
3776 - 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
5392
5493 - 9 commits contributed to the release over the course of 19 calendar days.
5594 - 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).
5796 - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427)
5897
5998 ### Thanks Clippy
@@ -91,7 +130,7 @@ This is a maintenance release with no functional changes. · 31 unmodified lines
91130
92131 - 3 commits contributed to the release over the course of 12 calendar days.
93132 - 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).
95134 - 0 issues like '(#ID)' where seen in commit messages
96135
97136 ### Commit Details
@@ -120,7 +159,7 @@ This is a maintenance release with no functional changes. · 22 unmodified lines
120159
121160 - 3 commits contributed to the release over the course of 5 calendar days.
122161 - 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).
124163 - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427)
125164
126165 ### Commit Details
@@ -151,7 +190,7 @@ This is a maintenance release with no functional changes. · 24 unmodified lines
151190
152191 - 10 commits contributed to the release over the course of 46 calendar days.
153192 - 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).
155194 - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#413](https://github.com/Byron/gitoxide/issues/413)
156195
157196 ### Commit Details
@@ -194,7 +233,7 @@ This is a maintenance release with no functional changes. · 36 unmodified lines
194233
195234 - 17 commits contributed to the release over the course of 54 calendar days.
196235 - 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).
198237 - 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)
199238
200239 ### Commit Details
@@ -243,7 +282,7 @@ A automated maintenance release without impact to the public API. · 42 unmodified lines
243282
244283 - 4 commits contributed to the release over the course of 8 calendar days.
245284 - 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).
247286 - 1 unique issue was worked on: [#298](https://github.com/Byron/gitoxide/issues/298)
248287
249288 ### Commit Details
@@ -315,7 +354,7 @@ A automated maintenance release without impact to the public API. · 65 unmodified lines
315354
316355 - 3 commits contributed to the release over the course of 3 calendar days.
317356 - 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).
319358 - 0 issues like '(#ID)' where seen in commit messages
320359
321360 ### Commit Details
@@ -381,7 +420,7 @@ A automated maintenance release without impact to the public API. · 59 unmodified lines
381420
382421 - 25 commits contributed to the release over the course of 30 calendar days.
383422 - 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).
385424 - 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)
386425
387426 ### Commit Details
@@ -433,7 +472,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 45 unmodified lines
433472
434473 - 5 commits contributed to the release over the course of 3 calendar days.
435474 - 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).
437476 - 1 unique issue was worked on: [#222](https://github.com/Byron/gitoxide/issues/222)
438477
439478 ## 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
452491
453492 - 16 commits contributed to the release over the course of 11 calendar days.
454493 - 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).
456495 - 1 unique issue was worked on: [#198](https://github.com/Byron/gitoxide/issues/198)
457496
458497 ### Commit Details
@@ -494,7 +533,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 35 unmodified lines
494533
495534 - 2 commits contributed to the release over the course of 1 calendar day.
496535 - 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).
498537 - 0 issues like '(#ID)' where seen in commit messages
499538
500539 ### Commit Details
@@ -516,7 +555,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines
516555
517556 - 2 commits contributed to the release over the course of 1 calendar day.
518557 - 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).
520559 - 0 issues like '(#ID)' where seen in commit messages
521560
522561 ### Commit Details
@@ -538,7 +577,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines
538577
539578 - 10 commits contributed to the release over the course of 74 calendar days.
540579 - 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).
542581 - 0 issues like '(#ID)' where seen in commit messages
543582
544583 ### Commit Details
@@ -574,7 +613,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 29 unmodified lines
574613
575614 - 5 commits contributed to the release over the course of 16 calendar days.
576615 - 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).
578617 - 0 issues like '(#ID)' where seen in commit messages
579618
580619 ### Commit Details
@@ -599,7 +638,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 18 unmodified lines
599638
600639 - 20 commits contributed to the release over the course of 1 calendar day.
601640 - 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).
603642 - 1 unique issue was worked on: [#63](https://github.com/Byron/gitoxide/issues/63)
604643
605644 ### Commit Details
@@ -640,7 +679,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 34 unmodified lines
640679
641680 - 3 commits contributed to the release over the course of 26 calendar days.
642681 - 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).
644683 - 0 issues like '(#ID)' where seen in commit messages
645684
646685 ### Commit Details
@@ -662,7 +701,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 15 unmodified lines
662701 <csr-read-only-do-not-edit/>
663702
664703 - 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).
666705 - 0 issues like '(#ID)' where seen in commit messages
667706
668707 ### Commit Details
@@ -683,7 +722,7 @@ A maintenance release due to reset the entire crate graph to new minor releases. · 14 unmodified lines
683722 <csr-read-only-do-not-edit/>
684723
685724 - 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).
687726 - 0 issues like '(#ID)' where seen in commit messages
688727
689728 ### Commit Details
git-hash/Cargo.toml+2−2
@@ -1,6 +1,6 @@
11 [package]
22 name = "git-hash"
3−version = "0.9.8"
3+version = "0.9.9"
44 description = "Borrowed and owned git hash digests used to identify git objects"
55 authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
66 repository = "https://github.com/Byron/gitoxide"
@@ -17,7 +17,7 @@ test = false · 10 unmodified lines
1717 serde1 = ["serde"]
1818
1919 [dependencies]
20−quick-error = "2.0.0"
20+thiserror = "1.0.33"
2121 hex = "0.4.2"
2222 serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
2323
git-hash/src/kind.rs+122−0
@@ -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+}
git-hash/src/lib.rs+13−164
@@ -9,57 +9,23 @@ · 8 unmodified lines
99 #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
1010 #![deny(missing_docs, rust_2018_idioms, unsafe_code)]
1111
12+#[path = "oid.rs"]
1213 mod borrowed;
13−
14−use std::{convert::TryFrom, str::FromStr};
15−
1614 pub use borrowed::oid;
1715
18−mod owned;
19−pub use owned::{prefix, ObjectId, Prefix};
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− }
16+mod object_id;
17+pub use object_id::{decode, ObjectId};
5518
56− impl FromStr for ObjectId {
57− type Err = Error;
19+///
20+pub mod prefix;
5821
59− fn from_str(s: &str) -> Result<Self, Self::Err> {
60− Self::from_hex(s.as_bytes())
61− }
62− }
22+/// An partial owned hash possibly identifying an object uniquely,
23+/// whose non-prefix bytes are zeroed.
24+#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)]
25+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
26+pub struct Prefix {
27+ bytes: ObjectId,
28+ hex_len: usize,
6329 }
6430
6531 /// The size of a SHA1 hash digest in bytes
@@ -73,121 +39,4 @@ pub enum Kind { · 7 unmodified lines
7339 Sha1 = 1,
7440 }
7541
76−impl Default for 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−}
42+mod kind;
git-hash/src/object_id.rsrenamed from git-hash/src/owned.rs+53−19
@@ -2,18 +2,6 @@ use std::{borrow::Borrow, convert::TryInto, fmt, ops::Deref}; · 1 unmodified line
22
33 use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
44
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−
175 /// An owned hash identifying objects, most commonly Sha1
186 #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
197 #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
@@ -22,15 +10,49 @@ pub enum ObjectId { · 2 unmodified lines
2210 Sha1([u8; SIZE_OF_SHA1_DIGEST]),
2311 }
2412
25−impl std::fmt::Debug for ObjectId {
26− fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27− match self {
28− ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
13+#[allow(missing_docs)]
14+pub mod decode {
15+ use std::str::FromStr;
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+ }
2947 }
30− for b in self.as_bytes() {
31− write!(f, "{:02x}", b)?;
48+ }
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())
3255 }
33− f.write_str(")")
3456 }
3557 }
3658
@@ -110,6 +132,18 @@ impl ObjectId { · 73 unmodified lines
110132 }
111133 }
112134
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+
113147 impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
114148 fn from(v: [u8; 20]) -> Self {
115149 Self::new_sha1(v)
git-hash/src/oid.rsrenamed from git-hash/src/borrowed.rs+4−9
@@ -51,15 +51,10 @@ impl fmt::Debug for oid { · 50 unmodified lines
5151 }
5252 }
5353
54−use quick_error::quick_error;
55−
56−quick_error! {
57− #[derive(Debug)]
58− pub enum Error {
59− InvalidByteSliceLength(length: usize) {
60− display("Cannot instantiate git hash from a digest of length {}", length)
61− }
62− }
54+#[derive(Debug, thiserror::Error)]
55+pub enum Error {
56+ #[error("Cannot instantiate git hash from a digest of length {0}")]
57+ InvalidByteSliceLength(usize),
6358 }
6459
6560 /// Conversion
git-hash/src/prefix.rsrenamed from git-hash/src/owned/prefix.rs+24−30
@@ -1,41 +1,35 @@
11 use std::{cmp::Ordering, convert::TryFrom};
22
3−use quick_error::quick_error;
4−
53 use crate::{oid, ObjectId, Prefix};
64
7−quick_error! {
8− /// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()].
9− #[derive(Debug)]
10− #[allow(missing_docs)]
11− pub enum Error {
12− TooShort { hex_len: usize } {
13− display("The minimum hex length of a short object id is {}, got {}", Prefix::MIN_HEX_LEN, hex_len)
14− }
15− TooLong { object_kind: crate::Kind, 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)
17− }
18− }
5+/// The error returned by [Prefix::try_from_id()].
6+#[derive(Debug, thiserror::Error)]
7+#[allow(missing_docs)]
8+pub enum Error {
9+ #[error(
10+ "The minimum hex length of a short object id is {}, got {hex_len}",
11+ Prefix::MIN_HEX_LEN
12+ )]
13+ TooShort { hex_len: usize },
14+ #[error("An object of kind {} cannot be larger than {object_kind} in hex, but {hex_len} was requested")]
15+ TooLong { object_kind: crate::Kind, hex_len: usize },
1916 }
2017
2118 ///
2219 pub mod from_hex {
23− use quick_error::quick_error;
24− quick_error! {
25− /// The error returned by [Prefix::from_hex][super::Prefix::from_hex()].
26− #[derive(Debug, Eq, PartialEq)]
27− #[allow(missing_docs)]
28− pub enum Error {
29− TooShort { hex_len: usize } {
30− display("The minimum hex length of a short object id is {}, got {}", super::Prefix::MIN_HEX_LEN, hex_len)
31− }
32− TooLong { hex_len: usize } {
33− display("An id cannot be larger than {} chars in hex, but {} was requested", crate::Kind::longest().len_in_hex(), hex_len)
34− }
35− Invalid { c: char, index: usize } {
36− display("Invalid character {} at position {}", c, index)
37− }
38− }
20+ /// The error returned by [Prefix::from_hex][super::Prefix::from_hex()].
21+ #[derive(Debug, Eq, PartialEq, thiserror::Error)]
22+ #[allow(missing_docs)]
23+ pub enum Error {
24+ #[error(
25+ "The minimum hex length of a short object id is {}, got {hex_len}",
26+ super::Prefix::MIN_HEX_LEN
27+ )]
28+ TooShort { 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())]
30+ TooLong { hex_len: usize },
31+ #[error("Invalid character {c} at position {index}")]
32+ Invalid { c: char, index: usize },
3933 }
4034 }
4135
git-hash/tests/hash.rs+4−0
@@ -0,0 +1,4 @@
1+mod kind;
2+mod object_id;
3+mod oid;
4+mod prefix;
git-hash/tests/integration.rs+0−2
@@ -1,2 +0,0 @@
1−mod kind;
2−mod oid;
git-hash/tests/object_id/mod.rs+43−0
@@ -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+}
git-hash/tests/oid/mod.rs+1−125
@@ -1,128 +1,4 @@
1−mod prefix {
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 {
1+mod to_hex_with_len {
1262 #[test]
1273 fn display_entire_range_sha1() {
1284 let id_hex = "0123456789abcdef123456789abcdef123456789";
git-hash/tests/prefix/mod.rs+123−0
@@ -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+}
git-index/Cargo.toml+1−1
@@ -31,7 +31,7 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] · 30 unmodified lines
3131
3232 [dependencies]
3333 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" }
3535 git-bitmap = { version = "^0.1.2", path = "../git-bitmap" }
3636 git-object = { version = "^0.20.2", path = "../git-object" }
3737
git-object/Cargo.toml+1−1
@@ -22,7 +22,7 @@ verbose-object-parsing-errors = ["nom/std"] · 21 unmodified lines
2222
2323 [dependencies]
2424 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" }
2626 git-validate = { version = "^0.5.5", path = "../git-validate" }
2727 git-actor = { version = "^0.11.3", path = "../git-actor" }
2828
git-odb/Cargo.toml+1−1
@@ -28,8 +28,8 @@ required-features = [] · 27 unmodified lines
2828
2929 [dependencies]
3030 git-features = { version = "^0.22.3", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] }
31+git-hash = { version = "^0.9.9", path = "../git-hash" }
3132 git-path = { version = "^0.4.2", path = "../git-path" }
32−git-hash = { version = "^0.9.8", path = "../git-hash" }
3333 git-quote = { version = "^0.2.1", path = "../git-quote" }
3434 git-object = { version = "^0.20.3", path = "../git-object" }
3535 git-pack = { version = "^0.22.0", path = "../git-pack" }
git-pack/Cargo.toml+1−1
@@ -38,8 +38,8 @@ required-features = ["internal-testing-to-avoid-being-run-by-cargo-test-all"] · 37 unmodified lines
3838
3939 [dependencies]
4040 git-features = { version = "^0.22.3", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] }
41−git-hash = { version = "^0.9.8", path = "../git-hash" }
4241 git-path = { version = "^0.4.2", path = "../git-path" }
42+git-hash = { version = "^0.9.9", path = "../git-hash" }
4343 git-chunk = { version = "^0.3.1", path = "../git-chunk" }
4444 git-object = { version = "^0.20.3", path = "../git-object" }
4545 git-traverse = { version = "^0.16.3", path = "../git-traverse" }
git-protocol/Cargo.toml+1−1
@@ -41,7 +41,7 @@ required-features = ["async-client"] · 40 unmodified lines
4141 [dependencies]
4242 git-features = { version = "^0.22.3", path = "../git-features", features = ["progress"] }
4343 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" }
4545 git-credentials = { version = "^0.4.0", path = "../git-credentials" }
4646
4747 thiserror = "1.0.32"
git-ref/Cargo.toml+1−1
@@ -25,7 +25,7 @@ required-features = ["internal-testing-git-features-parallel"] · 24 unmodified lines
2525 [dependencies]
2626 git-features = { version = "^0.22.3", path = "../git-features", features = ["walkdir"]}
2727 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" }
2929 git-object = { version = "^0.20.3", path = "../git-object" }
3030 git-validate = { version = "^0.5.5", path = "../git-validate" }
3131 git-actor = { version = "^0.11.3", path = "../git-actor" }
git-refspec/Cargo.toml+1−1
@@ -14,7 +14,7 @@ doctest = false · 13 unmodified lines
1414 [dependencies]
1515 git-revision = { version = "^0.4.4", path = "../git-revision" }
1616 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" }
1818
1919 bstr = { version = "0.2.13", default-features = false, features = ["std"]}
2020 thiserror = "1.0.26"
git-repository/Cargo.toml+1−1
@@ -87,7 +87,7 @@ git-refspec = { version = "^0.1.1", path = "../git-refspec" } · 86 unmodified lines
8787
8888 git-config = { version = "^0.7.1", path = "../git-config" }
8989 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" }
9191 git-object = { version = "^0.20.3", path = "../git-object" }
9292 git-actor = { version = "^0.11.4", path = "../git-actor" }
9393 git-pack = { version = "^0.22.0", path = "../git-pack", features = ["object-cache-dynamic"] }
git-repository/src/repository/identity.rs+3−2
@@ -1,4 +1,5 @@
11 use std::borrow::Cow;
2+use std::time::SystemTime;
23
34 use crate::{bstr::BString, permission};
45
@@ -125,13 +126,13 @@ impl Personas { · 120 unmodified lines
125126 committer_email = committer_email.or_else(|| env_var("GIT_COMMITTER_EMAIL"));
126127 committer_date = std::env::var("GIT_COMMITTER_DATE")
127128 .ok()
128− .and_then(|date| git_date::parse(&date));
129+ .and_then(|date| git_date::parse(&date, Some(SystemTime::now())).ok());
129130
130131 author_name = author_name.or_else(|| env_var("GIT_AUTHOR_NAME"));
131132 author_email = author_email.or_else(|| env_var("GIT_AUTHOR_EMAIL"));
132133 author_date = std::env::var("GIT_AUTHOR_DATE")
133134 .ok()
134− .and_then(|date| git_date::parse(&date));
135+ .and_then(|date| git_date::parse(&date, Some(SystemTime::now())).ok());
135136
136137 user_email = user_email.or_else(|| env_var("EMAIL")); // NOTE: we don't have permission for this specific one…
137138 }
git-revision/Cargo.toml+1−1
@@ -16,7 +16,7 @@ doctest = false · 15 unmodified lines
1616 serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ]
1717
1818 [dependencies]
19−git-hash = { version = "^0.9.8", path = "../git-hash" }
19+git-hash = { version = "^0.9.9", path = "../git-hash" }
2020 git-object = { version = "^0.20.2", path = "../git-object" }
2121 git-date = { version = "^0.1.0", path = "../git-date" }
2222
git-revision/src/spec/parse/function.rs+11−3
@@ -1,3 +1,4 @@
1+use std::time::SystemTime;
12 use std::{convert::TryInto, str::FromStr};
23
34 use bstr::{BStr, BString, ByteSlice, ByteVec};
@@ -434,9 +435,16 @@ where · 430 unmodified lines
434435 } else if has_ref_or_implied_name {
435436 let time = nav
436437 .to_str()
437− .ok()
438− .and_then(git_date::parse)
439− .ok_or_else(|| Error::Time { input: nav.into() })?;
438+ .map_err(|_| Error::Time {
439+ 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+ })?;
440448 delegate
441449 .reflog(delegate::ReflogLookup::Date(time))
442450 .ok_or(Error::Delegate)?;
git-revision/src/spec/parse/mod.rs+4−1
@@ -17,7 +17,10 @@ pub enum Error { · 16 unmodified lines
1717 #[error("Cannot peel to {:?} - unknown target.", .input)]
1818 InvalidObject { input: BString },
1919 #[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+ },
2124 #[error("Sibling branches like 'upstream' or 'push' require a branch name with remote configuration, got {:?}", .name)]
2225 SiblingBranchNeedsBranchName { name: BString },
2326 #[error("Reflog entries require a ref name, got {:?}", .name)]
git-revision/tests/spec/parse/anchor/at_symbol.rs+1−1
@@ -43,7 +43,7 @@ fn reflog_by_date_for_current_branch() { · 42 unmodified lines
4343 #[test]
4444 fn reflog_by_date_with_date_parse_failure() {
4545 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()));
4747 }
4848
4949 #[test]
git-traverse/Cargo.toml+1−1
@@ -12,7 +12,7 @@ include = ["src/**/*"] · 11 unmodified lines
1212 doctest = false
1313
1414 [dependencies]
15−git-hash = { version = "^0.9.8", path = "../git-hash" }
15+git-hash = { version = "^0.9.9", path = "../git-hash" }
1616 git-object = { version = "^0.20.3", path = "../git-object" }
1717 thiserror = "1.0.32"
1818 hash_hasher = "2.0.3"
git-worktree/Cargo.toml+1−1
@@ -30,7 +30,7 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] · 29 unmodified lines
3030
3131 [dependencies]
3232 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" }
3434 git-object = { version = "^0.20.2", path = "../git-object" }
3535 git-glob = { version = "^0.3.2", path = "../git-glob" }
3636 git-path = { version = "^0.4.2", path = "../git-path" }
tests/tools/Cargo.toml+1−1
@@ -14,7 +14,7 @@ path = "src/main.rs" · 13 unmodified lines
1414 doctest = false
1515
1616 [dependencies]
17−git-hash = { version = "^0.9.8", path = "../../git-hash" }
17+git-hash = { version = "^0.9.9", path = "../../git-hash" }
1818 git-lock = { version = "^2.0.0", path = "../../git-lock" }
1919 git-discover = { version = "^0.4.2", path = "../../git-discover" }
2020 git-attributes = { version = "^0.3.3", path = "../../git-attributes" }