From 6d0beddb20092a80b113a39c862d6b680d79deb6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 31 Aug 2021 20:30:06 +0800 Subject: [PATCH 01/91] [actor #190] methods to get an actor signature at the current time --- Cargo.lock | 1 + git-actor/Cargo.toml | 1 + git-actor/src/signature/mod.rs | 69 ++++++++++++++++++++++++++++++++ git-actor/src/time.rs | 10 +++++ git-actor/tests/actor.rs | 5 ++- git-actor/tests/signature/mod.rs | 40 ------------------ git-actor/tests/time/mod.rs | 37 +++++++++++++++++ git-features/src/time.rs | 4 +- 8 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 git-actor/tests/time/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b470b54440e..4a03a2c6bdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,6 +971,7 @@ version = "0.5.0" dependencies = [ "bstr", "btoi", + "git-features", "git-testtools", "itoa", "nom", diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index 87adb2415d4..f8b9164f98e 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -18,6 +18,7 @@ serde1 = ["serde", "bstr/serde1"] all-features = true [dependencies] +git-features = { version = "^0.16.0", path = "../git-features", features = ["time"] } quick-error = "2.0.0" btoi = "0.4.2" bstr = { version = "0.2.13", default-features = false, features = ["std"]} diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index 55a4db1426c..cf823cad32c 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -99,6 +99,75 @@ mod write { } } +mod init { + use crate::{Signature, Time}; + use bstr::BString; + + impl Signature { + /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from + /// UTC based on the hosts configuration. + pub fn now_local( + name: impl Into, + email: impl Into, + ) -> Result { + let offset = git_features::time::tz::current_utc_offset()?; + Ok(Signature { + name: name.into(), + email: email.into(), + time: Time { + time: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("the system time doesn't run backwards that much") + .as_secs() as u32, + offset, + sign: offset.into(), + }, + }) + } + + /// Return an actor identified `name` and `email` at the current local time, or UTC time if the current time zone could + /// not be obtained. + pub fn now_local_or_utc(name: impl Into, email: impl Into) -> Self { + let offset = git_features::time::tz::current_utc_offset().unwrap_or(0); + Signature { + name: name.into(), + email: email.into(), + time: Time { + time: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("the system time doesn't run backwards that much") + .as_secs() as u32, + offset, + sign: offset.into(), + }, + } + } + + /// Return an actor identified by `name` and `email` at the current time in UTC. + /// + /// This would be most useful for bot users, otherwise the [`now_local()`][Signature::now_local()] method should be preferred. + pub fn now_utc(name: impl Into, email: impl Into) -> Self { + let utc_offset = 0; + Signature { + name: name.into(), + email: email.into(), + time: Time { + time: seconds_since_epoch(), + offset: utc_offset, + sign: utc_offset.into(), + }, + } + } + } + + fn seconds_since_epoch() -> u32 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("the system time doesn't run backwards that much") + .as_secs() as u32 + } +} + /// mod decode; pub use decode::decode; diff --git a/git-actor/src/time.rs b/git-actor/src/time.rs index 39a52df9901..14f7fc5684f 100644 --- a/git-actor/src/time.rs +++ b/git-actor/src/time.rs @@ -2,6 +2,16 @@ use std::io; use crate::{Sign, Time, SPACE}; +impl From for Sign { + fn from(v: i32) -> Self { + if v < 0 { + Sign::Minus + } else { + Sign::Plus + } + } +} + impl Time { /// Serialize this instance to `out` in a format suitable for use in header fields of serialized git commits or tags. pub fn write_to(&self, mut out: impl io::Write) -> io::Result<()> { diff --git a/git-actor/tests/actor.rs b/git-actor/tests/actor.rs index 89d6b6d6b97..a629086be6c 100644 --- a/git-actor/tests/actor.rs +++ b/git-actor/tests/actor.rs @@ -1,9 +1,10 @@ use std::path::PathBuf; -mod signature; - pub use git_testtools::hex_to_id; pub fn fixture(path: &str) -> PathBuf { PathBuf::from("tests/fixtures").join(path) } + +mod signature; +mod time; diff --git a/git-actor/tests/signature/mod.rs b/git-actor/tests/signature/mod.rs index 2782b93baa3..611d88f0486 100644 --- a/git-actor/tests/signature/mod.rs +++ b/git-actor/tests/signature/mod.rs @@ -1,43 +1,3 @@ -mod time { - use bstr::ByteSlice; - use git_actor::{Sign, Time}; - - #[test] - fn write_to() -> Result<(), Box> { - for (time, expected) in &[ - ( - Time { - time: 500, - offset: 9000, - sign: Sign::Plus, - }, - "500 +0230", - ), - ( - Time { - time: 189009009, - offset: 36000, - sign: Sign::Minus, - }, - "189009009 -1000", - ), - ( - Time { - time: 0, - offset: 0, - sign: Sign::Minus, - }, - "0 -0000", - ), - ] { - let mut output = Vec::new(); - time.write_to(&mut output)?; - assert_eq!(output.as_bstr(), expected); - } - Ok(()) - } -} - mod write_to { mod invalid { use git_actor::{Sign, Signature, Time}; diff --git a/git-actor/tests/time/mod.rs b/git-actor/tests/time/mod.rs new file mode 100644 index 00000000000..e28719a8d38 --- /dev/null +++ b/git-actor/tests/time/mod.rs @@ -0,0 +1,37 @@ +use bstr::ByteSlice; +use git_actor::{Sign, Time}; + +#[test] +fn write_to() -> Result<(), Box> { + for (time, expected) in &[ + ( + Time { + time: 500, + offset: 9000, + sign: Sign::Plus, + }, + "500 +0230", + ), + ( + Time { + time: 189009009, + offset: 36000, + sign: Sign::Minus, + }, + "189009009 -1000", + ), + ( + Time { + time: 0, + offset: 0, + sign: Sign::Minus, + }, + "0 -0000", + ), + ] { + let mut output = Vec::new(); + time.write_to(&mut output)?; + assert_eq!(output.as_bstr(), expected); + } + Ok(()) +} diff --git a/git-features/src/time.rs b/git-features/src/time.rs index 85470d18f5b..9fb79253142 100644 --- a/git-features/src/time.rs +++ b/git-features/src/time.rs @@ -25,7 +25,9 @@ pub mod tz { /// Note that there may be various legitimate reasons for failure, which should be accounted for. pub fn current_utc_offset() -> Result { // TODO: make this work without cfg(unsound_local_offset), see - // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + // TODO: get a function to return the current time as well to avoid double-lookups + // (to get the offset, the current time is needed) time::UtcOffset::current_local_offset() .map(|ofs| ofs.whole_seconds()) .map_err(|_| Error) From 1927be7764f6af04ecc715dd52c631a3c8e16577 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 31 Aug 2021 20:45:53 +0800 Subject: [PATCH 02/91] =?UTF-8?q?[repository=20#190]=20Make=20local-offset?= =?UTF-8?q?=20available=20on=20demand=20only=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …in all relevant crates. That way server binaries don't have to deal with the extra dependencies. --- Cargo.toml | 1 + Makefile | 8 ++++++-- cargo-features.md | 9 +++++++++ git-actor/Cargo.toml | 3 ++- git-actor/src/signature/mod.rs | 2 ++ git-repository/Cargo.toml | 2 ++ gitoxide-core/Cargo.toml | 2 ++ 7 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 311ed97aa24..377cf5871cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ pretty-cli = ["clap", "prodash/local-time", "prodash-render-tui", "prodash-render-line", + "gitoxide-core/local-offset", "env_logger", "futures-lite"] lean-cli = ["argh", "prodash/progress-log", "env_logger" ] diff --git a/Makefile b/Makefile index e4b1836baed..94ac8e06beb 100644 --- a/Makefile +++ b/Makefile @@ -85,10 +85,12 @@ check: ## Build all code in suitable configurations cargo check --no-default-features --features lean-termion cargo check --no-default-features --features max cargo check --no-default-features --features max-termion - cd git-actor && cargo check + cd git-actor && cargo check \ + && cargo check --features local-offset cd gitoxide-core && cargo check \ && cargo check --features blocking-client \ - && cargo check --features async-client + && cargo check --features async-client \ + && cargo check --features local-offset cd gitoxide-core && if cargo check --all-features 2>/dev/null; then false; else true; fi cd git-hash && cargo check --all-features \ && cargo check @@ -110,6 +112,7 @@ check: ## Build all code in suitable configurations && cargo check --features rustsha1 \ && cargo check --features fast-sha1 \ && cargo check --features progress \ + && cargo check --features time \ && cargo check --features io-pipe \ && cargo check --features crc32 \ && cargo check --features zlib \ @@ -131,6 +134,7 @@ check: ## Build all code in suitable configurations cd git-repository && cargo check --all-features \ && cargo check --no-default-features --features local \ && cargo check --no-default-features --features network \ + && cargo check --no-default-features --features one-stop-shop \ && cargo check --no-default-features cd cargo-smart-release && cargo check cd experiments/object-access && cargo check diff --git a/cargo-features.md b/cargo-features.md index f44e283e0dc..978baf3ea96 100644 --- a/cargo-features.md +++ b/cargo-features.md @@ -72,6 +72,8 @@ The library powering the command-line interface. - **async-client** - The client to connect to git servers will be async, while supporting only the 'git' transport itself. It's the most limited and can be seen as example on how to use custom transports for custom servers. +* **local-offset** + - Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. [skim]: https://github.com/lotabout/skim [git-hours]: https://github.com/kimmobrunfeldt/git-hours @@ -83,6 +85,11 @@ The library powering the command-line interface. for the LRU-cache itself low. * **pack-cache-lru-dynamic** * Provide a hash-map based LRU cache whose eviction is based a memory cap calculated from object data. + +### git-actor + +* **local-offset** + - Make `Signature` initializers using the local time (with UTC offset) available. ### git-features @@ -185,6 +192,8 @@ be selected. * **unstable** - Re-export stability tier 2 crates for convenience and make `Repository` struct fields with types from these crates publicly accessible. - Doing so is less stable than the stability tier 1 that `git-repository` is a member of. +* **local-offset** + - Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. The following toggles can be used to reduce dependencies. diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index f8b9164f98e..53d4697ac37 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -13,12 +13,13 @@ doctest = false [features] serde1 = ["serde", "bstr/serde1"] +local-offset = ["git-features/time"] [package.metadata.docs.rs] all-features = true [dependencies] -git-features = { version = "^0.16.0", path = "../git-features", features = ["time"] } +git-features = { version = "^0.16.0", path = "../git-features", optional = true } quick-error = "2.0.0" btoi = "0.4.2" bstr = { version = "0.2.13", default-features = false, features = ["std"]} diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index cf823cad32c..0ef36c3f3ed 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -106,6 +106,7 @@ mod init { impl Signature { /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from /// UTC based on the hosts configuration. + #[cfg(feature = "local-offset")] pub fn now_local( name: impl Into, email: impl Into, @@ -127,6 +128,7 @@ mod init { /// Return an actor identified `name` and `email` at the current local time, or UTC time if the current time zone could /// not be obtained. + #[cfg(feature = "local-offset")] pub fn now_local_or_utc(name: impl Into, email: impl Into) -> Self { let offset = git_features::time::tz::current_utc_offset().unwrap_or(0); Signature { diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index efa6d4d6de1..ed8dddf1adc 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -17,6 +17,7 @@ default = ["max-performance", "one-stop-shop"] unstable = [] serde1 = ["git-pack/serde1", "git-object/serde1"] max-performance = ["git-features/zlib-ng-compat", "git-features/fast-sha1"] +local-offset = ["git-actor/local-offset"] local = [ "git-url", "git-traverse", @@ -30,6 +31,7 @@ network = [ one-stop-shop = [ "local", "network", + "local-offset" ] diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 26338115439..0739bfd9b6f 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -19,6 +19,8 @@ serde1 = ["git-commitgraph/serde1", "git-repository/serde1", "git-protocol-for-c blocking-client = ["git-protocol-for-configuration-only/blocking-client", "git-repository/network"] async-client = ["git-protocol-for-configuration-only/async-client", "git-repository/network", "async-trait", "futures-io", "async-net", "async-io", "futures-lite", "blocking"] +local-offset = ["git-repository/local-offset"] + # tools organize = ["git-url", "jwalk"] estimate-hours = ["itertools", "rayon", "bstr", "fs-err"] From 3a7d3793a235ac872437f3bfedb9dd8fde9b31b1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 31 Aug 2021 21:06:08 +0800 Subject: [PATCH 03/91] [various #190] rename 'local-offset' to 'local-time-support' --- Cargo.toml | 2 +- Makefile | 4 ++-- cargo-features.md | 6 +++--- git-actor/Cargo.toml | 2 +- git-actor/src/signature/mod.rs | 4 ++-- git-features/src/time.rs | 2 +- git-repository/Cargo.toml | 4 ++-- gitoxide-core/Cargo.toml | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 377cf5871cc..c8df2bd2e5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ pretty-cli = ["clap", "prodash/local-time", "prodash-render-tui", "prodash-render-line", - "gitoxide-core/local-offset", + "gitoxide-core/local-time-support", "env_logger", "futures-lite"] lean-cli = ["argh", "prodash/progress-log", "env_logger" ] diff --git a/Makefile b/Makefile index 94ac8e06beb..b00bf8bf3f5 100644 --- a/Makefile +++ b/Makefile @@ -86,11 +86,11 @@ check: ## Build all code in suitable configurations cargo check --no-default-features --features max cargo check --no-default-features --features max-termion cd git-actor && cargo check \ - && cargo check --features local-offset + && cargo check --features local-time-support cd gitoxide-core && cargo check \ && cargo check --features blocking-client \ && cargo check --features async-client \ - && cargo check --features local-offset + && cargo check --features local-time-support cd gitoxide-core && if cargo check --all-features 2>/dev/null; then false; else true; fi cd git-hash && cargo check --all-features \ && cargo check diff --git a/cargo-features.md b/cargo-features.md index 978baf3ea96..bc3c7278865 100644 --- a/cargo-features.md +++ b/cargo-features.md @@ -72,7 +72,7 @@ The library powering the command-line interface. - **async-client** - The client to connect to git servers will be async, while supporting only the 'git' transport itself. It's the most limited and can be seen as example on how to use custom transports for custom servers. -* **local-offset** +* **local-time-support** - Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. [skim]: https://github.com/lotabout/skim @@ -88,7 +88,7 @@ The library powering the command-line interface. ### git-actor -* **local-offset** +* **local-time-support** - Make `Signature` initializers using the local time (with UTC offset) available. ### git-features @@ -192,7 +192,7 @@ be selected. * **unstable** - Re-export stability tier 2 crates for convenience and make `Repository` struct fields with types from these crates publicly accessible. - Doing so is less stable than the stability tier 1 that `git-repository` is a member of. -* **local-offset** +* **local-time-support** - Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. The following toggles can be used to reduce dependencies. diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index 53d4697ac37..1587274a9dc 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [features] serde1 = ["serde", "bstr/serde1"] -local-offset = ["git-features/time"] +local-time-support = ["git-features/time"] [package.metadata.docs.rs] all-features = true diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index 0ef36c3f3ed..ad97086a34e 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -106,7 +106,7 @@ mod init { impl Signature { /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from /// UTC based on the hosts configuration. - #[cfg(feature = "local-offset")] + #[cfg(feature = "local-time-support")] pub fn now_local( name: impl Into, email: impl Into, @@ -128,7 +128,7 @@ mod init { /// Return an actor identified `name` and `email` at the current local time, or UTC time if the current time zone could /// not be obtained. - #[cfg(feature = "local-offset")] + #[cfg(feature = "local-time-support")] pub fn now_local_or_utc(name: impl Into, email: impl Into) -> Self { let offset = git_features::time::tz::current_utc_offset().unwrap_or(0); Signature { diff --git a/git-features/src/time.rs b/git-features/src/time.rs index 9fb79253142..e0a6b5b712e 100644 --- a/git-features/src/time.rs +++ b/git-features/src/time.rs @@ -3,7 +3,7 @@ pub mod tz { mod error { use std::fmt; - /// The error returned by [`offset()`] + /// The error returned by [`current_utc_offset()`][super::current_utc_offset()] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Error; diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index ed8dddf1adc..0e8e255162e 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -17,7 +17,7 @@ default = ["max-performance", "one-stop-shop"] unstable = [] serde1 = ["git-pack/serde1", "git-object/serde1"] max-performance = ["git-features/zlib-ng-compat", "git-features/fast-sha1"] -local-offset = ["git-actor/local-offset"] +local-time-support = ["git-actor/local-time-support"] local = [ "git-url", "git-traverse", @@ -31,7 +31,7 @@ network = [ one-stop-shop = [ "local", "network", - "local-offset" + "local-time-support" ] diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 0739bfd9b6f..826da953526 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -19,7 +19,7 @@ serde1 = ["git-commitgraph/serde1", "git-repository/serde1", "git-protocol-for-c blocking-client = ["git-protocol-for-configuration-only/blocking-client", "git-repository/network"] async-client = ["git-protocol-for-configuration-only/async-client", "git-repository/network", "async-trait", "futures-io", "async-net", "async-io", "futures-lite", "blocking"] -local-offset = ["git-repository/local-offset"] +local-time-support = ["git-repository/local-time-support"] # tools organize = ["git-url", "jwalk"] From 7c559d6e1b68bc89220bca426257f383bce586ae Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 1 Sep 2021 16:27:43 +0800 Subject: [PATCH 04/91] [repository #190] A way to write objects and the empty tree specifically --- Cargo.lock | 1 + STABILITY.md | 39 ++++---- crate-status.md | 1 + etc/crate-structure.monopic | Bin 3372 -> 3498 bytes git-actor/src/signature/mod.rs | 3 +- git-object/src/lib.rs | 7 ++ git-repository/Cargo.toml | 1 + git-repository/src/easy/ext/object.rs | 85 +++++++++--------- git-repository/src/easy/impls.rs | 2 +- git-repository/src/easy/object/mod.rs | 29 +++--- git-repository/src/easy/object/tree.rs | 4 +- git-repository/src/easy/oid.rs | 6 +- git-repository/src/lib.rs | 2 +- git-repository/src/repository.rs | 30 ++++++- .../tests/{access/mod.rs => easy/access.rs} | 0 git-repository/tests/easy/mod.rs | 3 + git-repository/tests/easy/object.rs | 35 ++++++++ .../{reference/mod.rs => easy/reference.rs} | 12 ++- git-repository/tests/object/mod.rs | 19 ---- git-repository/tests/repo.rs | 4 +- 20 files changed, 172 insertions(+), 111 deletions(-) rename git-repository/tests/{access/mod.rs => easy/access.rs} (100%) create mode 100644 git-repository/tests/easy/mod.rs create mode 100644 git-repository/tests/easy/object.rs rename git-repository/tests/{reference/mod.rs => easy/reference.rs} (87%) delete mode 100644 git-repository/tests/object/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4a03a2c6bdd..2791072d851 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,6 +1188,7 @@ name = "git-repository" version = "0.8.1" dependencies = [ "anyhow", + "bstr", "git-actor", "git-config", "git-diff", diff --git a/STABILITY.md b/STABILITY.md index 2391730b707..3a8acae0ab6 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -7,19 +7,19 @@ Please note that this guide isn't stable itself and may be adjusted to fit chang ## Terminology -* _dependent crate_ +- _dependent crate_ - A crate which depends on a crate in this workspace directly. -* _downstream crate_ +- _downstream crate_ - A crate which directly or indirectly depends on a crate in this workspace. -* _workspace crate_ +- _workspace crate_ - A crate which is a member of this workspace and hence is stored in this repository -* _breaking change_ +- _breaking change_ - A change in code that requires a `dependent crate` to adjust their code to fix compile errors. -* _release_ +- _release_ - A new version of a crate is published to crates.io -* _development version_ +- _development version_ - A crate version whose _major_ version is 0. -* _release version_ +- _release version_ - A crate version whose _major_ version is 1 or higher. ## Tiers @@ -56,17 +56,20 @@ The following schematic helps to visualize what follows. ║ │ ┌─────────────┐ ┌─────────────┐ │ ║ ║ │ │ git-ref │ │ git-config │ │ ║ │ ║ │ └─────────────┘ └─────────────┘ │ ║ - ║ └───────────────────────────────────┘ ║ │ - ║ ║ - ╚═════════════════════════════════════════════╝ │ - Stability Tier 2 ─────────────────────────────┐ - │ │ │ - │ Plumbing Crates─────────────────────┐ │ - │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ - │ │ │ git-odb │ │ git-diff │ │ │ - │ │ └─────────────┘ └─────────────┘ │ │ │ + ║ │ ┌─────────────┐ │ ║ │ + ║ │ │ git-object │ │ ║ + ║ │ └─────────────┘ │ ║ │ + ║ └───────────────────────────────────┘ ║ + ║ ║ │ + ╚═════════════════════════════════════════════╝ + Stability Tier 2 ─────────────────────────────┐ │ + │ │ + │ Plumbing Crates─────────────────────┐ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ - │ │ │git-traverse │ │ git-pack │ │◀─ ─│─ ┘ + │ │ │ git-odb │ │ git-diff │ │ │ │ + │ │ └─────────────┘ └─────────────┘ │ │ + │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ + │ │ │git-traverse │ │ git-pack │ │◀ ─ ┼ ─ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ │ │ …many more… │ │ @@ -95,7 +98,7 @@ If there are additional breaking changes without a release, these push back the ### Tier 1: released apps and application crates -Released apps and application crates are marked with major version number 1 or above, like `2.3.0+21.06` and live in tier 1 _(->ST1)_, +Released apps and application crates are marked with major version number 1 or above, like `2.3.0+21.06` and live in tier 1 _(->ST1)_, with the build identifiers for year (`21`) and and month `06` appended, based on the actual release year and month. Breaking changes are collected and may be released no more often than every 6 months by incrementing the major version number. If there are additional breaking changes, diff --git a/crate-status.md b/crate-status.md index d23797fac3b..03a48244449 100644 --- a/crate-status.md +++ b/crate-status.md @@ -239,6 +239,7 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. ### git-repository * [x] utilities for applications to make long running operations interruptible gracefully and to support timeouts in servers. +* [ ] handle `core.repositoryFormatVersion` and extensions * [x] discovery * [ ] option to not cross file systems * [ ] handle git-common-dir diff --git a/etc/crate-structure.monopic b/etc/crate-structure.monopic index 768eb25b20d0fd060bf8deb85e4b8f85fa369b76..2a672eebf69cab79802c8b935325f54ffe0597fd 100644 GIT binary patch literal 3498 zcmV;b4OR00O;1iwP)S1pABzY8000000u$|B%WmVy75x>1Yk5$&9;~9b1I#LmAd^La zNkgD5%GQi5IuzaQPQyUHCLfkBNj+FaN)~NW)D<<77q+Mei}gJBeeaJbzx_SCE7#G@ z4|919No+1>`Q5b0=FyG&Fq%ZOMX}Dx=!Q(9Lfk*}f~h$m6`{VAI+X8H}C zME|`o1y)i79E=wfYxhB64Q45TQ%A7pmOn&E$u9p*-JUS%U5N#(hvUa z%fx;1lrL6hnt%K9>iaBv_nCI=v!bTysuR=OWwx4S`j)bI&L+`gKFgL<)2ZsAdVu=V zr}gwUFJ@VxJHO0}=!R1jOGP=8lW6^r&hl?lJ4$_*{(V}O>D@!B+YarT7ViGIukV)% z5<*lpkaTtbyiAMgDf;_KG%wP}Y?`j_9`d3w3klT^s5)aGIlV31Q#U>|^KCv{=68RX z;myTZJFol@CS!eE$$WxJ`Ap^+&7O)J$)5LpMIlR%V!C{>t>;E>2hp zO2P!?^SNESh)hW2$G2YmTelhMc?8p&_4BR%wZ2z(a_x3Vb$57>?`XAvTdi(PLb2X4 zGiCMRNzu^8t_=`CD5*{>5*AMkn{)nFJYCYTk zx&HsUJz2|*88o+t6>tmXHU*faE9U84W{%|cuGk!Z4;;Wtzb@_k(WHrL*K~3%j*n~9qZsulrun1lLi!7JS-8f?KwJaL%+##EWtp3v#Y(T{beYW^ z@H8-I*MV}sE^~M9j!vh`#r^8ha&}-4=WFKPgeU1u^$^}KW?4W5H=El>E*v%K8@SEO zGJkXj(Lve$qP%`Ei|5DanB5LzBtyqY`i-$#^F0Tdzv#BE_1Vq*NV&k_ya7=Xkp!#H zgT#<1)!#lRy<8;qFDam)O0yR;X)*x>lHLg}GLkYlWFs>KPH18)$;7p9x`D#dnwYAxmd9{?)Te z{4&}dvRtgPE!?g4_!eyrKWqW!ElS-&7yAKd3mX*1A~s>3R=r%8-wkiW8Whkij;H?> zas7+_@s*j!ozx4Ms%R9j#9wh@Hi@0uRbHm<L}k67(%P?GAWF%e}hK%gJ6$_ENGJlC6x~gMPoI*zb~M`%yS*3QcgJ5|3JQ zJb4Esv_YFyV|0Z!vJ2W2>t)Ai)AuD1>y;A}N8cB>>ig7L>if8ySUt`0^?kpV>Fr{v z&+*mY7g=!ySDiI}YD44K9MsYDeahpQ@<66MlBob?$I$oj6S89r)%OWJJO+MTh08oL zi|gLs?f6aFXvci_eTPEE=}y4uPQd9-!0ArF3HjhOFyJ&W;50BJ>v80sGL_K1NIZW? z2{}tHIK3-%SHd6}A|aO>`#ou)-MV?#u&2>T?h%y4I1O{MrwAW6l`EnTXF}O1oJqSS z65=!EOhAmonb7XWN7yhvvPp7$UIaYn0iN@Kph5N8TuT15yokHIA1gxXKvsk!!Igxw zq-$l7e$6!CdO&s5=i&`6wB6uBgTaM`D?XONg>H7Cm%)X_M=-cB(aYdM0L^4zTTYix z`l(sj(8R5?e@$1SwqbDl+o()S*1-Ll%_Ky?Azz zOx0_^V*>WAd_M3yrE~Ak`Cl zs#q1lqiTD2Ky__fs$OXLPPjE?dJfdl(eP~Jt;f?~6q(&EdGWnFFm8rh!zf|^gm7yJ z@zxLmt|3HR!=;5>!|<4Eg%>^nDn3yQ(*8{6-?C+vuCuFO^LhC#EwZbx@Y6p+Xjng` zF*z}v3)mzrpGucQLay@w?Tog`1{Ng+~3>Chc0mjfMr8w zXTSYu*nYtGbHaeVRi_*JSHLrO4NS0dfF0@*njko4@I$D+IilWLw&|^V#gOhKY5aox zicPFn|5Prj4gv^K04gMPNybX*(&$#w+s8OLMpBogS5g;+=qe~h8&4?tP`eM^!=bqI z!G}Kbt`H|C?s9dO3Pv`PwEMsR{P!_keZP9li%eg4mU(TuuUh2gWDJp)d+g*MIt7VB zcIuGCuf9&ypW$J_`{muO}IsOD?Dr&n?^Ch2WK+~-ewTo%}mHmoq6iQjPOHU&t%dI zALyxhr2>4qr5@TsVzVQ6H%G&^Q~ekQk~##xj=`^k@Iw?n&cm3D{Iut)&>q2rw*@(}dyb=lkL?b>`mVz_xu3 zM<}S}HiEkfj2r|;4gw#u?xGMFKQ z6*3qhoei>2**o9Sb++`uS)0qZPenY2L+QIWCbx)jXeD=0H zR7WwV1=Cqb01Y7lG=v1urryY9nbXK%6`CYxSr?-Y702VU)X7UDY(jS!*4%`RCXob# zcx?|49_Aq?==QNHK~$WKfYNFgEok3W#;{q2j{OLTMHOcg-+ zYn|U7i?dLe*%RWuQgTot5GzIX;el9kVk{7&pB&JkYhTQ6J99v`nAk=@_ZT@NLy@~s zaF2OpWL}HI;>%)wm>SIc?CYIt+>8Tqsk6*QUe$+r4W?-{c*aYkB>P1M3229Y!h|}0 z05|{48hl{v&tLQrxz?bcb(ewpcGBQ6$|Uo^3G?7Y#)GpK4}qKf(vk*?!AXM&IYed; zg6Jc0jwZgX6y9MPk!;e89$=As1^i1mF-hV~D{_n}Nrckjohwl#Z^Vfayb(iTj!>5) zl;sFjIbwBL;)EPbM_kqDofWCs(A1G8js!~|#IWMnm_)30i)EE(XDQCva(SkGXhcX;f++v9L7dsE Y15a~AenelR8}|D8e~3-<)=R7a03+kolmGw# literal 3372 zcmV+{4b$@fO;1iwP)S1pABzY8000000u$|BOK;=475*!M*Ukl>BAM+4?y8HTw~GSp zz))=2QOB`TTbVoQAV7aje^`G>Nl`qO6*-YCAIoEBVMkV^BZ`mn_|AhrM%n#8lWD#g z-TiQfx70-LaF$Ic>tsH<^Cw5+(JWnWl6-VW#-p{pVe*u1(mc&pqdRKGqx|J5nWQuO zgN;YwUz7PVURcWXbzG2P-AP9+NlS{>YHEz5z4;?ao(e+g%95Nclewo`AvtbnfxkM> z{Jqm`nXN~6f+vZt|ztdSVxzE*G9+rw^_h zfUcu3{koVmbhvwwc(r(5#%t(GJJ06p_%WHptLa0wuJnTD_5o#MibqcF*Z!$HCzl(4 za+yuPy4Rb>%T3ZWA0E=#ELp(?)BItbJue>M#C>d=ZBMOc6W6_V9CyyuM>}5lafMMQ z&_UUJUX0y{j9sVt_uHiZwlqgVjhV71H_!L>YJ0D3qEf^~#L}-9D_Kun8lf?WLJr4e6@(QnfaP zyBo%ScOc7_m2aH7yX+i(vA0yFbJ;W7$xs}Ka3I4$6b{tr3p{8RZyvT?`D5g{z;p4P z+3(kSXDxSn(A^Fr;0MZ23gL!sJ&&h}TaxuzvHO;RWMyJ)myR2iNis|G%21d6@j>^& zu$VU^UM`Tg3vIWdfk9~3Gh1Gwo0d&@9U*7F{?R zV}o!sv6rJ!d7&Jwc(DOE8f(qM7^$+bTRt|Y=V3H(EE+f#RpCH~1M}1JFn@P97RG2# z7DgHWP=DI;A?ijZbxdZY$F|P{5wFBn2Q5$%ZiL>Y{*c6BdF?!ll*EO^VzPKjy&@N> zc<1Z*TVgv0#+@0Yv5{hju-P+*?C|(*4EUGnta&9jk_r# zdxS5%s@!EYtZd~be{m9(SC}v#7rk3bosy;h$zFWp*m^Sus1T^l7z(Tl!Md6V7}TDjXo z;o9{^FR1G{Fl)QpqO7eg?~PF$oIuN#?27bdSL9Ue>MzMMi8sm3@7X;6BVH#r-{>!Y z1HR=I_a=FzyT~irB(FF}10*7WHov$zObrlGMGr6%4}qgdl7=!YQ;QrV!YGvojzm=> zlW1>s1a-0Kn3^aaLe$b-Q5(I%XlkH6h};<6?M{q!#-Wk7KPh!ZTL zU=S4uP6w&@Rt~-^?21z`Q8k+W%oLum6L@MrRr~B97WEY?`oM)J#tfJO-hfeoWeF&; zPgE=3*R-Tc;j~&K^@f)zI0~lhJ-__Qx;U%%DV}}}ujm(eTL6_tW)Fg@Bc++(>f{J& zbdgRWyTJG$2-_SGC7e|i)(;sB!xINCz1fUWZA1W+nHA9oOqbWbqV zi<8#ef$#GeTi(E_Ci>*ufn6kbuxxfH?vUTElVTNS>zDS7g6;T=>G*aCHo<^RFkll5 z*aQPM!PJ$p3D(zbObJDUhziQ_^2u_DB(Ipbo8(`U)$`+hvMw}ET`aYzxPTUufECcQ zRkqn{;8g^Y-p|=#`Y0D;6|^-j@C+WJjsVj*6dR}eqFxaMcHLU; zK5{G9$`?1?or3O`kNqs{$Hl(H4hJbdZ`x7%CPeWqi|?rTR>ik2zD@B>iT^|WALjq~ z`>7)U?-WVARm1gRqiLT2v(1n*MbS$(cZmJ-Hs%Q?*fIRZ5h502kPu@ZW_s|-x+zzW z|1_)|zaF?>58ST@&Z`T-nUxne)0`y*LnqKyI%5~VNZEFg8YFy6avZu$552ZJh5Whz z>pl36P?6!9oZNyl>>}|UIiLmS!_8Y8Z)OMOvA@S`0PD>_c!vQy&VU_fz>YJ~b;bNr z(--8?cusg}@Z4Uc3uBYw%=lUnMtFI^6d#DK{qH~jeT-KxH;>smvB#~Mn`Aq@O^_jY zn+QR_A_V;k6Wrls1X)$@vNc+;HQIr#L3Hjpay7|&hXEs5O9BzwJJ4<1J5^63p|1RjlBS%4abMaL-r#If3vyWR_a=us- zSaNKLf>C}>_#*(zG=U??-PhL!pcNIgIvaA;)JcsP$*AOkX~$hyn262{ed6Y)2cK94 zt~na`#103ainO=3EK7)8j^%QjPS3M=U{oUTDG}I|2=Oq2AKNp0-nf&HePJhcXl%3# z^9CYl98WMb`%!2#wO3sk=oWRx+$3li5@OlpP^F40jp<&hNS>lpk?d5eNSRI+3q7}Z zFqeMw>1{f$x5?6)r;GMh3T5>`S2=*0LI98_V6YG{SST1QlwO&?iS~{OBY2XPUtq4( zpO?>%_vvbJ^Xod!lg&2Ak6dE5&Q4}C%owDg4!K8J@-6;ASE_6D2k1&4m1aGjs&q@6 zsyaFb@#GlvDk+l@_Fy)^?i4623bZE$nE*-*q(@a6C@M|us!=I*utvpNYg7s%8C2BC zi|H!>HhD3NqMNyo3_o@zLNyiGuph%5P4kE+kuBA3LB*Y3sU$ zh!fc$Z8o7hWrk65ZqUCmpGd&bYZlGh!vI9o!%hc)0Ss9D1T;(nDlh>RnD8q@0Z3o? zU&PkM=yhAI5iQIJv6Q&HOuRh%ZfV_Go&(z_ z367Wuz88a({V~X%Wtz5FiN{jX5?gWS^YV9A!B(av{u2J5G+->$gDKRbDb&L$%)=?n z!zs+eDa^wu%)=?n12W7bGR#w9o(l6+T2ze^1}`tPTWql;`*j$?r9OQ&zWA&ud}Jl2 z!bw_N>7rgzc!>QQ1vsDtcz*IRRt#wtRrRsV7- zoE`z8S7#5P@NtpbT|-d3CNvm`5g%Am*Kzr(YbB|+SL3vq8y#K?Ur5W5>MS6O#Tl`{Iw z%A*;2q4t;_#2$MaXVBIrGtsU|HbBCZ32G3O3x;aJP%PNU>oUenZ*1A`{@#SWb=n59 zxr&K6E%r7qHvw6d*VV6yD|FodUK+5uZm~`Fj|bz1h4ea=V^ieJJck#$&EJro!+afo zOYAyr&EF&a#(g*di-+C*V6#8i>kqd2i_~?w52N02n>w&ikF+ex1ZzWMRz8UiD+9ng z1x%3l3Yl;&PtEo&cK;VB=K)?cJ5L*O(6Wkh-)`uczAb@9H;qyPXn CYk^1r diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index ad97086a34e..8d40eb3f99b 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -100,9 +100,10 @@ mod write { } mod init { - use crate::{Signature, Time}; use bstr::BString; + use crate::{Signature, Time}; + impl Signature { /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from /// UTC based on the hosts configuration. diff --git a/git-object/src/lib.rs b/git-object/src/lib.rs index cad68e32f6f..da4ef88a42e 100644 --- a/git-object/src/lib.rs +++ b/git-object/src/lib.rs @@ -217,6 +217,13 @@ pub struct Tree { pub entries: Vec, } +impl Tree { + /// Return an empty tree which serializes to a well-known hash + pub fn empty() -> Self { + Tree { entries: Vec::new() } + } +} + /// #[cfg(feature = "verbose-object-parsing-errors")] pub mod decode { diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 0e8e255162e..55c78362aa0 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -54,6 +54,7 @@ git-protocol = { version ="^0.10.0", path = "../git-protocol", optional = true } git-diff = { version ="^0.9.0", path = "../git-diff", optional = true } git-features = { version = "^0.16.0", path = "../git-features", features = ["progress"] } +bstr = "0.2.15" signal-hook = { version = "0.3.9", default-features = false } thiserror = "1.0.26" parking_lot = { version = "0.11.2", features = ["arc_lock"] } diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 595fa826099..b134ae5d209 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -5,57 +5,56 @@ use git_odb::{Find, FindExt}; use crate::{ easy, - easy::{object, ObjectRef}, + easy::{object, ObjectRef, Oid}, }; -pub fn find_object( - access: &A, - id: impl Into, -) -> Result, object::find::existing::Error> { - let state = access.state(); - let id = id.into(); - let kind = { - let mut buf = access.state().try_borrow_mut_buf()?; - let obj = access - .repo()? - .odb - .find(&id, &mut buf, state.try_borrow_mut_pack_cache()?.deref_mut())?; - obj.kind - }; - - ObjectRef::from_current_buf(id, kind, access).map_err(Into::into) -} - -pub fn try_find_object( - access: &A, - id: impl Into, -) -> Result>, object::find::Error> { - let state = access.state(); - let id = id.into(); - access - .repo()? - .odb - .try_find( - &id, - state.try_borrow_mut_buf()?.deref_mut(), - state.try_borrow_mut_pack_cache()?.deref_mut(), - )? - .map(|obj| { - let kind = obj.kind; - drop(obj); - ObjectRef::from_current_buf(id, kind, access).map_err(Into::into) - }) - .transpose() -} - pub trait ObjectAccessExt: easy::Access + Sized { // NOTE: in order to get the actual kind of object, is must be fully decoded from storage in case of packs // even though partial decoding is possible for loose objects, it won't matter much here. fn find_object(&self, id: impl Into) -> Result, object::find::existing::Error> { - find_object(self, id) + let state = self.state(); + let id = id.into(); + let kind = { + let mut buf = self.state().try_borrow_mut_buf()?; + let obj = self + .repo()? + .odb + .find(&id, &mut buf, state.try_borrow_mut_pack_cache()?.deref_mut())?; + obj.kind + }; + + ObjectRef::from_current_buf(id, kind, self).map_err(Into::into) } fn try_find_object(&self, id: impl Into) -> Result>, object::find::Error> { - try_find_object(self, id) + let state = self.state(); + let id = id.into(); + self.repo()? + .odb + .try_find( + &id, + state.try_borrow_mut_buf()?.deref_mut(), + state.try_borrow_mut_pack_cache()?.deref_mut(), + )? + .map(|obj| { + let kind = obj.kind; + drop(obj); + ObjectRef::from_current_buf(id, kind, self).map_err(Into::into) + }) + .transpose() + } + + fn write_object(&self, object: &git_object::Object) -> Result, object::write::Error> { + use git_odb::Write; + + use crate::ext::ObjectIdExt; + + let repo = self.repo()?; + repo.odb + .write(object, repo.hash_kind) + .map(|oid| oid.attach(self)) + .map_err(Into::into) } } + +impl ObjectAccessExt for A where A: easy::Access + Sized {} diff --git a/git-repository/src/easy/impls.rs b/git-repository/src/easy/impls.rs index 3d8d27fa02d..1195c0754ee 100644 --- a/git-repository/src/easy/impls.rs +++ b/git-repository/src/easy/impls.rs @@ -70,7 +70,7 @@ impl<'repo> easy::Access for EasyShared<'repo> { } impl easy::Access for Easy { - type RepoRef = Rc; + type RepoRef = Rc; // TODO: this could be a reference with GATs type RepoRefMut = &'static mut Repository; // this is a lie fn repo(&self) -> Result { diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index 9b71006d2f9..2da1cbc8ba7 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -4,7 +4,6 @@ use std::{cell::Ref, convert::TryInto}; use git_hash::ObjectId; pub use git_object::Kind; use git_object::{CommitRefIter, TagRefIter}; -use git_odb as odb; use crate::{ easy, @@ -59,11 +58,10 @@ where } pub mod find { - use git_odb as odb; use crate::easy; - pub(crate) type OdbError = odb::compound::find::Error; + pub(crate) type OdbError = git_odb::compound::find::Error; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -76,11 +74,9 @@ pub mod find { } pub mod existing { - use git_odb as odb; - use crate::easy; - pub(crate) type OdbError = odb::pack::find::existing::Error; + pub(crate) type OdbError = git_odb::pack::find::existing::Error; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -94,6 +90,18 @@ pub mod find { } } +pub mod write { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + OdbWrite(#[from] git_odb::loose::write::Error), + #[error("BUG: The repository could not be borrowed")] + BorrowRepo(#[from] easy::borrow::repo::Error), + } +} + impl<'repo, A> ObjectRef<'repo, A> { pub fn to_owned(&self) -> Object { Object { @@ -121,11 +129,11 @@ where A: easy::Access + Sized, { pub fn to_commit_iter(&self) -> Option> { - odb::data::Object::new(self.kind, &self.data).into_commit_iter() + git_odb::data::Object::new(self.kind, &self.data).into_commit_iter() } pub fn to_tag_iter(&self) -> Option> { - odb::data::Object::new(self.kind, &self.data).into_tag_iter() + git_odb::data::Object::new(self.kind, &self.data).into_tag_iter() } } @@ -135,6 +143,7 @@ pub mod peel_to_kind { use crate::{ easy, easy::{ + ext::ObjectAccessExt, object::{peel_to_kind, Kind}, ObjectRef, }, @@ -155,13 +164,13 @@ pub mod peel_to_kind { let tree_id = self.to_commit_iter().expect("commit").tree_id().expect("valid commit"); let access = self.access; drop(self); - self = crate::easy::ext::object::find_object(access, tree_id)?; + self = access.find_object(tree_id)?; } Kind::Tag => { let target_id = self.to_tag_iter().expect("tag").target_id().expect("valid tag"); let access = self.access; drop(self); - self = crate::easy::ext::object::find_object(access, target_id)?; + self = access.find_object(target_id)?; } Kind::Tree | Kind::Blob => { return Err(peel_to_kind::Error::NotFound { diff --git a/git-repository/src/easy/object/tree.rs b/git-repository/src/easy/object/tree.rs index cc51a605b56..42feaac389d 100644 --- a/git-repository/src/easy/object/tree.rs +++ b/git-repository/src/easy/object/tree.rs @@ -2,7 +2,7 @@ use git_object::{bstr::BStr, TreeRefIter}; use crate::{ easy, - easy::{object::find, TreeRef}, + easy::{ext::ObjectAccessExt, object::find, TreeRef}, }; impl<'repo, A> TreeRef<'repo, A> @@ -30,7 +30,7 @@ where let access = self.access; drop(entry); drop(self); - self = match crate::easy::ext::object::find_object(access, next_id)?.try_into_tree() { + self = match access.find_object(next_id)?.try_into_tree() { Ok(tree) => tree, Err(_) => return Ok(None), }; diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 1dbedaf5450..b0680a48684 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -2,7 +2,7 @@ use git_hash::{oid, ObjectId}; use crate::{ easy, - easy::{object::find, Object, ObjectRef, Oid}, + easy::{ext::ObjectAccessExt, object::find, Object, ObjectRef, Oid}, }; impl<'repo, A, B> PartialEq> for Oid<'repo, B> { @@ -60,13 +60,13 @@ where // NOTE: Can't access other object data that is attached to the same cache. /// Find the [`ObjectRef`] associated with this object id, and assume it exists. pub fn object(&self) -> Result, find::existing::Error> { - crate::easy::ext::object::find_object(self.access, self.id) + self.access.find_object(self.id) } // NOTE: Can't access other object data that is attached to the same cache. /// Try find the [`ObjectRef`] associated with this object id, it might not be available locally. pub fn try_object(&self) -> Result>, find::Error> { - crate::easy::ext::object::try_find_object(self.access, self.id) + self.access.try_find_object(self.id) } } diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 2e7646399a7..03cdd76cc79 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -96,7 +96,6 @@ pub use git_features::{parallel, progress, progress::Progress}; pub use git_hash as hash; #[cfg(feature = "unstable")] pub use git_lock as lock; -#[cfg(feature = "unstable")] pub use git_object as objs; #[cfg(feature = "unstable")] pub use git_odb as odb; @@ -154,6 +153,7 @@ pub struct Repository { pub(crate) odb: git_odb::linked::Store, /// The path to the worktree at which to find checked out files pub work_tree: Option, + hash_kind: git_hash::Kind, // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how // packs are refreshed. This would be `git_config::fs::Config` when ready. diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 44d9c3249f8..81be37d8f98 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -35,9 +35,9 @@ pub mod from_path { } pub mod open { - use std::path::PathBuf; + use std::{borrow::Cow, path::PathBuf}; - use git_config::values::Boolean; + use git_config::values::{Boolean, Integer}; use crate::Repository; @@ -49,6 +49,8 @@ pub mod open { NotARepository(#[from] crate::path::is_git::Error), #[error(transparent)] ObjectStoreInitialization(#[from] git_odb::linked::init::Error), + #[error("Cannot handle objects formatted as {:?}", .name)] + UnsupportedObjectFormat { name: bstr::BString }, } impl Repository { @@ -70,8 +72,8 @@ pub mod open { git_dir: PathBuf, mut worktree_dir: Option, ) -> Result { + let config = git_config::file::GitConfig::open(git_dir.join("config"))?; if worktree_dir.is_none() { - let config = git_config::file::GitConfig::open(git_dir.join("config"))?; let is_bare = config .value::>("core", None, "bare") .map_or(false, |b| matches!(b, Boolean::True(_))); @@ -79,6 +81,27 @@ pub mod open { worktree_dir = Some(git_dir.parent().expect("parent is always available").to_owned()); } } + let hash_kind = if config + .value::("core", None, "repositoryFormatVersion") + .map_or(0, |v| v.value) + == 1 + { + if let Ok(format) = config.value::>("extensions", None, "objectFormat") { + match format.as_ref() { + b"sha1" => git_hash::Kind::Sha1, + _ => { + return Err(Error::UnsupportedObjectFormat { + name: format.to_vec().into(), + }) + } + } + } else { + git_hash::Kind::Sha1 + } + } else { + git_hash::Kind::Sha1 + }; + Ok(crate::Repository { odb: git_odb::linked::Store::at(git_dir.join("objects"))?, refs: git_ref::file::Store::at( @@ -90,6 +113,7 @@ pub mod open { }, ), work_tree: worktree_dir, + hash_kind, }) } } diff --git a/git-repository/tests/access/mod.rs b/git-repository/tests/easy/access.rs similarity index 100% rename from git-repository/tests/access/mod.rs rename to git-repository/tests/easy/access.rs diff --git a/git-repository/tests/easy/mod.rs b/git-repository/tests/easy/mod.rs new file mode 100644 index 00000000000..08204d0e258 --- /dev/null +++ b/git-repository/tests/easy/mod.rs @@ -0,0 +1,3 @@ +mod access; +mod object; +mod reference; diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs new file mode 100644 index 00000000000..69508931d35 --- /dev/null +++ b/git-repository/tests/easy/object.rs @@ -0,0 +1,35 @@ +use git_repository::easy; + +mod in_bare { + use git_repository::prelude::ObjectAccessExt; + + #[test] + fn write_empty_tree() { + let tmp = tempfile::tempdir().unwrap(); + let repo = git_repository::init_bare(&tmp).unwrap().into_easy(); + let oid = repo.write_object(&git_repository::objs::Tree::empty().into()).unwrap(); + assert_eq!( + oid, + git_repository::hash::ObjectId::empty_tree(), + "it produces a well-known empty tree id" + ) + } +} + +#[test] +fn object_ref_size_in_memory() { + assert_eq!( + std::mem::size_of::>(), + 56, + "the size of this structure should not changed unexpectedly" + ) +} + +#[test] +fn oid_size_in_memory() { + assert_eq!( + std::mem::size_of::>(), + 32, + "the size of this structure should not changed unexpectedly" + ) +} diff --git a/git-repository/tests/reference/mod.rs b/git-repository/tests/easy/reference.rs similarity index 87% rename from git-repository/tests/reference/mod.rs rename to git-repository/tests/easy/reference.rs index c0a7031690c..a27fa91d46a 100644 --- a/git-repository/tests/reference/mod.rs +++ b/git-repository/tests/easy/reference.rs @@ -1,18 +1,16 @@ -fn repo() -> crate::Result { - crate::repo("make_references_repo.sh").map(Into::into) -} - mod find { use std::convert::TryInto; use git_ref as refs; - use git_repository::prelude::*; + use git_repository::prelude::ReferenceAccessExt; use git_testtools::hex_to_id; - use crate::reference::repo; + fn repo() -> crate::Result { + crate::repo("make_references_repo.sh").map(Into::into) + } #[test] - fn find_and_peel() { + fn and_peel() { let repo = repo().unwrap(); let mut packed_tag_ref = repo.try_find_reference("dt1").unwrap().expect("tag to exist"); assert_eq!(packed_tag_ref.name(), "refs/tags/dt1".try_into().unwrap()); diff --git a/git-repository/tests/object/mod.rs b/git-repository/tests/object/mod.rs deleted file mode 100644 index 3fe89ebba1d..00000000000 --- a/git-repository/tests/object/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use git_repository::easy; - -#[test] -fn object_ref_size_in_memory() { - assert_eq!( - std::mem::size_of::>(), - 56, - "the size of this structure should not changed unexpectedly" - ) -} - -#[test] -fn oid_size_in_memory() { - assert_eq!( - std::mem::size_of::>(), - 32, - "the size of this structure should not changed unexpectedly" - ) -} diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index de538206f33..e24fcc39080 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -7,8 +7,6 @@ fn repo(name: &str) -> crate::Result { Ok(Repository::discover(repo_path)?) } -mod access; mod discover; +mod easy; mod init; -mod object; -mod reference; From 1e029b4beb6266853d5035c52b3d85bf98469556 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 1 Sep 2021 20:29:44 +0800 Subject: [PATCH 05/91] [repository #190] refactor --- git-repository/src/easy/ext/reference.rs | 6 +++--- git-repository/tests/easy/object.rs | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 203487b5997..d74324a1032 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -23,8 +23,8 @@ pub trait ReferenceAccessExt: easy::Access + Sized { lock_mode: lock::acquire::Fail, force: bool, ) -> Result, reference::edit::Error> { - self.edit_references( - Some(RefEdit { + self.edit_reference( + RefEdit { change: Change::Update { log: Default::default(), mode: if force { @@ -36,7 +36,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { }, name: format!("refs/tags/{}", name.as_ref()).try_into()?, deref: false, - }), + }, lock_mode, None, ) diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 69508931d35..50c5d0f5f99 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -4,15 +4,16 @@ mod in_bare { use git_repository::prelude::ObjectAccessExt; #[test] - fn write_empty_tree() { - let tmp = tempfile::tempdir().unwrap(); - let repo = git_repository::init_bare(&tmp).unwrap().into_easy(); - let oid = repo.write_object(&git_repository::objs::Tree::empty().into()).unwrap(); + fn write_empty_tree() -> crate::Result { + let tmp = tempfile::tempdir()?; + let repo = git_repository::init_bare(&tmp)?.into_easy(); + let oid = repo.write_object(&git_repository::objs::Tree::empty().into())?; assert_eq!( oid, git_repository::hash::ObjectId::empty_tree(), "it produces a well-known empty tree id" - ) + ); + Ok(()) } } From 26a6637222081997ad7c08f4dc8d8facfb9cf94e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 1 Sep 2021 20:50:57 +0800 Subject: [PATCH 06/91] =?UTF-8?q?[repository=20#190]=20put=20git-lock=20in?= =?UTF-8?q?to=20ST1=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …just to use its lock mode. --- STABILITY.md | 21 +++++++++++---------- etc/crate-structure.monopic | Bin 3498 -> 3617 bytes git-repository/src/lib.rs | 1 - 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/STABILITY.md b/STABILITY.md index 3a8acae0ab6..efab8d5b47c 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -56,20 +56,21 @@ The following schematic helps to visualize what follows. ║ │ ┌─────────────┐ ┌─────────────┐ │ ║ ║ │ │ git-ref │ │ git-config │ │ ║ │ ║ │ └─────────────┘ └─────────────┘ │ ║ - ║ │ ┌─────────────┐ │ ║ │ - ║ │ │ git-object │ │ ║ - ║ │ └─────────────┘ │ ║ │ + ║ │ ┌─────────────┐ ┌─────────────┐ │ ║ │ + ║ │ │ git-object │ │ git-lock │ │ ║ + ║ │ └─────────────┘ └─────────────┘ │ ║ │ ║ └───────────────────────────────────┘ ║ ║ ║ │ ╚═════════════════════════════════════════════╝ - Stability Tier 2 ─────────────────────────────┐ │ - │ │ - │ Plumbing Crates─────────────────────┐ │ │ - │ │ ┌─────────────┐ ┌─────────────┐ │ │ - │ │ │ git-odb │ │ git-diff │ │ │ │ - │ │ └─────────────┘ └─────────────┘ │ │ + │ + Stability Tier 2 ─────────────────────────────┐ + │ │ │ + │ Plumbing Crates─────────────────────┐ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ - │ │ │git-traverse │ │ git-pack │ │◀ ─ ┼ ─ + │ │ │ git-odb │ │ git-diff │ │ │ + │ │ └─────────────┘ └─────────────┘ │ │ │ + │ │ ┌─────────────┐ ┌─────────────┐ │ │ + │ │ │git-traverse │ │ git-pack │ │◀─ ─│─ ┘ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ │ │ …many more… │ │ diff --git a/etc/crate-structure.monopic b/etc/crate-structure.monopic index 2a672eebf69cab79802c8b935325f54ffe0597fd..dca74eb698e6084658b558d8c115d4a7c6c137d0 100644 GIT binary patch literal 3617 zcmV++4&L$qO;1iwP)S1pABzY8000000u$|B$!_b&75x>1Yq`N&16k$m1A3K3Bb`Nn zjv>$zEi)~Pjzqt<^I#xflMl<6qy|=rlqH+f71fd#mT8$R7HhcUxraZ3{O+IWtk?uM zKlI@>$VeZ~^VxKr-Um1K%U}}Bv-Kt|f}3y>tknh6r+kwYS-uKxfKP(r^(mcZbM*(A z1nytc`(?6FEyLTyG(*}qO&T`M0j<{{rEYtBu9)n(QIWl*OEZc^`ch11`Km})>c+p_ zPV6^NdA8ES{M+rD>vQ~RpSL#!-N8(^7_evpA}AXXL^o32ZhE&&SMyYzU#y?gN${A@ z)8$m(-ZdWzb%V#J&Gar`&(pQ)=Q3XhH;f1tN0JJ4&&@+J&;OX32U3@*^OK@TW)F#K z+&8RSnEk%kULOmPVJOORPF9QOWwI{sqW(V#?$^mD}7i zw)UkSTm9uSpZ%^M@IF~?(tQ*0kj>}m%AYVR9@hEu;=zA$m#BWLuU7M^9^K%E)V@ns z`Z@gw7RjPM5o9|;`Tf0_ry!h!fqTGB_HWf@pjH=E-))}n)XC~n)yIup4CTfCKCYkT z4DK{q8-w*`&%hMrmn(I5b+pKeYds*YKeK$j(lY?tTeziKj(?=i&DYr?Q-c;<(|E%D z#XFsG4eXip`UI9;Djzk|SE;@AYSw|@X62AnR)IYt{)qY`=8t%A>+d#CHV@l@{1H5v zsd_e_x%zzD9jaw*{Pg91{OvT^6~Xkxt?!drs^!J*lBhqbYqGMcwi`#!$uym3MP;JP zLHT41x6>29oA7dbyx(W_o;m>`T1&>LsbnM}UfZmVTw~#hi8V^Uq}sqzEuY^tmTFYg zgc=Jc*X;OGjXD8Fo&2KQ9YpRRzAS-8WlwnqgyNWEU~S0Z8)m$}ENIBE8G~B)=tMAF?N;sVVUATeG#hT*xq5JN*n6!uj_ zVLL@s&Sf`E^x~66v|Y03U{p1V1!45)o~@*3>evE|;@SSJA0yE#`s&*{Dbh`2?^cSs za|_4!ZKac~(g|1Tq^oq|RmPX)*2=!oRgR|YpCvI<`i3q6cr^QMO16`P=!1&4yt+Euu~U!6Gp-at!-=npPe_U z^Ityp)JBifLjh{@OqB9tt8v1=)cBBU{FmkP<6X8|v{mA?7NNc-lj{+nAH*qb;*>UV zP7!fl5xKDFH0>LlCZRk}8EC+YP7GR4aCluDBPD{Z8a7r&eG!+7WJl2X6A!HNF+woB zzGq0;%T4jB6$Pz`6KZ^dorug|)X7&gIFzk2-~boHb4Q@}z6rIj@*=UTmJAraNf&zU zsw?IPJ7c=AN3K_->SOX$OTp5hRUF5Rq8(GZPaV5n){}cg_v^!ePc>l`!T_Oq(?+s< zQthWjB_y{=|217HHI%N+o)Bh^B69+mCnRR^C^MbBRqmsgoQcxao-7P*-?q>{w0J2c z=NRqL_)pXIeEsUP4-C(_2(j{@lpjjX89Y_f)Ew+f%|R66MJOYrIDEv1*uYevd0r9= zG87@kd?j>F7e#JRI}}YcmQqo#%%L(Iw85b6udR6XqY*4i?ciwS)HciB>H#;d<&>G}$;nx`0R z(aGvV9ZgIK7n>9=HYq${!^I|ryetVJ`^F}@#PhtuHnJxsCuW>P{L3~O*fuHF$@f&P ztHx3%4&POdjeu>4JWk9Bp2!KF$O)e4!URv;8?lcFXk{=!EkBU+#6dsk)#bw(tsF=; zih1}!ZB=}x=Am)RMlcUabI*~~i3%oT)KmG=DPK9|vP5L-QT}P=vc0;c$4GmNWkj$r zKhG3Tr5@4cmdRPF4sX9XJ&ToW;s=vWynnJubgf9yp>^Lw(XEkcoIDm^2!vI7MGhkn zJm-w(Y|wegkq`*9-i1~O1R5U!fk2~H2n0d#@)CeoH46d>Xw<44Lu0y5ESX}sn!W-S zgR>+pDSSFUuWm1se!%E7TfB1 zyc`cAdz2-Q!dOv=1SSkIdSZhL`*wmU&FDpr!rIxsaB94@rt0*|_8rppO=d;DZceae zid}0+A~Hfm*jFEeFFyuff2{F??nqpk=^OS%5_N?;t99TdSjRRvnxN5)~?Q3Pj-;uE7e1~1KBf~e*a zXe9(AXd&?MJ@EKF@BlvW2tHkkcUi2vabKSxmaU}22h=Bof(XUYr33lm<{tiaH@@7t zv7n&4bw zZ3Qi*wVK(UUD%fT+EU=GEDhG*7vX3 zS%}5u5L*pQY&|it1;xa+LToF4NR{v@O0CO6QbaxYHB3$gv1q zw%t;lX$zl{#~^T#?Fd{f*ZN*UjNa)O{bs;(bS-r^-~2n5u)qotB2{{awwp8HM4h59 zVA<^VrpQ^}l_<{;vi-ga*3T|GOUCJ^f2=%42r)s8*|sE|_J$Orn};3V3DKC?ot^#W zB-_m?@u-qSt5Rhkryd(KHJ?dbo#?ukpZqkmPuY{F;U zv=O6-%o7KL^+s0o3)UkNyfQfeA8*{Mx&scQiN^wQaq$J>8WzN*Bf`ybTx18HF$#>! zgNVVSh{3~%bzWv$-gQ}CMQ=zk@Q#Tmf%pD$0^tz!`W@&ko4;Wlua1xa9yf;$BAsT| zpnGn4f7HZfnvn|331GeP=m5sW4#+1EmtDW51H|Ps;<4cnF4wS+6-Z0TCtbw6O3NC= z1N3h`rWE^_QtS%=V~^*HqmeK#l@3OguC_(L#IiH`g)~OL#6EQpi#9kIipN(FA$gn; zp1m&V0KO~$9@&OOa^eB0T7QV(01pvgquULa0G%tFYAb~eF*@hRfw3FBbiwmhMYRB@ z7UG6ber5Rw&hnR3DUE$$V7oL2BfO>6E(FI!wAC8!$eSHNFD%awy~Erw)?3pUp4DM_ zMC-eK1iPQs*M)fZ!jK(1UulhNW9#p+@NWk+&vyiiD{S?_(el9g3*J`uCUNXSPqEu~ zKE>@b+&;nW^V>GP-NJ779o;B*wlo;((^uInAKK^DlrhAIV&AQ3gZ2_=kp`7LpPMlP zlQbzh6!!e6GK9Ut8fb@pbbSgXb#!FQ!b-roKXQrRq3#IpbZyyg+e literal 3498 zcmV;b4OR00O;1iwP)S1pABzY8000000u$|B%WmVy75x>1Yk5$&9;~9b1I#LmAd^La zNkgD5%GQi5IuzaQPQyUHCLfkBNj+FaN)~NW)D<<77q+Mei}gJBeeaJbzx_SCE7#G@ z4|919No+1>`Q5b0=FyG&Fq%ZOMX}Dx=!Q(9Lfk*}f~h$m6`{VAI+X8H}C zME|`o1y)i79E=wfYxhB64Q45TQ%A7pmOn&E$u9p*-JUS%U5N#(hvUa z%fx;1lrL6hnt%K9>iaBv_nCI=v!bTysuR=OWwx4S`j)bI&L+`gKFgL<)2ZsAdVu=V zr}gwUFJ@VxJHO0}=!R1jOGP=8lW6^r&hl?lJ4$_*{(V}O>D@!B+YarT7ViGIukV)% z5<*lpkaTtbyiAMgDf;_KG%wP}Y?`j_9`d3w3klT^s5)aGIlV31Q#U>|^KCv{=68RX z;myTZJFol@CS!eE$$WxJ`Ap^+&7O)J$)5LpMIlR%V!C{>t>;E>2hp zO2P!?^SNESh)hW2$G2YmTelhMc?8p&_4BR%wZ2z(a_x3Vb$57>?`XAvTdi(PLb2X4 zGiCMRNzu^8t_=`CD5*{>5*AMkn{)nFJYCYTk zx&HsUJz2|*88o+t6>tmXHU*faE9U84W{%|cuGk!Z4;;Wtzb@_k(WHrL*K~3%j*n~9qZsulrun1lLi!7JS-8f?KwJaL%+##EWtp3v#Y(T{beYW^ z@H8-I*MV}sE^~M9j!vh`#r^8ha&}-4=WFKPgeU1u^$^}KW?4W5H=El>E*v%K8@SEO zGJkXj(Lve$qP%`Ei|5DanB5LzBtyqY`i-$#^F0Tdzv#BE_1Vq*NV&k_ya7=Xkp!#H zgT#<1)!#lRy<8;qFDam)O0yR;X)*x>lHLg}GLkYlWFs>KPH18)$;7p9x`D#dnwYAxmd9{?)Te z{4&}dvRtgPE!?g4_!eyrKWqW!ElS-&7yAKd3mX*1A~s>3R=r%8-wkiW8Whkij;H?> zas7+_@s*j!ozx4Ms%R9j#9wh@Hi@0uRbHm<L}k67(%P?GAWF%e}hK%gJ6$_ENGJlC6x~gMPoI*zb~M`%yS*3QcgJ5|3JQ zJb4Esv_YFyV|0Z!vJ2W2>t)Ai)AuD1>y;A}N8cB>>ig7L>if8ySUt`0^?kpV>Fr{v z&+*mY7g=!ySDiI}YD44K9MsYDeahpQ@<66MlBob?$I$oj6S89r)%OWJJO+MTh08oL zi|gLs?f6aFXvci_eTPEE=}y4uPQd9-!0ArF3HjhOFyJ&W;50BJ>v80sGL_K1NIZW? z2{}tHIK3-%SHd6}A|aO>`#ou)-MV?#u&2>T?h%y4I1O{MrwAW6l`EnTXF}O1oJqSS z65=!EOhAmonb7XWN7yhvvPp7$UIaYn0iN@Kph5N8TuT15yokHIA1gxXKvsk!!Igxw zq-$l7e$6!CdO&s5=i&`6wB6uBgTaM`D?XONg>H7Cm%)X_M=-cB(aYdM0L^4zTTYix z`l(sj(8R5?e@$1SwqbDl+o()S*1-Ll%_Ky?Azz zOx0_^V*>WAd_M3yrE~Ak`Cl zs#q1lqiTD2Ky__fs$OXLPPjE?dJfdl(eP~Jt;f?~6q(&EdGWnFFm8rh!zf|^gm7yJ z@zxLmt|3HR!=;5>!|<4Eg%>^nDn3yQ(*8{6-?C+vuCuFO^LhC#EwZbx@Y6p+Xjng` zF*z}v3)mzrpGucQLay@w?Tog`1{Ng+~3>Chc0mjfMr8w zXTSYu*nYtGbHaeVRi_*JSHLrO4NS0dfF0@*njko4@I$D+IilWLw&|^V#gOhKY5aox zicPFn|5Prj4gv^K04gMPNybX*(&$#w+s8OLMpBogS5g;+=qe~h8&4?tP`eM^!=bqI z!G}Kbt`H|C?s9dO3Pv`PwEMsR{P!_keZP9li%eg4mU(TuuUh2gWDJp)d+g*MIt7VB zcIuGCuf9&ypW$J_`{muO}IsOD?Dr&n?^Ch2WK+~-ewTo%}mHmoq6iQjPOHU&t%dI zALyxhr2>4qr5@TsVzVQ6H%G&^Q~ekQk~##xj=`^k@Iw?n&cm3D{Iut)&>q2rw*@(}dyb=lkL?b>`mVz_xu3 zM<}S}HiEkfj2r|;4gw#u?xGMFKQ z6*3qhoei>2**o9Sb++`uS)0qZPenY2L+QIWCbx)jXeD=0H zR7WwV1=Cqb01Y7lG=v1urryY9nbXK%6`CYxSr?-Y702VU)X7UDY(jS!*4%`RCXob# zcx?|49_Aq?==QNHK~$WKfYNFgEok3W#;{q2j{OLTMHOcg-+ zYn|U7i?dLe*%RWuQgTot5GzIX;el9kVk{7&pB&JkYhTQ6J99v`nAk=@_ZT@NLy@~s zaF2OpWL}HI;>%)wm>SIc?CYIt+>8Tqsk6*QUe$+r4W?-{c*aYkB>P1M3229Y!h|}0 z05|{48hl{v&tLQrxz?bcb(ewpcGBQ6$|Uo^3G?7Y#)GpK4}qKf(vk*?!AXM&IYed; zg6Jc0jwZgX6y9MPk!;e89$=As1^i1mF-hV~D{_n}Nrckjohwl#Z^Vfayb(iTj!>5) zl;sFjIbwBL;)EPbM_kqDofWCs(A1G8js!~|#IWMnm_)30i)EE(XDQCva(SkGXhcX;f++v9L7dsE Y15a~AenelR8}|D8e~3-<)=R7a03+kolmGw# diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 03cdd76cc79..0c8b2d38920 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -94,7 +94,6 @@ pub use git_diff as diff; #[cfg(feature = "unstable")] pub use git_features::{parallel, progress, progress::Progress}; pub use git_hash as hash; -#[cfg(feature = "unstable")] pub use git_lock as lock; pub use git_object as objs; #[cfg(feature = "unstable")] From 78bacf97d669f3adfebdb093054c162cfd5214fa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 1 Sep 2021 21:12:34 +0800 Subject: [PATCH 07/91] [object #190] More conversion methods for Object --- git-object/src/object/mod.rs | 28 ++++++++++++++++++++++++++++ git-ref/src/store/file/log/iter.rs | 1 - 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/git-object/src/object/mod.rs b/git-object/src/object/mod.rs index 6ec2b263ec8..628f10e9e37 100644 --- a/git-object/src/object/mod.rs +++ b/git-object/src/object/mod.rs @@ -24,6 +24,34 @@ mod write { /// Convenient extraction of typed object. impl Object { + /// Turns this instance into a [`Blob`][Blob] if it is one. + pub fn into_blob(self) -> Option { + match self { + Object::Blob(v) => Some(v), + _ => None, + } + } + /// Turns this instance into a [`Commit`][Commit] if it is one. + pub fn into_commit(self) -> Option { + match self { + Object::Commit(v) => Some(v), + _ => None, + } + } + /// Turns this instance into a [`Tree`][Tree] if it is one. + pub fn into_tree(self) -> Option { + match self { + Object::Tree(v) => Some(v), + _ => None, + } + } + /// Turns this instance into a [`Tag`][Tag] if it is one. + pub fn into_tag(self) -> Option { + match self { + Object::Tag(v) => Some(v), + _ => None, + } + } /// Returns a [`Blob`][Blob] if it is one. pub fn as_blob(&self) -> Option<&Blob> { match self { diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index f50aebcb246..04a73b6cc2b 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -61,7 +61,6 @@ pub fn forward(lines: &[u8]) -> impl Iterator, de } /// An iterator yielding parsed lines in a file in reverse. -#[allow(dead_code)] pub struct Reverse<'a, F> { buf: &'a mut [u8], count: usize, From 3f36a01976a149d518021f19d83e56dec43cfb98 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 1 Sep 2021 21:46:15 +0800 Subject: [PATCH 08/91] [ref #190] refactor --- git-ref/src/store/packed/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git-ref/src/store/packed/mod.rs b/git-ref/src/store/packed/mod.rs index a3d7bc60278..fd7f3570504 100644 --- a/git-ref/src/store/packed/mod.rs +++ b/git-ref/src/store/packed/mod.rs @@ -35,6 +35,7 @@ pub(crate) struct Transaction { buffer: Option, edits: Option>, lock: Option, + #[allow(dead_code)] // It just has to be kept alive, hence no reads closed_lock: Option, } From 507d710d837c3911a9329c1c132eee912a37e1a8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 08:37:50 +0800 Subject: [PATCH 09/91] [features #190] be more explicit about why sha1-asm is disabled --- git-features/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index 0d0b3dc89aa..3b2d99ea4e1 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -80,5 +80,6 @@ time = { version = "0.3.2", optional = true, default-features = false, features [package.metadata.docs.rs] all-features = true -[target.'cfg(not(windows))'.dependencies] +# Assembly doesn't yet compile on MSVC on windows, but does on GNU, see https://github.com/RustCrypto/asm-hashes/issues/17 +[target.'cfg(not(all(target_os = "windows", target_env = "msvc")))'.dependencies] sha-1 = { version = "0.9.1", optional = true, features = ["asm"] } From c5de433e569c2cc8e78f3f84e368a11fe95f522a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 11:06:24 +0800 Subject: [PATCH 10/91] [object #190] consistent method naming --- git-object/src/object/mod.rs | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/git-object/src/object/mod.rs b/git-object/src/object/mod.rs index 628f10e9e37..9223e0c20d6 100644 --- a/git-object/src/object/mod.rs +++ b/git-object/src/object/mod.rs @@ -24,34 +24,63 @@ mod write { /// Convenient extraction of typed object. impl Object { + /// Turns this instance into a [`Blob`][Blob], panic otherwise. + pub fn into_blob(self) -> Blob { + match self { + Object::Blob(v) => v, + _ => panic!("BUG: not a blob"), + } + } + /// Turns this instance into a [`Commit`][Commit] panic otherwise. + pub fn into_commit(self) -> Commit { + match self { + Object::Commit(v) => v, + _ => panic!("BUG: not a commit"), + } + } + /// Turns this instance into a [`Tree`][Tree] panic otherwise. + pub fn into_tree(self) -> Tree { + match self { + Object::Tree(v) => v, + _ => panic!("BUG: not a tree"), + } + } + /// Turns this instance into a [`Tag`][Tag] panic otherwise. + pub fn into_tag(self) -> Tag { + match self { + Object::Tag(v) => v, + _ => panic!("BUG: not a tag"), + } + } /// Turns this instance into a [`Blob`][Blob] if it is one. - pub fn into_blob(self) -> Option { + pub fn try_into_blob(self) -> Result { match self { - Object::Blob(v) => Some(v), - _ => None, + Object::Blob(v) => Ok(v), + _ => Err(self), } } /// Turns this instance into a [`Commit`][Commit] if it is one. - pub fn into_commit(self) -> Option { + pub fn try_into_commit(self) -> Result { match self { - Object::Commit(v) => Some(v), - _ => None, + Object::Commit(v) => Ok(v), + _ => Err(self), } } /// Turns this instance into a [`Tree`][Tree] if it is one. - pub fn into_tree(self) -> Option { + pub fn try_into_tree(self) -> Result { match self { - Object::Tree(v) => Some(v), - _ => None, + Object::Tree(v) => Ok(v), + _ => Err(self), } } /// Turns this instance into a [`Tag`][Tag] if it is one. - pub fn into_tag(self) -> Option { + pub fn try_into_tag(self) -> Result { match self { - Object::Tag(v) => Some(v), - _ => None, + Object::Tag(v) => Ok(v), + _ => Err(self), } } + /// Returns a [`Blob`][Blob] if it is one. pub fn as_blob(&self) -> Option<&Blob> { match self { From 63bedc7647bb584353289e19972adf351765a526 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 15:27:09 +0800 Subject: [PATCH 11/91] [refs #190] refactor; handle value-checks in dereffed symlinks correctly --- git-ref/CHANGELOG.md | 7 + git-ref/src/transaction.rs | 305 ------------------------------ git-ref/src/transaction/ext.rs | 167 ++++++++++++++++ git-ref/src/transaction/mod.rs | 138 ++++++++++++++ git-ref/tests/transaction/mod.rs | 7 +- git-repository/src/easy/commit.rs | 0 6 files changed, 316 insertions(+), 308 deletions(-) delete mode 100644 git-ref/src/transaction.rs create mode 100644 git-ref/src/transaction/ext.rs create mode 100644 git-ref/src/transaction/mod.rs create mode 100644 git-repository/src/easy/commit.rs diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index b0cdc068605..82aa8157c5d 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -1,3 +1,10 @@ +### v0.6.1 + +#### Bugfixes + +* splits of edits to symbolic references will now 'move' the desired previous values down to the + referents while resorting to not having any requirements in the symbolic ref instead. + ### v0.6.0 #### BREAKING diff --git a/git-ref/src/transaction.rs b/git-ref/src/transaction.rs deleted file mode 100644 index c3c459811d8..00000000000 --- a/git-ref/src/transaction.rs +++ /dev/null @@ -1,305 +0,0 @@ -//! **Transactions** are the only way make changes to the ref store in order to increase the chance of consistency in a multi-threaded -//! environment. -//! -//! Transactions currently allow to… -//! -//! * create or update reference -//! * delete references -//! -//! The following guarantees are made: -//! -//! * transactions are prepared which is when other writers are prevented from changing them -//! - errors during preparations will cause a perfect rollback -//! * prepared transactions are committed to finalize the change -//! - errors when committing while leave the ref store in an inconsistent, but operational state. -use bstr::BString; -use git_hash::ObjectId; - -use crate::{FullName, Target}; - -/// A change to the reflog. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct LogChange { - /// How to treat the reference log. - pub mode: RefLog, - /// If set, create a reflog even though it would otherwise not be the case as prohibited by general rules. - /// Note that ref-log writing might be prohibited in the entire repository which is when this flag has no effect either. - pub force_create_reflog: bool, - /// The message to put into the reference log. It must be a single line, hence newlines are forbidden. - /// The string can be empty to indicate there should be no message at all. - pub message: BString, -} - -impl Default for LogChange { - fn default() -> Self { - LogChange { - mode: RefLog::AndReference, - force_create_reflog: false, - message: Default::default(), - } - } -} - -/// A way to determine if a value should be created or created or updated. In the latter case the previous -/// value can be specified to indicate to what extend the previous value matters. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub enum Create { - /// Create a ref only. This fails if the ref exists and does not match the desired new value. - Only, - /// Create or update the reference with the `previous` value being controlling how to deal with existing ref values. - /// - OrUpdate { - /// Interpret… - /// * `None` so that existing values do not matter at all. This is the mode that always creates or updates a reference to the - /// desired new value. - /// * `Some(Target::Peeled(ObjectId::null_sha1())` so that the reference is required to exist even though its value doesn't matter. - /// * `Some(value)` so that the reference is required to exist and have the given `value`. - previous: Option, - }, -} - -impl Create { - pub(crate) fn previous_oid(&self) -> Option { - match self { - Create::OrUpdate { - previous: Some(Target::Peeled(oid)), - } => Some(*oid), - Create::Only | Create::OrUpdate { .. } => None, - } - } -} - -/// A description of an edit to perform. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub enum Change { - /// If previous is not `None`, the ref must exist and its `oid` must agree with the `previous`, and - /// we function like `update`. - /// Otherwise it functions as `create-or-update`. - Update { - /// The desired change to the reference log. - log: LogChange, - /// The create mode. - /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes - /// and for use in the reflog. - mode: Create, - /// The new state of the reference, either for updating an existing one or creating a new one. - new: Target, - }, - /// Delete a reference and optionally check if `previous` is its content. - Delete { - /// The previous state of the reference. If set, the reference is expected to exist and match the given value. - /// If the value is a peeled null-id the reference is expected to exist but the value doesn't matter, neither peeled nor symbolic. - /// If `None`, the actual value does not matter. - /// - /// If a previous ref existed, this value will be filled in automatically and can be accessed - /// if the transaction was committed successfully. - previous: Option, - /// How to thread the reference log during deletion. - log: RefLog, - }, -} - -impl Change { - /// Return references to values that are in common between all variants. - pub fn previous_value(&self) -> Option> { - match self { - Change::Update { mode: Create::Only, .. } => None, - Change::Update { - mode: Create::OrUpdate { previous }, - .. - } - | Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), - } - } -} - -/// A reference that is to be changed -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct RefEdit { - /// The change itself - pub change: Change, - /// The name of the reference to apply the change to - pub name: FullName, - /// If set, symbolic references identified by `name` will be dereferenced to have the `change` applied to their target. - /// This flag has no effect if the reference isn't symbolic. - pub deref: bool, -} - -/// The way to deal with the Reflog in deletions. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -pub enum RefLog { - /// Delete or update the reference and the log - AndReference, - /// Delete or update only the reflog - Only, -} - -mod ext { - use bstr::{BString, ByteVec}; - - use crate::{ - transaction::{Change, LogChange, RefEdit, RefLog, Target}, - Namespace, PartialNameRef, - }; - - /// An extension trait to perform commonly used operations on edits across different ref stores. - pub trait RefEditsExt - where - T: std::borrow::Borrow + std::borrow::BorrowMut, - { - /// Return true if each ref `name` has exactly one `edit` across multiple ref edits - fn assure_one_name_has_one_edit(&self) -> Result<(), BString>; - - /// Split all symbolic refs into updates for the symbolic ref as well as all their referents if the `deref` flag is enabled. - /// - /// Note no action is performed if deref isn't specified. - fn extend_with_splits_of_symbolic_refs( - &mut self, - find: impl FnMut(PartialNameRef<'_>) -> Option, - make_entry: impl FnMut(usize, RefEdit) -> T, - ) -> Result<(), std::io::Error>; - - /// If `namespace` is not `None`, alter all edit names by prefixing them with the given namespace. - /// Note that symbolic reference targets will also be rewritten to point into the namespace instead. - fn adjust_namespace(&mut self, namespace: Option); - - /// All processing steps in one and in the correct order. - /// - /// Users call this to assure derefs are honored and duplicate checks are done. - fn pre_process( - &mut self, - find: impl FnMut(PartialNameRef<'_>) -> Option, - make_entry: impl FnMut(usize, RefEdit) -> T, - namespace: impl Into>, - ) -> Result<(), std::io::Error> { - self.adjust_namespace(namespace.into()); - self.extend_with_splits_of_symbolic_refs(find, make_entry)?; - self.assure_one_name_has_one_edit().map_err(|name| { - std::io::Error::new( - std::io::ErrorKind::AlreadyExists, - format!("A reference named '{}' has multiple edits", name), - ) - }) - } - } - - impl RefEditsExt for Vec - where - E: std::borrow::Borrow + std::borrow::BorrowMut, - { - fn assure_one_name_has_one_edit(&self) -> Result<(), BString> { - let mut names: Vec<_> = self.iter().map(|e| &e.borrow().name).collect(); - names.sort(); - match names.windows(2).find(|v| v[0] == v[1]) { - Some(name) => Err(name[0].as_bstr().to_owned()), - None => Ok(()), - } - } - - fn extend_with_splits_of_symbolic_refs( - &mut self, - mut find: impl FnMut(PartialNameRef<'_>) -> Option, - mut make_entry: impl FnMut(usize, RefEdit) -> E, - ) -> Result<(), std::io::Error> { - let mut new_edits = Vec::new(); - let mut first = 0; - let mut round = 1; - loop { - for (eid, edit) in self[first..].iter_mut().enumerate().map(|(eid, v)| (eid + first, v)) { - let edit = edit.borrow_mut(); - if !edit.deref { - continue; - }; - - // we can't tell what happened and we are here because it's a non-existing ref or an invalid one. - // In any case, we don't want the following algorithms to try dereffing it and assume they deal with - // broken refs gracefully. - edit.deref = false; - if let Some(Target::Symbolic(referent)) = find(edit.name.to_partial()) { - new_edits.push(make_entry( - eid, - match &mut edit.change { - Change::Delete { previous, log: mode } => { - let current_mode = *mode; - *mode = RefLog::Only; - RefEdit { - change: Change::Delete { - previous: previous.clone(), - log: current_mode, - }, - name: referent, - deref: true, - } - } - Change::Update { - log, - mode: previous, - new, - } => { - let current = std::mem::replace( - log, - LogChange { - message: log.message.clone(), - mode: RefLog::Only, - force_create_reflog: log.force_create_reflog, - }, - ); - RefEdit { - change: Change::Update { - mode: previous.clone(), - new: new.clone(), - log: current, - }, - name: referent, - deref: true, - } - } - }, - )); - } - } - if new_edits.is_empty() { - break Ok(()); - } - if round == 5 { - break Err(std::io::Error::new( - std::io::ErrorKind::WouldBlock, - format!( - "Could not follow all splits after {} rounds, assuming reference cycle", - round - ), - )); - } - round += 1; - first = self.len(); - - self.extend(new_edits.drain(..)); - } - } - - fn adjust_namespace(&mut self, namespace: Option) { - if let Some(namespace) = namespace { - for entry in self.iter_mut() { - let entry = entry.borrow_mut(); - entry.name.0 = { - let mut new_name = namespace.0.clone(); - new_name.push_str(&entry.name.0); - new_name - }; - if let Change::Update { - new: Target::Symbolic(ref mut name), - .. - } = entry.change - { - name.0 = { - let mut new_name = namespace.0.clone(); - new_name.push_str(&name.0); - new_name - }; - } - } - } - } - } -} -pub use ext::RefEditsExt; diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs new file mode 100644 index 00000000000..8601ee2fc4a --- /dev/null +++ b/git-ref/src/transaction/ext.rs @@ -0,0 +1,167 @@ +use bstr::{BString, ByteVec}; + +use crate::{ + transaction::{Change, Create, LogChange, RefEdit, RefLog, Target}, + Namespace, PartialNameRef, +}; + +/// An extension trait to perform commonly used operations on edits across different ref stores. +pub trait RefEditsExt +where + T: std::borrow::Borrow + std::borrow::BorrowMut, +{ + /// Return true if each ref `name` has exactly one `edit` across multiple ref edits + fn assure_one_name_has_one_edit(&self) -> Result<(), BString>; + + /// Split all symbolic refs into updates for the symbolic ref as well as all their referents if the `deref` flag is enabled. + /// + /// Note no action is performed if deref isn't specified. + fn extend_with_splits_of_symbolic_refs( + &mut self, + find: impl FnMut(PartialNameRef<'_>) -> Option, + make_entry: impl FnMut(usize, RefEdit) -> T, + ) -> Result<(), std::io::Error>; + + /// If `namespace` is not `None`, alter all edit names by prefixing them with the given namespace. + /// Note that symbolic reference targets will also be rewritten to point into the namespace instead. + fn adjust_namespace(&mut self, namespace: Option); + + /// All processing steps in one and in the correct order. + /// + /// Users call this to assure derefs are honored and duplicate checks are done. + fn pre_process( + &mut self, + find: impl FnMut(PartialNameRef<'_>) -> Option, + make_entry: impl FnMut(usize, RefEdit) -> T, + namespace: impl Into>, + ) -> Result<(), std::io::Error> { + self.adjust_namespace(namespace.into()); + self.extend_with_splits_of_symbolic_refs(find, make_entry)?; + self.assure_one_name_has_one_edit().map_err(|name| { + std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!("A reference named '{}' has multiple edits", name), + ) + }) + } +} + +impl RefEditsExt for Vec +where + E: std::borrow::Borrow + std::borrow::BorrowMut, +{ + fn assure_one_name_has_one_edit(&self) -> Result<(), BString> { + let mut names: Vec<_> = self.iter().map(|e| &e.borrow().name).collect(); + names.sort(); + match names.windows(2).find(|v| v[0] == v[1]) { + Some(name) => Err(name[0].as_bstr().to_owned()), + None => Ok(()), + } + } + + fn extend_with_splits_of_symbolic_refs( + &mut self, + mut find: impl FnMut(PartialNameRef<'_>) -> Option, + mut make_entry: impl FnMut(usize, RefEdit) -> E, + ) -> Result<(), std::io::Error> { + let mut new_edits = Vec::new(); + let mut first = 0; + let mut round = 1; + loop { + for (eid, edit) in self[first..].iter_mut().enumerate().map(|(eid, v)| (eid + first, v)) { + let edit = edit.borrow_mut(); + if !edit.deref { + continue; + }; + + // we can't tell what happened and we are here because it's a non-existing ref or an invalid one. + // In any case, we don't want the following algorithms to try dereffing it and assume they deal with + // broken refs gracefully. + edit.deref = false; + if let Some(Target::Symbolic(referent)) = find(edit.name.to_partial()) { + new_edits.push(make_entry( + eid, + match &mut edit.change { + Change::Delete { previous, log: mode } => { + let current_mode = *mode; + *mode = RefLog::Only; + RefEdit { + change: Change::Delete { + previous: previous.clone(), + log: current_mode, + }, + name: referent, + deref: true, + } + } + Change::Update { + log, + mode: previous, + new, + } => { + let current = std::mem::replace( + log, + LogChange { + message: log.message.clone(), + mode: RefLog::Only, + force_create_reflog: log.force_create_reflog, + }, + ); + let next = std::mem::replace(previous, Create::OrUpdate { previous: None }); + RefEdit { + change: Change::Update { + mode: next, + new: new.clone(), + log: current, + }, + name: referent, + deref: true, + } + } + }, + )); + } + } + if new_edits.is_empty() { + break Ok(()); + } + if round == 5 { + break Err(std::io::Error::new( + std::io::ErrorKind::WouldBlock, + format!( + "Could not follow all splits after {} rounds, assuming reference cycle", + round + ), + )); + } + round += 1; + first = self.len(); + + self.extend(new_edits.drain(..)); + } + } + + fn adjust_namespace(&mut self, namespace: Option) { + if let Some(namespace) = namespace { + for entry in self.iter_mut() { + let entry = entry.borrow_mut(); + entry.name.0 = { + let mut new_name = namespace.0.clone(); + new_name.push_str(&entry.name.0); + new_name + }; + if let Change::Update { + new: Target::Symbolic(ref mut name), + .. + } = entry.change + { + name.0 = { + let mut new_name = namespace.0.clone(); + new_name.push_str(&name.0); + new_name + }; + } + } + } + } +} diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs new file mode 100644 index 00000000000..bfc699c3f73 --- /dev/null +++ b/git-ref/src/transaction/mod.rs @@ -0,0 +1,138 @@ +//! **Transactions** are the only way make changes to the ref store in order to increase the chance of consistency in a multi-threaded +//! environment. +//! +//! Transactions currently allow to… +//! +//! * create or update reference +//! * delete references +//! +//! The following guarantees are made: +//! +//! * transactions are prepared which is when other writers are prevented from changing them +//! - errors during preparations will cause a perfect rollback +//! * prepared transactions are committed to finalize the change +//! - errors when committing while leave the ref store in an inconsistent, but operational state. +use bstr::BString; +use git_hash::ObjectId; + +use crate::{FullName, Target}; + +/// A change to the reflog. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct LogChange { + /// How to treat the reference log. + pub mode: RefLog, + /// If set, create a reflog even though it would otherwise not be the case as prohibited by general rules. + /// Note that ref-log writing might be prohibited in the entire repository which is when this flag has no effect either. + pub force_create_reflog: bool, + /// The message to put into the reference log. It must be a single line, hence newlines are forbidden. + /// The string can be empty to indicate there should be no message at all. + pub message: BString, +} + +impl Default for LogChange { + fn default() -> Self { + LogChange { + mode: RefLog::AndReference, + force_create_reflog: false, + message: Default::default(), + } + } +} + +/// A way to determine if a value should be created or created or updated. In the latter case the previous +/// value can be specified to indicate to what extend the previous value matters. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum Create { + /// Create a ref only. This fails if the ref exists and does not match the desired new value. + Only, + /// Create or update the reference with the `previous` value being controlling how to deal with existing ref values. + /// + OrUpdate { + /// Interpret… + /// * `None` so that existing values do not matter at all. This is the mode that always creates or updates a reference to the + /// desired new value. + /// * `Some(Target::Peeled(ObjectId::null_sha1())` so that the reference is required to exist even though its value doesn't matter. + /// * `Some(value)` so that the reference is required to exist and have the given `value`. + previous: Option, + }, +} + +impl Create { + pub(crate) fn previous_oid(&self) -> Option { + match self { + Create::OrUpdate { + previous: Some(Target::Peeled(oid)), + } => Some(*oid), + Create::Only | Create::OrUpdate { .. } => None, + } + } +} + +/// A description of an edit to perform. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum Change { + /// If previous is not `None`, the ref must exist and its `oid` must agree with the `previous`, and + /// we function like `update`. + /// Otherwise it functions as `create-or-update`. + Update { + /// The desired change to the reference log. + log: LogChange, + /// The create mode. + /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes + /// and for use in the reflog. + mode: Create, + /// The new state of the reference, either for updating an existing one or creating a new one. + new: Target, + }, + /// Delete a reference and optionally check if `previous` is its content. + Delete { + /// The previous state of the reference. If set, the reference is expected to exist and match the given value. + /// If the value is a peeled null-id the reference is expected to exist but the value doesn't matter, neither peeled nor symbolic. + /// If `None`, the actual value does not matter. + /// + /// If a previous ref existed, this value will be filled in automatically and can be accessed + /// if the transaction was committed successfully. + previous: Option, + /// How to thread the reference log during deletion. + log: RefLog, + }, +} + +impl Change { + /// Return references to values that are in common between all variants. + pub fn previous_value(&self) -> Option> { + match self { + Change::Update { mode: Create::Only, .. } => None, + Change::Update { + mode: Create::OrUpdate { previous }, + .. + } + | Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), + } + } +} + +/// A reference that is to be changed +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct RefEdit { + /// The change itself + pub change: Change, + /// The name of the reference to apply the change to + pub name: FullName, + /// If set, symbolic references identified by `name` will be dereferenced to have the `change` applied to their target. + /// This flag has no effect if the reference isn't symbolic. + pub deref: bool, +} + +/// The way to deal with the Reflog in deletions. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum RefLog { + /// Delete or update the reference and the log + AndReference, + /// Delete or update only the reflog + Only, +} + +mod ext; +pub use ext::RefEditsExt; diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index e2443d17596..46d4a7c20a6 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -274,7 +274,8 @@ mod refedit_ext { } #[test] - fn symbolic_refs_are_split_into_referents_handling_the_reflog_recursively() -> crate::Result { + fn symbolic_refs_are_split_into_referents_handling_the_reflog_and_previous_values_recursively() -> crate::Result + { let store = MockStore::with(vec![ ( "refs/heads/delete-symbolic-1", @@ -358,7 +359,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::Only, + mode: Create::OrUpdate { previous: None }, log: log_only.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -375,7 +376,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::Only, + mode: Create::OrUpdate { previous: None }, log: log_only, new: Target::Peeled(ObjectId::null_sha1()), }, diff --git a/git-repository/src/easy/commit.rs b/git-repository/src/easy/commit.rs new file mode 100644 index 00000000000..e69de29bb2d From bfcf8f17c7a89027e5bbcb5f85e3d0ba4036e8a0 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 15:27:51 +0800 Subject: [PATCH 12/91] =?UTF-8?q?[repository=20#190]=20first=20version=20o?= =?UTF-8?q?f=20'commit(=E2=80=A6)'=20without=20reflog=20message=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- git-repository/src/easy/commit.rs | 15 +++++ git-repository/src/easy/ext/object.rs | 62 ++++++++++++++++++- git-repository/src/easy/ext/reference.rs | 5 +- git-repository/src/easy/mod.rs | 1 + git-repository/src/easy/object/mod.rs | 7 +++ git-repository/src/easy/reference.rs | 35 +++++------ git-repository/tests/easy/object.rs | 32 +++++++++- .../tests/fixtures/make_basic_repo.sh | 2 + git-repository/tests/repo.rs | 7 ++- 9 files changed, 137 insertions(+), 29 deletions(-) diff --git a/git-repository/src/easy/commit.rs b/git-repository/src/easy/commit.rs index e69de29bb2d..f733c2ad192 100644 --- a/git-repository/src/easy/commit.rs +++ b/git-repository/src/easy/commit.rs @@ -0,0 +1,15 @@ +#![allow(missing_docs)] +mod error { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + ReferenceNameValidation(#[from] git_ref::name::Error), + #[error(transparent)] + WriteObject(#[from] easy::object::write::Error), + #[error(transparent)] + ReferenceEdit(#[from] easy::reference::edit::Error), + } +} +pub use error::Error; diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index b134ae5d209..6d3764f2873 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -3,10 +3,14 @@ use std::ops::DerefMut; use git_hash::ObjectId; use git_odb::{Find, FindExt}; +use crate::ext::ObjectIdExt; use crate::{ easy, - easy::{object, ObjectRef, Oid}, + easy::{commit, object, ObjectRef, Oid}, }; +use bstr::BString; +use git_ref::FullName; +use std::convert::TryInto; pub trait ObjectAccessExt: easy::Access + Sized { // NOTE: in order to get the actual kind of object, is must be fully decoded from storage in case of packs @@ -47,14 +51,66 @@ pub trait ObjectAccessExt: easy::Access + Sized { fn write_object(&self, object: &git_object::Object) -> Result, object::write::Error> { use git_odb::Write; - use crate::ext::ObjectIdExt; - let repo = self.repo()?; repo.odb .write(object, repo.hash_kind) .map(|oid| oid.attach(self)) .map_err(Into::into) } + + // docs notes + // Fails immediately if lock can't be acquired as first parent depends on it + fn commit<'a, Name, E>( + &self, + reference: Name, + message: impl Into, + message_encoding: impl Into>, + author: impl Into, + committer: impl Into, + tree: impl Into>, + parents: impl IntoIterator>, + ) -> Result, commit::Error> + where + Name: TryInto, + commit::Error: From, + { + use crate::easy::ext::ReferenceAccessExt; + use git_ref::{ + transaction::{Change, Create, RefEdit}, + Target, + }; + + let reference = reference.try_into()?; + let commit: git_object::Object = git_object::Commit { + message: message.into(), + tree: tree.into().unwrap_or_else(git_hash::ObjectId::empty_tree), + author: author.into(), + committer: committer.into(), + encoding: message_encoding.into(), + parents: parents.into_iter().map(|id| id.into()).collect(), + extra_headers: Default::default(), + } + .into(); + + let commit_id = self.write_object(&commit)?.detach(); + let commit = commit.into_commit(); + self.edit_reference( + RefEdit { + change: Change::Update { + log: Default::default(), // TODO: generate commit summary + mode: Create::OrUpdate { + previous: commit.parents.get(0).map(|p| Target::Peeled(*p)), + }, + new: Target::Peeled(commit_id), + }, + name: reference, + deref: true, + }, + git_lock::acquire::Fail::Immediately, + Some(&commit.committer), + )?; + Ok(commit_id.attach(self)) + } } impl ObjectAccessExt for A where A: easy::Access + Sized {} diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index d74324a1032..15eced39b63 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -4,7 +4,6 @@ use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; use git_ref::{ - file::find::Error, transaction::{Change, Create, RefEdit}, PartialNameRef, Target, }; @@ -77,7 +76,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { fn find_reference<'a, Name, E>(&self, name: Name) -> Result, reference::find::existing::Error> where Name: TryInto, Error = E>, - Error: From, + git_ref::file::find::Error: From, { self.try_find_reference(name)? .ok_or(reference::find::existing::Error::NotFound) @@ -86,7 +85,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result>, reference::find::Error> where Name: TryInto, Error = E>, - Error: From, + git_ref::file::find::Error: From, { let state = self.state(); let repo = self.repo()?; diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 3e6cc25af2c..0bc37e33af6 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -30,6 +30,7 @@ mod impls; pub(crate) mod ext; pub mod borrow; +pub mod commit; pub mod object; mod oid; pub mod reference; diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index 2da1cbc8ba7..eee1f9cb1ef 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -128,6 +128,13 @@ impl<'repo, A> ObjectRef<'repo, A> where A: easy::Access + Sized, { + /// As [`to_commit_iter()`] but panics if this is not a commit + pub fn commit_iter(&self) -> CommitRefIter<'_> { + git_odb::data::Object::new(self.kind, &self.data) + .into_commit_iter() + .expect("BUG: This object must be a commit") + } + pub fn to_commit_iter(&self) -> Option> { git_odb::data::Object::new(self.kind, &self.data).into_commit_iter() } diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 40c5c4e5155..65d2f8fc6d6 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -3,7 +3,6 @@ use std::ops::DerefMut; use git_hash::ObjectId; use git_odb::Find; -use git_ref as refs; use crate::{ easy, @@ -13,27 +12,25 @@ use crate::{ pub(crate) enum Backing { OwnedPacked { /// The validated full name of the reference. - name: refs::FullName, + name: git_ref::FullName, /// The target object id of the reference, hex encoded. target: ObjectId, /// The fully peeled object id, hex encoded, that the ref is ultimately pointing to /// i.e. when all indirections are removed. object: Option, }, - LooseFile(refs::file::loose::Reference), + LooseFile(git_ref::file::loose::Reference), } pub mod edit { - use git_ref as refs; - use crate::easy; #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - FileTransactionPrepare(#[from] refs::file::transaction::prepare::Error), + FileTransactionPrepare(#[from] git_ref::file::transaction::prepare::Error), #[error(transparent)] - FileTransactionCommit(#[from] refs::file::transaction::commit::Error), + FileTransactionCommit(#[from] git_ref::file::transaction::commit::Error), #[error(transparent)] NameValidation(#[from] git_validate::reference::name::Error), #[error("BUG: The repository could not be borrowed")] @@ -42,16 +39,14 @@ pub mod edit { } pub mod peel_to_oid_in_place { - use git_ref as refs; - use crate::easy; #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - LoosePeelToId(#[from] refs::file::loose::reference::peel::to_id::Error), + LoosePeelToId(#[from] git_ref::file::loose::reference::peel::to_id::Error), #[error(transparent)] - PackedRefsOpen(#[from] refs::packed::buffer::open::Error), + PackedRefsOpen(#[from] git_ref::packed::buffer::open::Error), #[error("BUG: Part of interior state could not be borrowed.")] BorrowState(#[from] easy::borrow::state::Error), #[error("BUG: The repository could not be borrowed")] @@ -64,30 +59,30 @@ impl<'repo, A> Reference<'repo, A> where A: easy::Access + Sized, { - pub(crate) fn from_file_ref(reference: refs::file::Reference<'_>, access: &'repo A) -> Self { + pub(crate) fn from_file_ref(reference: git_ref::file::Reference<'_>, access: &'repo A) -> Self { Reference { backing: match reference { - refs::file::Reference::Packed(p) => Backing::OwnedPacked { + git_ref::file::Reference::Packed(p) => Backing::OwnedPacked { name: p.name.into(), target: p.target(), object: p .object .map(|hex| ObjectId::from_hex(hex).expect("a hash kind we know")), }, - refs::file::Reference::Loose(l) => Backing::LooseFile(l), + git_ref::file::Reference::Loose(l) => Backing::LooseFile(l), } .into(), access, } } - pub fn target(&self) -> refs::Target { + pub fn target(&self) -> git_ref::Target { match self.backing.as_ref().expect("always set") { - Backing::OwnedPacked { target, .. } => refs::Target::Peeled(target.to_owned()), + Backing::OwnedPacked { target, .. } => git_ref::Target::Peeled(target.to_owned()), Backing::LooseFile(r) => r.target.clone(), } } - pub fn name(&self) -> refs::FullNameRef<'_> { + pub fn name(&self) -> git_ref::FullNameRef<'_> { match self.backing.as_ref().expect("always set") { Backing::OwnedPacked { name, .. } => name, Backing::LooseFile(r) => &r.name, @@ -136,8 +131,6 @@ where } pub mod find { - use git_ref as refs; - use crate::easy; pub mod existing { @@ -156,9 +149,9 @@ pub mod find { #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - Find(#[from] refs::file::find::Error), + Find(#[from] git_ref::file::find::Error), #[error(transparent)] - PackedRefsOpen(#[from] refs::packed::buffer::open::Error), + PackedRefsOpen(#[from] git_ref::packed::buffer::open::Error), #[error("BUG: Part of interior state could not be borrowed.")] BorrowState(#[from] easy::borrow::state::Error), #[error("BUG: The repository could not be borrowed")] diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 50c5d0f5f99..1afcfcce286 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -1,6 +1,6 @@ use git_repository::easy; -mod in_bare { +mod in_empty_bare { use git_repository::prelude::ObjectAccessExt; #[test] @@ -17,6 +17,36 @@ mod in_bare { } } +mod commit { + use git_repository as git; + use git_repository::prelude::{ObjectAccessExt, ReferenceAccessExt}; + use git_testtools::hex_to_id; + + #[test] + fn multi_line_commit_message_uses_first_line_in_ref_log() { + let (repo, _keep) = crate::basic_rw_repo().unwrap(); + let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); + let tree_id = parent.object().unwrap().commit_iter().tree_id(); + let author = git::actor::Signature::empty(); + let commit_id = repo + .commit( + "HEAD", + "hello there \r\n\nthe body", + None, + author.clone(), + author, + tree_id, + Some(parent), + ) + .unwrap(); + assert_eq!( + commit_id, + hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), + "the commit id is stable" + ); + } +} + #[test] fn object_ref_size_in_memory() { assert_eq!( diff --git a/git-repository/tests/fixtures/make_basic_repo.sh b/git-repository/tests/fixtures/make_basic_repo.sh index 268f2b15662..8eef95913e5 100644 --- a/git-repository/tests/fixtures/make_basic_repo.sh +++ b/git-repository/tests/fixtures/make_basic_repo.sh @@ -8,6 +8,8 @@ git checkout -b main touch this git add this git commit -q -m c1 +echo hello >> this +git commit -q -am c2 mkdir -p some/very/deeply/nested/subdir diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index e24fcc39080..20f2dbbe6af 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -1,4 +1,4 @@ -use git_repository::Repository; +use git_repository::{Easy, Repository}; type Result = std::result::Result>; @@ -7,6 +7,11 @@ fn repo(name: &str) -> crate::Result { Ok(Repository::discover(repo_path)?) } +fn basic_rw_repo() -> crate::Result<(Easy, tempfile::TempDir)> { + let repo_path = git_testtools::scripted_fixture_repo_writable("make_basic_repo.sh")?; + Ok((Repository::open(repo_path.path())?.into(), repo_path)) +} + mod discover; mod easy; mod init; From 0763ac260450b53b42f3c139deae5736fef056ce Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 15:30:28 +0800 Subject: [PATCH 13/91] [repository #190] thanks clippy --- git-repository/src/easy/ext/object.rs | 6 +++--- git-repository/tests/easy/object.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 6d3764f2873..1b5e0b41049 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -60,11 +60,11 @@ pub trait ObjectAccessExt: easy::Access + Sized { // docs notes // Fails immediately if lock can't be acquired as first parent depends on it - fn commit<'a, Name, E>( + // Writes without message encoding + fn commit( &self, reference: Name, message: impl Into, - message_encoding: impl Into>, author: impl Into, committer: impl Into, tree: impl Into>, @@ -86,7 +86,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { tree: tree.into().unwrap_or_else(git_hash::ObjectId::empty_tree), author: author.into(), committer: committer.into(), - encoding: message_encoding.into(), + encoding: None, parents: parents.into_iter().map(|id| id.into()).collect(), extra_headers: Default::default(), } diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 1afcfcce286..7748c81d65a 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -32,7 +32,6 @@ mod commit { .commit( "HEAD", "hello there \r\n\nthe body", - None, author.clone(), author, tree_id, From 43f7568bd11fc310bac8350991ff3d4183dcd17b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 22:00:05 +0800 Subject: [PATCH 14/91] [repository #190] commit::summary() --- git-repository/src/commit.rs | 45 +++++++++++++++++++++++++++ git-repository/src/easy/ext/object.rs | 15 ++++++--- git-repository/src/lib.rs | 3 ++ git-repository/tests/commit/mod.rs | 35 +++++++++++++++++++++ git-repository/tests/repo.rs | 1 + 5 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 git-repository/src/commit.rs create mode 100644 git-repository/tests/commit/mod.rs diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs new file mode 100644 index 00000000000..e554ce49827 --- /dev/null +++ b/git-repository/src/commit.rs @@ -0,0 +1,45 @@ +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use std::borrow::Cow; + +/// Produce a commit summary for the given `message`. +/// +/// This means the following +/// +/// * Take the subject line which is delimited by two newlines (\n\n) +/// * transform intermediate consecutive whitespace including \r into one space +/// +pub fn summary(message: &BStr) -> Cow<'_, BStr> { + let message = message.trim(); + match message.find_byte(b'\n') { + Some(mut pos) => { + let mut out = BString::default(); + let mut previous_pos = None; + loop { + if let Some(previous_pos) = previous_pos { + if previous_pos + 1 == pos { + let len_after_trim = out.trim_end().len(); + out.resize(len_after_trim, 0); + break out.into(); + } + } + let message_to_newline = &message[previous_pos.map(|p| p + 1).unwrap_or(0)..pos]; + + if let Some(pos_before_whitespace) = message_to_newline.rfind_not_byteset(b"\t\n\x0C\r ") { + out.extend_from_slice(&message_to_newline[..pos_before_whitespace + 1]); + } + out.push_byte(b' '); + previous_pos = Some(pos); + match message.get(pos + 1..).and_then(|i| i.find_byte(b'\n')) { + Some(next_nl_pos) => pos += next_nl_pos + 1, + None => { + if let Some(slice) = message.get((pos + 1)..) { + out.extend_from_slice(slice); + } + break out.into(); + } + } + } + } + None => message.as_bstr().into(), + } +} diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 1b5e0b41049..8d6d33159e0 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -8,7 +8,8 @@ use crate::{ easy, easy::{commit, object, ObjectRef, Oid}, }; -use bstr::BString; +use bstr::{BString, ByteSlice}; +use git_ref::transaction::{LogChange, RefLog}; use git_ref::FullName; use std::convert::TryInto; @@ -92,16 +93,20 @@ pub trait ObjectAccessExt: easy::Access + Sized { } .into(); - let commit_id = self.write_object(&commit)?.detach(); + let commit_id = self.write_object(&commit)?; let commit = commit.into_commit(); self.edit_reference( RefEdit { change: Change::Update { - log: Default::default(), // TODO: generate commit summary + log: LogChange { + mode: RefLog::AndReference, + force_create_reflog: false, + message: crate::commit::summary(commit.message.as_bstr()).into_owned(), + }, // TODO: generate commit summary mode: Create::OrUpdate { previous: commit.parents.get(0).map(|p| Target::Peeled(*p)), }, - new: Target::Peeled(commit_id), + new: Target::Peeled(commit_id.id), }, name: reference, deref: true, @@ -109,7 +114,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { git_lock::acquire::Fail::Immediately, Some(&commit.committer), )?; - Ok(commit_id.attach(self)) + Ok(commit_id) } } diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 0c8b2d38920..a8d6be449a9 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -224,6 +224,9 @@ pub struct EasyArcExclusive { pub mod easy; +/// +pub mod commit; + /// The kind of `Repository` #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Kind { diff --git a/git-repository/tests/commit/mod.rs b/git-repository/tests/commit/mod.rs new file mode 100644 index 00000000000..3459aabd037 --- /dev/null +++ b/git-repository/tests/commit/mod.rs @@ -0,0 +1,35 @@ +pub mod summary { + use bstr::ByteSlice; + use git_repository as git; + use std::borrow::Cow; + + #[test] + fn no_newline_yields_the_message_itself() { + let input = b"hello world".as_bstr(); + assert_eq!(git::commit::summary(input), Cow::Borrowed(input)); + } + + #[test] + fn trailing_newlines_and_whitespace_are_trimmed() { + let input = b"hello world \t\r\n \n".as_bstr(); + assert_eq!(git::commit::summary(input), Cow::Borrowed(b"hello world".as_bstr())); + } + + #[test] + fn prefixed_newlines_and_whitespace_are_trimmed() { + let input = b" \t\r\n \nhello world".as_bstr(); + assert_eq!(git::commit::summary(input), Cow::Borrowed(b"hello world".as_bstr())); + } + + #[test] + fn whitespace_up_to_a_newline_is_collapsed_into_a_space() { + let input = b" \t\r\n \nhello\r\nworld \t\r\n \n".as_bstr(); + assert_eq!(git::commit::summary(input), Cow::Borrowed(b"hello world".as_bstr())); + } + + #[test] + fn lines_separated_by_double_newlines_are_subjects() { + let input = b" \t\r\n \nhello\t \r\nworld \t\r \nfoo\n\nsomething else we ignore".as_bstr(); + assert_eq!(git::commit::summary(input), Cow::Borrowed(b"hello world foo".as_bstr())); + } +} diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index 20f2dbbe6af..2183f1ee254 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -12,6 +12,7 @@ fn basic_rw_repo() -> crate::Result<(Easy, tempfile::TempDir)> { Ok((Repository::open(repo_path.path())?.into(), repo_path)) } +mod commit; mod discover; mod easy; mod init; From e7a8b62eb24f840f639aa436b4e79a4a567d3d05 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 2 Sep 2021 22:35:12 +0800 Subject: [PATCH 15/91] [repository #190] produce nice reflog messages --- git-actor/src/signature/mod.rs | 13 ++++++++++--- git-repository/src/commit.rs | 4 +++- git-repository/src/easy/ext/object.rs | 6 +++--- git-repository/src/easy/object/mod.rs | 2 +- git-repository/src/lib.rs | 2 ++ git-repository/src/reference.rs | 27 ++++++++++++++++++++++++++ git-repository/tests/commit/mod.rs | 9 +++++++++ git-repository/tests/reference/mod.rs | 28 +++++++++++++++++++++++++++ git-repository/tests/repo.rs | 1 + 9 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 git-repository/src/reference.rs create mode 100644 git-repository/tests/reference/mod.rs diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index 8d40eb3f99b..6ef38245a2d 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -3,9 +3,10 @@ mod _ref { impl<'a> SignatureRef<'a> { /// Deserialize a signature from the given `data`. - pub fn from_bytes + nom::error::ContextError<&'a [u8]>>( - data: &'a [u8], - ) -> Result, nom::Err> { + pub fn from_bytes(data: &'a [u8]) -> Result, nom::Err> + where + E: nom::error::ParseError<&'a [u8]> + nom::error::ContextError<&'a [u8]>, + { decode(data).map(|(_, t)| t) } } @@ -14,6 +15,12 @@ mod _ref { mod convert { use crate::{Sign, Signature, SignatureRef, Time}; + impl Default for Signature { + fn default() -> Self { + Signature::empty() + } + } + impl Signature { /// An empty signature, similar to 'null'. pub fn empty() -> Self { diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs index e554ce49827..f85be45b040 100644 --- a/git-repository/src/commit.rs +++ b/git-repository/src/commit.rs @@ -1,13 +1,15 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; -/// Produce a commit summary for the given `message`. +/// Produce a short commit summary for the given `message`. /// /// This means the following /// /// * Take the subject line which is delimited by two newlines (\n\n) /// * transform intermediate consecutive whitespace including \r into one space /// +/// The resulting summary will have folded whitespace before a newline into spaces and stopped that process +/// once two consecutive newlines are encountered. pub fn summary(message: &BStr) -> Cow<'_, BStr> { let message = message.trim(); match message.find_byte(b'\n') { diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 8d6d33159e0..3ae8fe4202d 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -8,7 +8,7 @@ use crate::{ easy, easy::{commit, object, ObjectRef, Oid}, }; -use bstr::{BString, ByteSlice}; +use bstr::BString; use git_ref::transaction::{LogChange, RefLog}; use git_ref::FullName; use std::convert::TryInto; @@ -101,8 +101,8 @@ pub trait ObjectAccessExt: easy::Access + Sized { log: LogChange { mode: RefLog::AndReference, force_create_reflog: false, - message: crate::commit::summary(commit.message.as_bstr()).into_owned(), - }, // TODO: generate commit summary + message: crate::reference::log::message("commit", &commit), + }, mode: Create::OrUpdate { previous: commit.parents.get(0).map(|p| Target::Peeled(*p)), }, diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index eee1f9cb1ef..f8151d66ce0 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -128,7 +128,7 @@ impl<'repo, A> ObjectRef<'repo, A> where A: easy::Access + Sized, { - /// As [`to_commit_iter()`] but panics if this is not a commit + /// As [`to_commit_iter()`][ObjectRef::to_commit_iter()] but panics if this is not a commit pub fn commit_iter(&self) -> CommitRefIter<'_> { git_odb::data::Object::new(self.kind, &self.data) .into_commit_iter() diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index a8d6be449a9..6486ddea68d 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -226,6 +226,8 @@ pub mod easy; /// pub mod commit; +/// +pub mod reference; /// The kind of `Repository` #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] diff --git a/git-repository/src/reference.rs b/git-repository/src/reference.rs new file mode 100644 index 00000000000..e93bd244de6 --- /dev/null +++ b/git-repository/src/reference.rs @@ -0,0 +1,27 @@ +/// +pub mod log { + use crate::commit; + use bstr::{BString, ByteSlice, ByteVec}; + use git_object::Commit; + + /// Generate a message typical for git commit logs based on the given `operation` + pub fn message(operation: &str, commit: &Commit) -> BString { + let mut out = BString::from(operation); + if let Some(commit_type) = commit_type_by_parents(commit.parents.len()) { + out.push_str(b" ("); + out.extend_from_slice(commit_type.as_bytes()); + out.push_byte(b')'); + } + out.push_str(b": "); + out.extend_from_slice(&commit::summary(commit.message.as_bstr())); + out + } + + pub(crate) fn commit_type_by_parents(count: usize) -> Option<&'static str> { + Some(match count { + 0 => "initial", + 1 => return None, + _two_or_more => "merge", + }) + } +} diff --git a/git-repository/tests/commit/mod.rs b/git-repository/tests/commit/mod.rs index 3459aabd037..9d63c77a0de 100644 --- a/git-repository/tests/commit/mod.rs +++ b/git-repository/tests/commit/mod.rs @@ -27,6 +27,15 @@ pub mod summary { assert_eq!(git::commit::summary(input), Cow::Borrowed(b"hello world".as_bstr())); } + #[test] + fn whitespace_without_newlines_is_ignored_except_for_leading_and_trailing_whitespace() { + let input = b" \t\r\n \nhello \t \rworld \t\r\n \n".as_bstr(); + assert_eq!( + git::commit::summary(input), + Cow::Borrowed(b"hello \t \rworld".as_bstr()) + ); + } + #[test] fn lines_separated_by_double_newlines_are_subjects() { let input = b" \t\r\n \nhello\t \r\nworld \t\r \nfoo\n\nsomething else we ignore".as_bstr(); diff --git a/git-repository/tests/reference/mod.rs b/git-repository/tests/reference/mod.rs new file mode 100644 index 00000000000..42d68a99079 --- /dev/null +++ b/git-repository/tests/reference/mod.rs @@ -0,0 +1,28 @@ +mod log { + use git_repository as git; + + #[test] + fn message() { + let mut commit = git::objs::Commit { + tree: git::hash::ObjectId::empty_tree(), + parents: Default::default(), + author: Default::default(), + committer: Default::default(), + encoding: None, + message: "the subject\n\nthe body".into(), + extra_headers: vec![], + }; + assert_eq!( + git::reference::log::message("commit", &commit), + "commit (initial): the subject" + ); + commit.parents.push(git::hash::ObjectId::null_sha1()); + assert_eq!(git::reference::log::message("other", &commit), "other: the subject"); + + commit.parents.push(git::hash::ObjectId::null_sha1()); + assert_eq!( + git::reference::log::message("rebase", &commit), + "rebase (merge): the subject" + ); + } +} diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index 2183f1ee254..c4c7122c046 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -16,3 +16,4 @@ mod commit; mod discover; mod easy; mod init; +mod reference; From 4ec631c92349bbffa69c786838d2127b0c51970e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 07:40:24 +0800 Subject: [PATCH 16/91] =?UTF-8?q?[repository=20#190]=20another=20commit()?= =?UTF-8?q?=20test=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to simulate the initial commit situation which led to improvements in the provided API. --- git-repository/src/commit.rs | 3 +++ git-repository/src/easy/ext/object.rs | 4 ++-- git-repository/tests/easy/object.rs | 31 ++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs index f85be45b040..c9686b50bbb 100644 --- a/git-repository/src/commit.rs +++ b/git-repository/src/commit.rs @@ -1,6 +1,9 @@ use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; +/// An empty array of a type usable with the `git::easy` API to help declaring no parents should be used +pub const NO_PARENT_IDS: [git_hash::ObjectId; 0] = []; + /// Produce a short commit summary for the given `message`. /// /// This means the following diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 3ae8fe4202d..155ac163942 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -68,7 +68,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { message: impl Into, author: impl Into, committer: impl Into, - tree: impl Into>, + tree: impl Into, parents: impl IntoIterator>, ) -> Result, commit::Error> where @@ -84,7 +84,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { let reference = reference.try_into()?; let commit: git_object::Object = git_object::Commit { message: message.into(), - tree: tree.into().unwrap_or_else(git_hash::ObjectId::empty_tree), + tree: tree.into(), author: author.into(), committer: committer.into(), encoding: None, diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 7748c81d65a..375b6f26c58 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -22,11 +22,39 @@ mod commit { use git_repository::prelude::{ObjectAccessExt, ReferenceAccessExt}; use git_testtools::hex_to_id; + #[test] + fn single_line_initial_commit_empty_tree() { + let tmp = tempfile::tempdir().unwrap(); + let repo = git::init_bare(&tmp).unwrap().into_easy(); + let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into()).unwrap(); + let author = git::actor::Signature::empty(); + let commit_id = repo + .commit( + "HEAD", + "initial", + author.clone(), + author, + empty_tree_id, + git::commit::NO_PARENT_IDS, + ) + .unwrap(); + assert_eq!( + commit_id, + hex_to_id("302ea5640358f98ba23cda66c1e664a6f274643f"), + "the commit id is stable" + ); + } + #[test] fn multi_line_commit_message_uses_first_line_in_ref_log() { let (repo, _keep) = crate::basic_rw_repo().unwrap(); let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); - let tree_id = parent.object().unwrap().commit_iter().tree_id(); + let tree_id = parent + .object() + .unwrap() + .commit_iter() + .tree_id() + .expect("tree to be set"); let author = git::actor::Signature::empty(); let commit_id = repo .commit( @@ -43,6 +71,7 @@ mod commit { hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), "the commit id is stable" ); + // TODO: check reflog } } From e4abcf39cba32803e650c60b9df6724ab9ae7378 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 07:42:24 +0800 Subject: [PATCH 17/91] =?UTF-8?q?allow=20incremental=20builds=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …because it still spams the disk anyway, and having it might improve build speeds. --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8df2bd2e5e..4d906481fdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,9 +80,6 @@ env_logger = { version = "0.9.0", optional = true, default-features = false, fea crosstermion = { version = "0.8.0", optional = true, default-features = false } futures-lite = { version = "1.12.0", optional = true, default-features = false, features = ["std"] } -[profile.dev] -incremental = false - [profile.release] overflow-checks = false lto = "fat" From 06b9270e67823e9e911a9fa9d6eeeedcd93e62cb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 08:06:28 +0800 Subject: [PATCH 18/91] =?UTF-8?q?[repository=20#190]=20show=20that=20uncon?= =?UTF-8?q?ditional=20creation=20of=20references=20doesn't=20is=20lacking?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …as we need a constraint that can't be represented with the current system. --- git-repository/src/easy/ext/object.rs | 2 +- git-repository/src/easy/ext/reference.rs | 2 +- git-repository/tests/easy/object.rs | 31 ++++++++++++++++++++---- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 155ac163942..7c05d055e3f 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -103,7 +103,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { force_create_reflog: false, message: crate::reference::log::message("commit", &commit), }, - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: commit.parents.get(0).map(|p| Target::Peeled(*p)), }, new: Target::Peeled(commit_id.id), diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 15eced39b63..42628196ffc 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -26,7 +26,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { RefEdit { change: Change::Update { log: Default::default(), - mode: if force { + previous: if force { Create::OrUpdate { previous: None } } else { Create::Only diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 375b6f26c58..bba135dc854 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -23,7 +23,7 @@ mod commit { use git_testtools::hex_to_id; #[test] - fn single_line_initial_commit_empty_tree() { + fn single_line_initial_commit_empty_tree_ref_nonexisting() { let tmp = tempfile::tempdir().unwrap(); let repo = git::init_bare(&tmp).unwrap().into_easy(); let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into()).unwrap(); @@ -43,13 +43,16 @@ mod commit { hex_to_id("302ea5640358f98ba23cda66c1e664a6f274643f"), "the commit id is stable" ); + + // TODO: check reflog } #[test] - fn multi_line_commit_message_uses_first_line_in_ref_log() { + #[ignore] + fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { let (repo, _keep) = crate::basic_rw_repo().unwrap(); let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); - let tree_id = parent + let empty_tree_id = parent .object() .unwrap() .commit_iter() @@ -61,8 +64,8 @@ mod commit { "HEAD", "hello there \r\n\nthe body", author.clone(), - author, - tree_id, + author.clone(), + empty_tree_id, Some(parent), ) .unwrap(); @@ -71,6 +74,24 @@ mod commit { hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), "the commit id is stable" ); + + let commit_id = repo + .commit( + "refs/heads/new-branch", + "committing into a new branch creates it", + author.clone(), + author, + empty_tree_id, + Some(commit_id), + ) + .unwrap(); + + assert_eq!( + commit_id, + hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), + "the second commit id is stable" + ); + // TODO: check reflog } } From 9741987e2f82b5ae202804882c728c1642d8e3a4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 08:07:11 +0800 Subject: [PATCH 19/91] [ref #190] prepare massive refactoring to get additional constraint --- git-ref/CHANGELOG.md | 6 +++ git-ref/src/store/file/transaction/commit.rs | 6 ++- git-ref/src/store/file/transaction/prepare.rs | 6 +-- git-ref/src/transaction/ext.rs | 8 +--- git-ref/src/transaction/mod.rs | 31 +++++++++++++-- .../prepare_and_commit/create_or_update.rs | 38 +++++++++---------- git-ref/tests/transaction/mod.rs | 14 +++---- 7 files changed, 69 insertions(+), 40 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index 82aa8157c5d..8cb9207f9d8 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -1,3 +1,9 @@ +### v0.7.0 + +#### Breaking + +* Replace `transaction::Create` with `transaction::PreviousValue` + ### v0.6.1 #### Bugfixes diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 5e149095491..182ac7107df 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -35,7 +35,11 @@ impl<'s> Transaction<'s> { assert!(!change.update.deref, "Deref mode is turned into splits and turned off"); match &change.update.change { // reflog first, then reference - Change::Update { log, new, mode } => { + Change::Update { + log, + new, + previous: mode, + } => { let lock = change.lock.take().expect("each ref is locked"); let (update_ref, update_reflog) = match log.mode { RefLog::Only => (false, true), diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 280fa49a4f9..2f648288a9d 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -89,9 +89,7 @@ impl<'s> Transaction<'s> { lock } - Change::Update { - mode: previous, new, .. - } => { + Change::Update { previous, new, .. } => { let mut lock = git_lock::File::acquire_to_update_resource( store.reference_path(&relative_path), lock_fail_mode, @@ -236,7 +234,7 @@ impl<'s> Transaction<'s> { } match edit.update.change { Change::Update { - mode: Create::OrUpdate { previous: None }, + previous: Create::OrUpdate { previous: None }, .. } => continue, Change::Delete { .. } => { diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index 8601ee2fc4a..cc40b998a6b 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -94,11 +94,7 @@ where deref: true, } } - Change::Update { - log, - mode: previous, - new, - } => { + Change::Update { log, previous, new } => { let current = std::mem::replace( log, LogChange { @@ -110,7 +106,7 @@ where let next = std::mem::replace(previous, Create::OrUpdate { previous: None }); RefEdit { change: Change::Update { - mode: next, + previous: next, new: new.clone(), log: current, }, diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index bfc699c3f73..d5770885b7b 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -40,6 +40,29 @@ impl Default for LogChange { } } +/// The desired value of an updated value +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum PreviousValue { + /// No requirements are made towards the current value, and the new value is set unconditionally. + Any, + /// Create the ref only. This fails if the ref exists. + MustNotExist, + /// The ref _must_ exist and have the given value. + MustExistAndMatch(Target), + /// The ref _may_ exist and have the given value, or may not exist at all. + ExistingMustMatch(Target), +} + +impl PreviousValue { + pub(crate) fn previous_oid(&self) -> Option { + match self { + PreviousValue::MustExistAndMatch(Target::Peeled(oid)) + | PreviousValue::ExistingMustMatch(Target::Peeled(oid)) => Some(*oid), + _ => None, + } + } +} + /// A way to determine if a value should be created or created or updated. In the latter case the previous /// value can be specified to indicate to what extend the previous value matters. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -81,7 +104,7 @@ pub enum Change { /// The create mode. /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes /// and for use in the reflog. - mode: Create, + previous: Create, /// The new state of the reference, either for updating an existing one or creating a new one. new: Target, }, @@ -103,9 +126,11 @@ impl Change { /// Return references to values that are in common between all variants. pub fn previous_value(&self) -> Option> { match self { - Change::Update { mode: Create::Only, .. } => None, Change::Update { - mode: Create::OrUpdate { previous }, + previous: Create::Only, .. + } => None, + Change::Update { + previous: Create::OrUpdate { previous }, .. } | Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 43b0a15cc00..560e30938c7 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -34,7 +34,7 @@ fn reference_with_equally_named_empty_or_non_empty_directory_already_in_place_ca Some(RefEdit { change: Change::Update { log: LogChange::default(), - mode: Create::Only, + previous: Create::Only, new: Target::Symbolic("refs/heads/main".try_into().unwrap()), }, name: "HEAD".try_into()?, @@ -73,7 +73,7 @@ fn reference_with_old_value_must_exist_when_creating_it() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(Target::must_exist()), }, }, @@ -104,7 +104,7 @@ fn reference_with_explicit_value_must_match_the_value_on_update() -> crate::Resu change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(Target::Peeled(hex_to_id("28ce6a8b26aa170e1de65536fe8abe1832bd3242"))), }, }, @@ -134,7 +134,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_if_the_val change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -173,7 +173,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/hello".try_into()?), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -198,7 +198,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/namespaces/foo/refs/heads/hello".try_into()?), - mode: Create::Only, + previous: Create::Only, }, name: "refs/namespaces/foo/HEAD".try_into()?, deref: false, @@ -222,7 +222,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -237,7 +237,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - mode: Create::OrUpdate { previous: Some(target) }, + previous: Create::OrUpdate { previous: Some(target) }, }, name: "HEAD".try_into()?, deref: false, @@ -269,7 +269,7 @@ fn cancellation_after_preparation_leaves_no_change() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/main".try_into().unwrap()), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -306,7 +306,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -320,7 +320,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - mode: Create::Only, + previous: Create::Only, }, name: "HEAD".try_into()?, deref: false, @@ -354,7 +354,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log.clone(), new: new.clone(), - mode: Create::OrUpdate { previous: None }, + previous: Create::OrUpdate { previous: None }, }, name: "HEAD".try_into()?, deref: true, @@ -370,7 +370,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_only.clone(), new: new.clone(), - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(new_head_value.clone()) }, }, @@ -381,7 +381,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log, new: new.clone(), - mode: Create::Only, + previous: Create::Only, }, name: referent.try_into()?, deref: false, @@ -448,7 +448,7 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(Target::must_exist()), }, new: Target::Peeled(new_id), @@ -470,7 +470,7 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(Target::Peeled(hex_to_id("02a7a22d90d7c02fb494ed25551850b868e634f0"))), }, new: Target::Peeled(new_id), @@ -515,7 +515,7 @@ fn packed_refs_are_looked_up_when_checking_existing_values() -> crate::Result { force_create_reflog: false, message: "for pack".into(), }, - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(Target::Peeled(old_id)), }, new: Target::Peeled(new_id), @@ -572,7 +572,7 @@ fn packed_refs_creation_with_packed_refs_mode_prune_removes_original_loose_refs( .map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: Some(r.target.clone()), }, new: r.target, @@ -625,7 +625,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() let edits = store.loose_iter()?.map(|r| r.expect("valid ref")).map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - mode: Create::OrUpdate { + previous: Create::OrUpdate { previous: r.target.clone().into(), }, new: r.target, diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index 46d4a7c20a6..9b054e3aadc 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -109,7 +109,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - mode: Create::Only, + previous: Create::Only, new: Target::Symbolic("refs/heads/main".try_into()?), }, name: "HEAD".try_into()?, @@ -131,7 +131,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - mode: Create::Only, + previous: Create::Only, new: Target::Symbolic("refs/namespaces/foo/refs/heads/main".try_into()?), }, name: "refs/namespaces/foo/HEAD".try_into()?, @@ -249,7 +249,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::Only, + previous: Create::Only, log: LogChange { mode: RefLog::AndReference, force_create_reflog: true, @@ -323,7 +323,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::Only, + previous: Create::Only, log: log.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -359,7 +359,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::OrUpdate { previous: None }, + previous: Create::OrUpdate { previous: None }, log: log_only.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -376,7 +376,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::OrUpdate { previous: None }, + previous: Create::OrUpdate { previous: None }, log: log_only, new: Target::Peeled(ObjectId::null_sha1()), }, @@ -393,7 +393,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - mode: Create::Only, + previous: Create::Only, log, new: Target::Peeled(ObjectId::null_sha1()), }, From 1a4786fb3bdb3d3a86b026dbf04e6baef6d3c695 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:01:52 +0800 Subject: [PATCH 20/91] [ref #190] Allow for explicit expected previous values --- git-ref/CHANGELOG.md | 2 +- git-ref/src/store/file/transaction/prepare.rs | 60 +++++++++--------- git-ref/src/transaction/ext.rs | 4 +- git-ref/src/transaction/mod.rs | 46 +++----------- .../prepare_and_commit/create_or_update.rs | 61 ++++++++----------- git-ref/tests/transaction/mod.rs | 18 +++--- git-repository/src/commit.rs | 3 +- git-repository/src/easy/ext/object.rs | 23 ++++--- git-repository/src/easy/ext/reference.rs | 6 +- git-repository/src/reference.rs | 3 +- git-repository/tests/commit/mod.rs | 3 +- 11 files changed, 98 insertions(+), 131 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index 8cb9207f9d8..e17cebe34f0 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -2,7 +2,7 @@ #### Breaking -* Replace `transaction::Create` with `transaction::PreviousValue` +* Replace `transaction::Create` with `transaction::PreviousValue` and remove `transaction::Create` ### v0.6.1 diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 2f648288a9d..b20011f85ed 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -8,7 +8,7 @@ use crate::{ Transaction, }, }, - transaction::{Change, Create, LogChange, RefEdit, RefEditsExt, RefLog}, + transaction::{Change, LogChange, RefEdit, RefEditsExt, RefLog}, Target, }; @@ -102,23 +102,29 @@ impl<'s> Transaction<'s> { let existing_ref = existing_ref?; match (&previous, &existing_ref) { - (Create::Only, Some(existing)) if existing.target() != new.to_ref() => { - let new = new.clone(); - return Err(Error::MustNotExist { - full_name: change.name(), - actual: existing.target(), - new, - }); + (PreviousValue::Any, _) + | (PreviousValue::MustExist, Some(_)) + | (PreviousValue::MustNotExist | PreviousValue::ExistingMustMatch(_), None) => {} + (PreviousValue::MustExist, None) => { + let expected = Target::Peeled(git_hash::ObjectId::null_sha1()); + let full_name = change.name(); + return Err(Error::MustExist { full_name, expected }); + } + (PreviousValue::MustNotExist, Some(existing)) => { + if existing.target() != new.to_ref() { + let new = new.clone(); + return Err(Error::MustNotExist { + full_name: change.name(), + actual: existing.target(), + new, + }); + } } ( - Create::OrUpdate { - previous: Some(previous), - }, + PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), Some(existing), - ) => match previous { - Target::Peeled(oid) if oid.is_null() => {} - any_target if *any_target == existing.target() => {} - _target_mismatch => { + ) => { + if *previous != existing.target() { let actual = existing.target(); let expected = previous.to_owned(); let full_name = change.name(); @@ -128,25 +134,17 @@ impl<'s> Transaction<'s> { expected, }); } - }, - ( - Create::OrUpdate { - previous: Some(previous), - }, - None, - ) => { + } + + (PreviousValue::MustExistAndMatch(previous), None) => { let expected = previous.to_owned(); let full_name = change.name(); return Err(Error::MustExist { full_name, expected }); } - (Create::Only | Create::OrUpdate { previous: None }, None | Some(_)) => {} }; - *previous = match existing_ref { - None => Create::Only, - Some(existing) => Create::OrUpdate { - previous: Some(existing.target()), - }, + if let Some(existing) = existing_ref { + *previous = PreviousValue::MustExistAndMatch(existing.target()); }; lock.with_mut(|file| match new { @@ -234,9 +232,9 @@ impl<'s> Transaction<'s> { } match edit.update.change { Change::Update { - previous: Create::OrUpdate { previous: None }, + previous: PreviousValue::ExistingMustMatch(_) | PreviousValue::MustExistAndMatch(_), .. - } => continue, + } => needs_packed_refs_lookups = true, Change::Delete { .. } => { edits_for_packed_transaction.push(edit.update.clone()); } @@ -403,3 +401,5 @@ mod error { } pub use error::Error; + +use crate::transaction::PreviousValue; diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index cc40b998a6b..fb1ea909038 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -1,7 +1,7 @@ use bstr::{BString, ByteVec}; use crate::{ - transaction::{Change, Create, LogChange, RefEdit, RefLog, Target}, + transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target}, Namespace, PartialNameRef, }; @@ -103,7 +103,7 @@ where force_create_reflog: log.force_create_reflog, }, ); - let next = std::mem::replace(previous, Create::OrUpdate { previous: None }); + let next = std::mem::replace(previous, PreviousValue::Any); RefEdit { change: Change::Update { previous: next, diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index d5770885b7b..eddfeaa0302 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -45,7 +45,9 @@ impl Default for LogChange { pub enum PreviousValue { /// No requirements are made towards the current value, and the new value is set unconditionally. Any, - /// Create the ref only. This fails if the ref exists. + /// The reference must exist and may have any value. + MustExist, + /// Create the ref only, hence the reference must not exist. MustNotExist, /// The ref _must_ exist and have the given value. MustExistAndMatch(Target), @@ -63,35 +65,6 @@ impl PreviousValue { } } -/// A way to determine if a value should be created or created or updated. In the latter case the previous -/// value can be specified to indicate to what extend the previous value matters. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub enum Create { - /// Create a ref only. This fails if the ref exists and does not match the desired new value. - Only, - /// Create or update the reference with the `previous` value being controlling how to deal with existing ref values. - /// - OrUpdate { - /// Interpret… - /// * `None` so that existing values do not matter at all. This is the mode that always creates or updates a reference to the - /// desired new value. - /// * `Some(Target::Peeled(ObjectId::null_sha1())` so that the reference is required to exist even though its value doesn't matter. - /// * `Some(value)` so that the reference is required to exist and have the given `value`. - previous: Option, - }, -} - -impl Create { - pub(crate) fn previous_oid(&self) -> Option { - match self { - Create::OrUpdate { - previous: Some(Target::Peeled(oid)), - } => Some(*oid), - Create::Only | Create::OrUpdate { .. } => None, - } - } -} - /// A description of an edit to perform. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Change { @@ -104,7 +77,7 @@ pub enum Change { /// The create mode. /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes /// and for use in the reflog. - previous: Create, + previous: PreviousValue, /// The new state of the reference, either for updating an existing one or creating a new one. new: Target, }, @@ -116,6 +89,7 @@ pub enum Change { /// /// If a previous ref existed, this value will be filled in automatically and can be accessed /// if the transaction was committed successfully. + // TODO: use PreviousValue here even though it will have a few unused cases previous: Option, /// How to thread the reference log during deletion. log: RefLog, @@ -127,13 +101,11 @@ impl Change { pub fn previous_value(&self) -> Option> { match self { Change::Update { - previous: Create::Only, .. - } => None, - Change::Update { - previous: Create::OrUpdate { previous }, + previous: PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), .. - } - | Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), + } => Some(previous.to_ref()), + Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), + _ => None, } } } diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 560e30938c7..b501bb17b75 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -6,12 +6,13 @@ use bstr::ByteSlice; use git_hash::ObjectId; use git_lock::acquire::Fail; use git_object::bstr::BString; +use git_ref::transaction::PreviousValue; use git_ref::{ file::{ transaction::{self, PackedRefs}, WriteReflog, }, - transaction::{Change, Create, LogChange, RefEdit, RefLog}, + transaction::{Change, LogChange, RefEdit, RefLog}, Target, }; use git_testtools::hex_to_id; @@ -34,7 +35,7 @@ fn reference_with_equally_named_empty_or_non_empty_directory_already_in_place_ca Some(RefEdit { change: Change::Update { log: LogChange::default(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, new: Target::Symbolic("refs/heads/main".try_into().unwrap()), }, name: "HEAD".try_into()?, @@ -73,9 +74,7 @@ fn reference_with_old_value_must_exist_when_creating_it() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: Create::OrUpdate { - previous: Some(Target::must_exist()), - }, + previous: PreviousValue::MustExist, }, name: "HEAD".try_into()?, deref: false, @@ -104,9 +103,9 @@ fn reference_with_explicit_value_must_match_the_value_on_update() -> crate::Resu change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: Create::OrUpdate { - previous: Some(Target::Peeled(hex_to_id("28ce6a8b26aa170e1de65536fe8abe1832bd3242"))), - }, + previous: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( + "28ce6a8b26aa170e1de65536fe8abe1832bd3242", + ))), }, name: "HEAD".try_into()?, deref: false, @@ -134,7 +133,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_if_the_val change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -173,7 +172,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/hello".try_into()?), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -198,7 +197,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/namespaces/foo/refs/heads/hello".try_into()?), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "refs/namespaces/foo/HEAD".try_into()?, deref: false, @@ -222,7 +221,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -237,7 +236,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - previous: Create::OrUpdate { previous: Some(target) }, + previous: PreviousValue::MustExistAndMatch(target) }, name: "HEAD".try_into()?, deref: false, @@ -269,7 +268,7 @@ fn cancellation_after_preparation_leaves_no_change() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/main".try_into().unwrap()), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -306,7 +305,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -320,7 +319,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -354,7 +353,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log.clone(), new: new.clone(), - previous: Create::OrUpdate { previous: None }, + previous: PreviousValue::Any, }, name: "HEAD".try_into()?, deref: true, @@ -370,9 +369,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_only.clone(), new: new.clone(), - previous: Create::OrUpdate { - previous: Some(new_head_value.clone()) - }, + previous: PreviousValue::MustExistAndMatch(new_head_value.clone()), }, name: "HEAD".try_into()?, deref: false, @@ -381,7 +378,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log, new: new.clone(), - previous: Create::Only, + previous: PreviousValue::Any, }, name: referent.try_into()?, deref: false, @@ -448,9 +445,7 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - previous: Create::OrUpdate { - previous: Some(Target::must_exist()), - }, + previous: PreviousValue::MustExist, new: Target::Peeled(new_id), }, name: referent.as_bstr().try_into()?, @@ -470,9 +465,9 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - previous: Create::OrUpdate { - previous: Some(Target::Peeled(hex_to_id("02a7a22d90d7c02fb494ed25551850b868e634f0"))), - }, + previous: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( + "02a7a22d90d7c02fb494ed25551850b868e634f0" + )),), new: Target::Peeled(new_id), }, name: referent.as_bstr().try_into()?, @@ -515,9 +510,7 @@ fn packed_refs_are_looked_up_when_checking_existing_values() -> crate::Result { force_create_reflog: false, message: "for pack".into(), }, - previous: Create::OrUpdate { - previous: Some(Target::Peeled(old_id)), - }, + previous: PreviousValue::MustExistAndMatch(Target::Peeled(old_id)), new: Target::Peeled(new_id), }, name: "refs/heads/main".try_into()?, @@ -572,9 +565,7 @@ fn packed_refs_creation_with_packed_refs_mode_prune_removes_original_loose_refs( .map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - previous: Create::OrUpdate { - previous: Some(r.target.clone()), - }, + previous: PreviousValue::MustExistAndMatch(r.target.clone()), new: r.target, }, name: r.name, @@ -625,9 +616,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() let edits = store.loose_iter()?.map(|r| r.expect("valid ref")).map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - previous: Create::OrUpdate { - previous: r.target.clone().into(), - }, + previous: PreviousValue::MustExistAndMatch(r.target.clone().into()), new: r.target, }, name: r.name, diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index 9b054e3aadc..85b8ac76bdb 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -3,7 +3,7 @@ mod refedit_ext { use bstr::{BString, ByteSlice}; use git_ref::{ - transaction::{Change, Create, RefEdit, RefEditsExt, RefLog}, + transaction::{Change, PreviousValue, RefEdit, RefEditsExt, RefLog}, PartialNameRef, Target, }; @@ -109,7 +109,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, new: Target::Symbolic("refs/heads/main".try_into()?), }, name: "HEAD".try_into()?, @@ -131,7 +131,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - previous: Create::Only, + previous: PreviousValue::MustNotExist, new: Target::Symbolic("refs/namespaces/foo/refs/heads/main".try_into()?), }, name: "refs/namespaces/foo/HEAD".try_into()?, @@ -148,7 +148,7 @@ mod refedit_ext { use git_hash::ObjectId; use git_ref::{ - transaction::{Change, Create, LogChange, RefEdit, RefEditsExt, RefLog}, + transaction::{Change, LogChange, PreviousValue, RefEdit, RefEditsExt, RefLog}, FullNameRef, PartialNameRef, Target, }; use git_testtools::hex_to_id; @@ -249,7 +249,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: Create::Only, + previous: PreviousValue::MustNotExist, log: LogChange { mode: RefLog::AndReference, force_create_reflog: true, @@ -323,7 +323,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: Create::Only, + previous: PreviousValue::MustNotExist, log: log.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -359,7 +359,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: Create::OrUpdate { previous: None }, + previous: PreviousValue::Any, log: log_only.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -376,7 +376,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: Create::OrUpdate { previous: None }, + previous: PreviousValue::Any, log: log_only, new: Target::Peeled(ObjectId::null_sha1()), }, @@ -393,7 +393,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: Create::Only, + previous: PreviousValue::MustNotExist, log, new: Target::Peeled(ObjectId::null_sha1()), }, diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs index c9686b50bbb..a3b545c948d 100644 --- a/git-repository/src/commit.rs +++ b/git-repository/src/commit.rs @@ -1,6 +1,7 @@ -use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::borrow::Cow; +use bstr::{BStr, BString, ByteSlice, ByteVec}; + /// An empty array of a type usable with the `git::easy` API to help declaring no parents should be used pub const NO_PARENT_IDS: [git_hash::ObjectId; 0] = []; diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 7c05d055e3f..5714e9d467f 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -1,17 +1,18 @@ -use std::ops::DerefMut; +use std::{convert::TryInto, ops::DerefMut}; +use bstr::BString; use git_hash::ObjectId; use git_odb::{Find, FindExt}; +use git_ref::{ + transaction::{LogChange, PreviousValue, RefLog}, + FullName, +}; -use crate::ext::ObjectIdExt; use crate::{ easy, easy::{commit, object, ObjectRef, Oid}, + ext::ObjectIdExt, }; -use bstr::BString; -use git_ref::transaction::{LogChange, RefLog}; -use git_ref::FullName; -use std::convert::TryInto; pub trait ObjectAccessExt: easy::Access + Sized { // NOTE: in order to get the actual kind of object, is must be fully decoded from storage in case of packs @@ -75,12 +76,13 @@ pub trait ObjectAccessExt: easy::Access + Sized { Name: TryInto, commit::Error: From, { - use crate::easy::ext::ReferenceAccessExt; use git_ref::{ - transaction::{Change, Create, RefEdit}, + transaction::{Change, RefEdit}, Target, }; + use crate::easy::ext::ReferenceAccessExt; + let reference = reference.try_into()?; let commit: git_object::Object = git_object::Commit { message: message.into(), @@ -103,8 +105,9 @@ pub trait ObjectAccessExt: easy::Access + Sized { force_create_reflog: false, message: crate::reference::log::message("commit", &commit), }, - previous: Create::OrUpdate { - previous: commit.parents.get(0).map(|p| Target::Peeled(*p)), + previous: match commit.parents.get(0).map(|p| Target::Peeled(*p)) { + Some(previous) => PreviousValue::ExistingMustMatch(previous), + None => PreviousValue::MustNotExist, }, new: Target::Peeled(commit_id.id), }, diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 42628196ffc..0419869f9e1 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -4,7 +4,7 @@ use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; use git_ref::{ - transaction::{Change, Create, RefEdit}, + transaction::{Change, PreviousValue, RefEdit}, PartialNameRef, Target, }; @@ -27,9 +27,9 @@ pub trait ReferenceAccessExt: easy::Access + Sized { change: Change::Update { log: Default::default(), previous: if force { - Create::OrUpdate { previous: None } + PreviousValue::Any } else { - Create::Only + PreviousValue::MustNotExist }, new: Target::Peeled(target.into()), }, diff --git a/git-repository/src/reference.rs b/git-repository/src/reference.rs index e93bd244de6..3ce5bac5c27 100644 --- a/git-repository/src/reference.rs +++ b/git-repository/src/reference.rs @@ -1,9 +1,10 @@ /// pub mod log { - use crate::commit; use bstr::{BString, ByteSlice, ByteVec}; use git_object::Commit; + use crate::commit; + /// Generate a message typical for git commit logs based on the given `operation` pub fn message(operation: &str, commit: &Commit) -> BString { let mut out = BString::from(operation); diff --git a/git-repository/tests/commit/mod.rs b/git-repository/tests/commit/mod.rs index 9d63c77a0de..7860c268f62 100644 --- a/git-repository/tests/commit/mod.rs +++ b/git-repository/tests/commit/mod.rs @@ -1,7 +1,8 @@ pub mod summary { + use std::borrow::Cow; + use bstr::ByteSlice; use git_repository as git; - use std::borrow::Cow; #[test] fn no_newline_yields_the_message_itself() { From 07126d65946e981b339b6535986597cb328a1c9e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:06:47 +0800 Subject: [PATCH 21/91] [ref #190] refactor --- git-ref/src/store/file/transaction/commit.rs | 2 +- git-ref/src/store/file/transaction/prepare.rs | 8 ++-- git-ref/src/transaction/ext.rs | 6 +-- git-ref/src/transaction/mod.rs | 6 +-- .../prepare_and_commit/create_or_update.rs | 38 +++++++++---------- git-ref/tests/transaction/mod.rs | 14 +++---- git-repository/src/easy/ext/object.rs | 2 +- git-repository/src/easy/ext/reference.rs | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 182ac7107df..83a6bda914f 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -38,7 +38,7 @@ impl<'s> Transaction<'s> { Change::Update { log, new, - previous: mode, + expected: mode, } => { let lock = change.lock.take().expect("each ref is locked"); let (update_ref, update_reflog) = match log.mode { diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index b20011f85ed..1ae41d430d0 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -89,7 +89,7 @@ impl<'s> Transaction<'s> { lock } - Change::Update { previous, new, .. } => { + Change::Update { expected, new, .. } => { let mut lock = git_lock::File::acquire_to_update_resource( store.reference_path(&relative_path), lock_fail_mode, @@ -101,7 +101,7 @@ impl<'s> Transaction<'s> { })?; let existing_ref = existing_ref?; - match (&previous, &existing_ref) { + match (&expected, &existing_ref) { (PreviousValue::Any, _) | (PreviousValue::MustExist, Some(_)) | (PreviousValue::MustNotExist | PreviousValue::ExistingMustMatch(_), None) => {} @@ -144,7 +144,7 @@ impl<'s> Transaction<'s> { }; if let Some(existing) = existing_ref { - *previous = PreviousValue::MustExistAndMatch(existing.target()); + *expected = PreviousValue::MustExistAndMatch(existing.target()); }; lock.with_mut(|file| match new { @@ -232,7 +232,7 @@ impl<'s> Transaction<'s> { } match edit.update.change { Change::Update { - previous: PreviousValue::ExistingMustMatch(_) | PreviousValue::MustExistAndMatch(_), + expected: PreviousValue::ExistingMustMatch(_) | PreviousValue::MustExistAndMatch(_), .. } => needs_packed_refs_lookups = true, Change::Delete { .. } => { diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index fb1ea909038..548ca31125d 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -94,7 +94,7 @@ where deref: true, } } - Change::Update { log, previous, new } => { + Change::Update { log, expected, new } => { let current = std::mem::replace( log, LogChange { @@ -103,10 +103,10 @@ where force_create_reflog: log.force_create_reflog, }, ); - let next = std::mem::replace(previous, PreviousValue::Any); + let next = std::mem::replace(expected, PreviousValue::Any); RefEdit { change: Change::Update { - previous: next, + expected: next, new: new.clone(), log: current, }, diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index eddfeaa0302..0a77c9c35b8 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -74,10 +74,10 @@ pub enum Change { Update { /// The desired change to the reference log. log: LogChange, - /// The create mode. + /// The expected value already present in the reference. /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes /// and for use in the reflog. - previous: PreviousValue, + expected: PreviousValue, /// The new state of the reference, either for updating an existing one or creating a new one. new: Target, }, @@ -101,7 +101,7 @@ impl Change { pub fn previous_value(&self) -> Option> { match self { Change::Update { - previous: PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), + expected: PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), .. } => Some(previous.to_ref()), Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index b501bb17b75..5e104c0dd69 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -35,7 +35,7 @@ fn reference_with_equally_named_empty_or_non_empty_directory_already_in_place_ca Some(RefEdit { change: Change::Update { log: LogChange::default(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, new: Target::Symbolic("refs/heads/main".try_into().unwrap()), }, name: "HEAD".try_into()?, @@ -74,7 +74,7 @@ fn reference_with_old_value_must_exist_when_creating_it() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: PreviousValue::MustExist, + expected: PreviousValue::MustExist, }, name: "HEAD".try_into()?, deref: false, @@ -103,7 +103,7 @@ fn reference_with_explicit_value_must_match_the_value_on_update() -> crate::Resu change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( + expected: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( "28ce6a8b26aa170e1de65536fe8abe1832bd3242", ))), }, @@ -133,7 +133,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_if_the_val change: Change::Update { log: LogChange::default(), new: Target::Peeled(ObjectId::null_sha1()), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -172,7 +172,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/hello".try_into()?), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -197,7 +197,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/namespaces/foo/refs/heads/hello".try_into()?), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "refs/namespaces/foo/HEAD".try_into()?, deref: false, @@ -221,7 +221,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -236,7 +236,7 @@ fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the change: Change::Update { log: LogChange::default(), new: target.clone(), - previous: PreviousValue::MustExistAndMatch(target) + expected: PreviousValue::MustExistAndMatch(target) }, name: "HEAD".try_into()?, deref: false, @@ -268,7 +268,7 @@ fn cancellation_after_preparation_leaves_no_change() -> crate::Result { change: Change::Update { log: LogChange::default(), new: Target::Symbolic("refs/heads/main".try_into().unwrap()), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -305,7 +305,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -319,7 +319,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_ignored.clone(), new: new_head_value.clone(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, }, name: "HEAD".try_into()?, deref: false, @@ -353,7 +353,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log.clone(), new: new.clone(), - previous: PreviousValue::Any, + expected: PreviousValue::Any, }, name: "HEAD".try_into()?, deref: true, @@ -369,7 +369,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log: log_only.clone(), new: new.clone(), - previous: PreviousValue::MustExistAndMatch(new_head_value.clone()), + expected: PreviousValue::MustExistAndMatch(new_head_value.clone()), }, name: "HEAD".try_into()?, deref: false, @@ -378,7 +378,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { change: Change::Update { log, new: new.clone(), - previous: PreviousValue::Any, + expected: PreviousValue::Any, }, name: referent.try_into()?, deref: false, @@ -445,7 +445,7 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - previous: PreviousValue::MustExist, + expected: PreviousValue::MustExist, new: Target::Peeled(new_id), }, name: referent.as_bstr().try_into()?, @@ -465,7 +465,7 @@ fn write_reference_to_which_head_points_to_does_not_update_heads_reflog_even_tho force_create_reflog: false, message: "".into(), }, - previous: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( + expected: PreviousValue::MustExistAndMatch(Target::Peeled(hex_to_id( "02a7a22d90d7c02fb494ed25551850b868e634f0" )),), new: Target::Peeled(new_id), @@ -510,7 +510,7 @@ fn packed_refs_are_looked_up_when_checking_existing_values() -> crate::Result { force_create_reflog: false, message: "for pack".into(), }, - previous: PreviousValue::MustExistAndMatch(Target::Peeled(old_id)), + expected: PreviousValue::MustExistAndMatch(Target::Peeled(old_id)), new: Target::Peeled(new_id), }, name: "refs/heads/main".try_into()?, @@ -565,7 +565,7 @@ fn packed_refs_creation_with_packed_refs_mode_prune_removes_original_loose_refs( .map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - previous: PreviousValue::MustExistAndMatch(r.target.clone()), + expected: PreviousValue::MustExistAndMatch(r.target.clone()), new: r.target, }, name: r.name, @@ -616,7 +616,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() let edits = store.loose_iter()?.map(|r| r.expect("valid ref")).map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - previous: PreviousValue::MustExistAndMatch(r.target.clone().into()), + expected: PreviousValue::MustExistAndMatch(r.target.clone().into()), new: r.target, }, name: r.name, diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index 85b8ac76bdb..0f5390aa266 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -109,7 +109,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, new: Target::Symbolic("refs/heads/main".try_into()?), }, name: "HEAD".try_into()?, @@ -131,7 +131,7 @@ mod refedit_ext { RefEdit { change: Change::Update { log: Default::default(), - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, new: Target::Symbolic("refs/namespaces/foo/refs/heads/main".try_into()?), }, name: "refs/namespaces/foo/HEAD".try_into()?, @@ -249,7 +249,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, log: LogChange { mode: RefLog::AndReference, force_create_reflog: true, @@ -323,7 +323,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, log: log.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -359,7 +359,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: PreviousValue::Any, + expected: PreviousValue::Any, log: log_only.clone(), new: Target::Peeled(ObjectId::null_sha1()), }, @@ -376,7 +376,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: PreviousValue::Any, + expected: PreviousValue::Any, log: log_only, new: Target::Peeled(ObjectId::null_sha1()), }, @@ -393,7 +393,7 @@ mod refedit_ext { }, RefEdit { change: Change::Update { - previous: PreviousValue::MustNotExist, + expected: PreviousValue::MustNotExist, log, new: Target::Peeled(ObjectId::null_sha1()), }, diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 5714e9d467f..3efa2d0913b 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -105,7 +105,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { force_create_reflog: false, message: crate::reference::log::message("commit", &commit), }, - previous: match commit.parents.get(0).map(|p| Target::Peeled(*p)) { + expected: match commit.parents.get(0).map(|p| Target::Peeled(*p)) { Some(previous) => PreviousValue::ExistingMustMatch(previous), None => PreviousValue::MustNotExist, }, diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 0419869f9e1..6b773d97c58 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -26,7 +26,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { RefEdit { change: Change::Update { log: Default::default(), - previous: if force { + expected: if force { PreviousValue::Any } else { PreviousValue::MustNotExist From 68f7fc2f2f57c32412ee2e46befc9cd2fdd7e973 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:18:22 +0800 Subject: [PATCH 22/91] =?UTF-8?q?[ref=20#190]=20don't=20claim=20there=20wa?= =?UTF-8?q?s=20a=20previous=20oid=20unnecessarily=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …maybe this is where we should see that mutating this just to use it in reflogs isn't the way. --- git-ref/src/store/file/transaction/commit.rs | 8 ++------ git-ref/src/transaction/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 83a6bda914f..c897c9eb99b 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -35,11 +35,7 @@ impl<'s> Transaction<'s> { assert!(!change.update.deref, "Deref mode is turned into splits and turned off"); match &change.update.change { // reflog first, then reference - Change::Update { - log, - new, - expected: mode, - } => { + Change::Update { log, new, expected } => { let lock = change.lock.take().expect("each ref is locked"); let (update_ref, update_reflog) = match log.mode { RefLog::Only => (false, true), @@ -49,7 +45,7 @@ impl<'s> Transaction<'s> { match new { Target::Symbolic(_) => {} // no reflog for symref changes Target::Peeled(new_oid) => { - let previous = mode.previous_oid().or(change.leaf_referent_previous_oid); + let previous = expected.previous_oid().or(change.leaf_referent_previous_oid); let do_update = previous.as_ref().map_or(true, |previous| previous != new_oid); if do_update { self.store.reflog_create_or_append( diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index 0a77c9c35b8..bffa4a10e11 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -58,8 +58,7 @@ pub enum PreviousValue { impl PreviousValue { pub(crate) fn previous_oid(&self) -> Option { match self { - PreviousValue::MustExistAndMatch(Target::Peeled(oid)) - | PreviousValue::ExistingMustMatch(Target::Peeled(oid)) => Some(*oid), + PreviousValue::MustExistAndMatch(Target::Peeled(oid)) => Some(*oid), _ => None, } } From c04c8b98a074d277067cee73ddef0609419a7bb8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:25:56 +0800 Subject: [PATCH 23/91] =?UTF-8?q?[ref=20#190]=20be=20explicit=20about=20wh?= =?UTF-8?q?at=20the=20previous=20reflog=20oid=20is=20for=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to avoid bugs that we otherwise would have introduced (without a failing test mind you). --- git-ref/src/store/file/transaction/commit.rs | 4 ++-- git-ref/src/store/file/transaction/mod.rs | 2 ++ git-ref/src/store/file/transaction/prepare.rs | 6 +++++- git-ref/src/target.rs | 7 +++++++ git-ref/src/transaction/mod.rs | 10 ---------- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index c897c9eb99b..4908babae5a 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -35,7 +35,7 @@ impl<'s> Transaction<'s> { assert!(!change.update.deref, "Deref mode is turned into splits and turned off"); match &change.update.change { // reflog first, then reference - Change::Update { log, new, expected } => { + Change::Update { log, new, expected: _ } => { let lock = change.lock.take().expect("each ref is locked"); let (update_ref, update_reflog) = match log.mode { RefLog::Only => (false, true), @@ -45,7 +45,7 @@ impl<'s> Transaction<'s> { match new { Target::Symbolic(_) => {} // no reflog for symref changes Target::Peeled(new_oid) => { - let previous = expected.previous_oid().or(change.leaf_referent_previous_oid); + let previous = change.previous_oid.or(change.leaf_referent_previous_oid); let do_update = previous.as_ref().map_or(true, |previous| previous != new_oid); if do_update { self.store.reflog_create_or_append( diff --git a/git-ref/src/store/file/transaction/mod.rs b/git-ref/src/store/file/transaction/mod.rs index 9c8bb647b52..578ff1ce9ff 100644 --- a/git-ref/src/store/file/transaction/mod.rs +++ b/git-ref/src/store/file/transaction/mod.rs @@ -43,6 +43,8 @@ pub(in crate::store::file) struct Edit { /// For symbolic refs, this is the previous OID to put into the reflog instead of our own previous value. It's the /// peeled value of the leaf referent. leaf_referent_previous_oid: Option, + /// The previous object id of an existing reference, if present, for use in the reflog + previous_oid: Option, } impl Edit { diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 1ae41d430d0..51cf343a235 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -144,7 +144,9 @@ impl<'s> Transaction<'s> { }; if let Some(existing) = existing_ref { - *expected = PreviousValue::MustExistAndMatch(existing.target()); + let target = existing.target(); + change.previous_oid = target.as_id().map(ToOwned::to_owned); + *expected = PreviousValue::MustExistAndMatch(target); }; lock.with_mut(|file| match new { @@ -180,6 +182,7 @@ impl<'s> Transaction<'s> { lock: None, parent_index: None, leaf_referent_previous_oid: None, + previous_oid: None, }) .collect(); updates @@ -196,6 +199,7 @@ impl<'s> Transaction<'s> { lock: None, parent_index: Some(idx), leaf_referent_previous_oid: None, + previous_oid: None, }, self.namespace.take(), ) diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 34b9f5bf705..41d11acf8de 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -65,6 +65,13 @@ impl Target { Target::Peeled(oid) => Some(oid), } } + /// Return the contained object id if the target is peeled or itself if it is not. + pub fn try_into_id(self) -> Result { + match self { + Target::Symbolic(_) => Err(self), + Target::Peeled(oid) => Ok(oid), + } + } /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. pub fn as_name(&self) -> Option<&BStr> { match self { diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index bffa4a10e11..036a1676273 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -13,7 +13,6 @@ //! * prepared transactions are committed to finalize the change //! - errors when committing while leave the ref store in an inconsistent, but operational state. use bstr::BString; -use git_hash::ObjectId; use crate::{FullName, Target}; @@ -55,15 +54,6 @@ pub enum PreviousValue { ExistingMustMatch(Target), } -impl PreviousValue { - pub(crate) fn previous_oid(&self) -> Option { - match self { - PreviousValue::MustExistAndMatch(Target::Peeled(oid)) => Some(*oid), - _ => None, - } - } -} - /// A description of an edit to perform. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Change { From 0e65559e6d5a4b06c552e99e9c463559737f4b4d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:32:12 +0800 Subject: [PATCH 24/91] [ref #190] refactor --- git-ref/src/store/file/transaction/commit.rs | 9 +++++++-- git-ref/src/store/file/transaction/mod.rs | 2 -- git-ref/src/store/file/transaction/prepare.rs | 6 +----- .../transaction/prepare_and_commit/create_or_update.rs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 4908babae5a..87ea22771d3 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -35,7 +35,7 @@ impl<'s> Transaction<'s> { assert!(!change.update.deref, "Deref mode is turned into splits and turned off"); match &change.update.change { // reflog first, then reference - Change::Update { log, new, expected: _ } => { + Change::Update { log, new, expected } => { let lock = change.lock.take().expect("each ref is locked"); let (update_ref, update_reflog) = match log.mode { RefLog::Only => (false, true), @@ -45,7 +45,11 @@ impl<'s> Transaction<'s> { match new { Target::Symbolic(_) => {} // no reflog for symref changes Target::Peeled(new_oid) => { - let previous = change.previous_oid.or(change.leaf_referent_previous_oid); + let previous = match expected { + PreviousValue::MustExistAndMatch(Target::Peeled(oid)) => Some(oid.to_owned()), + _ => None, + } + .or(change.leaf_referent_previous_oid); let do_update = previous.as_ref().map_or(true, |previous| previous != new_oid); if do_update { self.store.reflog_create_or_append( @@ -189,4 +193,5 @@ mod error { } } } +use crate::transaction::PreviousValue; pub use error::Error; diff --git a/git-ref/src/store/file/transaction/mod.rs b/git-ref/src/store/file/transaction/mod.rs index 578ff1ce9ff..9c8bb647b52 100644 --- a/git-ref/src/store/file/transaction/mod.rs +++ b/git-ref/src/store/file/transaction/mod.rs @@ -43,8 +43,6 @@ pub(in crate::store::file) struct Edit { /// For symbolic refs, this is the previous OID to put into the reflog instead of our own previous value. It's the /// peeled value of the leaf referent. leaf_referent_previous_oid: Option, - /// The previous object id of an existing reference, if present, for use in the reflog - previous_oid: Option, } impl Edit { diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 51cf343a235..1ae41d430d0 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -144,9 +144,7 @@ impl<'s> Transaction<'s> { }; if let Some(existing) = existing_ref { - let target = existing.target(); - change.previous_oid = target.as_id().map(ToOwned::to_owned); - *expected = PreviousValue::MustExistAndMatch(target); + *expected = PreviousValue::MustExistAndMatch(existing.target()); }; lock.with_mut(|file| match new { @@ -182,7 +180,6 @@ impl<'s> Transaction<'s> { lock: None, parent_index: None, leaf_referent_previous_oid: None, - previous_oid: None, }) .collect(); updates @@ -199,7 +196,6 @@ impl<'s> Transaction<'s> { lock: None, parent_index: Some(idx), leaf_referent_previous_oid: None, - previous_oid: None, }, self.namespace.take(), ) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 5e104c0dd69..dd84d53c3fa 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -616,7 +616,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() let edits = store.loose_iter()?.map(|r| r.expect("valid ref")).map(|r| RefEdit { change: Change::Update { log: LogChange::default(), - expected: PreviousValue::MustExistAndMatch(r.target.clone().into()), + expected: PreviousValue::MustExistAndMatch(r.target.clone()), new: r.target, }, name: r.name, From 74f85b1fd8d9c34eca34a5ae516c4768f96b092f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 10:52:10 +0800 Subject: [PATCH 25/91] [ref #190] deletions also use PreviousValue now --- etc/check-package-size.sh | 2 +- git-ref/src/store/file/transaction/prepare.rs | 22 +++++++++----- git-ref/src/transaction/ext.rs | 7 +++-- git-ref/src/transaction/mod.rs | 26 +++++++++-------- .../prepare_and_commit/create_or_update.rs | 4 +-- .../transaction/prepare_and_commit/delete.rs | 29 ++++++++++--------- git-ref/tests/transaction/mod.rs | 26 ++++++++--------- 7 files changed, 65 insertions(+), 51 deletions(-) diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 15975ce8974..0eea015adbe 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -34,6 +34,6 @@ indent cargo diet -n --package-size-limit 25KB (enter git-odb && indent cargo diet -n --package-size-limit 15KB) (enter git-protocol && indent cargo diet -n --package-size-limit 25KB) (enter git-packetline && indent cargo diet -n --package-size-limit 15KB) -(enter git-repository && indent cargo diet -n --package-size-limit 30KB) +(enter git-repository && indent cargo diet -n --package-size-limit 35KB) (enter git-transport && indent cargo diet -n --package-size-limit 30KB) (enter gitoxide-core && indent cargo diet -n --package-size-limit 20KB) diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 1ae41d430d0..4eeb28d8766 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -51,7 +51,7 @@ impl<'s> Transaction<'s> { (maybe_loose, _) => Ok(maybe_loose), }); let lock = match &mut change.update.change { - Change::Delete { previous, .. } => { + Change::Delete { expected, .. } => { let lock = git_lock::Marker::acquire_to_hold_resource( store.reference_path(&relative_path), lock_fail_mode, @@ -62,16 +62,24 @@ impl<'s> Transaction<'s> { full_name: "borrowchk wont allow change.name()".into(), })?; let existing_ref = existing_ref?; - match (&previous, &existing_ref) { - (None, None | Some(_)) => {} - (Some(_previous), None) => { + match (&expected, &existing_ref) { + (PreviousValue::MustNotExist, _) => { + panic!("BUG: MustNotExist constraint makes no sense if references are to be deleted") + } + (PreviousValue::ExistingMustMatch(_), None) + | (PreviousValue::MustExist, Some(_)) + | (PreviousValue::Any, None | Some(_)) => {} + (PreviousValue::MustExist | PreviousValue::MustExistAndMatch(_), None) => { return Err(Error::DeleteReferenceMustExist { full_name: change.name(), }) } - (Some(previous), Some(existing)) => { + ( + PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), + Some(existing), + ) => { let actual = existing.target(); - if !previous.is_null() && *previous != actual { + if *previous != actual { let expected = previous.clone(); return Err(Error::ReferenceOutOfDate { full_name: change.name(), @@ -84,7 +92,7 @@ impl<'s> Transaction<'s> { // Keep the previous value for the caller and ourselves. Maybe they want to keep a log of sorts. if let Some(existing) = existing_ref { - *previous = Some(existing.target()); + *expected = PreviousValue::MustExistAndMatch(existing.target()); } lock diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index 548ca31125d..f33ce89c971 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -82,12 +82,15 @@ where new_edits.push(make_entry( eid, match &mut edit.change { - Change::Delete { previous, log: mode } => { + Change::Delete { + expected: previous, + log: mode, + } => { let current_mode = *mode; *mode = RefLog::Only; RefEdit { change: Change::Delete { - previous: previous.clone(), + expected: previous.clone(), log: current_mode, }, name: referent, diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index 036a1676273..a2641b6ddcd 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -64,22 +64,19 @@ pub enum Change { /// The desired change to the reference log. log: LogChange, /// The expected value already present in the reference. - /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes - /// and for use in the reflog. + /// If a ref was existing previously it will be overwritten at `MustExistAndMatch(actual_value)` for use after + /// the transaction was committed successfully. expected: PreviousValue, /// The new state of the reference, either for updating an existing one or creating a new one. new: Target, }, /// Delete a reference and optionally check if `previous` is its content. Delete { - /// The previous state of the reference. If set, the reference is expected to exist and match the given value. - /// If the value is a peeled null-id the reference is expected to exist but the value doesn't matter, neither peeled nor symbolic. - /// If `None`, the actual value does not matter. + /// The expected value of the reference, with the `MustNotExist` variant being invalid. /// - /// If a previous ref existed, this value will be filled in automatically and can be accessed - /// if the transaction was committed successfully. - // TODO: use PreviousValue here even though it will have a few unused cases - previous: Option, + /// If a previous ref existed, this value will be filled in automatically as `MustExistAndMatch(actual_value)` and + /// can be accessed if the transaction was committed successfully. + expected: PreviousValue, /// How to thread the reference log during deletion. log: RefLog, }, @@ -92,10 +89,15 @@ impl Change { Change::Update { expected: PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), .. - } => Some(previous.to_ref()), - Change::Delete { previous, .. } => previous.as_ref().map(|t| t.to_ref()), - _ => None, + } => previous, + Change::Delete { + expected: PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), + .. + } => previous, + _ => return None, } + .to_ref() + .into() } } diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index dd84d53c3fa..4326eeca4b8 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -162,7 +162,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/for/deletion".try_into()?, @@ -187,7 +187,7 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/namespaces/foo/refs/for/deletion".try_into()?, diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index 2d9732de036..c517ae845e4 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -3,6 +3,7 @@ use crate::file::{ transaction::prepare_and_commit::{committer, empty_store}, }; use git_lock::acquire::Fail; +use git_ref::transaction::PreviousValue; use git_ref::{ transaction::{Change, RefEdit, RefLog}, Target, @@ -18,7 +19,7 @@ fn delete_a_ref_which_is_gone_succeeds() -> crate::Result { .prepare( Some(RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "DOES_NOT_EXIST".try_into()?, @@ -37,7 +38,7 @@ fn delete_a_ref_which_is_gone_but_must_exist_fails() -> crate::Result { let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::must_exist()), + expected: PreviousValue::MustExist, log: RefLog::AndReference, }, name: "DOES_NOT_EXIST".try_into()?, @@ -67,7 +68,7 @@ fn delete_ref_and_reflog_on_symbolic_no_deref() -> crate::Result { .prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::must_exist()), + expected: PreviousValue::MustExist, log: RefLog::AndReference, }, name: head.name.clone(), @@ -81,7 +82,7 @@ fn delete_ref_and_reflog_on_symbolic_no_deref() -> crate::Result { edits, vec![RefEdit { change: Change::Delete { - previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), + expected: PreviousValue::MustExistAndMatch(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::AndReference, }, name: head.name, @@ -107,7 +108,7 @@ fn delete_ref_with_incorrect_previous_value_fails() -> crate::Result { let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), + expected: PreviousValue::MustExistAndMatch(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::Only, }, name: head.name, @@ -141,7 +142,7 @@ fn delete_reflog_only_of_symbolic_no_deref() -> crate::Result { .prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), + expected: PreviousValue::MustExistAndMatch(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::Only, }, name: head.name, @@ -175,7 +176,7 @@ fn delete_reflog_only_of_symbolic_with_deref() -> crate::Result { .prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::must_exist()), + expected: PreviousValue::MustExist, log: RefLog::Only, }, name: head.name, @@ -208,7 +209,7 @@ fn delete_broken_ref_that_must_exist_fails_as_it_is_no_valid_ref() -> crate::Res let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::must_exist()), + expected: PreviousValue::MustExist, log: RefLog::AndReference, }, name: "HEAD".try_into()?, @@ -240,7 +241,7 @@ fn delete_broken_ref_that_may_not_exist_works_even_in_deref_mode() -> crate::Res .prepare( Some(RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "HEAD".try_into()?, @@ -255,7 +256,7 @@ fn delete_broken_ref_that_may_not_exist_works_even_in_deref_mode() -> crate::Res edits, vec![RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "HEAD".try_into()?, @@ -278,7 +279,7 @@ fn store_write_mode_has_no_effect_and_reflogs_are_always_deleted() -> crate::Res .prepare( Some(RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::Only, }, name: "HEAD".try_into()?, @@ -313,7 +314,7 @@ fn packed_refs_are_consulted_when_determining_previous_value_of_ref_to_be_delete .prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::Peeled(old_id)), + expected: PreviousValue::MustExistAndMatch(Target::Peeled(old_id)), log: RefLog::AndReference, }, name: "refs/heads/main".try_into()?, @@ -346,7 +347,7 @@ fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_ .prepare( Some(RefEdit { change: Change::Delete { - previous: Some(Target::Peeled(branch_id)), + expected: PreviousValue::MustExistAndMatch(Target::Peeled(branch_id)), log: RefLog::AndReference, }, name: branch.name().into(), @@ -387,7 +388,7 @@ fn all_contained_references_deletes_the_packed_ref_file_too() { let r = r.expect("valid ref"); RefEdit { change: Change::Delete { - previous: Target::Peeled(r.target()).into(), + expected: PreviousValue::MustExistAndMatch(Target::Peeled(r.target())), log: RefLog::AndReference, }, name: r.name.into(), diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index 0f5390aa266..e51bfbfa200 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -33,7 +33,7 @@ mod refedit_ext { fn named_edit(name: &str) -> RefEdit { RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: name.try_into().expect("valid name"), @@ -48,7 +48,7 @@ mod refedit_ext { let mut edits = vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "HEAD".try_into()?, @@ -56,7 +56,7 @@ mod refedit_ext { }, RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/main".try_into()?, @@ -100,7 +100,7 @@ mod refedit_ext { let mut edits = vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/tags/deleted".try_into()?, @@ -122,7 +122,7 @@ mod refedit_ext { vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/namespaces/foo/refs/tags/deleted".try_into()?, @@ -172,7 +172,7 @@ mod refedit_ext { let mut edits = vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "SYMBOLIC_PROBABLY_BUT_DEREF_IS_FALSE_SO_IGNORED".try_into()?, @@ -180,7 +180,7 @@ mod refedit_ext { }, RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/anything-but-not-symbolic".try_into()?, @@ -188,7 +188,7 @@ mod refedit_ext { }, RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/does-not-exist-and-deref-is-ignored".try_into()?, @@ -241,7 +241,7 @@ mod refedit_ext { let mut edits = vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/delete-symbolic-1".try_into()?, @@ -315,7 +315,7 @@ mod refedit_ext { let mut edits = vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/delete-symbolic-1".try_into()?, @@ -351,7 +351,7 @@ mod refedit_ext { vec![ RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::Only, }, name: "refs/heads/delete-symbolic-1".try_into()?, @@ -368,7 +368,7 @@ mod refedit_ext { }, RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::Only, }, name: "refs/heads/delete-symbolic-2".try_into()?, @@ -385,7 +385,7 @@ mod refedit_ext { }, RefEdit { change: Change::Delete { - previous: None, + expected: PreviousValue::Any, log: RefLog::AndReference, }, name: "refs/heads/delete-symbolic-3".try_into()?, From 980e16a10806edba4553716d9533716a727f0c9e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 11:56:33 +0800 Subject: [PATCH 26/91] [ref #190] more tests --- .../prepare_and_commit/create_or_update.rs | 116 +++++++++++++++++- .../transaction/prepare_and_commit/delete.rs | 92 +++++++++----- 2 files changed, 175 insertions(+), 33 deletions(-) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 4326eeca4b8..df3810d3ce7 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -123,7 +123,73 @@ fn reference_with_explicit_value_must_match_the_value_on_update() -> crate::Resu } #[test] -fn reference_with_create_only_must_not_exist_already_when_creating_it_if_the_value_does_not_match() -> crate::Result { +fn the_existing_must_match_constraint_allow_non_existing_references_to_be_created() -> crate::Result { + let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; + let expected = PreviousValue::ExistingMustMatch(Target::Peeled(ObjectId::empty_tree())); + let edits = store + .transaction() + .prepare( + Some(RefEdit { + change: Change::Update { + log: LogChange::default(), + new: Target::Peeled(ObjectId::null_sha1()), + expected: expected.clone(), + }, + name: "refs/heads/new".try_into()?, + deref: false, + }), + Fail::Immediately, + )? + .commit(&committer())?; + + assert_eq!( + edits, + vec![RefEdit { + change: Change::Update { + log: LogChange::default(), + new: Target::Peeled(ObjectId::null_sha1()), + expected, + }, + name: "refs/heads/new".try_into()?, + deref: false, + }] + ); + Ok(()) +} + +#[test] +fn the_existing_must_match_constraint_requires_existing_references_to_have_the_given_value_to_cause_failure_on_mismatch( +) -> crate::Result { + let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; + let head = store.try_find_loose("HEAD")?.expect("head exists already"); + let target = head.target; + + let res = store.transaction().prepare( + Some(RefEdit { + change: Change::Update { + log: LogChange::default(), + new: Target::Peeled(ObjectId::null_sha1()), + expected: PreviousValue::ExistingMustMatch(Target::Peeled(hex_to_id( + "28ce6a8b26aa170e1de65536fe8abe1832bd3242", + ))), + }, + name: "HEAD".try_into()?, + deref: false, + }), + Fail::Immediately, + ); + match res { + Err(transaction::prepare::Error::ReferenceOutOfDate { full_name, actual, .. }) => { + assert_eq!(full_name, "HEAD"); + assert_eq!(actual, target); + } + _ => unreachable!("unexpected result"), + } + Ok(()) +} + +#[test] +fn reference_with_must_not_exist_constraint_cannot_be_created_if_it_exists_already() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.try_find_loose("HEAD")?.expect("head exists already"); let target = head.target; @@ -208,7 +274,53 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs } #[test] -fn reference_with_create_only_must_not_exist_already_when_creating_it_unless_the_value_matches() -> crate::Result { +fn reference_with_must_exist_constraint_must_exist_already_with_any_value() -> crate::Result { + let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; + let head = store.try_find_loose("HEAD")?.expect("head exists already"); + let target = head.target; + let previous_reflog_count = reflog_lines(&store, "HEAD")?.len(); + + let new_target = Target::Peeled(ObjectId::empty_tree()); + let edits = store + .transaction() + .prepare( + Some(RefEdit { + change: Change::Update { + log: LogChange::default(), + new: new_target.clone(), + expected: PreviousValue::MustExist, + }, + name: "HEAD".try_into()?, + deref: false, + }), + Fail::Immediately, + )? + .commit(&committer())?; + + assert_eq!( + edits, + vec![RefEdit { + change: Change::Update { + log: LogChange::default(), + new: new_target, + expected: PreviousValue::MustExistAndMatch(target) + }, + name: "HEAD".try_into()?, + deref: false, + }] + ); + + assert_eq!( + reflog_lines(&store, "HEAD")?.len(), + previous_reflog_count + 1, + "a new reflog is added" + ); + Ok(()) +} + +#[test] +fn reference_with_must_not_exist_constraint_may_exist_already_if_the_new_value_matches_the_existing_one( +) -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.try_find_loose("HEAD")?.expect("head exists already"); let target = head.target; diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index c517ae845e4..9afa2b13526 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -229,6 +229,40 @@ fn delete_broken_ref_that_must_exist_fails_as_it_is_no_valid_ref() -> crate::Res Ok(()) } +#[test] +fn non_existing_can_be_deleted_with_the_may_exist_match_constraint() -> crate::Result { + let (_keep, store) = empty_store()?; + let previous_value = + PreviousValue::ExistingMustMatch(Target::Peeled(hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"))); + let edits = store + .transaction() + .prepare( + Some(RefEdit { + change: Change::Delete { + expected: previous_value.clone(), + log: RefLog::AndReference, + }, + name: "refs/heads/not-there".try_into()?, + deref: true, + }), + Fail::Immediately, + )? + .commit(&committer())?; + + assert_eq!( + edits, + vec![RefEdit { + change: Change::Delete { + expected: previous_value, + log: RefLog::AndReference, + }, + name: "refs/heads/not-there".try_into()?, + deref: false, + }] + ); + Ok(()) +} + #[test] /// Based on https://github.com/git/git/blob/master/refs/files-backend.c#L514:L515 fn delete_broken_ref_that_may_not_exist_works_even_in_deref_mode() -> crate::Result { @@ -372,46 +406,42 @@ fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_ } #[test] -fn all_contained_references_deletes_the_packed_ref_file_too() { - let (_keep, store) = store_writable("make_packed_ref_repository.sh").unwrap(); +fn all_contained_references_deletes_the_packed_ref_file_too() -> crate::Result { + for mode in ["must-exist", "may-exist"] { + let (_keep, store) = store_writable("make_packed_ref_repository.sh")?; - let edits = store - .transaction() - .prepare( - store - .packed_buffer() - .unwrap() - .expect("packed-refs") - .iter() - .unwrap() - .map(|r| { + let edits = store + .transaction() + .prepare( + store.packed_buffer()?.expect("packed-refs").iter()?.map(|r| { let r = r.expect("valid ref"); RefEdit { change: Change::Delete { - expected: PreviousValue::MustExistAndMatch(Target::Peeled(r.target())), + expected: match mode { + "must-exist" => PreviousValue::MustExistAndMatch(Target::Peeled(r.target())), + "may-exist" => PreviousValue::ExistingMustMatch(Target::Peeled(r.target())), + _ => unimplemented!("unknown mode: {}", mode), + }, log: RefLog::AndReference, }, name: r.name.into(), deref: false, } }), - git_lock::acquire::Fail::Immediately, - ) - .unwrap() - .commit(&committer()) - .unwrap(); - - assert!(!store.packed_refs_path().is_file(), "packed-refs was entirely removed"); - - let packed = store.packed_buffer().unwrap(); - assert!(packed.is_none(), "it won't make up packed refs"); - for edit in edits { - assert!( - store - .try_find(edit.name.to_partial(), packed.as_ref()) - .unwrap() - .is_none(), - "delete ref cannot be found" - ); + git_lock::acquire::Fail::Immediately, + )? + .commit(&committer())?; + + assert!(!store.packed_refs_path().is_file(), "packed-refs was entirely removed"); + + let packed = store.packed_buffer()?; + assert!(packed.is_none(), "it won't make up packed refs"); + for edit in edits { + assert!( + store.try_find(edit.name.to_partial(), packed.as_ref())?.is_none(), + "delete ref cannot be found" + ); + } } + Ok(()) } From 86343416dec8026f32c57d164dec4bf9b75b6536 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 15:16:09 +0800 Subject: [PATCH 27/91] =?UTF-8?q?[ref=20#190]=20introduce=20Raw=20referenc?= =?UTF-8?q?e=20type=20that=20simplifies=20everything=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …such as the distinction between packed and loose refs as well as how consumers can use references. This means git-repository will have a much simpler time to deal with differences between file stores and reftables stores and it will be easier to grant access to peeling and symref following. --- git-ref/src/lib.rs | 1 + git-ref/src/raw.rs | 160 ++++++++++++++++++ .../src/store/file/loose/reference/logiter.rs | 2 +- git-ref/src/store/file/loose/reference/mod.rs | 2 +- git-ref/src/target.rs | 8 + git-repository/src/easy/ext/reference.rs | 7 + git-repository/src/easy/mod.rs | 2 + git-repository/tests/easy/ext/mod.rs | 0 git-repository/tests/easy/ext/object.rs | 0 git-repository/tests/easy/ext/reference.rs | 0 git-repository/tests/easy/object.rs | 3 + 11 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 git-ref/src/raw.rs create mode 100644 git-repository/tests/easy/ext/mod.rs create mode 100644 git-repository/tests/easy/ext/object.rs create mode 100644 git-repository/tests/easy/ext/reference.rs diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 8b9f7163a38..82e0dc930cf 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -81,4 +81,5 @@ pub enum TargetRef<'a> { } mod parse; +mod raw; mod target; diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs new file mode 100644 index 00000000000..252f4fff170 --- /dev/null +++ b/git-ref/src/raw.rs @@ -0,0 +1,160 @@ +use git_hash::ObjectId; + +use crate::{FullName, Target}; + +/// A fully owned backend agnostic reference +#[derive(Debug)] +pub struct Reference { + /// The path to uniquely identify this ref within its store. + pub name: FullName, + /// The target of the reference, either a symbolic reference by full name or a possibly intermediate object by its id. + pub target: Target, + /// The fully peeled object to which this reference ultimately points to + peeled: Option, +} + +mod convert { + use crate::raw::Reference; + use crate::store::file::loose; + use crate::store::packed; + use crate::Target; + use git_hash::ObjectId; + + impl From for loose::Reference { + fn from(value: Reference) -> Self { + loose::Reference { + name: value.name, + target: value.target, + } + } + } + + impl From for Reference { + fn from(value: loose::Reference) -> Self { + Reference { + name: value.name, + target: value.target, + peeled: None, + } + } + } + + impl<'p> From> for Reference { + fn from(value: packed::Reference<'p>) -> Self { + Reference { + name: value.name.into(), + target: Target::Peeled(value.target()), + peeled: value + .object + .map(|hex| ObjectId::from_hex(hex).expect("parser validation")), + } + } + } +} + +mod log { + use crate::raw::Reference; + use crate::store::file; + use crate::store::file::log; + use crate::store::file::loose::reference::logiter::must_be_io_err; + + impl Reference { + /// Obtain a reverse iterator over logs of this reference. See [crate::file::loose::Reference::log_iter_rev()] for details. + pub fn log_iter_rev<'b>( + &self, + store: &file::Store, + buf: &'b mut [u8], + ) -> std::io::Result>> { + store.reflog_iter_rev(self.name.to_ref(), buf).map_err(must_be_io_err) + } + + /// Obtain an iterator over logs of this reference. See [crate::file::loose::Reference::log_iter()] for details. + pub fn log_iter<'a, 'b: 'a>( + &'a self, + store: &file::Store, + buf: &'b mut Vec, + ) -> std::io::Result, log::iter::decode::Error>> + 'a>> + { + store.reflog_iter(self.name.to_ref(), buf).map_err(must_be_io_err) + } + + /// For details, see [loose::Reference::log_exists()]. + pub fn log_exists(&self, store: &file::Store) -> bool { + store + .reflog_exists(self.name.to_ref()) + .expect("infallible name conversion") + } + } +} + +mod access { + use crate::raw::Reference; + use crate::{FullNameRef, Namespace}; + use bstr::ByteSlice; + + impl Reference { + /// Returns the kind of reference based on its target + pub fn kind(&self) -> crate::Kind { + self.target.kind() + } + + /// Return the full validated name of the reference, which may include a namespace. + pub fn name(&self) -> FullNameRef<'_> { + self.name.to_ref() + } + + /// Return the full validated name of the reference, with the given namespace stripped if possible. + /// + /// If the reference name wasn't prefixed with `namespace`, `None` is returned instead. + pub fn name_without_namespace(&self, namespace: &Namespace) -> Option> { + self.name() + .0 + .as_bstr() + .strip_prefix(namespace.0.as_bstr().as_ref()) + .map(|stripped| FullNameRef(stripped.as_bstr())) + } + } +} + +// impl Reference { +// /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. +// pub fn peel_to_id_in_place( +// &mut self, +// store: &file::Store, +// packed: Option<&packed::Buffer>, +// find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, +// ) -> Result { +// match self { +// Reference::Loose(r) => r.peel_to_id_in_place(store, packed, find).map(ToOwned::to_owned), +// Reference::Packed(p) => { +// if let Some(object) = p.object { +// p.target = object; +// } +// p.object = None; +// Ok(p.target()) +// } +// } +// } +// +// /// For details, see [crate::file::loose::Reference::follow_symbolic()]. +// pub fn peel_one_level<'p2>( +// &self, +// store: &file::Store, +// packed: Option<&'p2 packed::Buffer>, +// ) -> Option, crate::store::file::loose::reference::peel::Error>> { +// match self { +// Reference::Loose(r) => r.follow_symbolic(store, packed), +// Reference::Packed(p) => packed +// .and_then(|packed| packed.try_find(p.name).ok().flatten()) // needed to get data with 'p2 lifetime +// .and_then(|np| { +// p.object.and(np.object).map(|peeled| { +// Ok(Reference::Packed(packed::Reference { +// name: np.name, +// target: peeled, +// object: None, +// })) +// }) +// }), +// } +// } +// } diff --git a/git-ref/src/store/file/loose/reference/logiter.rs b/git-ref/src/store/file/loose/reference/logiter.rs index 0b6ee882105..cf82b6c938b 100644 --- a/git-ref/src/store/file/loose/reference/logiter.rs +++ b/git-ref/src/store/file/loose/reference/logiter.rs @@ -3,7 +3,7 @@ use crate::store::{ file::{log, loose, loose::Reference}, }; -pub(in crate::store::file) fn must_be_io_err(err: loose::reflog::Error) -> std::io::Error { +pub(crate) fn must_be_io_err(err: loose::reflog::Error) -> std::io::Error { match err { loose::reflog::Error::Io(err) => err, loose::reflog::Error::RefnameValidation(_) => unreachable!("we are called from a valid ref"), diff --git a/git-ref/src/store/file/loose/reference/mod.rs b/git-ref/src/store/file/loose/reference/mod.rs index fa1811942e8..45eec712b90 100644 --- a/git-ref/src/store/file/loose/reference/mod.rs +++ b/git-ref/src/store/file/loose/reference/mod.rs @@ -1,4 +1,4 @@ -pub(in crate::store::file) mod logiter; +pub(in crate) mod logiter; /// pub mod peel; diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 41d11acf8de..58d934bc395 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -65,6 +65,14 @@ impl Target { Target::Peeled(oid) => Some(oid), } } + /// Return the contained object id or panic + pub fn into_id(self) -> ObjectId { + match self { + Target::Symbolic(_) => panic!("BUG: expected peeled reference target but found symbolic one"), + Target::Peeled(oid) => oid, + } + } + /// Return the contained object id if the target is peeled or itself if it is not. pub fn try_into_id(self) -> Result { match self { diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 6b773d97c58..c249a102d82 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -73,6 +73,13 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .map_err(Into::into) } + fn head(&self) -> Result>, reference::find::existing::Error> { + let _head = self.find_reference("HEAD")?; + + todo!("follow symrefs") + // head.backing + } + fn find_reference<'a, Name, E>(&self, name: Name) -> Result, reference::find::existing::Error> where Name: TryInto, Error = E>, diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 0bc37e33af6..da6dbd94aae 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -84,6 +84,8 @@ pub struct Object { } /// A reference that points to an object or reference, with access to its source repository. +/// +/// Note that these are snapshots and won't recognize if they are stale. pub struct Reference<'r, A> { pub(crate) backing: Option, pub(crate) access: &'r A, diff --git a/git-repository/tests/easy/ext/mod.rs b/git-repository/tests/easy/ext/mod.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index bba135dc854..717d7f0b5be 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -93,6 +93,9 @@ mod commit { ); // TODO: check reflog + let branch = repo.head().unwrap().expect("head is not detached"); + let current_commit = branch.target().as_id().expect("peeled").to_owned(); + assert_eq!(current_commit, commit_id.detach(), "the commit was set"); } } From d6bef3afe7168659a75e26fb3ae2aa722fecf853 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 15:18:12 +0800 Subject: [PATCH 28/91] [repository #190] refactor Follow module structure --- git-repository/src/easy/ext/reference.rs | 2 +- git-repository/src/easy/mod.rs | 3 +- git-repository/tests/easy/ext/mod.rs | 2 + git-repository/tests/easy/ext/object.rs | 98 +++++++++++++++++++++ git-repository/tests/easy/ext/reference.rs | 15 ++++ git-repository/tests/easy/mod.rs | 1 + git-repository/tests/easy/object.rs | 99 ---------------------- git-repository/tests/repo.rs | 5 ++ 8 files changed, 124 insertions(+), 101 deletions(-) diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index c249a102d82..0687701b2dd 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -75,7 +75,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { fn head(&self) -> Result>, reference::find::existing::Error> { let _head = self.find_reference("HEAD")?; - + // _head.peel_to_oid_in_place() todo!("follow symrefs") // head.backing } diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index da6dbd94aae..11affd93b43 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -105,7 +105,8 @@ struct ModifieablePackedRefsBuffer { /// State for use in `Easy*` to provide mutable parts of a repository such as caches and buffers. #[derive(Default)] pub struct State { - /// As the packed-buffer may hold onto a memory map, we avoid that to exist once per thread, multiplying system resources. + /// As the packed-buffer may hold onto a memory map, we avoid that to exist once per thread, multiplying system resources, cloning + /// it with every clone of the owning `Easy`. /// This seems worth the cost of always going through an `Arc>>`. Note that `EasyArcExclusive` uses the same construct /// but the reason we make this distinction at all is that there are other easy's that allows to chose exactly what you need in /// your application. `State` is one size fits all with supporting single-threaded applications only. diff --git a/git-repository/tests/easy/ext/mod.rs b/git-repository/tests/easy/ext/mod.rs index e69de29bb2d..b0e326e04f0 100644 --- a/git-repository/tests/easy/ext/mod.rs +++ b/git-repository/tests/easy/ext/mod.rs @@ -0,0 +1,2 @@ +mod object; +mod reference; diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index e69de29bb2d..31cd4a7b0cd 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -0,0 +1,98 @@ +mod write_object { + use git_repository::prelude::ObjectAccessExt; + + #[test] + fn empty_tree() -> crate::Result { + let tmp = tempfile::tempdir()?; + let repo = git_repository::init_bare(&tmp)?.into_easy(); + let oid = repo.write_object(&git_repository::objs::Tree::empty().into())?; + assert_eq!( + oid, + git_repository::hash::ObjectId::empty_tree(), + "it produces a well-known empty tree id" + ); + Ok(()) + } +} + +mod commit { + use git_repository as git; + use git_repository::prelude::{ObjectAccessExt, ReferenceAccessExt}; + use git_testtools::hex_to_id; + + #[test] + fn single_line_initial_commit_empty_tree_ref_nonexisting() { + let tmp = tempfile::tempdir().unwrap(); + let repo = git::init_bare(&tmp).unwrap().into_easy(); + let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into()).unwrap(); + let author = git::actor::Signature::empty(); + let commit_id = repo + .commit( + "HEAD", + "initial", + author.clone(), + author, + empty_tree_id, + git::commit::NO_PARENT_IDS, + ) + .unwrap(); + assert_eq!( + commit_id, + hex_to_id("302ea5640358f98ba23cda66c1e664a6f274643f"), + "the commit id is stable" + ); + + // TODO: check reflog + } + + #[test] + #[ignore] + fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { + let (repo, _keep) = crate::basic_rw_repo().unwrap(); + let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); + let empty_tree_id = parent + .object() + .unwrap() + .commit_iter() + .tree_id() + .expect("tree to be set"); + let author = git::actor::Signature::empty(); + let commit_id = repo + .commit( + "HEAD", + "hello there \r\n\nthe body", + author.clone(), + author.clone(), + empty_tree_id, + Some(parent), + ) + .unwrap(); + assert_eq!( + commit_id, + hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), + "the commit id is stable" + ); + + let commit_id = repo + .commit( + "refs/heads/new-branch", + "committing into a new branch creates it", + author.clone(), + author, + empty_tree_id, + Some(commit_id), + ) + .unwrap(); + + assert_eq!( + commit_id, + hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), + "the second commit id is stable" + ); + + // TODO: check reflog + let branch = repo.head().unwrap().expect("head is not detached"); + let current_commit = branch.target().as_id().expect("peeled").to_owned(); + assert_eq!(current_commit, commit_id.detach(), "the commit was set"); + } +} diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index e69de29bb2d..83c370ea523 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -0,0 +1,15 @@ +mod head { + use git_repository::prelude::ReferenceAccessExt; + use git_testtools::hex_to_id; + + #[test] + #[ignore] + fn symbolic() { + let repo = crate::basic_repo().unwrap(); + let head = repo.head().unwrap().expect("HEAD is symbolic"); + assert_eq!( + head.target().into_id(), + hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") + ); + } +} diff --git a/git-repository/tests/easy/mod.rs b/git-repository/tests/easy/mod.rs index 08204d0e258..73647542bbf 100644 --- a/git-repository/tests/easy/mod.rs +++ b/git-repository/tests/easy/mod.rs @@ -1,3 +1,4 @@ mod access; +mod ext; mod object; mod reference; diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 717d7f0b5be..3fe89ebba1d 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -1,104 +1,5 @@ use git_repository::easy; -mod in_empty_bare { - use git_repository::prelude::ObjectAccessExt; - - #[test] - fn write_empty_tree() -> crate::Result { - let tmp = tempfile::tempdir()?; - let repo = git_repository::init_bare(&tmp)?.into_easy(); - let oid = repo.write_object(&git_repository::objs::Tree::empty().into())?; - assert_eq!( - oid, - git_repository::hash::ObjectId::empty_tree(), - "it produces a well-known empty tree id" - ); - Ok(()) - } -} - -mod commit { - use git_repository as git; - use git_repository::prelude::{ObjectAccessExt, ReferenceAccessExt}; - use git_testtools::hex_to_id; - - #[test] - fn single_line_initial_commit_empty_tree_ref_nonexisting() { - let tmp = tempfile::tempdir().unwrap(); - let repo = git::init_bare(&tmp).unwrap().into_easy(); - let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into()).unwrap(); - let author = git::actor::Signature::empty(); - let commit_id = repo - .commit( - "HEAD", - "initial", - author.clone(), - author, - empty_tree_id, - git::commit::NO_PARENT_IDS, - ) - .unwrap(); - assert_eq!( - commit_id, - hex_to_id("302ea5640358f98ba23cda66c1e664a6f274643f"), - "the commit id is stable" - ); - - // TODO: check reflog - } - - #[test] - #[ignore] - fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { - let (repo, _keep) = crate::basic_rw_repo().unwrap(); - let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); - let empty_tree_id = parent - .object() - .unwrap() - .commit_iter() - .tree_id() - .expect("tree to be set"); - let author = git::actor::Signature::empty(); - let commit_id = repo - .commit( - "HEAD", - "hello there \r\n\nthe body", - author.clone(), - author.clone(), - empty_tree_id, - Some(parent), - ) - .unwrap(); - assert_eq!( - commit_id, - hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), - "the commit id is stable" - ); - - let commit_id = repo - .commit( - "refs/heads/new-branch", - "committing into a new branch creates it", - author.clone(), - author, - empty_tree_id, - Some(commit_id), - ) - .unwrap(); - - assert_eq!( - commit_id, - hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), - "the second commit id is stable" - ); - - // TODO: check reflog - let branch = repo.head().unwrap().expect("head is not detached"); - let current_commit = branch.target().as_id().expect("peeled").to_owned(); - assert_eq!(current_commit, commit_id.detach(), "the commit was set"); - } -} - #[test] fn object_ref_size_in_memory() { assert_eq!( diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index c4c7122c046..8025fc15f65 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -7,6 +7,11 @@ fn repo(name: &str) -> crate::Result { Ok(Repository::discover(repo_path)?) } +fn basic_repo() -> crate::Result { + let repo_path = git_testtools::scripted_fixture_repo_read_only("make_basic_repo.sh")?; + Ok(Repository::open(repo_path)?.into()) +} + fn basic_rw_repo() -> crate::Result<(Easy, tempfile::TempDir)> { let repo_path = git_testtools::scripted_fixture_repo_writable("make_basic_repo.sh")?; Ok((Repository::open(repo_path.path())?.into(), repo_path)) From 9473a71e5533e1474181241f8d3e1aebd9dea8d8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 15:39:50 +0800 Subject: [PATCH 29/91] [ref #190] raw reference peeling --- git-ref/src/lib.rs | 11 +++-- git-ref/src/raw.rs | 106 +++++++++++++++++++++++++++------------------ 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 82e0dc930cf..0fe4c87d7cc 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -32,6 +32,13 @@ pub mod namespace; /// pub mod transaction; +mod parse; +mod raw; + +pub use raw::Reference; + +mod target; + /// Indicate that the given BString is a validate reference name or path that can be used as path on disk or written as target /// of a symbolic reference #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] @@ -79,7 +86,3 @@ pub enum TargetRef<'a> { /// A ref that points to another reference by its validated name, adding a level of indirection. Symbolic(&'a BStr), } - -mod parse; -mod raw; -mod target; diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 252f4fff170..e3da1610bd5 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -3,7 +3,7 @@ use git_hash::ObjectId; use crate::{FullName, Target}; /// A fully owned backend agnostic reference -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Reference { /// The path to uniquely identify this ref within its store. pub name: FullName, @@ -52,6 +52,7 @@ mod convert { } } +// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet mod log { use crate::raw::Reference; use crate::store::file; @@ -116,45 +117,64 @@ mod access { } } -// impl Reference { -// /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. -// pub fn peel_to_id_in_place( -// &mut self, -// store: &file::Store, -// packed: Option<&packed::Buffer>, -// find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, -// ) -> Result { -// match self { -// Reference::Loose(r) => r.peel_to_id_in_place(store, packed, find).map(ToOwned::to_owned), -// Reference::Packed(p) => { -// if let Some(object) = p.object { -// p.target = object; -// } -// p.object = None; -// Ok(p.target()) -// } -// } -// } -// -// /// For details, see [crate::file::loose::Reference::follow_symbolic()]. -// pub fn peel_one_level<'p2>( -// &self, -// store: &file::Store, -// packed: Option<&'p2 packed::Buffer>, -// ) -> Option, crate::store::file::loose::reference::peel::Error>> { -// match self { -// Reference::Loose(r) => r.follow_symbolic(store, packed), -// Reference::Packed(p) => packed -// .and_then(|packed| packed.try_find(p.name).ok().flatten()) // needed to get data with 'p2 lifetime -// .and_then(|np| { -// p.object.and(np.object).map(|peeled| { -// Ok(Reference::Packed(packed::Reference { -// name: np.name, -// target: peeled, -// object: None, -// })) -// }) -// }), -// } -// } -// } +// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet +mod peel { + use crate::raw::Reference; + use crate::store::{file, file::loose, packed}; + use crate::{FullName, Target}; + use git_hash::ObjectId; + + impl Reference { + /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. + pub fn peel_to_id_in_place( + &mut self, + store: &file::Store, + packed: Option<&packed::Buffer>, + find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, + ) -> Result { + match self.peeled.take() { + Some(peeled) => { + self.target = Target::Peeled(peeled); + Ok(peeled) + } + None => { + let mut loose_self = loose::Reference { + name: FullName(std::mem::take(&mut self.name.0)), + target: std::mem::replace(&mut self.target, Target::Symbolic(FullName(Default::default()))), + }; + let res = loose_self + .peel_to_id_in_place(store, packed, find) + .map(ToOwned::to_owned); + self.name = loose_self.name; + self.target = loose_self.target; + res + } + } + } + + /// For details, see [crate::file::loose::Reference::follow_symbolic()]. + pub fn peel_one_level( + &self, + _store: &file::Store, + _packed: Option<&packed::Buffer>, + ) -> Option> { + match self.peeled { + Some(peeled) => Some(Ok(Reference { + name: self.name.clone(), + target: Target::Peeled(peeled), + peeled: None, + })), + None => { + match self.target { + Target::Peeled(_) => None, + Target::Symbolic(_) => { + let _loose_self: loose::Reference = self.clone().into(); + todo!("get rid of file::Reference") + // loose_self.follow_symbolic(store, packed) + } + } + } + } + } + } +} From 7aeea9c36d4da04a806e68968356f8cc0dc11475 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 17:52:40 +0800 Subject: [PATCH 30/91] =?UTF-8?q?[ref=20#190]=20Use=20Raw=20Reference=20ev?= =?UTF-8?q?erywhere=20for=20great=20simplification=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …but two tests still fail --- experiments/diffing/src/main.rs | 2 +- experiments/traversal/src/main.rs | 2 +- git-ref/src/lib.rs | 3 + git-ref/src/peel.rs | 44 +++++ git-ref/src/raw.rs | 133 +++++++++----- git-ref/src/store/file/find.rs | 32 ++-- git-ref/src/store/file/loose/reference/mod.rs | 3 - .../src/store/file/loose/reference/peel.rs | 173 ------------------ git-ref/src/store/file/mod.rs | 3 - git-ref/src/store/file/overlay.rs | 14 +- git-ref/src/store/file/reference.rs | 171 ----------------- git-ref/src/store/file/transaction/commit.rs | 3 +- git-ref/src/store/file/transaction/prepare.rs | 25 ++- git-ref/tests/file/reference.rs | 35 ++-- git-ref/tests/file/store/find.rs | 4 +- git-ref/tests/file/store/iter.rs | 26 ++- .../prepare_and_commit/create_or_update.rs | 4 +- .../transaction/prepare_and_commit/delete.rs | 14 +- git-repository/src/easy/ext/object.rs | 2 +- git-repository/src/easy/mod.rs | 6 +- git-repository/src/easy/oid.rs | 27 +-- git-repository/src/easy/reference.rs | 90 ++------- git-repository/tests/easy/ext/reference.rs | 2 +- git-repository/tests/easy/reference.rs | 2 +- 24 files changed, 250 insertions(+), 570 deletions(-) create mode 100644 git-ref/src/peel.rs delete mode 100644 git-ref/src/store/file/loose/reference/peel.rs delete mode 100644 git-ref/src/store/file/reference.rs diff --git a/experiments/diffing/src/main.rs b/experiments/diffing/src/main.rs index 2d657d1107b..0378d1058d3 100644 --- a/experiments/diffing/src/main.rs +++ b/experiments/diffing/src/main.rs @@ -9,7 +9,7 @@ use git_repository::{ objs::{bstr::BStr, TreeRefIter}, odb, prelude::*, - refs::file::loose::reference::peel, + refs::peel, }; use rayon::prelude::*; diff --git a/experiments/traversal/src/main.rs b/experiments/traversal/src/main.rs index c3079b55923..c8c897a6813 100644 --- a/experiments/traversal/src/main.rs +++ b/experiments/traversal/src/main.rs @@ -10,7 +10,7 @@ use git_repository::{ objs::{bstr::BStr, tree::EntryRef}, odb, prelude::*, - refs::file::loose::reference::peel, + refs::peel, traverse::{tree, tree::visit::Action}, }; diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 0fe4c87d7cc..c4277de74bf 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -39,6 +39,9 @@ pub use raw::Reference; mod target; +/// +pub mod peel; + /// Indicate that the given BString is a validate reference name or path that can be used as path on disk or written as target /// of a symbolic reference #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs new file mode 100644 index 00000000000..ad12a6aad77 --- /dev/null +++ b/git-ref/src/peel.rs @@ -0,0 +1,44 @@ +/// A function for use in [`loose::Reference::peel_to_id_in_place()`] to indicate no peeling should happen. +pub fn none( + _id: git_hash::ObjectId, + _buf: &mut Vec, +) -> Result, std::convert::Infallible> { + Ok(Some((git_object::Kind::Commit, &[]))) +} + +/// +pub mod to_id { + use std::path::PathBuf; + + use bstr::BString; + use quick_error::quick_error; + + use crate::file; + + quick_error! { + /// The error returned by [`crate::Reference::peel_to_id_in_place()`]. + #[derive(Debug)] + #[allow(missing_docs)] + pub enum Error { + Follow(err: file::find::existing::Error) { + display("Could not follow a single level of a symbolic reference") + from() + source(err) + } + Cycle(start_absolute: PathBuf){ + display("Aborting due to reference cycle with first seen path being '{}'", start_absolute.display()) + } + DepthLimitExceeded { max_depth: usize } { + display("Refusing to follow more than {} levels of indirection", max_depth) + } + Find(err: Box) { + display("An error occurred when trying to resolve an object a refererence points to") + from() + source(&**err) + } + NotFound{oid: git_hash::ObjectId, name: BString} { + display("Object {} as referred to by '{}' could not be found", oid, name) + } + } + } +} diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index e3da1610bd5..fea6fd7a148 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -14,12 +14,14 @@ pub struct Reference { } mod convert { - use crate::raw::Reference; - use crate::store::file::loose; - use crate::store::packed; - use crate::Target; use git_hash::ObjectId; + use crate::{ + raw::Reference, + store::{file::loose, packed}, + Target, + }; + impl From for loose::Reference { fn from(value: Reference) -> Self { loose::Reference { @@ -54,10 +56,13 @@ mod convert { // TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet mod log { - use crate::raw::Reference; - use crate::store::file; - use crate::store::file::log; - use crate::store::file::loose::reference::logiter::must_be_io_err; + use crate::{ + raw::Reference, + store::{ + file, + file::{log, loose::reference::logiter::must_be_io_err}, + }, + }; impl Reference { /// Obtain a reverse iterator over logs of this reference. See [crate::file::loose::Reference::log_iter_rev()] for details. @@ -89,26 +94,21 @@ mod log { } mod access { - use crate::raw::Reference; - use crate::{FullNameRef, Namespace}; use bstr::ByteSlice; + use crate::{raw::Reference, FullNameRef, Namespace}; + impl Reference { /// Returns the kind of reference based on its target pub fn kind(&self) -> crate::Kind { self.target.kind() } - /// Return the full validated name of the reference, which may include a namespace. - pub fn name(&self) -> FullNameRef<'_> { - self.name.to_ref() - } - /// Return the full validated name of the reference, with the given namespace stripped if possible. /// /// If the reference name wasn't prefixed with `namespace`, `None` is returned instead. pub fn name_without_namespace(&self, namespace: &Namespace) -> Option> { - self.name() + self.name .0 .as_bstr() .strip_prefix(namespace.0.as_bstr().as_ref()) @@ -119,61 +119,102 @@ mod access { // TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet mod peel { - use crate::raw::Reference; - use crate::store::{file, file::loose, packed}; - use crate::{FullName, Target}; + use std::collections::BTreeSet; + use git_hash::ObjectId; + use crate::{ + peel, + raw::Reference, + store::{file, packed}, + Target, + }; + impl Reference { /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. pub fn peel_to_id_in_place( &mut self, store: &file::Store, packed: Option<&packed::Buffer>, - find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, - ) -> Result { + mut find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, + ) -> Result { match self.peeled.take() { Some(peeled) => { self.target = Target::Peeled(peeled); Ok(peeled) } - None => { - let mut loose_self = loose::Reference { - name: FullName(std::mem::take(&mut self.name.0)), - target: std::mem::replace(&mut self.target, Target::Symbolic(FullName(Default::default()))), - }; - let res = loose_self - .peel_to_id_in_place(store, packed, find) - .map(ToOwned::to_owned); - self.name = loose_self.name; - self.target = loose_self.target; - res - } + None => match self.target { + Target::Peeled(peeled) => Ok(peeled.clone()), + Target::Symbolic(_) => { + let mut seen = BTreeSet::new(); + let cursor = &mut *self; + while let Some(next) = cursor.follow_symbolic(store, packed) { + *cursor = next?; + if seen.contains(&cursor.name) { + return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path()))); + } + seen.insert(cursor.name.clone()); + const MAX_REF_DEPTH: usize = 5; + if seen.len() == MAX_REF_DEPTH { + return Err(peel::to_id::Error::DepthLimitExceeded { + max_depth: MAX_REF_DEPTH, + }); + } + } + + let mut buf = Vec::new(); + let mut oid = self.target.as_id().expect("peeled ref").to_owned(); + self.target = Target::Peeled(loop { + let (kind, data) = find(oid, &mut buf) + .map_err(|err| Box::new(err) as Box)? + .ok_or_else(|| peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + })?; + match kind { + git_object::Kind::Tag => { + oid = git_object::TagRefIter::from_bytes(data).target_id().ok_or_else(|| { + peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + } + })?; + } + _ => break oid, + }; + }); + Ok(self.target.as_id().expect("to be peeled").to_owned()) + } + }, } } - /// For details, see [crate::file::loose::Reference::follow_symbolic()]. - pub fn peel_one_level( + /// Follow this symbolic reference one level and return the ref it refers to, + /// possibly providing access to `packed` references for lookup if it contains the referent. + /// + /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. + pub fn follow_symbolic( &self, - _store: &file::Store, - _packed: Option<&packed::Buffer>, - ) -> Option> { + store: &file::Store, + packed: Option<&packed::Buffer>, + ) -> Option> { match self.peeled { Some(peeled) => Some(Ok(Reference { name: self.name.clone(), target: Target::Peeled(peeled), peeled: None, })), - None => { - match self.target { - Target::Peeled(_) => None, - Target::Symbolic(_) => { - let _loose_self: loose::Reference = self.clone().into(); - todo!("get rid of file::Reference") - // loose_self.follow_symbolic(store, packed) + None => match &self.target { + Target::Peeled(_) => None, + Target::Symbolic(full_name) => { + let path = full_name.to_path(); + match store.find_one_with_verified_input(path.as_ref(), packed) { + Ok(Some(next)) => Some(Ok(next)), + Ok(None) => Some(Err(file::find::existing::Error::NotFound(path.into_owned()))), + Err(err) => Some(Err(file::find::existing::Error::Find(err))), } } - } + }, } } } diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 9f2d736ba04..9e73d496306 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -13,7 +13,7 @@ use crate::{ file::{loose, path_to_name}, packed, }, - FullName, PartialNameRef, + FullName, PartialNameRef, Reference, }; enum Transform { @@ -34,11 +34,11 @@ impl file::Store { /// The lookup algorithm follows the one in [the git documentation][git-lookup-docs]. /// /// [git-lookup-docs]: https://github.com/git/git/blob/5d5b1473453400224ebb126bf3947e0a3276bdf5/Documentation/revisions.txt#L34-L46 - pub fn try_find<'a, 'p, 's, Name, E>( - &'s self, + pub fn try_find<'a, Name, E>( + &self, partial: Name, - packed: Option<&'p packed::Buffer>, - ) -> Result>, Error> + packed: Option<&packed::Buffer>, + ) -> Result, Error> where Name: TryInto, Error = E>, Error: From, @@ -61,11 +61,11 @@ impl file::Store { .map(|r| r.map(|r| r.try_into().expect("only loose refs are found without pack"))) } - pub(in crate::store::file) fn find_one_with_verified_input<'p>( + pub(crate) fn find_one_with_verified_input<'p>( &self, relative_path: &Path, packed: Option<&'p packed::Buffer>, - ) -> Result>, Error> { + ) -> Result, Error> { let is_all_uppercase = relative_path .to_string_lossy() .as_ref() @@ -94,13 +94,13 @@ impl file::Store { ) } - fn find_inner<'p>( + fn find_inner( &self, inbetween: &str, relative_path: &Path, - packed: Option<&'p packed::Buffer>, + packed: Option<&packed::Buffer>, transform: Transform, - ) -> Result>, Error> { + ) -> Result, Error> { let (base, is_definitely_absolute) = match transform { Transform::EnforceRefsPrefix => ( if relative_path.starts_with("refs") { @@ -121,7 +121,7 @@ impl file::Store { let full_name = path_to_name(relative_path); let full_name = PartialNameRef((*full_name).as_bstr()); if let Some(packed_ref) = packed.try_find(full_name)? { - return Ok(Some(file::Reference::Packed(packed_ref))); + return Ok(Some(packed_ref.into())); }; } } @@ -132,7 +132,7 @@ impl file::Store { Ok(Some({ let full_name = path_to_name(&relative_path); loose::Reference::try_from_path(FullName(full_name), &contents) - .map(file::Reference::Loose) + .map(Into::into) .map_err(|err| Error::ReferenceCreation { err, relative_path })? })) } @@ -176,16 +176,12 @@ pub mod existing { file::{find, loose}, packed, }, - PartialNameRef, + PartialNameRef, Reference, }; impl file::Store { /// Similar to [`file::Store::find()`] but a non-existing ref is treated as error. - pub fn find<'a, 'p, 's, Name, E>( - &'s self, - partial: Name, - packed: Option<&'p packed::Buffer>, - ) -> Result, Error> + pub fn find<'a, Name, E>(&self, partial: Name, packed: Option<&packed::Buffer>) -> Result where Name: TryInto, Error = E>, crate::name::Error: From, diff --git a/git-ref/src/store/file/loose/reference/mod.rs b/git-ref/src/store/file/loose/reference/mod.rs index 45eec712b90..78bb22c1091 100644 --- a/git-ref/src/store/file/loose/reference/mod.rs +++ b/git-ref/src/store/file/loose/reference/mod.rs @@ -1,7 +1,4 @@ pub(in crate) mod logiter; -/// -pub mod peel; - /// pub mod decode; diff --git a/git-ref/src/store/file/loose/reference/peel.rs b/git-ref/src/store/file/loose/reference/peel.rs deleted file mode 100644 index 865ebd8af71..00000000000 --- a/git-ref/src/store/file/loose/reference/peel.rs +++ /dev/null @@ -1,173 +0,0 @@ -use quick_error::quick_error; - -use crate::{ - file, - store::{ - file::{find, loose}, - packed, - }, - Target, -}; - -quick_error! { - /// The error returned by [`loose::Reference::follow_symbolic()`]. - #[derive(Debug)] - #[allow(missing_docs)] - pub enum Error { - FindExisting(err: find::existing::Error) { - display("Could not resolve symbolic reference name that is expected to exist") - source(err) - } - Decode(err: loose::reference::decode::Error) { - display("The reference could not be decoded.") - source(err) - } - } -} - -/// A function for use in [`loose::Reference::peel_to_id_in_place()`] to indicate no peeling should happen. -pub fn none( - _id: git_hash::ObjectId, - _buf: &mut Vec, -) -> Result, std::convert::Infallible> { - Ok(Some((git_object::Kind::Commit, &[]))) -} - -impl loose::Reference { - /// Follow this symbolic reference one level and return the ref it refers to, possibly providing access to `packed` references for lookup. - /// - /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. - pub fn follow_symbolic<'p>( - &self, - store: &file::Store, - packed: Option<&'p packed::Buffer>, - ) -> Option, Error>> { - match &self.target { - Target::Peeled(_) => None, - Target::Symbolic(full_name) => { - let path = full_name.to_path(); - match store.find_one_with_verified_input(path.as_ref(), packed) { - Ok(Some(next)) => Some(Ok(next)), - Ok(None) => Some(Err(Error::FindExisting(find::existing::Error::NotFound( - path.into_owned(), - )))), - Err(err) => Some(Err(Error::FindExisting(find::existing::Error::Find(err)))), - } - } - } - } -} - -/// -pub mod to_id { - use std::{collections::BTreeSet, path::PathBuf}; - - use bstr::BString; - use git_hash::oid; - use quick_error::quick_error; - - use crate::{ - store::{file, file::loose, packed}, - FullName, Target, - }; - - quick_error! { - /// The error returned by [`Reference::peel_to_id_in_place()`]. - #[derive(Debug)] - #[allow(missing_docs)] - pub enum Error { - Follow(err: loose::reference::peel::Error) { - display("Could not follow a single level of a symbolic reference") - from() - source(err) - } - Cycle(start_absolute: PathBuf){ - display("Aborting due to reference cycle with first seen path being '{}'", start_absolute.display()) - } - DepthLimitExceeded{ max_depth: usize } { - display("Refusing to follow more than {} levels of indirection", max_depth) - } - Find(err: Box) { - display("An error occurred when trying to resolve an object a refererence points to") - from() - source(&**err) - } - NotFound{oid: git_hash::ObjectId, name: BString} { - display("Object {} as referred to by '{}' could not be found", oid, name) - } - } - } - - impl loose::Reference { - /// Follow this symbolic reference until the end of the chain is reached and an object ID is available, - /// and possibly peel this object until the final target object is revealed. - /// - /// Use [`peel::none()`][super::none()] - /// - /// If an error occurs this reference remains unchanged. - pub fn peel_to_id_in_place( - &mut self, - store: &file::Store, - packed: Option<&packed::Buffer>, - mut find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, - ) -> Result<&oid, Error> { - let mut seen = BTreeSet::new(); - let mut storage; - let mut cursor = &mut *self; - while let Some(next) = cursor.follow_symbolic(store, packed) { - let next_ref = next?; - if let crate::Kind::Peeled = next_ref.kind() { - match next_ref { - file::Reference::Loose(r) => { - *self = r; - break; - } - file::Reference::Packed(p) => { - self.target = Target::Peeled(p.object()); - self.name = FullName(p.name.0.to_owned()); - return Ok(self.target.as_id().expect("we just set a peeled id")); - } - }; - } - storage = next_ref; - cursor = match &mut storage { - file::Reference::Loose(r) => r, - file::Reference::Packed(_) => unreachable!("handled above - we are done"), - }; - if seen.contains(&cursor.name) { - return Err(Error::Cycle(store.base.join(cursor.name.to_path()))); - } - seen.insert(cursor.name.clone()); - const MAX_REF_DEPTH: usize = 5; - if seen.len() == MAX_REF_DEPTH { - return Err(Error::DepthLimitExceeded { - max_depth: MAX_REF_DEPTH, - }); - } - } - - let mut buf = Vec::new(); - let mut oid = self.target.as_id().expect("peeled ref").to_owned(); - self.target = Target::Peeled(loop { - let (kind, data) = find(oid, &mut buf) - .map_err(|err| Box::new(err) as Box)? - .ok_or_else(|| Error::NotFound { - oid, - name: self.name.0.clone(), - })?; - match kind { - git_object::Kind::Tag => { - oid = git_object::TagRefIter::from_bytes(data) - .target_id() - .ok_or_else(|| Error::NotFound { - oid, - name: self.name.0.clone(), - })?; - } - _ => break oid, - }; - }); - Ok(self.target.as_id().expect("to be peeled")) - } - } -} diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 023a907426c..6e34b702325 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -72,9 +72,6 @@ pub mod log; /// pub mod find; -mod reference; -pub use reference::Reference; - /// pub mod transaction; diff --git a/git-ref/src/store/file/overlay.rs b/git-ref/src/store/file/overlay.rs index f77805229e3..a8c8864d6c9 100644 --- a/git-ref/src/store/file/overlay.rs +++ b/git-ref/src/store/file/overlay.rs @@ -6,9 +6,9 @@ use std::{ }; use crate::{ - file::{loose, path_to_name, Reference}, + file::{loose, path_to_name}, store::{file, packed}, - FullName, + FullName, Reference, }; /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise @@ -26,8 +26,8 @@ impl<'p, 's> LooseThenPacked<'p, 's> { fn convert_packed( &mut self, packed: Result, packed::iter::Error>, - ) -> Result, Error> { - packed.map(Reference::Packed).map_err(|err| match err { + ) -> Result { + packed.map(Into::into).map_err(|err| match err { packed::iter::Error::Reference { invalid_line, line_number, @@ -39,7 +39,7 @@ impl<'p, 's> LooseThenPacked<'p, 's> { }) } - fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result, Error> { + fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result { let (refpath, name) = res.map_err(Error::Traversal)?; std::fs::File::open(&refpath) .and_then(|mut f| { @@ -52,12 +52,12 @@ impl<'p, 's> LooseThenPacked<'p, 's> { err, relative_path: refpath.strip_prefix(&self.base).expect("base contains path").into(), }) - .map(Reference::Loose) + .map(Into::into) } } impl<'p, 's> Iterator for LooseThenPacked<'p, 's> { - type Item = Result, Error>; + type Item = Result; fn next(&mut self) -> Option { match self.packed.as_mut() { diff --git a/git-ref/src/store/file/reference.rs b/git-ref/src/store/file/reference.rs deleted file mode 100644 index c79f992f0c2..00000000000 --- a/git-ref/src/store/file/reference.rs +++ /dev/null @@ -1,171 +0,0 @@ -use std::convert::TryFrom; - -use bstr::ByteSlice; -use git_hash::ObjectId; - -use crate::{ - file::loose::reference::logiter::must_be_io_err, - store::{ - file, - file::{log, loose}, - packed, - }, - FullNameRef, Namespace, Target, -}; - -/// Either a loose or packed reference, depending on where it was found. -#[derive(Debug)] -pub enum Reference<'p> { - /// A reference originating in a pack - Packed(packed::Reference<'p>), - /// A reference from the filesystem - Loose(loose::Reference), -} - -impl<'p> TryFrom> for loose::Reference { - type Error = (); - - fn try_from(value: Reference<'p>) -> Result { - match value { - Reference::Loose(l) => Ok(l), - Reference::Packed(_) => Err(()), - } - } -} - -impl<'p> TryFrom> for packed::Reference<'p> { - type Error = (); - - fn try_from(value: Reference<'p>) -> Result { - match value { - Reference::Loose(_) => Err(()), - Reference::Packed(p) => Ok(p), - } - } -} - -impl<'p> Reference<'p> { - /// For details, see [loose::Reference::log_exists()]. - pub fn log_exists(&self, store: &file::Store) -> bool { - match self { - Reference::Loose(r) => r.log_exists(store), - Reference::Packed(p) => store.reflog_exists(p.name).expect("infallible name conversion"), - } - } - - /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. - pub fn peel_to_id_in_place( - &mut self, - store: &file::Store, - packed: Option<&packed::Buffer>, - find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, - ) -> Result { - match self { - Reference::Loose(r) => r.peel_to_id_in_place(store, packed, find).map(ToOwned::to_owned), - Reference::Packed(p) => { - if let Some(object) = p.object { - p.target = object; - } - p.object = None; - Ok(p.target()) - } - } - } - - /// For details, see [crate::file::loose::Reference::follow_symbolic()]. - pub fn peel_one_level<'p2>( - &self, - store: &file::Store, - packed: Option<&'p2 packed::Buffer>, - ) -> Option, crate::store::file::loose::reference::peel::Error>> { - match self { - Reference::Loose(r) => r.follow_symbolic(store, packed), - Reference::Packed(p) => packed - .and_then(|packed| packed.try_find(p.name).ok().flatten()) // needed to get data with 'p2 lifetime - .and_then(|np| { - p.object.and(np.object).map(|peeled| { - Ok(Reference::Packed(packed::Reference { - name: np.name, - target: peeled, - object: None, - })) - }) - }), - } - } - - /// Obtain a reverse iterator over logs of this reference. See [crate::file::loose::Reference::log_iter_rev()] for details. - pub fn log_iter_rev<'b>( - &self, - store: &file::Store, - buf: &'b mut [u8], - ) -> std::io::Result>> { - match self { - Reference::Loose(r) => r.log_iter_rev(store, buf), - Reference::Packed(p) => store.reflog_iter_rev(p.name, buf).map_err(must_be_io_err), - } - } - - /// Obtain an iterator over logs of this reference. See [crate::file::loose::Reference::log_iter()] for details. - pub fn log_iter<'a, 'b: 'a>( - &'a self, - store: &file::Store, - buf: &'b mut Vec, - ) -> std::io::Result, log::iter::decode::Error>> + 'a>> { - match self { - Reference::Loose(r) => store.reflog_iter(r.name.to_ref(), buf).map_err(must_be_io_err), - Reference::Packed(p) => store.reflog_iter(p.name, buf).map_err(must_be_io_err), - } - } - - /// Returns the kind of reference - pub fn kind(&self) -> crate::Kind { - match self { - Reference::Loose(r) => r.kind(), - Reference::Packed(_) => crate::Kind::Peeled, - } - } - - /// Transform this reference into an owned `Target` - pub fn into_target(self) -> Target { - match self { - Reference::Packed(p) => Target::Peeled(p.object()), - Reference::Loose(r) => r.target, - } - } - - /// Returns true if this ref is located in a packed ref buffer. - pub fn is_packed(&self) -> bool { - match self { - Reference::Packed(_) => true, - Reference::Loose(_) => false, - } - } - - /// Return the full validated name of the reference, which may include a namespace. - pub fn name(&self) -> FullNameRef<'_> { - match self { - Reference::Packed(p) => p.name, - Reference::Loose(l) => l.name.to_ref(), - } - } - - /// Return the full validated name of the reference, with the given namespace stripped if possible. - /// - /// If the reference name wasn't prefixed with `namespace`, `None` is returned instead. - pub fn name_without_namespace(&self, namespace: &Namespace) -> Option> { - self.name() - .0 - .as_bstr() - .strip_prefix(namespace.0.as_bstr().as_ref()) - .map(|stripped| FullNameRef(stripped.as_bstr())) - } - - /// Return the target to which the reference points to. - pub fn target(&self) -> Target { - match self { - Reference::Packed(p) => Target::Peeled(p.target()), - Reference::Loose(l) => l.target.clone(), - } - } -} diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 87ea22771d3..4e84bab5975 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -193,5 +193,6 @@ mod error { } } } -use crate::transaction::PreviousValue; pub use error::Error; + +use crate::transaction::PreviousValue; diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 4eeb28d8766..7b623f26552 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -9,7 +9,7 @@ use crate::{ }, }, transaction::{Change, LogChange, RefEdit, RefEditsExt, RefLog}, - Target, + Reference, Target, }; impl<'s> Transaction<'s> { @@ -33,7 +33,7 @@ impl<'s> Transaction<'s> { maybe_loose .map(|buf| { loose::Reference::try_from_path(change.update.name.clone(), &buf) - .map(file::Reference::Loose) + .map(Reference::from) .map_err(Error::from) }) .transpose() @@ -45,7 +45,7 @@ impl<'s> Transaction<'s> { .and_then(|maybe_loose| match (maybe_loose, packed) { (None, Some(packed)) => packed .try_find(change.update.name.to_ref()) - .map(|opt| opt.map(file::Reference::Packed)) + .map(|opt| opt.map(Into::into)) .map_err(Error::from), (None, None) => Ok(None), (maybe_loose, _) => Ok(maybe_loose), @@ -78,7 +78,7 @@ impl<'s> Transaction<'s> { PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), Some(existing), ) => { - let actual = existing.target(); + let actual = existing.target.clone(); if *previous != actual { let expected = previous.clone(); return Err(Error::ReferenceOutOfDate { @@ -92,7 +92,7 @@ impl<'s> Transaction<'s> { // Keep the previous value for the caller and ourselves. Maybe they want to keep a log of sorts. if let Some(existing) = existing_ref { - *expected = PreviousValue::MustExistAndMatch(existing.target()); + *expected = PreviousValue::MustExistAndMatch(existing.target); } lock @@ -119,11 +119,11 @@ impl<'s> Transaction<'s> { return Err(Error::MustExist { full_name, expected }); } (PreviousValue::MustNotExist, Some(existing)) => { - if existing.target() != new.to_ref() { + if existing.target != *new { let new = new.clone(); return Err(Error::MustNotExist { full_name: change.name(), - actual: existing.target(), + actual: existing.target.clone(), new, }); } @@ -132,8 +132,8 @@ impl<'s> Transaction<'s> { PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous), Some(existing), ) => { - if *previous != existing.target() { - let actual = existing.target(); + if *previous != existing.target { + let actual = existing.target.clone(); let expected = previous.to_owned(); let full_name = change.name(); return Err(Error::ReferenceOutOfDate { @@ -152,7 +152,7 @@ impl<'s> Transaction<'s> { }; if let Some(existing) = existing_ref { - *expected = PreviousValue::MustExistAndMatch(existing.target()); + *expected = PreviousValue::MustExistAndMatch(existing.target); }; lock.with_mut(|file| match new { @@ -194,10 +194,7 @@ impl<'s> Transaction<'s> { .pre_process( |name| { let symbolic_refs_are_never_packed = None; - store - .find(name, symbolic_refs_are_never_packed) - .map(|r| r.into_target()) - .ok() + store.find(name, symbolic_refs_are_never_packed).map(|r| r.target).ok() }, |idx, update| Edit { update, diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 0e481625c5e..48d7dcead8f 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -49,10 +49,8 @@ mod reflog { } mod peel { - use std::convert::TryFrom; - use git_odb::Find; - use git_ref::file::loose::reference::peel; + use git_ref::{peel, Reference}; use git_testtools::hex_to_id; use crate::{file, file::store_with_packed_refs}; @@ -63,10 +61,10 @@ mod peel { let r = store.find_loose("HEAD")?; assert_eq!(r.kind(), git_ref::Kind::Symbolic, "there is something to peel"); - let nr = git_ref::file::loose::Reference::try_from( - r.follow_symbolic(&store, None).expect("exists").expect("no failure"), - ) - .expect("loose ref"); + let nr = Reference::from(r) + .follow_symbolic(&store, None) + .expect("exists") + .expect("no failure"); assert!( matches!(nr.target.to_ref(), git_ref::TargetRef::Peeled(_)), "iteration peels a single level" @@ -83,7 +81,7 @@ mod peel { #[test] fn peel_with_packed_involvement() -> crate::Result { let store = store_with_packed_refs()?; - let mut head = store.find_loose("HEAD")?; + let mut head: Reference = store.find_loose("HEAD")?.into(); let packed = store.packed_buffer()?; let expected = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(head.peel_to_id_in_place(&store, packed.as_ref(), peel::none)?, expected); @@ -91,7 +89,7 @@ mod peel { let mut head = store.find("dt1", packed.as_ref())?; assert_eq!(head.peel_to_id_in_place(&store, packed.as_ref(), peel::none)?, expected); - assert_eq!(head.target().as_id().map(ToOwned::to_owned), Some(expected)); + assert_eq!(head.target.into_id(), expected); Ok(()) } @@ -101,9 +99,8 @@ mod peel { let packed = store.packed_buffer()?; let head = store.find("dt1", packed.as_ref())?; - assert!(head.is_packed()); assert_eq!( - head.target().as_id().map(ToOwned::to_owned), + head.target.as_id().map(ToOwned::to_owned), Some(hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3")) ); assert_eq!( @@ -113,29 +110,30 @@ mod peel { ); let peeled = head - .peel_one_level(&store, packed.as_ref()) + .follow_symbolic(&store, packed.as_ref()) .expect("a peeled ref for the object")?; assert_eq!( - peeled.target().as_id().map(ToOwned::to_owned), + peeled.target.as_id().map(ToOwned::to_owned), Some(hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")), "packed refs are always peeled (at least the ones we choose to read)" ); assert_eq!(peeled.kind(), git_ref::Kind::Peeled, "it's terminally peeled now"); - assert!(peeled.peel_one_level(&store, packed.as_ref()).is_none()); + assert!(peeled.follow_symbolic(&store, packed.as_ref()).is_none()); Ok(()) } #[test] + #[ignore] fn to_id_multi_hop() -> crate::Result { let store = file::store()?; - let mut r = store.find_loose("multi-link")?; + let mut r: Reference = store.find_loose("multi-link")?.into(); assert_eq!(r.kind(), git_ref::Kind::Symbolic, "there is something to peel"); let commit = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(r.peel_to_id_in_place(&store, None, peel::none)?, commit); assert_eq!(r.name.as_bstr(), "refs/remotes/origin/multi-link-target3"); - let mut r = store.find_loose("dt1")?; + let mut r: Reference = store.find_loose("dt1")?.into(); assert_eq!( r.peel_to_id_in_place(&store, None, peel::none)?, hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3"), @@ -156,15 +154,16 @@ mod peel { } #[test] + #[ignore] fn to_id_cycle() -> crate::Result { let store = file::store()?; - let mut r = store.find_loose("loop-a")?; + let mut r: Reference = store.find_loose("loop-a")?.into(); assert_eq!(r.kind(), git_ref::Kind::Symbolic, "there is something to peel"); assert_eq!(r.name.as_bstr(), "refs/loop-a"); assert!(matches!( r.peel_to_id_in_place(&store, None, peel::none).unwrap_err(), - git_ref::file::loose::reference::peel::to_id::Error::Cycle { .. } + git_ref::peel::to_id::Error::Cycle { .. } )); assert_eq!(r.name.as_bstr(), "refs/loop-a", "the ref is not changed on error"); Ok(()) diff --git a/git-ref/tests/file/store/find.rs b/git-ref/tests/file/store/find.rs index e6f8c4dabcf..45b511353e6 100644 --- a/git-ref/tests/file/store/find.rs +++ b/git-ref/tests/file/store/find.rs @@ -9,8 +9,8 @@ mod existing { let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); let packed = store.packed_buffer()?; let r = store.find("main", packed.as_ref())?; - assert_eq!(r.target().to_ref().as_id().expect("peeled"), c1); - assert_eq!(r.name().as_bstr(), "refs/heads/main"); + assert_eq!(r.target.into_id(), c1); + assert_eq!(r.name.as_bstr(), "refs/heads/main"); Ok(()) } } diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index ce46c3cd8bd..d79a2e365a6 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -22,7 +22,7 @@ mod with_namespace { .iter_prefixed(packed.as_ref(), ns_two.to_path()) .unwrap() .map(Result::unwrap) - .map(|r: git_ref::file::Reference| r.name().as_bstr().to_owned()) + .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) .collect::>(), vec![ "refs/namespaces/bar/refs/heads/multi-link-target1", @@ -38,8 +38,8 @@ mod with_namespace { .iter_prefixed(packed.as_ref(), ns_one.to_path()) .unwrap() .map(Result::unwrap) - .map(|r: git_ref::file::Reference| ( - r.name().as_bstr().to_owned(), + .map(|r: git_ref::Reference| ( + r.name.as_bstr().to_owned(), r.name_without_namespace(&ns_one) .expect("stripping correct namespace always works") .as_bstr() @@ -65,10 +65,10 @@ mod with_namespace { .unwrap() .map(Result::unwrap) .filter_map( - |r: git_ref::file::Reference| if r.name().as_bstr().starts_with_str("refs/namespaces") { + |r: git_ref::Reference| if r.name.as_bstr().starts_with_str("refs/namespaces") { None } else { - Some(r.name().as_bstr().to_owned()) + Some(r.name.as_bstr().to_owned()) } ) .collect::>(), @@ -207,25 +207,23 @@ fn overlay_iter() -> crate::Result { let store = store_at("make_packed_ref_repository_for_overlay.sh")?; let ref_names = store .iter(store.packed_buffer()?.as_ref())? - .map(|r| r.map(|r| (r.name().as_bstr().to_owned(), r.target(), r.is_packed()))) + .map(|r| r.map(|r| (r.name.as_bstr().to_owned(), r.target))) .collect::, _>>()?; let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); assert_eq!( ref_names, vec![ - (b"refs/heads/main".as_bstr().to_owned(), Peeled(c1), true), - ("refs/heads/newer-as-loose".into(), Peeled(c2), false), + (b"refs/heads/main".as_bstr().to_owned(), Peeled(c1)), + ("refs/heads/newer-as-loose".into(), Peeled(c2)), ( "refs/remotes/origin/HEAD".into(), Symbolic("refs/remotes/origin/main".try_into()?), - false ), - ("refs/remotes/origin/main".into(), Peeled(c1), true), + ("refs/remotes/origin/main".into(), Peeled(c1)), ( "refs/tags/tag-object".into(), Peeled(hex_to_id("b3109a7e51fc593f85b145a76c70ddd1d133fafd")), - true ) ] ); @@ -254,15 +252,15 @@ fn overlay_prefixed_iter() -> crate::Result { let store = store_at("make_packed_ref_repository_for_overlay.sh")?; let ref_names = store .iter_prefixed(store.packed_buffer()?.as_ref(), "refs/heads")? - .map(|r| r.map(|r| (r.name().as_bstr().to_owned(), r.target(), r.is_packed()))) + .map(|r| r.map(|r| (r.name.as_bstr().to_owned(), r.target))) .collect::, _>>()?; let c1 = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); let c2 = hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7"); assert_eq!( ref_names, vec![ - (b"refs/heads/main".as_bstr().to_owned(), Peeled(c1), true), - ("refs/heads/newer-as-loose".into(), Peeled(c2), false), + (b"refs/heads/main".as_bstr().to_owned(), Peeled(c1)), + ("refs/heads/newer-as-loose".into(), Peeled(c2)), ] ); Ok(()) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index df3810d3ce7..ff2deadeb9b 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -718,7 +718,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() let packed = store.packed_buffer()?.expect("packed-refs"); assert_ne!( packed.find("newer-as-loose")?.target(), - branch.target().as_id().expect("peeled"), + branch.target.as_id().expect("peeled"), "the packed ref is outdated" ); let mut buf = Vec::new(); @@ -767,7 +767,7 @@ fn packed_refs_creation_with_packed_refs_mode_leave_keeps_original_loose_refs() ); assert_eq!( packed.find("newer-as-loose")?.target(), - store.find("newer-as-loose", None)?.target().as_id().expect("peeled"), + store.find("newer-as-loose", None)?.target.into_id(), "the packed ref is now up to date and the loose ref definitely still exists" ); Ok(()) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index 9afa2b13526..b7114ce3322 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -6,7 +6,7 @@ use git_lock::acquire::Fail; use git_ref::transaction::PreviousValue; use git_ref::{ transaction::{Change, RefEdit, RefLog}, - Target, + Reference, Target, }; use git_testtools::hex_to_id; use std::convert::TryInto; @@ -153,13 +153,13 @@ fn delete_reflog_only_of_symbolic_no_deref() -> crate::Result { .commit(&committer())?; assert_eq!(edits.len(), 1); - let head = store.find_loose("HEAD")?; + let head: Reference = store.find_loose("HEAD")?.into(); assert!(!head.log_exists(&store)); let main = store.find_loose("main").expect("referent still exists"); assert!(main.log_exists(&store), "log is untouched, too"); assert_eq!( main.target, - head.follow_symbolic(&store, None).expect("a symref")?.target(), + head.follow_symbolic(&store, None).expect("a symref")?.target, "head points to main" ); Ok(()) @@ -187,13 +187,13 @@ fn delete_reflog_only_of_symbolic_with_deref() -> crate::Result { .commit(&committer())?; assert_eq!(edits.len(), 2); - let head = store.find_loose("HEAD")?; + let head: Reference = store.find_loose("HEAD")?.into(); assert!(!head.log_exists(&store)); let main = store.find_loose("main").expect("referent still exists"); assert!(!main.log_exists(&store), "log is removed"); assert_eq!( main.target, - head.follow_symbolic(&store, None).expect("a symref")?.target(), + head.follow_symbolic(&store, None).expect("a symref")?.target, "head points to main" ); Ok(()) @@ -369,7 +369,7 @@ fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_ let (_keep, store) = store_writable("make_packed_ref_repository_for_overlay.sh")?; let packed = store.packed_buffer()?.expect("packed-refs"); let branch = store.find("newer-as-loose", Some(&packed))?; - let branch_id = branch.target().as_id().map(ToOwned::to_owned).expect("peeled"); + let branch_id = branch.target.as_id().map(ToOwned::to_owned).expect("peeled"); assert_ne!( packed.find("newer-as-loose")?.target(), branch_id, @@ -384,7 +384,7 @@ fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_ expected: PreviousValue::MustExistAndMatch(Target::Peeled(branch_id)), log: RefLog::AndReference, }, - name: branch.name().into(), + name: branch.name.into(), deref: false, }), git_lock::acquire::Fail::Immediately, diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 3efa2d0913b..5b1daa4a8d8 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -109,7 +109,7 @@ pub trait ObjectAccessExt: easy::Access + Sized { Some(previous) => PreviousValue::ExistingMustMatch(previous), None => PreviousValue::MustNotExist, }, - new: Target::Peeled(commit_id.id), + new: Target::Peeled(commit_id.inner), }, name: reference, deref: true, diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 11affd93b43..23f00c6dd3e 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -39,7 +39,8 @@ pub mod state; /// An [ObjectId] with access to a repository. #[derive(Eq, Hash, Ord, PartialOrd, Clone, Copy)] pub struct Oid<'r, A> { - id: ObjectId, + /// The actual object id + pub inner: ObjectId, access: &'r A, } @@ -87,7 +88,8 @@ pub struct Object { /// /// Note that these are snapshots and won't recognize if they are stale. pub struct Reference<'r, A> { - pub(crate) backing: Option, + /// The actual reference data + pub inner: git_ref::Reference, pub(crate) access: &'r A, } diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index b0680a48684..5232ddb6cfb 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -7,49 +7,49 @@ use crate::{ impl<'repo, A, B> PartialEq> for Oid<'repo, B> { fn eq(&self, other: &Oid<'repo, A>) -> bool { - self.id == other.id + self.inner == other.inner } } impl<'repo, A> PartialEq for Oid<'repo, A> { fn eq(&self, other: &ObjectId) -> bool { - &self.id == other + &self.inner == other } } impl<'repo, A> PartialEq for Oid<'repo, A> { fn eq(&self, other: &oid) -> bool { - self.id == other + self.inner == other } } impl<'repo, A, B> PartialEq> for Oid<'repo, B> { fn eq(&self, other: &ObjectRef<'repo, A>) -> bool { - self.id == other.id + self.inner == other.id } } impl<'repo, A> PartialEq for Oid<'repo, A> { fn eq(&self, other: &Object) -> bool { - self.id == other.id + self.inner == other.id } } impl<'repo, A> std::fmt::Debug for Oid<'repo, A> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.id.fmt(f) + self.inner.fmt(f) } } impl<'repo, A> AsRef for Oid<'repo, A> { fn as_ref(&self) -> &oid { - &self.id + &self.inner } } impl<'repo, A> From> for ObjectId { fn from(v: Oid<'repo, A>) -> Self { - v.id + v.inner } } @@ -60,13 +60,13 @@ where // NOTE: Can't access other object data that is attached to the same cache. /// Find the [`ObjectRef`] associated with this object id, and assume it exists. pub fn object(&self) -> Result, find::existing::Error> { - self.access.find_object(self.id) + self.access.find_object(self.inner) } // NOTE: Can't access other object data that is attached to the same cache. /// Try find the [`ObjectRef`] associated with this object id, it might not be available locally. pub fn try_object(&self) -> Result>, find::Error> { - self.access.try_find_object(self.id) + self.access.try_find_object(self.inner) } } @@ -75,11 +75,14 @@ where A: easy::Access + Sized, { pub(crate) fn from_id(id: impl Into, access: &'repo A) -> Self { - Oid { id: id.into(), access } + Oid { + inner: id.into(), + access, + } } /// Turn this instance into its bare [ObjectId]. pub fn detach(self) -> ObjectId { - self.id + self.inner } } diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 65d2f8fc6d6..8300236bb15 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -1,7 +1,6 @@ #![allow(missing_docs)] use std::ops::DerefMut; -use git_hash::ObjectId; use git_odb::Find; use crate::{ @@ -9,19 +8,6 @@ use crate::{ easy::{Oid, Reference}, }; -pub(crate) enum Backing { - OwnedPacked { - /// The validated full name of the reference. - name: git_ref::FullName, - /// The target object id of the reference, hex encoded. - target: ObjectId, - /// The fully peeled object id, hex encoded, that the ref is ultimately pointing to - /// i.e. when all indirections are removed. - object: Option, - }, - LooseFile(git_ref::file::loose::Reference), -} - pub mod edit { use crate::easy; @@ -44,7 +30,7 @@ pub mod peel_to_oid_in_place { #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - LoosePeelToId(#[from] git_ref::file::loose::reference::peel::to_id::Error), + PeelToId(#[from] git_ref::peel::to_id::Error), #[error(transparent)] PackedRefsOpen(#[from] git_ref::packed::buffer::open::Error), #[error("BUG: Part of interior state could not be borrowed.")] @@ -59,74 +45,34 @@ impl<'repo, A> Reference<'repo, A> where A: easy::Access + Sized, { - pub(crate) fn from_file_ref(reference: git_ref::file::Reference<'_>, access: &'repo A) -> Self { + pub(crate) fn from_file_ref(reference: git_ref::Reference, access: &'repo A) -> Self { Reference { - backing: match reference { - git_ref::file::Reference::Packed(p) => Backing::OwnedPacked { - name: p.name.into(), - target: p.target(), - object: p - .object - .map(|hex| ObjectId::from_hex(hex).expect("a hash kind we know")), - }, - git_ref::file::Reference::Loose(l) => Backing::LooseFile(l), - } - .into(), + inner: reference.into(), access, } } - pub fn target(&self) -> git_ref::Target { - match self.backing.as_ref().expect("always set") { - Backing::OwnedPacked { target, .. } => git_ref::Target::Peeled(target.to_owned()), - Backing::LooseFile(r) => r.target.clone(), - } + pub fn target(&self) -> git_ref::TargetRef<'_> { + self.inner.target.to_ref() } pub fn name(&self) -> git_ref::FullNameRef<'_> { - match self.backing.as_ref().expect("always set") { - Backing::OwnedPacked { name, .. } => name, - Backing::LooseFile(r) => &r.name, - } - .to_ref() + self.inner.name.to_ref() } pub fn peel_to_oid_in_place(&mut self) -> Result, peel_to_oid_in_place::Error> { let repo = self.access.repo()?; - match self.backing.take().expect("a ref must be set") { - Backing::LooseFile(mut r) => { - let state = self.access.state(); - let mut pack_cache = state.try_borrow_mut_pack_cache()?; - let oid = r - .peel_to_id_in_place( - &repo.refs, - state.assure_packed_refs_uptodate(&repo.refs)?.as_ref(), - |oid, buf| { - repo.odb - .try_find(oid, buf, pack_cache.deref_mut()) - .map(|po| po.map(|o| (o.kind, o.data))) - }, - )? - .to_owned(); - self.backing = Backing::LooseFile(r).into(); - Ok(Oid::from_id(oid, self.access)) - } - Backing::OwnedPacked { - mut target, - mut object, - name, - } => { - if let Some(peeled_id) = object.take() { - target = peeled_id; - } - self.backing = Backing::OwnedPacked { - name, - target, - object: None, - } - .into(); - Ok(Oid::from_id(target, self.access)) - } - } + let state = self.access.state(); + let mut pack_cache = state.try_borrow_mut_pack_cache()?; + let oid = self.inner.peel_to_id_in_place( + &repo.refs, + state.assure_packed_refs_uptodate(&repo.refs)?.as_ref(), + |oid, buf| { + repo.odb + .try_find(oid, buf, pack_cache.deref_mut()) + .map(|po| po.map(|o| (o.kind, o.data))) + }, + )?; + Ok(Oid::from_id(oid, self.access)) } } diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 83c370ea523..60f4122bab4 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -8,7 +8,7 @@ mod head { let repo = crate::basic_repo().unwrap(); let head = repo.head().unwrap().expect("HEAD is symbolic"); assert_eq!( - head.target().into_id(), + head.inner.target.into_id(), hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") ); } diff --git a/git-repository/tests/easy/reference.rs b/git-repository/tests/easy/reference.rs index a27fa91d46a..9b0e3e3a045 100644 --- a/git-repository/tests/easy/reference.rs +++ b/git-repository/tests/easy/reference.rs @@ -16,7 +16,7 @@ mod find { assert_eq!(packed_tag_ref.name(), "refs/tags/dt1".try_into().unwrap()); assert_eq!( - packed_tag_ref.target(), + packed_tag_ref.inner.target, refs::Target::Peeled(hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3")), "it points to a tag object" ); From 14dff63fbc0d318bbc8a2618e0d72aaa98948acf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 17:55:06 +0800 Subject: [PATCH 31/91] thanks clippy --- git-ref/src/raw.rs | 2 +- git-ref/tests/file/transaction/prepare_and_commit/delete.rs | 2 +- git-repository/src/easy/reference.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index fea6fd7a148..f8bd540082e 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -144,7 +144,7 @@ mod peel { Ok(peeled) } None => match self.target { - Target::Peeled(peeled) => Ok(peeled.clone()), + Target::Peeled(peeled) => Ok(peeled), Target::Symbolic(_) => { let mut seen = BTreeSet::new(); let cursor = &mut *self; diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index b7114ce3322..54153280ffd 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -384,7 +384,7 @@ fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_ expected: PreviousValue::MustExistAndMatch(Target::Peeled(branch_id)), log: RefLog::AndReference, }, - name: branch.name.into(), + name: branch.name, deref: false, }), git_lock::acquire::Fail::Immediately, diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 8300236bb15..3c2ce4a374e 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -47,7 +47,7 @@ where { pub(crate) fn from_file_ref(reference: git_ref::Reference, access: &'repo A) -> Self { Reference { - inner: reference.into(), + inner: reference, access, } } From df21f25baaf867015fc9fc46a2cf4e778b0e80ee Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 18:23:07 +0800 Subject: [PATCH 32/91] [ref #190] fix remaining tests --- git-ref/src/raw.rs | 57 ++++++++++++++++----------------- git-ref/tests/file/reference.rs | 2 -- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index f8bd540082e..551a9f4c66a 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -143,16 +143,16 @@ mod peel { self.target = Target::Peeled(peeled); Ok(peeled) } - None => match self.target { - Target::Peeled(peeled) => Ok(peeled), - Target::Symbolic(_) => { + None => { + if self.target.kind() == crate::Kind::Symbolic { let mut seen = BTreeSet::new(); let cursor = &mut *self; while let Some(next) = cursor.follow_symbolic(store, packed) { - *cursor = next?; - if seen.contains(&cursor.name) { + let next = next?; + if seen.contains(&next.name) { return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path()))); } + *cursor = next; seen.insert(cursor.name.clone()); const MAX_REF_DEPTH: usize = 5; if seen.len() == MAX_REF_DEPTH { @@ -161,31 +161,30 @@ mod peel { }); } } - - let mut buf = Vec::new(); - let mut oid = self.target.as_id().expect("peeled ref").to_owned(); - self.target = Target::Peeled(loop { - let (kind, data) = find(oid, &mut buf) - .map_err(|err| Box::new(err) as Box)? - .ok_or_else(|| peel::to_id::Error::NotFound { - oid, - name: self.name.0.clone(), + }; + let mut buf = Vec::new(); + let mut oid = self.target.as_id().expect("peeled ref").to_owned(); + self.target = Target::Peeled(loop { + let (kind, data) = find(oid, &mut buf) + .map_err(|err| Box::new(err) as Box)? + .ok_or_else(|| peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + })?; + match kind { + git_object::Kind::Tag => { + oid = git_object::TagRefIter::from_bytes(data).target_id().ok_or_else(|| { + peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + } })?; - match kind { - git_object::Kind::Tag => { - oid = git_object::TagRefIter::from_bytes(data).target_id().ok_or_else(|| { - peel::to_id::Error::NotFound { - oid, - name: self.name.0.clone(), - } - })?; - } - _ => break oid, - }; - }); - Ok(self.target.as_id().expect("to be peeled").to_owned()) - } - }, + } + _ => break oid, + }; + }); + Ok(self.target.as_id().expect("to be peeled").to_owned()) + } } } diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 48d7dcead8f..1015dd6fea1 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -123,7 +123,6 @@ mod peel { } #[test] - #[ignore] fn to_id_multi_hop() -> crate::Result { let store = file::store()?; let mut r: Reference = store.find_loose("multi-link")?.into(); @@ -154,7 +153,6 @@ mod peel { } #[test] - #[ignore] fn to_id_cycle() -> crate::Result { let store = file::store()?; let mut r: Reference = store.find_loose("loop-a")?.into(); From c55ce4d8453c1ab4a107f5c6fb01521b422ee5c4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 18:42:19 +0800 Subject: [PATCH 33/91] =?UTF-8?q?[repository=20#190]=20experiment=20with?= =?UTF-8?q?=20'HEAD'=20API=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and what's currently there isn't great. --- git-repository/src/easy/ext/reference.rs | 10 ++++--- git-repository/src/easy/reference.rs | 5 +++- git-repository/tests/easy/ext/object.rs | 5 ++-- git-repository/tests/easy/ext/reference.rs | 34 ++++++++++++++++++---- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 0687701b2dd..fd33962a394 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -73,11 +73,13 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .map_err(Into::into) } + // TODO: encode that the ref is unborn (i.e. uninitialized repo) fn head(&self) -> Result>, reference::find::existing::Error> { - let _head = self.find_reference("HEAD")?; - // _head.peel_to_oid_in_place() - todo!("follow symrefs") - // head.backing + let head = self.find_reference("HEAD")?; + match head.inner.target { + Target::Symbolic(branch) => self.find_reference(branch.to_partial()).map(Some), + Target::Peeled(_) => Ok(None), + } } fn find_reference<'a, Name, E>(&self, name: Name) -> Result, reference::find::existing::Error> diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 3c2ce4a374e..51bf512b33b 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -40,7 +40,6 @@ pub mod peel_to_oid_in_place { } } -// TODO: think about how to detach a Reference. It should essentially be a 'Raw' reference that should exist in `git-ref` rather than here. impl<'repo, A> Reference<'repo, A> where A: easy::Access + Sized, @@ -59,6 +58,10 @@ where self.inner.name.to_ref() } + pub fn detach(self) -> git_ref::Reference { + self.inner + } + pub fn peel_to_oid_in_place(&mut self) -> Result, peel_to_oid_in_place::Error> { let repo = self.access.repo()?; let state = self.access.state(); diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 31cd4a7b0cd..d769eae09b6 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -91,8 +91,7 @@ mod commit { ); // TODO: check reflog - let branch = repo.head().unwrap().expect("head is not detached"); - let current_commit = branch.target().as_id().expect("peeled").to_owned(); - assert_eq!(current_commit, commit_id.detach(), "the commit was set"); + let current_commit = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); + assert_eq!(current_commit, commit_id, "the commit was set"); } } diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 60f4122bab4..f90915e55e9 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -1,15 +1,39 @@ mod head { + use git_ref::transaction::{Change, PreviousValue, RefEdit}; + use git_ref::Target; use git_repository::prelude::ReferenceAccessExt; use git_testtools::hex_to_id; + use std::convert::TryInto; #[test] - #[ignore] - fn symbolic() { - let repo = crate::basic_repo().unwrap(); - let head = repo.head().unwrap().expect("HEAD is symbolic"); + fn symbolic() -> crate::Result { + let repo = crate::basic_repo()?; + let head = repo.head()?.expect("HEAD is symbolic"); assert_eq!( head.inner.target.into_id(), - hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391") + hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41") ); + Ok(()) + } + + #[test] + fn detached() { + let (repo, _keep) = crate::basic_rw_repo().unwrap(); + repo.edit_reference( + RefEdit { + change: Change::Update { + log: Default::default(), + expected: PreviousValue::Any, + new: Target::Peeled(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")), + }, + name: "HEAD".try_into().unwrap(), + deref: false, + }, + git_lock::acquire::Fail::Immediately, + None, + ) + .unwrap(); + + assert!(repo.head().unwrap().is_none(), "head is detached"); } } From ac4413ce4e45703d5fe722e7220d039217f0bdef Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 18:45:45 +0800 Subject: [PATCH 34/91] Bump git-ref v0.7.0 --- Cargo.lock | 2 +- git-ref/Cargo.toml | 2 +- git-repository/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2791072d851..565aa0cee99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1163,7 +1163,7 @@ dependencies = [ [[package]] name = "git-ref" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bstr", "filebuffer", diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 43139a4607d..07f5d38f55d 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-ref" -version = "0.6.0" +version = "0.7.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A crate to handle git references" diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 55c78362aa0..bad2374bcd5 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -36,7 +36,7 @@ one-stop-shop = [ [dependencies] -git-ref = { version ="^0.6.0", path = "../git-ref" } +git-ref = { version ="^0.7.0", path = "../git-ref" } git-tempfile = { version ="^1.0.0", path = "../git-tempfile" } git-lock = { version ="^1.0.0", path = "../git-lock" } git-validate = { version = "^0.5.0", path = "../git-validate" } From 3e64ec102146e348b8d870377f180f8dadf5e876 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 19:25:51 +0800 Subject: [PATCH 35/91] [ref #190] fix docs --- git-ref/src/lib.rs | 2 +- git-ref/src/peel.rs | 2 +- git-ref/src/raw.rs | 4 ++-- git-ref/src/store/file/mod.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index c4277de74bf..3013f18f6bc 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -66,7 +66,7 @@ pub enum Kind { Peeled, /// A ref that points to another reference, adding a level of indirection. /// - /// It can be resolved to an id using the [`peel_in_place_to_id()`][file::Reference::peel_to_id_in_place()] method. + /// It can be resolved to an id using the [`peel_in_place_to_id()`][Reference::peel_to_id_in_place()] method. Symbolic, } diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs index ad12a6aad77..1b711c4f86e 100644 --- a/git-ref/src/peel.rs +++ b/git-ref/src/peel.rs @@ -1,4 +1,4 @@ -/// A function for use in [`loose::Reference::peel_to_id_in_place()`] to indicate no peeling should happen. +/// A function for use in [`crate::Reference::peel_to_id_in_place()`] to indicate no peeling should happen. pub fn none( _id: git_hash::ObjectId, _buf: &mut Vec, diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 551a9f4c66a..86ce757ab6b 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -84,7 +84,7 @@ mod log { store.reflog_iter(self.name.to_ref(), buf).map_err(must_be_io_err) } - /// For details, see [loose::Reference::log_exists()]. + /// For details, see [Reference::log_exists()]. pub fn log_exists(&self, store: &file::Store) -> bool { store .reflog_exists(self.name.to_ref()) @@ -131,7 +131,7 @@ mod peel { }; impl Reference { - /// For details, see [crate::file::loose::Reference::peel_to_id_in_place]. + /// For details, see [Reference::peel_to_id_in_place()]. pub fn peel_to_id_in_place( &mut self, store: &file::Store, diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 6e34b702325..8564fd5e541 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -18,7 +18,7 @@ impl Default for WriteReflog { /// A store for reference which uses plain files. /// /// Each ref is represented as a single file on disk in a folder structure that follows the relative path -/// used to identify [references][Reference]. +/// used to identify [references][crate::Reference]. #[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone)] pub struct Store { /// The location at which loose references can be found as per conventions of a typical git repository. From 2cb511efe5833f860f3c17b8e5f5b4cd643baddb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 3 Sep 2021 19:49:04 +0800 Subject: [PATCH 36/91] [ref #190] cache peeled objects properly --- git-ref/CHANGELOG.md | 1 + git-ref/src/raw.rs | 28 +++++++++++++++---- git-ref/tests/file/reference.rs | 11 ++++---- .../transaction/prepare_and_commit/delete.rs | 4 +-- git-repository/tests/easy/ext/object.rs | 24 ++++++++++------ 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index e17cebe34f0..c2d7c25a327 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -3,6 +3,7 @@ #### Breaking * Replace `transaction::Create` with `transaction::PreviousValue` and remove `transaction::Create` +* Remove `file::Reference` in favor of `Reference` ### v0.6.1 diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 86ce757ab6b..1eabbd53099 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -138,16 +138,16 @@ mod peel { packed: Option<&packed::Buffer>, mut find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, ) -> Result { - match self.peeled.take() { + match self.peeled { Some(peeled) => { - self.target = Target::Peeled(peeled); + self.target = Target::Peeled(peeled.to_owned()); Ok(peeled) } None => { if self.target.kind() == crate::Kind::Symbolic { let mut seen = BTreeSet::new(); let cursor = &mut *self; - while let Some(next) = cursor.follow_symbolic(store, packed) { + while let Some(next) = cursor.follow(store, packed) { let next = next?; if seen.contains(&next.name) { return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path()))); @@ -164,7 +164,7 @@ mod peel { }; let mut buf = Vec::new(); let mut oid = self.target.as_id().expect("peeled ref").to_owned(); - self.target = Target::Peeled(loop { + let peeled_id = loop { let (kind, data) = find(oid, &mut buf) .map_err(|err| Box::new(err) as Box)? .ok_or_else(|| peel::to_id::Error::NotFound { @@ -182,7 +182,9 @@ mod peel { } _ => break oid, }; - }); + }; + self.peeled = Some(peeled_id); + self.target = Target::Peeled(peeled_id); Ok(self.target.as_id().expect("to be peeled").to_owned()) } } @@ -192,7 +194,7 @@ mod peel { /// possibly providing access to `packed` references for lookup if it contains the referent. /// /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. - pub fn follow_symbolic( + pub fn follow( &self, store: &file::Store, packed: Option<&packed::Buffer>, @@ -218,3 +220,17 @@ mod peel { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn size_of_reference() { + assert_eq!( + std::mem::size_of::(), + 80, + "let's not let it change size undetected" + ); + } +} diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 1015dd6fea1..2499adf3be9 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -1,5 +1,5 @@ mod reflog { - mod packedd { + mod packed { use crate::file; #[test] @@ -62,14 +62,14 @@ mod peel { assert_eq!(r.kind(), git_ref::Kind::Symbolic, "there is something to peel"); let nr = Reference::from(r) - .follow_symbolic(&store, None) + .follow(&store, None) .expect("exists") .expect("no failure"); assert!( matches!(nr.target.to_ref(), git_ref::TargetRef::Peeled(_)), "iteration peels a single level" ); - assert!(nr.follow_symbolic(&store, None).is_none(), "end of iteration"); + assert!(nr.follow(&store, None).is_none(), "end of iteration"); assert_eq!( nr.target.to_ref(), git_ref::TargetRef::Peeled(&hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")), @@ -110,7 +110,7 @@ mod peel { ); let peeled = head - .follow_symbolic(&store, packed.as_ref()) + .follow(&store, packed.as_ref()) .expect("a peeled ref for the object")?; assert_eq!( peeled.target.as_id().map(ToOwned::to_owned), @@ -118,7 +118,7 @@ mod peel { "packed refs are always peeled (at least the ones we choose to read)" ); assert_eq!(peeled.kind(), git_ref::Kind::Peeled, "it's terminally peeled now"); - assert!(peeled.follow_symbolic(&store, packed.as_ref()).is_none()); + assert!(peeled.follow(&store, packed.as_ref()).is_none()); Ok(()) } @@ -140,6 +140,7 @@ mod peel { ); let odb = git_odb::linked::Store::at(store.base.join("objects"))?; + let mut r: Reference = store.find_loose("dt1")?.into(); assert_eq!( r.peel_to_id_in_place(&store, None, |oid, buf| { odb.try_find(oid, buf, &mut git_odb::pack::cache::Never) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index 54153280ffd..a264a3f5a10 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -159,7 +159,7 @@ fn delete_reflog_only_of_symbolic_no_deref() -> crate::Result { assert!(main.log_exists(&store), "log is untouched, too"); assert_eq!( main.target, - head.follow_symbolic(&store, None).expect("a symref")?.target, + head.follow(&store, None).expect("a symref")?.target, "head points to main" ); Ok(()) @@ -193,7 +193,7 @@ fn delete_reflog_only_of_symbolic_with_deref() -> crate::Result { assert!(!main.log_exists(&store), "log is removed"); assert_eq!( main.target, - head.follow_symbolic(&store, None).expect("a symref")?.target, + head.follow(&store, None).expect("a symref")?.target, "head points to main" ); Ok(()) diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index d769eae09b6..b8aecb7bf90 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -46,7 +46,6 @@ mod commit { } #[test] - #[ignore] fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { let (repo, _keep) = crate::basic_rw_repo().unwrap(); let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); @@ -57,7 +56,7 @@ mod commit { .tree_id() .expect("tree to be set"); let author = git::actor::Signature::empty(); - let commit_id = repo + let first_commit_id = repo .commit( "HEAD", "hello there \r\n\nthe body", @@ -68,30 +67,37 @@ mod commit { ) .unwrap(); assert_eq!( - commit_id, + first_commit_id, hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), "the commit id is stable" ); - let commit_id = repo + let current_commit = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); + assert_eq!(current_commit, first_commit_id, "the commit was set"); + + let second_commit_id = repo .commit( "refs/heads/new-branch", "committing into a new branch creates it", author.clone(), author, empty_tree_id, - Some(commit_id), + Some(first_commit_id), ) .unwrap(); assert_eq!( - commit_id, - hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), + second_commit_id, + hex_to_id("b0d041ade77e51d31c79c7147fb769336ccc77b1"), "the second commit id is stable" ); // TODO: check reflog - let current_commit = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); - assert_eq!(current_commit, commit_id, "the commit was set"); + let current_commit = repo + .find_reference("new-branch") + .unwrap() + .peel_to_oid_in_place() + .unwrap(); + assert_eq!(current_commit, second_commit_id, "the commit was set"); } } From 43ac4f5acbe3ace5d43ed3ed1bc394d721f0e273 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 09:19:35 +0800 Subject: [PATCH 37/91] [repository #190] a major step forward with `head()` access --- .../src/command/release/git.rs | 6 +- git-ref/src/raw.rs | 6 +- git-repository/CHANGELOG.md | 5 +- git-repository/src/easy/ext/reference.rs | 19 +++-- git-repository/src/easy/head.rs | 77 +++++++++++++++++++ git-repository/src/easy/mod.rs | 8 ++ git-repository/src/easy/object/mod.rs | 57 +++++++++----- git-repository/src/easy/oid.rs | 10 +++ git-repository/src/easy/reference.rs | 27 ++++--- git-repository/src/ext/mod.rs | 2 + git-repository/src/ext/reference.rs | 17 ++++ git-repository/tests/easy/ext/head.rs | 50 ++++++++++++ git-repository/tests/easy/ext/mod.rs | 2 +- git-repository/tests/easy/ext/object.rs | 8 +- git-repository/tests/easy/ext/reference.rs | 39 ---------- git-repository/tests/easy/reference.rs | 8 +- 16 files changed, 248 insertions(+), 93 deletions(-) create mode 100644 git-repository/src/easy/head.rs create mode 100644 git-repository/src/ext/reference.rs create mode 100644 git-repository/tests/easy/ext/head.rs delete mode 100644 git-repository/tests/easy/ext/reference.rs diff --git a/cargo-smart-release/src/command/release/git.rs b/cargo-smart-release/src/command/release/git.rs index 28e34819c68..5c623012812 100644 --- a/cargo-smart-release/src/command/release/git.rs +++ b/cargo-smart-release/src/command/release/git.rs @@ -46,8 +46,8 @@ pub(in crate::command::release_impl) fn has_changed_since_last_release( .strip_prefix(&ctx.root) .expect("workspace members are releative to the root directory"); - let current_commit = ctx.git_easy.find_reference("HEAD")?.peel_to_oid_in_place()?; - let released_target = tag_ref.peel_to_oid_in_place()?; + let current_commit = ctx.git_easy.find_reference("HEAD")?.peel_to_id_in_place()?; + let released_target = tag_ref.peel_to_id_in_place()?; if repo_relative_crate_dir.as_os_str().is_empty() { Ok(current_commit != released_target) @@ -126,7 +126,7 @@ pub(in crate::command::release_impl) fn commit_changes( if !cmd.status()?.success() { bail!("Failed to commit changed manifests"); } - Ok(Some(ctx.git_easy.find_reference("HEAD")?.peel_to_oid_in_place()?)) + Ok(Some(ctx.git_easy.find_reference("HEAD")?.peel_to_id_in_place()?)) } pub(in crate::command::release_impl) fn create_version_tag<'repo>( diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 1eabbd53099..7ed90c11e32 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -9,8 +9,8 @@ pub struct Reference { pub name: FullName, /// The target of the reference, either a symbolic reference by full name or a possibly intermediate object by its id. pub target: Target, - /// The fully peeled object to which this reference ultimately points to - peeled: Option, + /// The fully peeled object to which this reference ultimately points to. Only guaranteed to be set after `peel_to_id_in_place()` was called. + pub peeled: Option, } mod convert { @@ -54,7 +54,7 @@ mod convert { } } -// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet +// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet. Make it an Extension trait! mod log { use crate::{ raw::Reference, diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index 94899973d62..50800f678fe 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -1,4 +1,4 @@ -### v0.9.0 (2021-08-??) +### v0.9.0 (2021-09-??) #### New @@ -7,6 +7,9 @@ - `Repository::init(Kind)` - `open()` - `Repository::open()` +- `easy::Head` +- `easy::ext::ReferenceAccessExt::head()` +- `ext::ReferenceExt` trait #### Breaking - **renames / moves / Signature Changes** diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index fd33962a394..70525529b57 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -73,13 +73,20 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .map_err(Into::into) } - // TODO: encode that the ref is unborn (i.e. uninitialized repo) - fn head(&self) -> Result>, reference::find::existing::Error> { + fn head(&self) -> Result, reference::find::existing::Error> { let head = self.find_reference("HEAD")?; - match head.inner.target { - Target::Symbolic(branch) => self.find_reference(branch.to_partial()).map(Some), - Target::Peeled(_) => Ok(None), + Ok(match head.inner.target { + Target::Symbolic(branch) => match self.find_reference(branch.to_partial()) { + Ok(r) => easy::head::Kind::Symbolic(r.detach()), + Err(reference::find::existing::Error::NotFound) => easy::head::Kind::Unborn(branch), + Err(err) => return Err(err), + }, + Target::Peeled(target) => easy::head::Kind::Detached { + target, + peeled: head.inner.peeled, + }, } + .attach(self)) } fn find_reference<'a, Name, E>(&self, name: Name) -> Result, reference::find::existing::Error> @@ -103,7 +110,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .try_find(name, state.assure_packed_refs_uptodate(&repo.refs)?.as_ref()) { Ok(r) => match r { - Some(r) => Ok(Some(Reference::from_file_ref(r, self))), + Some(r) => Ok(Some(Reference::from_ref(r, self))), None => Ok(None), }, Err(err) => Err(err.into()), diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs new file mode 100644 index 00000000000..a1e348d93a6 --- /dev/null +++ b/git-repository/src/easy/head.rs @@ -0,0 +1,77 @@ +#![allow(missing_docs)] + +use git_hash::ObjectId; +use git_ref::FullNameRef; + +use crate::easy::Head; + +pub enum Kind { + /// The existing reference the symbolic HEAD points to. + Symbolic(git_ref::Reference), + /// The not-yet-existing reference the symbolic HEAD refers to. + Unborn(git_ref::FullName), + Detached { + target: ObjectId, + peeled: Option, + }, +} + +impl Kind { + pub fn attach(self, access: &A) -> Head<'_, A> { + Head { kind: self, access } + } +} + +impl<'repo, A> Head<'repo, A> { + pub fn name(&self) -> Option> { + Some(match &self.kind { + Kind::Symbolic(r) => r.name.to_ref(), + Kind::Unborn(name) => name.to_ref(), + Kind::Detached { .. } => return None, + }) + } + pub fn is_detached(&self) -> bool { + match self.kind { + Kind::Detached { .. } => true, + _ => false, + } + } +} + +pub mod peel { + use git_hash::ObjectId; + + use crate::{ + easy::{head::Kind, Access, Head}, + ext::{ObjectIdExt, ReferenceExt}, + }; + + mod error { + use crate::easy::{object, reference}; + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FindExistingObject(#[from] object::find::existing::Error), + #[error(transparent)] + PeelReference(#[from] reference::peel_to_id_in_place::Error), + } + } + pub use error::Error; + + impl<'repo, A> Head<'repo, A> + where + A: Access + Sized, + { + /// Resolve to an + pub fn into_fully_peeled_id(self) -> Result, Error> { + Ok(Some(match self.kind { + Kind::Unborn(_name) => return Ok(None), + Kind::Detached { + peeled: Some(peeled), .. + } => peeled, + Kind::Detached { peeled: None, target } => target.attach(self.access).object()?.peel_to_end()?.id, + Kind::Symbolic(r) => r.attach(self.access).peel_to_id_in_place()?.detach(), + })) + } + } +} diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 23f00c6dd3e..67f08ac2a02 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -31,11 +31,19 @@ pub(crate) mod ext; pub mod borrow; pub mod commit; +pub mod head; pub mod object; mod oid; pub mod reference; pub mod state; +/// The head reference, as created from looking at `.git/HEAD`. +pub struct Head<'repo, A> { + /// One of various possible states for the HEAD reference + pub kind: head::Kind, + access: &'repo A, +} + /// An [ObjectId] with access to a repository. #[derive(Eq, Hash, Ord, PartialOrd, Clone, Copy)] pub struct Oid<'r, A> { diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index f8151d66ce0..129ecd413da 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -144,24 +144,42 @@ where } } -pub mod peel_to_kind { - pub use error::Error; - +pub mod peel { use crate::{ easy, easy::{ ext::ObjectAccessExt, - object::{peel_to_kind, Kind}, + object, + object::{peel, Kind}, ObjectRef, }, }; + pub mod to_kind { + mod error { + + use crate::easy::object; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FindExistingObject(#[from] object::find::existing::Error), + #[error("Last encountered object kind was {} while trying to peel to {}", .actual, .expected)] + NotFound { + actual: object::Kind, + expected: object::Kind, + }, + } + } + pub use error::Error; + } + impl<'repo, A> ObjectRef<'repo, A> where A: easy::Access + Sized, { // TODO: tests - pub fn peel_to_kind(mut self, kind: Kind) -> Result { + pub fn peel_to_kind(mut self, kind: Kind) -> Result { loop { match self.kind { any_kind if kind == any_kind => { @@ -180,7 +198,7 @@ pub mod peel_to_kind { self = access.find_object(target_id)?; } Kind::Tree | Kind::Blob => { - return Err(peel_to_kind::Error::NotFound { + return Err(peel::to_kind::Error::NotFound { actual: self.kind, expected: kind, }) @@ -188,21 +206,20 @@ pub mod peel_to_kind { } } } - } - - mod error { - use crate::easy::{object, object::find}; - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - FindExisting(#[from] find::existing::Error), - #[error("Last encountered object kind was {} while trying to peel to {}", .actual, .expected)] - NotFound { - actual: object::Kind, - expected: object::Kind, - }, + // TODO: tests + pub fn peel_to_end(mut self) -> Result { + loop { + match self.kind { + Kind::Commit | Kind::Tree | Kind::Blob => break Ok(self), + Kind::Tag => { + let target_id = self.to_tag_iter().expect("tag").target_id().expect("valid tag"); + let access = self.access; + drop(self); + self = access.find_object(target_id)?; + } + } + } } } } diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 5232ddb6cfb..1a74f8ee1df 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use git_hash::{oid, ObjectId}; use crate::{ @@ -70,6 +72,14 @@ where } } +impl<'repo, A> Deref for Oid<'repo, A> { + type Target = oid; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + impl<'repo, A> Oid<'repo, A> where A: easy::Access + Sized, diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 51bf512b33b..36aa02c17f0 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -24,7 +24,7 @@ pub mod edit { } } -pub mod peel_to_oid_in_place { +pub mod peel_to_id_in_place { use crate::easy; #[derive(Debug, thiserror::Error)] @@ -40,16 +40,7 @@ pub mod peel_to_oid_in_place { } } -impl<'repo, A> Reference<'repo, A> -where - A: easy::Access + Sized, -{ - pub(crate) fn from_file_ref(reference: git_ref::Reference, access: &'repo A) -> Self { - Reference { - inner: reference, - access, - } - } +impl<'repo, A> Reference<'repo, A> { pub fn target(&self) -> git_ref::TargetRef<'_> { self.inner.target.to_ref() } @@ -61,8 +52,20 @@ where pub fn detach(self) -> git_ref::Reference { self.inner } +} + +impl<'repo, A> Reference<'repo, A> +where + A: easy::Access + Sized, +{ + pub(crate) fn from_ref(reference: git_ref::Reference, access: &'repo A) -> Self { + Reference { + inner: reference, + access, + } + } - pub fn peel_to_oid_in_place(&mut self) -> Result, peel_to_oid_in_place::Error> { + pub fn peel_to_id_in_place(&mut self) -> Result, peel_to_id_in_place::Error> { let repo = self.access.repo()?; let state = self.access.state(); let mut pack_cache = state.try_borrow_mut_pack_cache()?; diff --git a/git-repository/src/ext/mod.rs b/git-repository/src/ext/mod.rs index 1697ca48e6c..d342bf15387 100644 --- a/git-repository/src/ext/mod.rs +++ b/git-repository/src/ext/mod.rs @@ -1,5 +1,7 @@ pub use object_id::ObjectIdExt; +pub use reference::ReferenceExt; pub use tree::TreeIterExt; mod object_id; +mod reference; mod tree; diff --git a/git-repository/src/ext/reference.rs b/git-repository/src/ext/reference.rs new file mode 100644 index 00000000000..0726f1d17f4 --- /dev/null +++ b/git-repository/src/ext/reference.rs @@ -0,0 +1,17 @@ +use crate::easy; + +pub trait Sealed {} + +impl Sealed for git_ref::Reference {} + +/// Extensions for [references][Reference]. +pub trait ReferenceExt { + /// Attach [`easy::Access`] to the given reference. It can be detached later with [`detach()]`. + fn attach(self, access: &A) -> easy::Reference<'_, A>; +} + +impl ReferenceExt for git_ref::Reference { + fn attach(self, access: &A) -> easy::Reference<'_, A> { + easy::Reference::from_ref(self, access) + } +} diff --git a/git-repository/tests/easy/ext/head.rs b/git-repository/tests/easy/ext/head.rs new file mode 100644 index 00000000000..ec546750e0a --- /dev/null +++ b/git-repository/tests/easy/ext/head.rs @@ -0,0 +1,50 @@ +use std::convert::TryInto; + +use git_ref::{ + transaction::{Change, PreviousValue, RefEdit}, + Target, +}; +use git_repository as git; +use git_repository::prelude::ReferenceAccessExt; +use git_testtools::hex_to_id; + +#[test] +fn symbolic() -> crate::Result { + let repo = crate::basic_repo()?; + let head = repo.head()?; + match &head.kind { + git::easy::head::Kind::Symbolic(r) => { + assert_eq!( + r.target.as_id().map(ToOwned::to_owned), + Some(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")) + ); + } + _ => panic!("unexpected head kind"), + } + assert_eq!(head.name().expect("born").as_bstr(), "refs/heads/main"); + assert!(!head.is_detached()); + Ok(()) +} + +#[test] +fn detached() { + let (repo, _keep) = crate::basic_rw_repo().unwrap(); + repo.edit_reference( + RefEdit { + change: Change::Update { + log: Default::default(), + expected: PreviousValue::Any, + new: Target::Peeled(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")), + }, + name: "HEAD".try_into().unwrap(), + deref: false, + }, + git_lock::acquire::Fail::Immediately, + None, + ) + .unwrap(); + + let head = repo.head().unwrap(); + assert!(head.is_detached(), "head is detached"); + assert!(head.name().is_none()); +} diff --git a/git-repository/tests/easy/ext/mod.rs b/git-repository/tests/easy/ext/mod.rs index b0e326e04f0..5fedc06139e 100644 --- a/git-repository/tests/easy/ext/mod.rs +++ b/git-repository/tests/easy/ext/mod.rs @@ -1,2 +1,2 @@ +mod head; mod object; -mod reference; diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index b8aecb7bf90..cc62d1ae3ad 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -48,7 +48,7 @@ mod commit { #[test] fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { let (repo, _keep) = crate::basic_rw_repo().unwrap(); - let parent = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); + let parent = repo.find_reference("HEAD").unwrap().peel_to_id_in_place().unwrap(); let empty_tree_id = parent .object() .unwrap() @@ -72,8 +72,8 @@ mod commit { "the commit id is stable" ); - let current_commit = repo.find_reference("HEAD").unwrap().peel_to_oid_in_place().unwrap(); - assert_eq!(current_commit, first_commit_id, "the commit was set"); + let current_commit = repo.head().unwrap().into_fully_peeled_id().unwrap().expect("born"); + assert_eq!(current_commit, &*first_commit_id, "the commit was set"); let second_commit_id = repo .commit( @@ -96,7 +96,7 @@ mod commit { let current_commit = repo .find_reference("new-branch") .unwrap() - .peel_to_oid_in_place() + .peel_to_id_in_place() .unwrap(); assert_eq!(current_commit, second_commit_id, "the commit was set"); } diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs deleted file mode 100644 index f90915e55e9..00000000000 --- a/git-repository/tests/easy/ext/reference.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod head { - use git_ref::transaction::{Change, PreviousValue, RefEdit}; - use git_ref::Target; - use git_repository::prelude::ReferenceAccessExt; - use git_testtools::hex_to_id; - use std::convert::TryInto; - - #[test] - fn symbolic() -> crate::Result { - let repo = crate::basic_repo()?; - let head = repo.head()?.expect("HEAD is symbolic"); - assert_eq!( - head.inner.target.into_id(), - hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41") - ); - Ok(()) - } - - #[test] - fn detached() { - let (repo, _keep) = crate::basic_rw_repo().unwrap(); - repo.edit_reference( - RefEdit { - change: Change::Update { - log: Default::default(), - expected: PreviousValue::Any, - new: Target::Peeled(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")), - }, - name: "HEAD".try_into().unwrap(), - deref: false, - }, - git_lock::acquire::Fail::Immediately, - None, - ) - .unwrap(); - - assert!(repo.head().unwrap().is_none(), "head is detached"); - } -} diff --git a/git-repository/tests/easy/reference.rs b/git-repository/tests/easy/reference.rs index 9b0e3e3a045..c4034b9614f 100644 --- a/git-repository/tests/easy/reference.rs +++ b/git-repository/tests/easy/reference.rs @@ -21,23 +21,23 @@ mod find { "it points to a tag object" ); - let object = packed_tag_ref.peel_to_oid_in_place().unwrap(); + let object = packed_tag_ref.peel_to_id_in_place().unwrap(); let the_commit = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(object, the_commit, "it is assumed to be fully peeled"); assert_eq!( object, - packed_tag_ref.peel_to_oid_in_place().unwrap(), + packed_tag_ref.peel_to_id_in_place().unwrap(), "peeling again yields the same object" ); let mut symbolic_ref = repo.find_reference("multi-link-target1").unwrap(); assert_eq!(symbolic_ref.name(), "refs/heads/multi-link-target1".try_into().unwrap()); - assert_eq!(symbolic_ref.peel_to_oid_in_place().unwrap(), the_commit); + assert_eq!(symbolic_ref.peel_to_id_in_place().unwrap(), the_commit); assert_eq!( symbolic_ref.name(), "refs/remotes/origin/multi-link-target3".try_into().unwrap(), "it follows symbolic refs, too" ); - assert_eq!(symbolic_ref.peel_to_oid_in_place().unwrap(), the_commit, "idempotency"); + assert_eq!(symbolic_ref.peel_to_id_in_place().unwrap(), the_commit, "idempotency"); } } From 15d4ac8f4b08716f6b06938f01396fb8ba8e7086 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 09:25:19 +0800 Subject: [PATCH 38/91] [repository #190] refactor --- git-repository/src/easy/head.rs | 24 ++++++++++++++++-------- git-repository/tests/easy/ext/head.rs | 12 ++++++------ git-repository/tests/easy/ext/object.rs | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index a1e348d93a6..6b8a22219e0 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -62,16 +62,24 @@ pub mod peel { where A: Access + Sized, { - /// Resolve to an - pub fn into_fully_peeled_id(self) -> Result, Error> { - Ok(Some(match self.kind { - Kind::Unborn(_name) => return Ok(None), + pub fn into_fully_peeled_id(self) -> Option> { + Some(match self.kind { + Kind::Unborn(_name) => return None, Kind::Detached { peeled: Some(peeled), .. - } => peeled, - Kind::Detached { peeled: None, target } => target.attach(self.access).object()?.peel_to_end()?.id, - Kind::Symbolic(r) => r.attach(self.access).peel_to_id_in_place()?.detach(), - })) + } => Ok(peeled), + Kind::Detached { peeled: None, target } => target + .attach(self.access) + .object() + .map_err(Into::into) + .and_then(|obj| obj.peel_to_end().map_err(Into::into)) + .map(|peeled| peeled.id), + Kind::Symbolic(r) => r + .attach(self.access) + .peel_to_id_in_place() + .map_err(Into::into) + .map(|id| id.detach()), + }) } } } diff --git a/git-repository/tests/easy/ext/head.rs b/git-repository/tests/easy/ext/head.rs index ec546750e0a..36e998add52 100644 --- a/git-repository/tests/easy/ext/head.rs +++ b/git-repository/tests/easy/ext/head.rs @@ -27,8 +27,8 @@ fn symbolic() -> crate::Result { } #[test] -fn detached() { - let (repo, _keep) = crate::basic_rw_repo().unwrap(); +fn detached() -> crate::Result { + let (repo, _keep) = crate::basic_rw_repo()?; repo.edit_reference( RefEdit { change: Change::Update { @@ -36,15 +36,15 @@ fn detached() { expected: PreviousValue::Any, new: Target::Peeled(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")), }, - name: "HEAD".try_into().unwrap(), + name: "HEAD".try_into()?, deref: false, }, git_lock::acquire::Fail::Immediately, None, - ) - .unwrap(); + )?; - let head = repo.head().unwrap(); + let head = repo.head()?; assert!(head.is_detached(), "head is detached"); assert!(head.name().is_none()); + Ok(()) } diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index cc62d1ae3ad..3a29464760b 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -72,7 +72,7 @@ mod commit { "the commit id is stable" ); - let current_commit = repo.head().unwrap().into_fully_peeled_id().unwrap().expect("born"); + let current_commit = repo.head().unwrap().into_fully_peeled_id().expect("born").unwrap(); assert_eq!(current_commit, &*first_commit_id, "the commit was set"); let second_commit_id = repo From 376c045cf589e51b639cf6c3633c4a8fcae7b6aa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 09:26:04 +0800 Subject: [PATCH 39/91] thanks clippy --- git-repository/src/easy/head.rs | 5 +---- git-repository/src/ext/reference.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index 6b8a22219e0..f6f7ba5420b 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -31,10 +31,7 @@ impl<'repo, A> Head<'repo, A> { }) } pub fn is_detached(&self) -> bool { - match self.kind { - Kind::Detached { .. } => true, - _ => false, - } + matches!(self.kind, Kind::Detached { .. }) } } diff --git a/git-repository/src/ext/reference.rs b/git-repository/src/ext/reference.rs index 0726f1d17f4..be5835ca437 100644 --- a/git-repository/src/ext/reference.rs +++ b/git-repository/src/ext/reference.rs @@ -4,7 +4,7 @@ pub trait Sealed {} impl Sealed for git_ref::Reference {} -/// Extensions for [references][Reference]. +/// Extensions for [references][git_ref::Reference]. pub trait ReferenceExt { /// Attach [`easy::Access`] to the given reference. It can be detached later with [`detach()]`. fn attach(self, access: &A) -> easy::Reference<'_, A>; From 0b635e9778a98235cc9b47b12e58a175d1ca02b7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 09:48:22 +0800 Subject: [PATCH 40/91] [ref #190] Move file-log-specific functionality into own extension trait. --- git-ref/src/raw.rs | 39 -------------- git-ref/src/store/file/log/iter.rs | 25 +++++++-- git-ref/src/store/file/loose/reflog.rs | 2 +- git-ref/src/store/file/mod.rs | 3 ++ git-ref/src/store/file/raw_ext.rs | 52 +++++++++++++++++++ git-ref/tests/file/reference.rs | 2 + .../prepare_and_commit/create_or_update.rs | 1 + .../transaction/prepare_and_commit/delete.rs | 1 + 8 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 git-ref/src/store/file/raw_ext.rs diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 7ed90c11e32..f3b6ff54cec 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -54,45 +54,6 @@ mod convert { } } -// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet. Make it an Extension trait! -mod log { - use crate::{ - raw::Reference, - store::{ - file, - file::{log, loose::reference::logiter::must_be_io_err}, - }, - }; - - impl Reference { - /// Obtain a reverse iterator over logs of this reference. See [crate::file::loose::Reference::log_iter_rev()] for details. - pub fn log_iter_rev<'b>( - &self, - store: &file::Store, - buf: &'b mut [u8], - ) -> std::io::Result>> { - store.reflog_iter_rev(self.name.to_ref(), buf).map_err(must_be_io_err) - } - - /// Obtain an iterator over logs of this reference. See [crate::file::loose::Reference::log_iter()] for details. - pub fn log_iter<'a, 'b: 'a>( - &'a self, - store: &file::Store, - buf: &'b mut Vec, - ) -> std::io::Result, log::iter::decode::Error>> + 'a>> - { - store.reflog_iter(self.name.to_ref(), buf).map_err(must_be_io_err) - } - - /// For details, see [Reference::log_exists()]. - pub fn log_exists(&self, store: &file::Store) -> bool { - store - .reflog_exists(self.name.to_ref()) - .expect("infallible name conversion") - } - } -} - mod access { use bstr::ByteSlice; diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 04a73b6cc2b..b5f9727d013 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -54,13 +54,28 @@ pub mod decode { /// This iterator is useful when the ref log file is going to be rewritten which forces processing of the entire file. /// It will continue parsing even if individual log entries failed to parse, leaving it to the driver to decide whether to /// abort or continue. -pub fn forward(lines: &[u8]) -> impl Iterator, decode::Error>> { - lines.as_bstr().lines().enumerate().map(|(ln, line)| { - log::LineRef::from_bytes(line).map_err(|err| decode::Error::new(err, decode::LineNumber::FromStart(ln))) - }) +pub fn forward(lines: &[u8]) -> Forward<'_> { + Forward { + inner: lines.as_bstr().lines().enumerate(), + } +} + +/// An iterator yielding parsed lines in a file from start to end, oldest to newest. +pub struct Forward<'a> { + inner: std::iter::Enumerate>, +} + +impl<'a> Iterator for Forward<'a> { + type Item = Result, decode::Error>; + + fn next(&mut self) -> Option { + self.inner.next().map(|(ln, line)| { + log::LineRef::from_bytes(line).map_err(|err| decode::Error::new(err, decode::LineNumber::FromStart(ln))) + }) + } } -/// An iterator yielding parsed lines in a file in reverse. +/// An iterator yielding parsed lines in a file in reverse, most recent to oldest. pub struct Reverse<'a, F> { buf: &'a mut [u8], count: usize, diff --git a/git-ref/src/store/file/loose/reflog.rs b/git-ref/src/store/file/loose/reflog.rs index e602c1a263e..9f5006238e3 100644 --- a/git-ref/src/store/file/loose/reflog.rs +++ b/git-ref/src/store/file/loose/reflog.rs @@ -52,7 +52,7 @@ impl file::Store { &self, name: Name, buf: &'b mut Vec, - ) -> Result, log::iter::decode::Error>>>, Error> + ) -> Result>, Error> where Name: TryInto, Error = E>, crate::name::Error: From, diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 8564fd5e541..3ee93c30e9c 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -77,3 +77,6 @@ pub mod transaction; /// pub mod packed; + +mod raw_ext; +pub use raw_ext::ReferenceExt; diff --git a/git-ref/src/store/file/raw_ext.rs b/git-ref/src/store/file/raw_ext.rs new file mode 100644 index 00000000000..54035422f31 --- /dev/null +++ b/git-ref/src/store/file/raw_ext.rs @@ -0,0 +1,52 @@ +use crate::store::{ + file, + file::{log, loose::reference::logiter::must_be_io_err}, +}; + +pub trait Sealed {} +impl Sealed for crate::Reference {} + +/// A trait to extend [Reference][crate::Reference] with functionality requiring a [file::Store]. +pub trait ReferenceExt: Sealed { + /// Obtain a reverse iterator over logs of this reference. See [crate::file::loose::Reference::log_iter_rev()] for details. + fn log_iter_rev<'b>( + &self, + store: &file::Store, + buf: &'b mut [u8], + ) -> std::io::Result>>; + + /// Obtain an iterator over logs of this reference. See [crate::file::loose::Reference::log_iter()] for details. + fn log_iter<'a, 'b: 'a>( + &'a self, + store: &file::Store, + buf: &'b mut Vec, + ) -> std::io::Result>>; + + /// For details, see [Reference::log_exists()]. + fn log_exists(&self, store: &file::Store) -> bool; +} + +use crate::raw::Reference; +impl ReferenceExt for Reference { + fn log_iter_rev<'b>( + &self, + store: &file::Store, + buf: &'b mut [u8], + ) -> std::io::Result>> { + store.reflog_iter_rev(self.name.to_ref(), buf).map_err(must_be_io_err) + } + + fn log_iter<'a, 'b: 'a>( + &'a self, + store: &file::Store, + buf: &'b mut Vec, + ) -> std::io::Result>> { + store.reflog_iter(self.name.to_ref(), buf).map_err(must_be_io_err) + } + + fn log_exists(&self, store: &file::Store) -> bool { + store + .reflog_exists(self.name.to_ref()) + .expect("infallible name conversion") + } +} diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 2499adf3be9..edf072f9338 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -1,5 +1,7 @@ mod reflog { mod packed { + use git_ref::file::ReferenceExt; + use crate::file; #[test] diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index ff2deadeb9b..484f1c81444 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -6,6 +6,7 @@ use bstr::ByteSlice; use git_hash::ObjectId; use git_lock::acquire::Fail; use git_object::bstr::BString; +use git_ref::file::ReferenceExt; use git_ref::transaction::PreviousValue; use git_ref::{ file::{ diff --git a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs index a264a3f5a10..8d36f66ea1d 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/delete.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/delete.rs @@ -3,6 +3,7 @@ use crate::file::{ transaction::prepare_and_commit::{committer, empty_store}, }; use git_lock::acquire::Fail; +use git_ref::file::ReferenceExt; use git_ref::transaction::PreviousValue; use git_ref::{ transaction::{Change, RefEdit, RefLog}, From 60fc215ccac529b4a14cb9d8260ab9ddec86758a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 09:52:26 +0800 Subject: [PATCH 41/91] [ref #190] move remaining file store functions to extension trait --- experiments/diffing/src/main.rs | 2 +- experiments/traversal/src/main.rs | 2 +- git-ref/src/raw.rs | 104 ----------------------- git-ref/src/store/file/raw_ext.rs | 118 ++++++++++++++++++++++++++- git-ref/tests/file/reference.rs | 2 +- git-repository/src/easy/reference.rs | 1 + gitoxide-core/src/hours.rs | 4 +- gitoxide-core/src/pack/create.rs | 4 +- 8 files changed, 124 insertions(+), 113 deletions(-) diff --git a/experiments/diffing/src/main.rs b/experiments/diffing/src/main.rs index 0378d1058d3..8ecae90c2c7 100644 --- a/experiments/diffing/src/main.rs +++ b/experiments/diffing/src/main.rs @@ -9,7 +9,7 @@ use git_repository::{ objs::{bstr::BStr, TreeRefIter}, odb, prelude::*, - refs::peel, + refs::{file::ReferenceExt, peel}, }; use rayon::prelude::*; diff --git a/experiments/traversal/src/main.rs b/experiments/traversal/src/main.rs index c8c897a6813..20fd4a033c1 100644 --- a/experiments/traversal/src/main.rs +++ b/experiments/traversal/src/main.rs @@ -10,7 +10,7 @@ use git_repository::{ objs::{bstr::BStr, tree::EntryRef}, odb, prelude::*, - refs::peel, + refs::{file::ReferenceExt, peel}, traverse::{tree, tree::visit::Action}, }; diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index f3b6ff54cec..f375682ed97 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -78,110 +78,6 @@ mod access { } } -// TODO: peeling depends on file store, that should be generic but we don't have a trait for that yet -mod peel { - use std::collections::BTreeSet; - - use git_hash::ObjectId; - - use crate::{ - peel, - raw::Reference, - store::{file, packed}, - Target, - }; - - impl Reference { - /// For details, see [Reference::peel_to_id_in_place()]. - pub fn peel_to_id_in_place( - &mut self, - store: &file::Store, - packed: Option<&packed::Buffer>, - mut find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, - ) -> Result { - match self.peeled { - Some(peeled) => { - self.target = Target::Peeled(peeled.to_owned()); - Ok(peeled) - } - None => { - if self.target.kind() == crate::Kind::Symbolic { - let mut seen = BTreeSet::new(); - let cursor = &mut *self; - while let Some(next) = cursor.follow(store, packed) { - let next = next?; - if seen.contains(&next.name) { - return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path()))); - } - *cursor = next; - seen.insert(cursor.name.clone()); - const MAX_REF_DEPTH: usize = 5; - if seen.len() == MAX_REF_DEPTH { - return Err(peel::to_id::Error::DepthLimitExceeded { - max_depth: MAX_REF_DEPTH, - }); - } - } - }; - let mut buf = Vec::new(); - let mut oid = self.target.as_id().expect("peeled ref").to_owned(); - let peeled_id = loop { - let (kind, data) = find(oid, &mut buf) - .map_err(|err| Box::new(err) as Box)? - .ok_or_else(|| peel::to_id::Error::NotFound { - oid, - name: self.name.0.clone(), - })?; - match kind { - git_object::Kind::Tag => { - oid = git_object::TagRefIter::from_bytes(data).target_id().ok_or_else(|| { - peel::to_id::Error::NotFound { - oid, - name: self.name.0.clone(), - } - })?; - } - _ => break oid, - }; - }; - self.peeled = Some(peeled_id); - self.target = Target::Peeled(peeled_id); - Ok(self.target.as_id().expect("to be peeled").to_owned()) - } - } - } - - /// Follow this symbolic reference one level and return the ref it refers to, - /// possibly providing access to `packed` references for lookup if it contains the referent. - /// - /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. - pub fn follow( - &self, - store: &file::Store, - packed: Option<&packed::Buffer>, - ) -> Option> { - match self.peeled { - Some(peeled) => Some(Ok(Reference { - name: self.name.clone(), - target: Target::Peeled(peeled), - peeled: None, - })), - None => match &self.target { - Target::Peeled(_) => None, - Target::Symbolic(full_name) => { - let path = full_name.to_path(); - match store.find_one_with_verified_input(path.as_ref(), packed) { - Ok(Some(next)) => Some(Ok(next)), - Ok(None) => Some(Err(file::find::existing::Error::NotFound(path.into_owned()))), - Err(err) => Some(Err(file::find::existing::Error::Find(err))), - } - } - }, - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/git-ref/src/store/file/raw_ext.rs b/git-ref/src/store/file/raw_ext.rs index 54035422f31..7d03a231ee6 100644 --- a/git-ref/src/store/file/raw_ext.rs +++ b/git-ref/src/store/file/raw_ext.rs @@ -1,6 +1,16 @@ -use crate::store::{ - file, - file::{log, loose::reference::logiter::must_be_io_err}, +use std::collections::BTreeSet; + +use git_hash::ObjectId; + +use crate::{ + peel, + raw::Reference, + store::{ + file, + file::{log, loose::reference::logiter::must_be_io_err}, + packed, + }, + Target, }; pub trait Sealed {} @@ -24,9 +34,26 @@ pub trait ReferenceExt: Sealed { /// For details, see [Reference::log_exists()]. fn log_exists(&self, store: &file::Store) -> bool; + + /// For details, see [Reference::peel_to_id_in_place()]. + fn peel_to_id_in_place( + &mut self, + store: &file::Store, + packed: Option<&packed::Buffer>, + find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, + ) -> Result; + + /// Follow this symbolic reference one level and return the ref it refers to, + /// possibly providing access to `packed` references for lookup if it contains the referent. + /// + /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain. + fn follow( + &self, + store: &file::Store, + packed: Option<&packed::Buffer>, + ) -> Option>; } -use crate::raw::Reference; impl ReferenceExt for Reference { fn log_iter_rev<'b>( &self, @@ -49,4 +76,87 @@ impl ReferenceExt for Reference { .reflog_exists(self.name.to_ref()) .expect("infallible name conversion") } + + fn peel_to_id_in_place( + &mut self, + store: &file::Store, + packed: Option<&packed::Buffer>, + mut find: impl FnMut(git_hash::ObjectId, &mut Vec) -> Result, E>, + ) -> Result { + match self.peeled { + Some(peeled) => { + self.target = Target::Peeled(peeled.to_owned()); + Ok(peeled) + } + None => { + if self.target.kind() == crate::Kind::Symbolic { + let mut seen = BTreeSet::new(); + let cursor = &mut *self; + while let Some(next) = cursor.follow(store, packed) { + let next = next?; + if seen.contains(&next.name) { + return Err(peel::to_id::Error::Cycle(store.base.join(cursor.name.to_path()))); + } + *cursor = next; + seen.insert(cursor.name.clone()); + const MAX_REF_DEPTH: usize = 5; + if seen.len() == MAX_REF_DEPTH { + return Err(peel::to_id::Error::DepthLimitExceeded { + max_depth: MAX_REF_DEPTH, + }); + } + } + }; + let mut buf = Vec::new(); + let mut oid = self.target.as_id().expect("peeled ref").to_owned(); + let peeled_id = loop { + let (kind, data) = find(oid, &mut buf) + .map_err(|err| Box::new(err) as Box)? + .ok_or_else(|| peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + })?; + match kind { + git_object::Kind::Tag => { + oid = git_object::TagRefIter::from_bytes(data).target_id().ok_or_else(|| { + peel::to_id::Error::NotFound { + oid, + name: self.name.0.clone(), + } + })?; + } + _ => break oid, + }; + }; + self.peeled = Some(peeled_id); + self.target = Target::Peeled(peeled_id); + Ok(self.target.as_id().expect("to be peeled").to_owned()) + } + } + } + + fn follow( + &self, + store: &file::Store, + packed: Option<&packed::Buffer>, + ) -> Option> { + match self.peeled { + Some(peeled) => Some(Ok(Reference { + name: self.name.clone(), + target: Target::Peeled(peeled), + peeled: None, + })), + None => match &self.target { + Target::Peeled(_) => None, + Target::Symbolic(full_name) => { + let path = full_name.to_path(); + match store.find_one_with_verified_input(path.as_ref(), packed) { + Ok(Some(next)) => Some(Ok(next)), + Ok(None) => Some(Err(file::find::existing::Error::NotFound(path.into_owned()))), + Err(err) => Some(Err(file::find::existing::Error::Find(err))), + } + } + }, + } + } } diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index edf072f9338..13bd067ca80 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -52,7 +52,7 @@ mod reflog { mod peel { use git_odb::Find; - use git_ref::{peel, Reference}; + use git_ref::{file::ReferenceExt, peel, Reference}; use git_testtools::hex_to_id; use crate::{file, file::store_with_packed_refs}; diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index 36aa02c17f0..dfc1633df06 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -2,6 +2,7 @@ use std::ops::DerefMut; use git_odb::Find; +use git_ref::file::ReferenceExt; use crate::{ easy, diff --git a/gitoxide-core/src/hours.rs b/gitoxide-core/src/hours.rs index a37b81e7649..6e0510d06f6 100644 --- a/gitoxide-core/src/hours.rs +++ b/gitoxide-core/src/hours.rs @@ -10,7 +10,9 @@ use std::{ use anyhow::{anyhow, bail}; use bstr::BString; -use git_repository::{actor, interrupt, objs, odb, odb::pack, prelude::*, progress, Progress}; +use git_repository::{ + actor, interrupt, objs, odb, odb::pack, prelude::*, progress, refs::file::ReferenceExt, Progress, +}; use itertools::Itertools; use rayon::prelude::*; diff --git a/gitoxide-core/src/pack/create.rs b/gitoxide-core/src/pack/create.rs index b574f7e097b..7d1c59e3f2b 100644 --- a/gitoxide-core/src/pack/create.rs +++ b/gitoxide-core/src/pack/create.rs @@ -8,7 +8,9 @@ use git_repository::{ objs::bstr::ByteVec, odb::{pack, pack::cache::DecodeEntry, Find}, prelude::{Finalize, FindExt}, - progress, traverse, Progress, + progress, + refs::file::ReferenceExt, + traverse, Progress, }; use crate::OutputFormat; From 4a36dedc17ce3124802d1b72330abc524fd98c6f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 10:25:50 +0800 Subject: [PATCH 42/91] [ref #190] First working sketch of reverse log iter access --- git-repository/src/easy/reference.rs | 45 +++++++++++++++++++++++++ git-repository/tests/easy/ext/object.rs | 23 +++++++++---- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference.rs index dfc1633df06..6aeb374c622 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference.rs @@ -83,6 +83,51 @@ where } } +pub mod log { + use crate::easy; + use crate::easy::Reference; + use git_ref::file::ReferenceExt; + use std::cell::RefMut; + use std::ops::DerefMut; + + pub struct Buffer<'a, 'repo, A> { + reference: &'a Reference<'repo, A>, + buf: RefMut<'repo, Vec>, + } + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + BorrowRepo(#[from] easy::borrow::repo::Error), + } + + impl<'a, 'repo, A> Buffer<'a, 'repo, A> + where + A: easy::Access + Sized, + { + pub fn reverse_iter(&mut self) -> Result>, Error> { + Ok(self + .reference + .inner + .log_iter_rev(&self.reference.access.repo()?.refs, self.buf.deref_mut())?) + } + } + + impl<'repo, A> Reference<'repo, A> + where + A: easy::Access + Sized, + { + pub fn log(&self) -> Result, easy::borrow::state::Error> { + Ok(Buffer { + reference: self, + buf: self.access.state().try_borrow_mut_buf()?, + }) + } + } +} + pub mod find { use crate::easy; diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 3a29464760b..1805afdc015 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -92,12 +92,23 @@ mod commit { "the second commit id is stable" ); - // TODO: check reflog - let current_commit = repo - .find_reference("new-branch") - .unwrap() - .peel_to_id_in_place() - .unwrap(); + let mut branch = repo.find_reference("new-branch").unwrap(); + let current_commit = branch.peel_to_id_in_place().unwrap(); assert_eq!(current_commit, second_commit_id, "the commit was set"); + + assert_eq!( + branch + .log() + .unwrap() + .reverse_iter() + .unwrap() + .expect("log present") + .next() + .expect("one line") + .unwrap() + .unwrap() + .message, + "commit: committing into a new branch creates it" + ); } } From 641edde5608ff22bf18cea845ba1925b84a7b9f2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 11:07:30 +0800 Subject: [PATCH 43/91] [repository #190] reflog tests --- git-repository/src/easy/head.rs | 14 ++++- git-repository/src/easy/reference/log.rs | 44 +++++++++++++++ .../easy/{reference.rs => reference/mod.rs} | 45 +-------------- git-repository/tests/easy/ext/object.rs | 56 ++++++++++--------- 4 files changed, 88 insertions(+), 71 deletions(-) create mode 100644 git-repository/src/easy/reference/log.rs rename git-repository/src/easy/{reference.rs => reference/mod.rs} (72%) diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index f6f7ba5420b..dcfc8bd469d 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -3,7 +3,7 @@ use git_hash::ObjectId; use git_ref::FullNameRef; -use crate::easy::Head; +use crate::{easy, easy::Head, ext::ReferenceExt}; pub enum Kind { /// The existing reference the symbolic HEAD points to. @@ -35,6 +35,18 @@ impl<'repo, A> Head<'repo, A> { } } +impl<'repo, A> Head<'repo, A> +where + A: easy::Access + Sized, +{ + pub fn into_reference(self) -> easy::Reference<'repo, A> { + match self.kind { + Kind::Symbolic(r) => r.attach(self.access), + _ => panic!("BUG: Expected head to be a born symbolic reference"), + } + } +} + pub mod peel { use git_hash::ObjectId; diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs new file mode 100644 index 00000000000..717c02082b2 --- /dev/null +++ b/git-repository/src/easy/reference/log.rs @@ -0,0 +1,44 @@ +use std::{cell::RefMut, ops::DerefMut}; + +use git_ref::file::ReferenceExt; + +use crate::{easy, easy::Reference}; + +pub struct Buffer<'a, 'repo, A> { + reference: &'a Reference<'repo, A>, + buf: RefMut<'repo, Vec>, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + BorrowRepo(#[from] easy::borrow::repo::Error), +} + +impl<'a, 'repo, A> Buffer<'a, 'repo, A> +where + A: easy::Access + Sized, +{ + pub fn reverse_iter(&mut self) -> Result>, Error> { + let buf = self.buf.deref_mut(); + buf.resize(512, 0); + Ok(self + .reference + .inner + .log_iter_rev(&self.reference.access.repo()?.refs, buf)?) + } +} + +impl<'repo, A> Reference<'repo, A> +where + A: easy::Access + Sized, +{ + pub fn log(&self) -> Result, easy::borrow::state::Error> { + Ok(Buffer { + reference: self, + buf: self.access.state().try_borrow_mut_buf()?, + }) + } +} diff --git a/git-repository/src/easy/reference.rs b/git-repository/src/easy/reference/mod.rs similarity index 72% rename from git-repository/src/easy/reference.rs rename to git-repository/src/easy/reference/mod.rs index 6aeb374c622..1e38f94f19b 100644 --- a/git-repository/src/easy/reference.rs +++ b/git-repository/src/easy/reference/mod.rs @@ -83,50 +83,7 @@ where } } -pub mod log { - use crate::easy; - use crate::easy::Reference; - use git_ref::file::ReferenceExt; - use std::cell::RefMut; - use std::ops::DerefMut; - - pub struct Buffer<'a, 'repo, A> { - reference: &'a Reference<'repo, A>, - buf: RefMut<'repo, Vec>, - } - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - BorrowRepo(#[from] easy::borrow::repo::Error), - } - - impl<'a, 'repo, A> Buffer<'a, 'repo, A> - where - A: easy::Access + Sized, - { - pub fn reverse_iter(&mut self) -> Result>, Error> { - Ok(self - .reference - .inner - .log_iter_rev(&self.reference.access.repo()?.refs, self.buf.deref_mut())?) - } - } - - impl<'repo, A> Reference<'repo, A> - where - A: easy::Access + Sized, - { - pub fn log(&self) -> Result, easy::borrow::state::Error> { - Ok(Buffer { - reference: self, - buf: self.access.state().try_borrow_mut_buf()?, - }) - } - } -} +pub mod log; pub mod find { use crate::easy; diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 1805afdc015..7e24840df3f 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -21,28 +21,36 @@ mod commit { use git_testtools::hex_to_id; #[test] - fn single_line_initial_commit_empty_tree_ref_nonexisting() { - let tmp = tempfile::tempdir().unwrap(); - let repo = git::init_bare(&tmp).unwrap().into_easy(); - let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into()).unwrap(); + fn single_line_initial_commit_empty_tree_ref_nonexisting() -> crate::Result { + let tmp = tempfile::tempdir()?; + let repo = git::init(&tmp)?.into_easy(); + let empty_tree_id = repo.write_object(&git::objs::Tree::empty().into())?; let author = git::actor::Signature::empty(); - let commit_id = repo - .commit( - "HEAD", - "initial", - author.clone(), - author, - empty_tree_id, - git::commit::NO_PARENT_IDS, - ) - .unwrap(); + let commit_id = repo.commit( + "HEAD", + "initial", + author.clone(), + author, + empty_tree_id, + git::commit::NO_PARENT_IDS, + )?; assert_eq!( commit_id, hex_to_id("302ea5640358f98ba23cda66c1e664a6f274643f"), "the commit id is stable" ); - // TODO: check reflog + let head = repo.head()?.into_reference(); + assert_eq!( + head.log()? + .reverse_iter()? + .expect("log present") + .next() + .expect("one line")?? + .message, + "commit (initial): initial" + ); + Ok(()) } #[test] @@ -96,19 +104,15 @@ mod commit { let current_commit = branch.peel_to_id_in_place().unwrap(); assert_eq!(current_commit, second_commit_id, "the commit was set"); + let mut log = branch.log().unwrap(); + let mut log_iter = log.reverse_iter().unwrap().expect("log present"); assert_eq!( - branch - .log() - .unwrap() - .reverse_iter() - .unwrap() - .expect("log present") - .next() - .expect("one line") - .unwrap() - .unwrap() - .message, + log_iter.next().expect("one line").unwrap().unwrap().message, "commit: committing into a new branch creates it" ); + assert!( + log_iter.next().is_none(), + "there is only one log line in the new branch" + ); } } From 998c7c65abb2c3eb5fc248b11ba816d09f1bedea Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 11:31:16 +0800 Subject: [PATCH 44/91] =?UTF-8?q?[ref=20#190]=20check=20for=20zero=20sized?= =?UTF-8?q?=20buffers=20in=20reverse=20log=20iterators=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to avoid surprises for users. --- git-ref/src/store/file/log/iter.rs | 6 ++++++ git-ref/tests/file/log.rs | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index b5f9727d013..63a998845c9 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -98,6 +98,12 @@ where F: std::io::Read + std::io::Seek, { let pos = log.seek(std::io::SeekFrom::End(0))?; + if buf.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Zero sized buffers are not allowed, use 256 bytes or more for typical logs", + )); + } Ok(Reverse { buf, count: 0, diff --git a/git-ref/tests/file/log.rs b/git-ref/tests/file/log.rs index 6f1af0a80d2..5d175c17eca 100644 --- a/git-ref/tests/file/log.rs +++ b/git-ref/tests/file/log.rs @@ -46,6 +46,19 @@ mod iter { } mod backward { + mod with_zero_sized_buffer { + + #[test] + fn any_line() { + let mut buf = [0u8; 0]; + assert!( + git_ref::file::log::iter::reverse(std::io::Cursor::new(b"won't matter".as_ref()), &mut buf) + .is_err(), + "zero sized buffers aren't allowed" + ); + } + } + mod with_buffer_too_small_for_single_line { #[test] fn single_line() -> crate::Result { From 946bbf19ed3f793b0eb1c5c90a655140e12d7e21 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 11:58:01 +0800 Subject: [PATCH 45/91] [repository #190] ref log for HEAD specifically --- git-repository/src/easy/head.rs | 65 +++++++++++++++++++- git-repository/src/easy/reference/log.rs | 21 ++++--- git-repository/tests/easy/ext/object.rs | 78 +++++++++++++----------- 3 files changed, 120 insertions(+), 44 deletions(-) diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index dcfc8bd469d..6acf604db2f 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -39,7 +39,7 @@ impl<'repo, A> Head<'repo, A> where A: easy::Access + Sized, { - pub fn into_reference(self) -> easy::Reference<'repo, A> { + pub fn into_referent(self) -> easy::Reference<'repo, A> { match self.kind { Kind::Symbolic(r) => r.attach(self.access), _ => panic!("BUG: Expected head to be a born symbolic reference"), @@ -47,6 +47,36 @@ where } } +pub mod log { + use std::marker::PhantomData; + + use crate::{ + easy, + easy::{ext::ReferenceAccessExt, Head}, + }; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + BorrowState(#[from] easy::borrow::state::Error), + #[error(transparent)] + FindExistingReference(#[from] easy::reference::find::existing::Error), + } + + impl<'repo, A> Head<'repo, A> + where + A: easy::Access + Sized, + { + pub fn log(&self) -> Result>, Error> { + Ok(easy::reference::log::Buffer { + reference: self.access.find_reference("HEAD")?, + buf: self.access.state().try_borrow_mut_buf()?, + _phantom: PhantomData::default(), + }) + } + } +} + pub mod peel { use git_hash::ObjectId; @@ -71,6 +101,39 @@ pub mod peel { where A: Access + Sized, { + pub fn peel_to_id_in_place(&mut self) -> Option> { + Some(match &mut self.kind { + Kind::Unborn(_name) => return None, + Kind::Detached { + peeled: Some(peeled), .. + } => Ok(*peeled), + Kind::Detached { peeled: None, target } => { + match target + .attach(self.access) + .object() + .map_err(Into::into) + .and_then(|obj| obj.peel_to_end().map_err(Into::into)) + .map(|peeled| peeled.id) + { + Ok(peeled) => { + self.kind = Kind::Detached { + peeled: Some(peeled), + target: *target, + }; + Ok(peeled) + } + Err(err) => Err(err), + } + } + Kind::Symbolic(r) => { + let mut nr = r.clone().attach(self.access); + let peeled = nr.peel_to_id_in_place().map_err(Into::into).map(|id| id.detach()); + *r = nr.detach(); + peeled + } + }) + } + pub fn into_fully_peeled_id(self) -> Option> { Some(match self.kind { Kind::Unborn(_name) => return None, diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs index 717c02082b2..75c10588de7 100644 --- a/git-repository/src/easy/reference/log.rs +++ b/git-repository/src/easy/reference/log.rs @@ -1,12 +1,16 @@ -use std::{cell::RefMut, ops::DerefMut}; +use std::{borrow::Borrow, cell::RefMut, marker::PhantomData, ops::DerefMut}; use git_ref::file::ReferenceExt; use crate::{easy, easy::Reference}; -pub struct Buffer<'a, 'repo, A> { - reference: &'a Reference<'repo, A>, - buf: RefMut<'repo, Vec>, +pub struct Buffer<'repo, A: 'repo, R> +where + R: Borrow>, +{ + pub(crate) reference: R, + pub(crate) buf: RefMut<'repo, Vec>, + pub(crate) _phantom: PhantomData, } #[derive(Debug, thiserror::Error)] @@ -17,17 +21,19 @@ pub enum Error { BorrowRepo(#[from] easy::borrow::repo::Error), } -impl<'a, 'repo, A> Buffer<'a, 'repo, A> +impl<'repo, A, R> Buffer<'repo, A, R> where A: easy::Access + Sized, + R: Borrow>, { pub fn reverse_iter(&mut self) -> Result>, Error> { let buf = self.buf.deref_mut(); buf.resize(512, 0); Ok(self .reference + .borrow() .inner - .log_iter_rev(&self.reference.access.repo()?.refs, buf)?) + .log_iter_rev(&self.reference.borrow().access.repo()?.refs, buf)?) } } @@ -35,10 +41,11 @@ impl<'repo, A> Reference<'repo, A> where A: easy::Access + Sized, { - pub fn log(&self) -> Result, easy::borrow::state::Error> { + pub fn log(&self) -> Result>, easy::borrow::state::Error> { Ok(Buffer { reference: self, buf: self.access.state().try_borrow_mut_buf()?, + _phantom: Default::default(), }) } } diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 7e24840df3f..3b83fb34e2c 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -40,7 +40,7 @@ mod commit { "the commit id is stable" ); - let head = repo.head()?.into_reference(); + let head = repo.head()?.into_referent(); assert_eq!( head.log()? .reverse_iter()? @@ -54,45 +54,50 @@ mod commit { } #[test] - fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() { - let (repo, _keep) = crate::basic_rw_repo().unwrap(); - let parent = repo.find_reference("HEAD").unwrap().peel_to_id_in_place().unwrap(); - let empty_tree_id = parent - .object() - .unwrap() - .commit_iter() - .tree_id() - .expect("tree to be set"); + fn multi_line_commit_message_uses_first_line_in_ref_log_ref_nonexisting() -> crate::Result { + let (repo, _keep) = crate::basic_rw_repo()?; + let parent = repo.find_reference("HEAD")?.peel_to_id_in_place()?; + let empty_tree_id = parent.object()?.commit_iter().tree_id().expect("tree to be set"); let author = git::actor::Signature::empty(); - let first_commit_id = repo - .commit( - "HEAD", - "hello there \r\n\nthe body", - author.clone(), - author.clone(), - empty_tree_id, - Some(parent), - ) - .unwrap(); + let first_commit_id = repo.commit( + "HEAD", + "hello there \r\n\nthe body", + author.clone(), + author.clone(), + empty_tree_id, + Some(parent), + )?; assert_eq!( first_commit_id, hex_to_id("1ff7decccf76bfa15bfdb0b66bac0c9144b4b083"), "the commit id is stable" ); - let current_commit = repo.head().unwrap().into_fully_peeled_id().expect("born").unwrap(); + let head_log_entries: Vec<_> = repo + .head()? + .log()? + .reverse_iter()? + .expect("log present") + .map(Result::unwrap) + .map(Result::unwrap) + .map(|l| l.message.to_owned()) + .collect(); + assert_eq!( + head_log_entries, + vec!["commit: hello there", "commit: c2", "commit (initial): c1"], + "we get the actual HEAD log, not the log of some reference" + ); + let current_commit = repo.head()?.into_fully_peeled_id().expect("born")?; assert_eq!(current_commit, &*first_commit_id, "the commit was set"); - let second_commit_id = repo - .commit( - "refs/heads/new-branch", - "committing into a new branch creates it", - author.clone(), - author, - empty_tree_id, - Some(first_commit_id), - ) - .unwrap(); + let second_commit_id = repo.commit( + "refs/heads/new-branch", + "committing into a new branch creates it", + author.clone(), + author, + empty_tree_id, + Some(first_commit_id), + )?; assert_eq!( second_commit_id, @@ -100,19 +105,20 @@ mod commit { "the second commit id is stable" ); - let mut branch = repo.find_reference("new-branch").unwrap(); - let current_commit = branch.peel_to_id_in_place().unwrap(); + let mut branch = repo.find_reference("new-branch")?; + let current_commit = branch.peel_to_id_in_place()?; assert_eq!(current_commit, second_commit_id, "the commit was set"); - let mut log = branch.log().unwrap(); - let mut log_iter = log.reverse_iter().unwrap().expect("log present"); + let mut log = branch.log()?; + let mut log_iter = log.reverse_iter()?.expect("log present"); assert_eq!( - log_iter.next().expect("one line").unwrap().unwrap().message, + log_iter.next().expect("one line")??.message, "commit: committing into a new branch creates it" ); assert!( log_iter.next().is_none(), "there is only one log line in the new branch" ); + Ok(()) } } From 2de86f904f6ee63e292f9c701cc3524e8bfe87e4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 12:08:54 +0800 Subject: [PATCH 46/91] [ref #190] reverse reflog ergonomics --- git-ref/src/lib.rs | 2 +- git-ref/src/peel.rs | 4 +- git-ref/src/store/file/log/iter.rs | 57 +++++++++++++++++++------ git-ref/tests/file/log.rs | 12 +++--- git-repository/tests/easy/ext/object.rs | 5 +-- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 3013f18f6bc..2c9c92adda0 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -66,7 +66,7 @@ pub enum Kind { Peeled, /// A ref that points to another reference, adding a level of indirection. /// - /// It can be resolved to an id using the [`peel_in_place_to_id()`][Reference::peel_to_id_in_place()] method. + /// It can be resolved to an id using the [`peel_in_place_to_id()`][`crate::file::ReferenceExt::peel_to_id_in_place()`] method. Symbolic, } diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs index 1b711c4f86e..91477ae3ce7 100644 --- a/git-ref/src/peel.rs +++ b/git-ref/src/peel.rs @@ -1,4 +1,4 @@ -/// A function for use in [`crate::Reference::peel_to_id_in_place()`] to indicate no peeling should happen. +/// A function for use in [`crate::file::ReferenceExt::peel_to_id_in_place()`] to indicate no peeling should happen. pub fn none( _id: git_hash::ObjectId, _buf: &mut Vec, @@ -16,7 +16,7 @@ pub mod to_id { use crate::file; quick_error! { - /// The error returned by [`crate::Reference::peel_to_id_in_place()`]. + /// The error returned by [`crate::file::ReferenceExt::peel_to_id_in_place()`]. #[derive(Debug)] #[allow(missing_docs)] pub enum Error { diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 63a998845c9..65ac9c9a78e 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -112,11 +112,35 @@ where }) } +/// +pub mod reverse { + use super::decode; + use quick_error::quick_error; + + quick_error! { + /// The error returned by the [`Reverse`][super::Reverse] iterator + #[derive(Debug)] + #[allow(missing_docs)] + pub enum Error { + Io(err: std::io::Error) { + display("The buffer could not be filled to make more lines available") + from() + source(err) + } + Decode(err: decode::Error) { + display("Could not decode log line") + from() + source(err) + } + } + } +} + impl<'a, F> Iterator for Reverse<'a, F> where F: std::io::Read + std::io::Seek, { - type Item = std::io::Result>; + type Item = Result; fn next(&mut self) -> Option { match (self.last_nl_pos.take(), self.read_and_pos.take()) { @@ -124,7 +148,7 @@ where (None, Some((mut read, pos))) => { let npos = pos.saturating_sub(self.buf.len() as u64); if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) { - return Some(Err(err)); + return Some(Err(err.into())); } let n = (pos - npos) as usize; @@ -133,7 +157,7 @@ where } let buf = &mut self.buf[..n]; if let Err(err) = read.read_exact(buf) { - return Some(Err(err)); + return Some(Err(err.into())); }; let last_byte = *buf.last().expect("we have read non-zero bytes before"); @@ -147,9 +171,13 @@ where self.read_and_pos = Some(read_and_pos); self.last_nl_pos = Some(start); let buf = &self.buf[start + 1..end]; - let res = Some(Ok(log::LineRef::from_bytes(buf) - .map_err(|err| decode::Error::new(err, LineNumber::FromEnd(self.count))) - .map(Into::into))); + let res = Some( + log::LineRef::from_bytes(buf) + .map_err(|err| { + reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count))) + }) + .map(Into::into), + ); self.count += 1; res } @@ -157,24 +185,29 @@ where let (mut read, last_read_pos) = read_and_pos; if last_read_pos == 0 { let buf = &self.buf[..end]; - Some(Ok(log::LineRef::from_bytes(buf) - .map_err(|err| decode::Error::new(err, LineNumber::FromEnd(self.count))) - .map(Into::into))) + Some( + log::LineRef::from_bytes(buf) + .map_err(|err| { + reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count))) + }) + .map(Into::into), + ) } else { let npos = last_read_pos.saturating_sub((self.buf.len() - end) as u64); if npos == last_read_pos { return Some(Err(std::io::Error::new( std::io::ErrorKind::Other, "buffer too small for line size", - ))); + ) + .into())); } let n = (last_read_pos - npos) as usize; self.buf.copy_within(0..end, n); if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) { - return Some(Err(err)); + return Some(Err(err.into())); } if let Err(err) = read.read_exact(&mut self.buf[..n]) { - return Some(Err(err)); + return Some(Err(err.into())); } self.read_and_pos = Some((read, npos)); self.last_nl_pos = Some(n + end); diff --git a/git-ref/tests/file/log.rs b/git-ref/tests/file/log.rs index 5d175c17eca..047f94f12e6 100644 --- a/git-ref/tests/file/log.rs +++ b/git-ref/tests/file/log.rs @@ -60,6 +60,8 @@ mod iter { } mod with_buffer_too_small_for_single_line { + use std::error::Error; + #[test] fn single_line() -> crate::Result { let mut buf = [0u8; 128]; @@ -76,8 +78,8 @@ mod iter { iter.next() .expect("an error") .expect_err("buffer too small") - .get_ref() - .expect("inner error") + .source() + .expect("source") .to_string(), "buffer too small for line size" ); @@ -108,7 +110,7 @@ mod iter { new_oid, signature: _, message, - } = iter.next().expect("a single line")??; + } = iter.next().expect("a single line")?; assert_eq!(previous_oid, hex_to_id("0000000000000000000000000000000000000000")); assert_eq!(new_oid, hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")); assert_eq!(message, "commit (initial): c1"); @@ -136,7 +138,7 @@ mod iter { new_oid, signature: _, message, - } = iter.next().expect("a single line")??; + } = iter.next().expect("a single line")?; assert_eq!(previous_oid, hex_to_id("0000000000000000000000000000000000000000")); assert_eq!(new_oid, hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")); assert_eq!(message, "commit (initial): c1"); @@ -145,7 +147,7 @@ mod iter { new_oid, signature: _, message, - } = iter.next().expect("a single line")??; + } = iter.next().expect("a single line")?; assert_eq!(message, "commit (initial): c2"); assert_eq!(previous_oid, hex_to_id("1000000000000000000000000000000000000000")); assert_eq!(new_oid, hex_to_id("234385f6d781b7e97062102c6a483440bfda2a03")); diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 3b83fb34e2c..12d7f4b83ec 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -46,7 +46,7 @@ mod commit { .reverse_iter()? .expect("log present") .next() - .expect("one line")?? + .expect("one line")? .message, "commit (initial): initial" ); @@ -79,7 +79,6 @@ mod commit { .reverse_iter()? .expect("log present") .map(Result::unwrap) - .map(Result::unwrap) .map(|l| l.message.to_owned()) .collect(); assert_eq!( @@ -112,7 +111,7 @@ mod commit { let mut log = branch.log()?; let mut log_iter = log.reverse_iter()?.expect("log present"); assert_eq!( - log_iter.next().expect("one line")??.message, + log_iter.next().expect("one line")?.message, "commit: committing into a new branch creates it" ); assert!( From 023dedc41aa859cd49d208392a586deaf77bd1bd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 12:11:57 +0800 Subject: [PATCH 47/91] thanks clippy --- git-repository/tests/easy/ext/object.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 12d7f4b83ec..8d5a090b8e4 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -79,7 +79,7 @@ mod commit { .reverse_iter()? .expect("log present") .map(Result::unwrap) - .map(|l| l.message.to_owned()) + .map(|l| l.message) .collect(); assert_eq!( head_log_entries, From 49fe1dc37c040206839c9d4399001ff12dc91174 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 12:18:30 +0800 Subject: [PATCH 48/91] [ref #190] refactor --- git-ref/CHANGELOG.md | 1 + git-ref/src/lib.rs | 20 +++++++++++++++++++ git-ref/src/store/file/log/iter.rs | 7 ++----- git-ref/src/store/file/log/line.rs | 5 +++-- git-ref/src/store/file/log/mod.rs | 16 --------------- .../loose/reflog/create_or_update/tests.rs | 10 +++++----- git-ref/tests/file/log.rs | 2 +- git-ref/tests/file/transaction/mod.rs | 8 ++++---- 8 files changed, 36 insertions(+), 33 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index c2d7c25a327..03bf9592ba6 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -4,6 +4,7 @@ * Replace `transaction::Create` with `transaction::PreviousValue` and remove `transaction::Create` * Remove `file::Reference` in favor of `Reference` +* Move `file::log::Line` to `log::Line` ### v0.6.1 diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 2c9c92adda0..26eed1a4314 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -39,6 +39,26 @@ pub use raw::Reference; mod target; +/// +pub mod log { + use bstr::BString; + use git_hash::ObjectId; + + /// A parsed ref log line that can be changed + #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] + #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] + pub struct Line { + /// The previous object id. Can be a null-sha to indicate this is a line for a new ref. + pub previous_oid: ObjectId, + /// The new object id. Can be a null-sha to indicate this ref is being deleted. + pub new_oid: ObjectId, + /// The signature of the currently configured committer. + pub signature: git_actor::Signature, + /// The message providing details about the operation performed in this log line. + pub message: BString, + } +} + /// pub mod peel; diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 65ac9c9a78e..44003dbda55 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -1,9 +1,6 @@ use bstr::ByteSlice; -use crate::store::{ - file, - file::{log, log::iter::decode::LineNumber}, -}; +use crate::store::file::{log, log::iter::decode::LineNumber}; /// pub mod decode { @@ -140,7 +137,7 @@ impl<'a, F> Iterator for Reverse<'a, F> where F: std::io::Read + std::io::Seek, { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { match (self.last_nl_pos.take(), self.read_and_pos.take()) { diff --git a/git-ref/src/store/file/log/line.rs b/git-ref/src/store/file/log/line.rs index 3c7cd67b06e..c0530a2aca8 100644 --- a/git-ref/src/store/file/log/line.rs +++ b/git-ref/src/store/file/log/line.rs @@ -1,6 +1,7 @@ use git_hash::ObjectId; -use crate::store::file::log::{Line, LineRef}; +use crate::log::Line; +use crate::store::file::log::LineRef; impl<'a> LineRef<'a> { /// Convert this instance into its mutable counterpart @@ -15,7 +16,7 @@ mod write { use bstr::{BStr, ByteSlice}; use quick_error::quick_error; - use crate::store::file::log::Line; + use crate::log::Line; quick_error! { /// The Error produced by [`Line::write_to()`] (but wrapped in an io error). diff --git a/git-ref/src/store/file/log/mod.rs b/git-ref/src/store/file/log/mod.rs index c4630a72484..ab7ce165232 100644 --- a/git-ref/src/store/file/log/mod.rs +++ b/git-ref/src/store/file/log/mod.rs @@ -1,6 +1,4 @@ use bstr::BStr; -use git_hash::ObjectId; -use git_object::bstr::BString; pub use super::loose::reflog::{create_or_update, Error}; @@ -23,17 +21,3 @@ pub struct LineRef<'a> { /// The message providing details about the operation performed in this log line. pub message: &'a BStr, } - -/// A parsed ref log line that can be changed -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Line { - /// The previous object id. Can be a null-sha to indicate this is a line for a new ref. - pub previous_oid: ObjectId, - /// The new object id. Can be a null-sha to indicate this ref is being deleted. - pub new_oid: ObjectId, - /// The signature of the currently configured committer. - pub signature: git_actor::Signature, - /// The message providing details about the operation performed in this log line. - pub message: BString, -} diff --git a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs index 95389187b6b..b91162b7044 100644 --- a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{file::WriteReflog, store::file::log, FullNameRef}; +use crate::{file::WriteReflog, FullNameRef}; use bstr::ByteSlice; use git_actor::{Sign, Signature, Time}; use git_lock::acquire::Fail; @@ -25,11 +25,11 @@ fn reflock(store: &file::Store, full_name: &str) -> Result { .map_err(Into::into) } -fn reflog_lines(store: &file::Store, name: &str, buf: &mut Vec) -> Result> { +fn reflog_lines(store: &file::Store, name: &str, buf: &mut Vec) -> Result> { store .reflog_iter(name, buf)? .expect("existing reflog") - .map(|l| l.map(log::Line::from)) + .map(|l| l.map(crate::log::Line::from)) .collect::, _>>() .map_err(Into::into) } @@ -83,7 +83,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append WriteReflog::Normal => { assert_eq!( reflog_lines(&store, full_name, &mut buf)?, - vec![log::Line { + vec![crate::log::Line { previous_oid: ObjectId::null_sha1(), new_oid: new, signature: committer.clone(), @@ -104,7 +104,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append assert_eq!(lines.len(), 2, "now there is another line"); assert_eq!( lines.last().expect("non-empty"), - &log::Line { + &crate::log::Line { previous_oid: previous, new_oid: new, signature: committer.clone(), diff --git a/git-ref/tests/file/log.rs b/git-ref/tests/file/log.rs index 047f94f12e6..a100027ffb7 100644 --- a/git-ref/tests/file/log.rs +++ b/git-ref/tests/file/log.rs @@ -90,7 +90,7 @@ mod iter { } mod with_buffer_big_enough_for_largest_line { - use git_ref::file::log::Line; + use git_ref::log::Line; use git_testtools::hex_to_id; #[test] diff --git a/git-ref/tests/file/transaction/mod.rs b/git-ref/tests/file/transaction/mod.rs index b5c52b83d58..9f56ba84469 100644 --- a/git-ref/tests/file/transaction/mod.rs +++ b/git-ref/tests/file/transaction/mod.rs @@ -4,12 +4,12 @@ mod prepare_and_commit { use git_hash::ObjectId; use git_ref::file; - fn reflog_lines(store: &file::Store, name: &str) -> crate::Result> { + fn reflog_lines(store: &file::Store, name: &str) -> crate::Result> { let mut buf = Vec::new(); let res = store .reflog_iter(name, &mut buf)? .expect("existing reflog") - .map(|l| l.map(file::log::Line::from)) + .map(|l| l.map(git_ref::log::Line::from)) .collect::, _>>()?; Ok(res) } @@ -32,8 +32,8 @@ mod prepare_and_commit { } } - fn log_line(previous: ObjectId, new: ObjectId, message: impl Into) -> file::log::Line { - file::log::Line { + fn log_line(previous: ObjectId, new: ObjectId, message: impl Into) -> git_ref::log::Line { + git_ref::log::Line { previous_oid: previous, new_oid: new, signature: committer(), From e751688a5378552b73cfddd07f38a0d0bb491b83 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 22:17:07 +0800 Subject: [PATCH 49/91] [repository #190] refactor --- git-repository/src/easy/reference/log.rs | 2 +- git-repository/tests/easy/ext/object.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs index 75c10588de7..e22bd8b41e6 100644 --- a/git-repository/src/easy/reference/log.rs +++ b/git-repository/src/easy/reference/log.rs @@ -26,7 +26,7 @@ where A: easy::Access + Sized, R: Borrow>, { - pub fn reverse_iter(&mut self) -> Result>, Error> { + pub fn iter_rev(&mut self) -> Result>, Error> { let buf = self.buf.deref_mut(); buf.resize(512, 0); Ok(self diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index 8d5a090b8e4..c6dec8dfa43 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -43,7 +43,7 @@ mod commit { let head = repo.head()?.into_referent(); assert_eq!( head.log()? - .reverse_iter()? + .iter_rev()? .expect("log present") .next() .expect("one line")? @@ -76,7 +76,7 @@ mod commit { let head_log_entries: Vec<_> = repo .head()? .log()? - .reverse_iter()? + .iter_rev()? .expect("log present") .map(Result::unwrap) .map(|l| l.message) @@ -109,7 +109,7 @@ mod commit { assert_eq!(current_commit, second_commit_id, "the commit was set"); let mut log = branch.log()?; - let mut log_iter = log.reverse_iter()?.expect("log present"); + let mut log_iter = log.iter_rev()?.expect("log present"); assert_eq!( log_iter.next().expect("one line")?.message, "commit: committing into a new branch creates it" From c3e240da47021226311681f3bcd48983f354243f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 22:23:27 +0800 Subject: [PATCH 50/91] =?UTF-8?q?[ref=20#190]=20add=20forward=20log=20iter?= =?UTF-8?q?=20and=20localize=20iter=20types=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …in the hopes people will use these instead of other types as one day in reflog the iterator will have to abstract over the actual log source. --- git-repository/src/easy/head.rs | 1 + git-repository/src/easy/reference/log.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index 6acf604db2f..e3b14258108 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -101,6 +101,7 @@ pub mod peel { where A: Access + Sized, { + // TODO: tests pub fn peel_to_id_in_place(&mut self) -> Option> { Some(match &mut self.kind { Kind::Unborn(_name) => return None, diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs index e22bd8b41e6..c966a68f9ed 100644 --- a/git-repository/src/easy/reference/log.rs +++ b/git-repository/src/easy/reference/log.rs @@ -21,12 +21,15 @@ pub enum Error { BorrowRepo(#[from] easy::borrow::repo::Error), } +pub type ReverseIter<'a> = git_ref::file::log::iter::Reverse<'a, std::fs::File>; +pub type ForwardIter<'a> = git_ref::file::log::iter::Forward<'a>; + impl<'repo, A, R> Buffer<'repo, A, R> where A: easy::Access + Sized, R: Borrow>, { - pub fn iter_rev(&mut self) -> Result>, Error> { + pub fn iter_rev(&mut self) -> Result>, Error> { let buf = self.buf.deref_mut(); buf.resize(512, 0); Ok(self @@ -35,6 +38,16 @@ where .inner .log_iter_rev(&self.reference.borrow().access.repo()?.refs, buf)?) } + + // TODO: tests + pub fn iter(&mut self) -> Result>, Error> { + let buf = self.buf.deref_mut(); + Ok(self + .reference + .borrow() + .inner + .log_iter(&self.reference.borrow().access.repo()?.refs, buf)?) + } } impl<'repo, A> Reference<'repo, A> From 28afd8e7cf09a17410c4a6ad57cddda608371364 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 4 Sep 2021 22:49:05 +0800 Subject: [PATCH 51/91] [repository #190] shortcut to create references --- git-repository/src/easy/ext/mod.rs | 14 ++++++ git-repository/src/easy/ext/reference.rs | 55 ++++++++++++++++++++---- git-repository/src/easy/reference/mod.rs | 14 ++++++ git-repository/tests/easy/ext/head.rs | 24 +++-------- 4 files changed, 81 insertions(+), 26 deletions(-) diff --git a/git-repository/src/easy/ext/mod.rs b/git-repository/src/easy/ext/mod.rs index 5fa97899240..fd2e928d643 100644 --- a/git-repository/src/easy/ext/mod.rs +++ b/git-repository/src/easy/ext/mod.rs @@ -4,3 +4,17 @@ pub use object::ObjectAccessExt; mod reference; pub use reference::ReferenceAccessExt; + +mod config { + use crate::easy; + + pub trait ConfigAccessExt: easy::Access + Sized { + // TODO: actual implementation + fn committer(&self) -> git_actor::Signature { + // TODO: actually read the committer information from git-config, probably it should be provided here + git_actor::Signature::empty() + } + } + impl ConfigAccessExt for A where A: easy::Access + Sized {} +} +pub use config::ConfigAccessExt; diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 70525529b57..5c29b5342a5 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,16 +1,18 @@ use std::convert::TryInto; +use crate::easy::ext::ConfigAccessExt; +use crate::{ + easy, + easy::{reference, Reference}, +}; +use bstr::BString; use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; +use git_ref::transaction::{LogChange, RefLog}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, - PartialNameRef, Target, -}; - -use crate::{ - easy, - easy::{reference, Reference}, + FullName, PartialNameRef, Target, }; /// Obtain and alter references comfortably @@ -41,6 +43,44 @@ pub trait ReferenceAccessExt: easy::Access + Sized { ) } + // TODO: more tests or usage + fn reference( + &self, + name: Name, + target: impl Into, + constraint: PreviousValue, + log_message: impl Into, + ) -> Result, reference::create::Error> + where + Name: TryInto, + reference::create::Error: From, + { + let name = name.try_into()?; + let edits = self.edit_reference( + RefEdit { + change: Change::Update { + log: LogChange { + mode: RefLog::AndReference, + force_create_reflog: false, + message: log_message.into(), + }, + expected: constraint, + new: Target::Peeled(target.into()), + }, + name, + deref: false, + }, + git_lock::acquire::Fail::Immediately, + None, + )?; + assert_eq!( + edits.len(), + 1, + "only one reference can be created, splits aren't possible" + ); + Ok(self.find_reference(edits[0].name.to_partial())?) + } + fn edit_reference( &self, edit: RefEdit, @@ -60,8 +100,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { let committer = match log_committer { Some(c) => c, None => { - // TODO: actually read the committer information from git-config, probably it should be provided here - committer_storage = actor::Signature::empty(); + committer_storage = self.committer(); &committer_storage } }; diff --git a/git-repository/src/easy/reference/mod.rs b/git-repository/src/easy/reference/mod.rs index 1e38f94f19b..9e389066eca 100644 --- a/git-repository/src/easy/reference/mod.rs +++ b/git-repository/src/easy/reference/mod.rs @@ -9,6 +9,20 @@ use crate::{ easy::{Oid, Reference}, }; +pub mod create { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Edit(#[from] easy::reference::edit::Error), + #[error(transparent)] + FindExistingReference(#[from] easy::reference::find::existing::Error), + #[error(transparent)] + NameValidation(#[from] git_validate::reference::name::Error), + } +} + pub mod edit { use crate::easy; diff --git a/git-repository/tests/easy/ext/head.rs b/git-repository/tests/easy/ext/head.rs index 36e998add52..e93639db3bc 100644 --- a/git-repository/tests/easy/ext/head.rs +++ b/git-repository/tests/easy/ext/head.rs @@ -1,9 +1,4 @@ -use std::convert::TryInto; - -use git_ref::{ - transaction::{Change, PreviousValue, RefEdit}, - Target, -}; +use git_ref::transaction::PreviousValue; use git_repository as git; use git_repository::prelude::ReferenceAccessExt; use git_testtools::hex_to_id; @@ -29,18 +24,11 @@ fn symbolic() -> crate::Result { #[test] fn detached() -> crate::Result { let (repo, _keep) = crate::basic_rw_repo()?; - repo.edit_reference( - RefEdit { - change: Change::Update { - log: Default::default(), - expected: PreviousValue::Any, - new: Target::Peeled(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")), - }, - name: "HEAD".try_into()?, - deref: false, - }, - git_lock::acquire::Fail::Immediately, - None, + repo.reference( + "HEAD", + hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41"), + PreviousValue::Any, + "", )?; let head = repo.head()?; From 7a111b126cfb318acb2d09d119315150a38b7cd3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 13:13:13 +0800 Subject: [PATCH 52/91] [repository #190] refactor --- git-ref/src/store/file/log/iter.rs | 3 ++- git-ref/src/store/file/log/line.rs | 3 +-- git-repository/src/easy/ext/reference.rs | 27 +++++++++++++++--------- git-repository/src/easy/reference/mod.rs | 2 -- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 44003dbda55..5283875da54 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -111,9 +111,10 @@ where /// pub mod reverse { - use super::decode; use quick_error::quick_error; + use super::decode; + quick_error! { /// The error returned by the [`Reverse`][super::Reverse] iterator #[derive(Debug)] diff --git a/git-ref/src/store/file/log/line.rs b/git-ref/src/store/file/log/line.rs index c0530a2aca8..21f6ee51c47 100644 --- a/git-ref/src/store/file/log/line.rs +++ b/git-ref/src/store/file/log/line.rs @@ -1,7 +1,6 @@ use git_hash::ObjectId; -use crate::log::Line; -use crate::store::file::log::LineRef; +use crate::{log::Line, store::file::log::LineRef}; impl<'a> LineRef<'a> { /// Convert this instance into its mutable counterpart diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 5c29b5342a5..189fff785dc 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,20 +1,20 @@ use std::convert::TryInto; -use crate::easy::ext::ConfigAccessExt; -use crate::{ - easy, - easy::{reference, Reference}, -}; use bstr::BString; use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; -use git_ref::transaction::{LogChange, RefLog}; use git_ref::{ - transaction::{Change, PreviousValue, RefEdit}, + transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, FullName, PartialNameRef, Target, }; +use crate::{ + easy, + easy::{ext::ConfigAccessExt, reference, Reference}, + ext::ReferenceExt, +}; + /// Obtain and alter references comfortably pub trait ReferenceAccessExt: easy::Access + Sized { fn tag( @@ -56,7 +56,8 @@ pub trait ReferenceAccessExt: easy::Access + Sized { reference::create::Error: From, { let name = name.try_into()?; - let edits = self.edit_reference( + let id = target.into(); + let mut edits = self.edit_reference( RefEdit { change: Change::Update { log: LogChange { @@ -65,7 +66,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { message: log_message.into(), }, expected: constraint, - new: Target::Peeled(target.into()), + new: Target::Peeled(id), }, name, deref: false, @@ -78,7 +79,13 @@ pub trait ReferenceAccessExt: easy::Access + Sized { 1, "only one reference can be created, splits aren't possible" ); - Ok(self.find_reference(edits[0].name.to_partial())?) + + Ok(git_ref::Reference { + name: edits.pop().expect("exactly one edit").name, + target: Target::Peeled(id), + peeled: None, + } + .attach(self)) } fn edit_reference( diff --git a/git-repository/src/easy/reference/mod.rs b/git-repository/src/easy/reference/mod.rs index 9e389066eca..ed41c55d0db 100644 --- a/git-repository/src/easy/reference/mod.rs +++ b/git-repository/src/easy/reference/mod.rs @@ -17,8 +17,6 @@ pub mod create { #[error(transparent)] Edit(#[from] easy::reference::edit::Error), #[error(transparent)] - FindExistingReference(#[from] easy::reference::find::existing::Error), - #[error(transparent)] NameValidation(#[from] git_validate::reference::name::Error), } } From 1fe1b42ac2b04f8145fc7312ea03cb47f791aec5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 13:30:12 +0800 Subject: [PATCH 53/91] =?UTF-8?q?[ref=20#190]=20more=20Target=20conversion?= =?UTF-8?q?s=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to make creating RefEdits easier. --- git-ref/src/target.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 58d934bc395..5574e7d1d5b 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -113,6 +113,18 @@ impl<'a> PartialEq> for Target { } } +impl From for Target { + fn from(id: ObjectId) -> Self { + Target::Peeled(id) + } +} + +impl From for Target { + fn from(name: FullName) -> Self { + Target::Symbolic(name) + } +} + impl fmt::Display for Target { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { From e34be7e24ee49a539b6ee8dc5737fdb23f416922 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 13:35:12 +0800 Subject: [PATCH 54/91] [ref #190] refactor --- git-ref/CHANGELOG.md | 1 + git-ref/src/fullname.rs | 9 ++++++++- git-ref/src/lib.rs | 2 +- git-ref/src/target.rs | 10 +++++----- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index 03bf9592ba6..b9575b397f7 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -5,6 +5,7 @@ * Replace `transaction::Create` with `transaction::PreviousValue` and remove `transaction::Create` * Remove `file::Reference` in favor of `Reference` * Move `file::log::Line` to `log::Line` +* `TargetRef::Symbolic(&BStr)` -> `TargetRef::Symbolic(FullNameRef)` ### v0.6.1 diff --git a/git-ref/src/fullname.rs b/git-ref/src/fullname.rs index df8eaf7a43c..3b8be4d4bf6 100644 --- a/git-ref/src/fullname.rs +++ b/git-ref/src/fullname.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom, path::Path}; use bstr::{BStr, BString, ByteSlice}; -use crate::FullName; +use crate::{FullName, FullNameRef}; impl TryFrom<&str> for FullName { type Error = git_validate::refname::Error; @@ -69,3 +69,10 @@ impl FullName { self.0.as_bstr() } } + +impl<'a> FullNameRef<'a> { + /// Create an owned copy of ourself + pub fn to_owned(&self) -> FullName { + FullName(self.0.to_owned()) + } +} diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 26eed1a4314..e461be64701 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -107,5 +107,5 @@ pub enum TargetRef<'a> { /// A ref that points to an object id Peeled(&'a oid), /// A ref that points to another reference by its validated name, adding a level of indirection. - Symbolic(&'a BStr), + Symbolic(FullNameRef<'a>), } diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 5574e7d1d5b..241b90e0e88 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,6 +1,6 @@ use std::fmt; -use bstr::{BStr, ByteSlice}; +use bstr::BStr; use git_hash::{oid, ObjectId}; use crate::{FullName, Kind, Target, TargetRef}; @@ -23,7 +23,7 @@ impl<'a> TargetRef<'a> { /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. pub fn as_name(&self) -> Option<&BStr> { match self { - TargetRef::Symbolic(path) => Some(path), + TargetRef::Symbolic(path) => Some(path.as_bstr()), TargetRef::Peeled(_) => None, } } @@ -54,7 +54,7 @@ impl Target { pub fn to_ref(&self) -> crate::TargetRef<'_> { match self { Target::Peeled(oid) => crate::TargetRef::Peeled(oid), - Target::Symbolic(name) => crate::TargetRef::Symbolic(name.0.as_bstr()), + Target::Symbolic(name) => crate::TargetRef::Symbolic(name.to_ref()), } } @@ -98,7 +98,7 @@ impl<'a> From> for Target { fn from(src: crate::TargetRef<'a>) -> Self { match src { crate::TargetRef::Peeled(oid) => Target::Peeled(oid.to_owned()), - crate::TargetRef::Symbolic(name) => Target::Symbolic(FullName(name.to_owned())), + crate::TargetRef::Symbolic(name) => Target::Symbolic(name.to_owned()), } } } @@ -107,7 +107,7 @@ impl<'a> PartialEq> for Target { fn eq(&self, other: &crate::TargetRef<'a>) -> bool { match (self, other) { (Target::Peeled(lhs), crate::TargetRef::Peeled(rhs)) => lhs == rhs, - (Target::Symbolic(lhs), crate::TargetRef::Symbolic(rhs)) => lhs.as_bstr() == *rhs, + (Target::Symbolic(lhs), crate::TargetRef::Symbolic(rhs)) => lhs.as_bstr() == rhs.as_bstr(), _ => false, } } From a985491bcea5f76942b863de8a9a89dd235dd0c9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 13:53:50 +0800 Subject: [PATCH 55/91] [repository #190] obtain the kind fo hash used in a repo --- git-hash/CHANGELOG.md | 6 +++++ git-hash/Cargo.toml | 2 +- git-hash/src/lib.rs | 1 - git-hash/src/owned.rs | 26 ++++++++++++++----- git-ref/src/store/file/loose/reflog.rs | 2 +- .../prepare_and_commit/create_or_update.rs | 4 +-- git-repository/src/easy/ext/config.rs | 16 ++++++++++++ git-repository/src/easy/ext/mod.rs | 13 +--------- git-repository/src/lib.rs | 2 +- git-repository/tests/easy/ext/object.rs | 4 +-- git-repository/tests/reference/mod.rs | 2 +- 11 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 git-hash/CHANGELOG.md create mode 100644 git-repository/src/easy/ext/config.rs diff --git a/git-hash/CHANGELOG.md b/git-hash/CHANGELOG.md new file mode 100644 index 00000000000..8056a99e898 --- /dev/null +++ b/git-hash/CHANGELOG.md @@ -0,0 +1,6 @@ +### 0.6.0 + +#### Breaking + +- `ObjectId::empty_tree()` now has a parameter: `Kind` +- `ObjectId::null_sha(…)` -> `ObjectId::null(…)` diff --git a/git-hash/Cargo.toml b/git-hash/Cargo.toml index d5acd4376d6..7c9829bf8aa 100644 --- a/git-hash/Cargo.toml +++ b/git-hash/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" edition = "2018" -include = ["src/**/*"] +include = ["src/**/*", "CHANGELOG.md"] [lib] doctest = false diff --git a/git-hash/src/lib.rs b/git-hash/src/lib.rs index 116c9bc7773..6fd705c27a1 100644 --- a/git-hash/src/lib.rs +++ b/git-hash/src/lib.rs @@ -7,7 +7,6 @@ mod borrowed; pub use borrowed::oid; -#[allow(missing_docs)] mod owned; pub use owned::ObjectId; diff --git a/git-hash/src/owned.rs b/git-hash/src/owned.rs index 1352f1342a1..2858fd676e7 100644 --- a/git-hash/src/owned.rs +++ b/git-hash/src/owned.rs @@ -1,11 +1,12 @@ -use std::{borrow::Borrow, fmt, io, ops::Deref}; +use std::{borrow::Borrow, convert::TryInto, fmt, io, ops::Deref}; -use crate::{borrowed::oid, SIZE_OF_SHA1_DIGEST}; +use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; /// An owned hash identifying objects, most commonly Sha1 #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum ObjectId { + /// A SHA 1 hash digest Sha1([u8; SIZE_OF_SHA1_DIGEST]), } @@ -45,8 +46,13 @@ impl ObjectId { out.write_all(&self.to_sha1_hex()) } - pub const fn empty_tree() -> ObjectId { - ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04") + /// The hash of an empty tree + pub const fn empty_tree(hash: Kind) -> ObjectId { + match hash { + Kind::Sha1 => { + ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04") + } + } } /// Returns true if this hash consists of all null bytes @@ -57,7 +63,7 @@ impl ObjectId { } /// Returns an Digest representing a hash with whose memory is zeroed. - pub const fn null_sha(kind: crate::Kind) -> ObjectId { + pub const fn null(kind: crate::Kind) -> ObjectId { match kind { crate::Kind::Sha1 => Self::null_sha1(), } @@ -118,7 +124,6 @@ impl ObjectId { } /// Returns an Digest representing a Sha1 with whose memory is zeroed. - /// TODO: remove this method replace its usage with `null_sha(kind)` to probably become hash independent. pub const fn null_sha1() -> ObjectId { ObjectId::Sha1([0u8; 20]) } @@ -130,6 +135,15 @@ impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId { } } +impl From<&[u8]> for ObjectId { + fn from(v: &[u8]) -> Self { + match v.len() { + 20 => Self::Sha1(v.try_into().expect("prior length validation")), + other => panic!("BUG: unsupported hash len: {}", other), + } + } +} + impl From<&crate::oid> for ObjectId { fn from(v: &oid) -> Self { match v.kind() { diff --git a/git-ref/src/store/file/loose/reflog.rs b/git-ref/src/store/file/loose/reflog.rs index 9f5006238e3..07234716e4b 100644 --- a/git-ref/src/store/file/loose/reflog.rs +++ b/git-ref/src/store/file/loose/reflog.rs @@ -152,7 +152,7 @@ pub mod create_or_update { write!( file, "{} {} ", - previous_oid.unwrap_or_else(|| ObjectId::null_sha(new.kind())), + previous_oid.unwrap_or_else(|| ObjectId::null(new.kind())), new ) .and_then(|_| committer.write_to(&mut file)) diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 484f1c81444..dd84206ef72 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -126,7 +126,7 @@ fn reference_with_explicit_value_must_match_the_value_on_update() -> crate::Resu #[test] fn the_existing_must_match_constraint_allow_non_existing_references_to_be_created() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; - let expected = PreviousValue::ExistingMustMatch(Target::Peeled(ObjectId::empty_tree())); + let expected = PreviousValue::ExistingMustMatch(Target::Peeled(ObjectId::empty_tree(git_hash::Kind::Sha1))); let edits = store .transaction() .prepare( @@ -281,7 +281,7 @@ fn reference_with_must_exist_constraint_must_exist_already_with_any_value() -> c let target = head.target; let previous_reflog_count = reflog_lines(&store, "HEAD")?.len(); - let new_target = Target::Peeled(ObjectId::empty_tree()); + let new_target = Target::Peeled(ObjectId::empty_tree(git_hash::Kind::Sha1)); let edits = store .transaction() .prepare( diff --git a/git-repository/src/easy/ext/config.rs b/git-repository/src/easy/ext/config.rs new file mode 100644 index 00000000000..af25315f4c2 --- /dev/null +++ b/git-repository/src/easy/ext/config.rs @@ -0,0 +1,16 @@ +use crate::easy; + +pub trait ConfigAccessExt: easy::Access + Sized { + // TODO: actual implementation + fn committer(&self) -> git_actor::Signature { + // TODO: actually read the committer information from git-config, probably it should be provided here + git_actor::Signature::empty() + } + + /// The kind of hash the repository is configured to use + fn hash_kind(&self) -> Result { + self.repo().map(|r| r.hash_kind) + } +} + +impl ConfigAccessExt for A where A: easy::Access + Sized {} diff --git a/git-repository/src/easy/ext/mod.rs b/git-repository/src/easy/ext/mod.rs index fd2e928d643..eb1807f168a 100644 --- a/git-repository/src/easy/ext/mod.rs +++ b/git-repository/src/easy/ext/mod.rs @@ -5,16 +5,5 @@ pub use object::ObjectAccessExt; mod reference; pub use reference::ReferenceAccessExt; -mod config { - use crate::easy; - - pub trait ConfigAccessExt: easy::Access + Sized { - // TODO: actual implementation - fn committer(&self) -> git_actor::Signature { - // TODO: actually read the committer information from git-config, probably it should be provided here - git_actor::Signature::empty() - } - } - impl ConfigAccessExt for A where A: easy::Access + Sized {} -} +mod config; pub use config::ConfigAccessExt; diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 6486ddea68d..79b1f9fb11b 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -152,7 +152,7 @@ pub struct Repository { pub(crate) odb: git_odb::linked::Store, /// The path to the worktree at which to find checked out files pub work_tree: Option, - hash_kind: git_hash::Kind, + pub(crate) hash_kind: git_hash::Kind, // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how // packs are refreshed. This would be `git_config::fs::Config` when ready. diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index c6dec8dfa43..dd8fdefb523 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -1,5 +1,5 @@ mod write_object { - use git_repository::prelude::ObjectAccessExt; + use git_repository::prelude::{ConfigAccessExt, ObjectAccessExt}; #[test] fn empty_tree() -> crate::Result { @@ -8,7 +8,7 @@ mod write_object { let oid = repo.write_object(&git_repository::objs::Tree::empty().into())?; assert_eq!( oid, - git_repository::hash::ObjectId::empty_tree(), + git_repository::hash::ObjectId::empty_tree(repo.hash_kind()?), "it produces a well-known empty tree id" ); Ok(()) diff --git a/git-repository/tests/reference/mod.rs b/git-repository/tests/reference/mod.rs index 42d68a99079..f607d954f06 100644 --- a/git-repository/tests/reference/mod.rs +++ b/git-repository/tests/reference/mod.rs @@ -4,7 +4,7 @@ mod log { #[test] fn message() { let mut commit = git::objs::Commit { - tree: git::hash::ObjectId::empty_tree(), + tree: git::hash::ObjectId::empty_tree(git_hash::Kind::Sha1), parents: Default::default(), author: Default::default(), committer: Default::default(), From 6efd90db54f7f7441b76159dba3be80c15657a3d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 13:55:41 +0800 Subject: [PATCH 56/91] Bump git-hash v0.6.0 --- Cargo.lock | 2 +- git-commitgraph/Cargo.toml | 2 +- git-diff/Cargo.toml | 2 +- git-features/Cargo.toml | 2 +- git-hash/Cargo.toml | 2 +- git-object/Cargo.toml | 2 +- git-odb/Cargo.toml | 2 +- git-pack/Cargo.toml | 2 +- git-protocol/Cargo.toml | 2 +- git-ref/Cargo.toml | 2 +- git-repository/Cargo.toml | 2 +- git-traverse/Cargo.toml | 2 +- tests/tools/Cargo.toml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 565aa0cee99..d636bb5c903 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "git-hash" -version = "0.5.1" +version = "0.6.0" dependencies = [ "hex", "quick-error", diff --git a/git-commitgraph/Cargo.toml b/git-commitgraph/Cargo.toml index 66871a6badb..1014ad3a1d3 100644 --- a/git-commitgraph/Cargo.toml +++ b/git-commitgraph/Cargo.toml @@ -17,7 +17,7 @@ serde1 = ["serde", "git-hash/serde1", "bstr/serde1"] [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["rustsha1"] } -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } bstr = { version = "0.2.13", default-features = false, features = ["std"] } byteorder = "1.2.3" diff --git a/git-diff/Cargo.toml b/git-diff/Cargo.toml index c2fdb81d871..1d66e178bba 100644 --- a/git-diff/Cargo.toml +++ b/git-diff/Cargo.toml @@ -14,7 +14,7 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } quick-error = "2.0.0" diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index 3b2d99ea4e1..a9c2ec5b1cd 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -49,7 +49,7 @@ path = "tests/pipe.rs" required-features = ["io-pipe"] [dependencies] -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } # 'parallel' feature crossbeam-utils = { version = "0.8.5", optional = true } diff --git a/git-hash/Cargo.toml b/git-hash/Cargo.toml index 7c9829bf8aa..22194b21407 100644 --- a/git-hash/Cargo.toml +++ b/git-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-hash" -version = "0.5.1" +version = "0.6.0" description = "Borrowed and owned git hash digests used to identify git objects" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" diff --git a/git-object/Cargo.toml b/git-object/Cargo.toml index e5066684842..9ff8f4edf4c 100644 --- a/git-object/Cargo.toml +++ b/git-object/Cargo.toml @@ -19,7 +19,7 @@ verbose-object-parsing-errors = ["nom/std"] all-features = true [dependencies] -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-validate = { version = "^0.5.0", path = "../git-validate" } git-actor = { version ="^0.5.0", path = "../git-actor" } diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index 27655ffa838..340658947a2 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -29,7 +29,7 @@ all-features = true [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["rustsha1", "walkdir", "zlib"] } -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } git-pack = { version ="^0.9.0", path = "../git-pack" } diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index 3b2c9079eb4..31b96eac2cc 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -33,7 +33,7 @@ all-features = true [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } git-traverse = { version ="^0.8.0", path = "../git-traverse" } git-diff = { version ="^0.9.0", path = "../git-diff" } diff --git a/git-protocol/Cargo.toml b/git-protocol/Cargo.toml index d6d7ac4e249..20b12293527 100644 --- a/git-protocol/Cargo.toml +++ b/git-protocol/Cargo.toml @@ -29,7 +29,7 @@ required-features = ["async-client"] [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["progress"] } git-transport = { version ="^0.11.0", path = "../git-transport" } -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } quick-error = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 07f5d38f55d..297ba88a553 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -25,7 +25,7 @@ required-features = ["internal-testing-git-features-parallel"] [dependencies] git-features = { version = "^0.16.0", path = "../git-features", features = ["walkdir"]} -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } git-validate = { version = "^0.5.0", path = "../git-validate" } git-actor = { version ="^0.5.0", path = "../git-actor" } diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index bad2374bcd5..b24ab16f3f2 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -43,7 +43,7 @@ git-validate = { version = "^0.5.0", path = "../git-validate" } git-config = { version ="^0.1.0", path = "../git-config" } git-odb = { version ="^0.21.0", path = "../git-odb" } -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } git-actor = { version ="^0.5.0", path = "../git-actor" } git-pack = { version ="^0.9.0", path = "../git-pack" } diff --git a/git-traverse/Cargo.toml b/git-traverse/Cargo.toml index 71f06abdb0e..b729a7b4b2e 100644 --- a/git-traverse/Cargo.toml +++ b/git-traverse/Cargo.toml @@ -14,7 +14,7 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.5.0", path = "../git-hash" } +git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } quick-error = "2.0.0" diff --git a/tests/tools/Cargo.toml b/tests/tools/Cargo.toml index ff745069c0d..8b4dde592f9 100644 --- a/tests/tools/Cargo.toml +++ b/tests/tools/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.5.0", path = "../../git-hash" } +git-hash = { version ="^0.6.0", path = "../../git-hash" } nom = { version = "7", default-features = false, features = ["std"]} bstr = "0.2.15" crc = "2.0.0" From 427f14622fb98e0397de2cae4d36a29f5915d375 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 14:18:21 +0800 Subject: [PATCH 57/91] [repository #190] prepare reference iteration --- git-repository/src/easy/ext/reference.rs | 12 +++++++ git-repository/src/easy/head.rs | 4 +-- git-repository/src/easy/iter.rs | 21 +++++++++++ git-repository/src/easy/mod.rs | 1 + git-repository/src/easy/reference/log.rs | 8 ++--- git-repository/src/easy/state.rs | 3 +- git-repository/tests/easy/ext/head.rs | 38 -------------------- git-repository/tests/easy/ext/mod.rs | 2 +- git-repository/tests/easy/ext/reference.rs | 41 ++++++++++++++++++++++ 9 files changed, 84 insertions(+), 46 deletions(-) create mode 100644 git-repository/src/easy/iter.rs delete mode 100644 git-repository/tests/easy/ext/head.rs create mode 100644 git-repository/tests/easy/ext/reference.rs diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 189fff785dc..4321d0483b2 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -144,6 +144,18 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .ok_or(reference::find::existing::Error::NotFound) } + fn iter_references(&self) -> Result, easy::iter::references::Error> { + let _state = self.state(); + todo!("") + // Ok(easy::iter::References { + // inner: self + // .repo()? + // .refs + // .iter(state.assure_packed_refs_uptodate(&repo.refs)?.as_ref())?, + // access: self, + // }) + } + fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result>, reference::find::Error> where Name: TryInto, Error = E>, diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index e3b14258108..3227f17a7f9 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -67,8 +67,8 @@ pub mod log { where A: easy::Access + Sized, { - pub fn log(&self) -> Result>, Error> { - Ok(easy::reference::log::Buffer { + pub fn log(&self) -> Result>, Error> { + Ok(easy::reference::log::State { reference: self.access.find_reference("HEAD")?, buf: self.access.state().try_borrow_mut_buf()?, _phantom: PhantomData::default(), diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs new file mode 100644 index 00000000000..00fc9f25020 --- /dev/null +++ b/git-repository/src/easy/iter.rs @@ -0,0 +1,21 @@ +#![allow(missing_docs)] +pub mod references { + /// An iterator over references + pub struct State<'r, A> { + // pub(crate) inner: git_ref::file::iter::LooseThenPacked<'r, 'r>, + pub(crate) access: &'r A, + } + + mod error { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("BUG: The repository could not be borrowed")] + BorrowRepo(#[from] easy::borrow::repo::Error), + } + } + pub use error::Error; +} diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 67f08ac2a02..baf1ce2ac6d 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -32,6 +32,7 @@ pub(crate) mod ext; pub mod borrow; pub mod commit; pub mod head; +pub mod iter; pub mod object; mod oid; pub mod reference; diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs index c966a68f9ed..20a0e92b2f0 100644 --- a/git-repository/src/easy/reference/log.rs +++ b/git-repository/src/easy/reference/log.rs @@ -4,7 +4,7 @@ use git_ref::file::ReferenceExt; use crate::{easy, easy::Reference}; -pub struct Buffer<'repo, A: 'repo, R> +pub struct State<'repo, A: 'repo, R> where R: Borrow>, { @@ -24,7 +24,7 @@ pub enum Error { pub type ReverseIter<'a> = git_ref::file::log::iter::Reverse<'a, std::fs::File>; pub type ForwardIter<'a> = git_ref::file::log::iter::Forward<'a>; -impl<'repo, A, R> Buffer<'repo, A, R> +impl<'repo, A, R> State<'repo, A, R> where A: easy::Access + Sized, R: Borrow>, @@ -54,8 +54,8 @@ impl<'repo, A> Reference<'repo, A> where A: easy::Access + Sized, { - pub fn log(&self) -> Result>, easy::borrow::state::Error> { - Ok(Buffer { + pub fn log(&self) -> Result>, easy::borrow::state::Error> { + Ok(State { reference: self, buf: self.access.state().try_borrow_mut_buf()?, _phantom: Default::default(), diff --git a/git-repository/src/easy/state.rs b/git-repository/src/easy/state.rs index 353045686d5..fb87faea13a 100644 --- a/git-repository/src/easy/state.rs +++ b/git-repository/src/easy/state.rs @@ -50,7 +50,8 @@ impl easy::ModifieablePackedRefsBuffer { } impl easy::State { - // TODO: this method should be on the Store itself, as one day there will be reftable support which lacks packed-refs + // TODO: ~~this method should be on the Store itself, as one day there will be reftable support which lacks packed-refs~~ + // Actually make it a RefMut instead and don't share across threads. Iteration would have to hold the lock for too long pub(crate) fn assure_packed_refs_uptodate( &self, file: &file::Store, diff --git a/git-repository/tests/easy/ext/head.rs b/git-repository/tests/easy/ext/head.rs deleted file mode 100644 index e93639db3bc..00000000000 --- a/git-repository/tests/easy/ext/head.rs +++ /dev/null @@ -1,38 +0,0 @@ -use git_ref::transaction::PreviousValue; -use git_repository as git; -use git_repository::prelude::ReferenceAccessExt; -use git_testtools::hex_to_id; - -#[test] -fn symbolic() -> crate::Result { - let repo = crate::basic_repo()?; - let head = repo.head()?; - match &head.kind { - git::easy::head::Kind::Symbolic(r) => { - assert_eq!( - r.target.as_id().map(ToOwned::to_owned), - Some(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")) - ); - } - _ => panic!("unexpected head kind"), - } - assert_eq!(head.name().expect("born").as_bstr(), "refs/heads/main"); - assert!(!head.is_detached()); - Ok(()) -} - -#[test] -fn detached() -> crate::Result { - let (repo, _keep) = crate::basic_rw_repo()?; - repo.reference( - "HEAD", - hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41"), - PreviousValue::Any, - "", - )?; - - let head = repo.head()?; - assert!(head.is_detached(), "head is detached"); - assert!(head.name().is_none()); - Ok(()) -} diff --git a/git-repository/tests/easy/ext/mod.rs b/git-repository/tests/easy/ext/mod.rs index 5fedc06139e..b0e326e04f0 100644 --- a/git-repository/tests/easy/ext/mod.rs +++ b/git-repository/tests/easy/ext/mod.rs @@ -1,2 +1,2 @@ -mod head; mod object; +mod reference; diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs new file mode 100644 index 00000000000..9035722a4c6 --- /dev/null +++ b/git-repository/tests/easy/ext/reference.rs @@ -0,0 +1,41 @@ +pub mod head { + + use git_ref::transaction::PreviousValue; + use git_repository as git; + use git_repository::prelude::ReferenceAccessExt; + use git_testtools::hex_to_id; + + #[test] + fn symbolic() -> crate::Result { + let repo = crate::basic_repo()?; + let head = repo.head()?; + match &head.kind { + git::easy::head::Kind::Symbolic(r) => { + assert_eq!( + r.target.as_id().map(ToOwned::to_owned), + Some(hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41")) + ); + } + _ => panic!("unexpected head kind"), + } + assert_eq!(head.name().expect("born").as_bstr(), "refs/heads/main"); + assert!(!head.is_detached()); + Ok(()) + } + + #[test] + fn detached() -> crate::Result { + let (repo, _keep) = crate::basic_rw_repo()?; + repo.reference( + "HEAD", + hex_to_id("3189cd3cb0af8586c39a838aa3e54fd72a872a41"), + PreviousValue::Any, + "", + )?; + + let head = repo.head()?; + assert!(head.is_detached(), "head is detached"); + assert!(head.name().is_none()); + Ok(()) + } +} From 8c532a4c78452dd11115cf36a906a27741858774 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 14:41:37 +0800 Subject: [PATCH 58/91] [repository #190] refactor --- git-repository/src/easy/ext/reference.rs | 8 +-- git-repository/src/easy/mod.rs | 28 ++------ git-repository/src/easy/reference/mod.rs | 87 +++++++++++++++++++++++- git-repository/src/easy/state.rs | 49 ++----------- 4 files changed, 103 insertions(+), 69 deletions(-) diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 4321d0483b2..52646989f01 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -163,10 +163,10 @@ pub trait ReferenceAccessExt: easy::Access + Sized { { let state = self.state(); let repo = self.repo()?; - match repo - .refs - .try_find(name, state.assure_packed_refs_uptodate(&repo.refs)?.as_ref()) - { + match repo.refs.try_find( + name, + state.assure_packed_refs_uptodate(&repo.refs)?.packed_refs.as_ref(), + ) { Ok(r) => match r { Some(r) => Ok(Some(Reference::from_ref(r, self))), None => Ok(None), diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index baf1ce2ac6d..1f6a5918660 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -14,14 +14,9 @@ use std::{ cell::RefCell, ops::{Deref, DerefMut}, - sync::Arc, - time::SystemTime, }; use git_hash::ObjectId; -use git_object as objs; -use git_odb as odb; -use git_ref as refs; use crate::Repository; @@ -63,7 +58,7 @@ pub struct ObjectRef<'repo, A> { /// The id of the object pub id: ObjectId, /// The kind of the object - pub kind: objs::Kind, + pub kind: git_object::Kind, /// The fully decoded object data pub data: std::cell::Ref<'repo, [u8]>, access: &'repo A, @@ -88,7 +83,7 @@ pub struct Object { /// The id of the object pub id: ObjectId, /// The kind of the object - pub kind: objs::Kind, + pub kind: git_object::Kind, /// The fully decoded object data pub data: Vec, } @@ -103,25 +98,16 @@ pub struct Reference<'r, A> { } #[cfg(not(feature = "local"))] -type PackCache = odb::pack::cache::Never; +type PackCache = git_odb::pack::cache::Never; #[cfg(feature = "local")] -type PackCache = odb::pack::cache::lru::StaticLinkedList<64>; - -#[derive(Default)] -struct ModifieablePackedRefsBuffer { - packed_refs: Option, - modified: Option, -} +type PackCache = git_odb::pack::cache::lru::StaticLinkedList<64>; /// State for use in `Easy*` to provide mutable parts of a repository such as caches and buffers. #[derive(Default)] pub struct State { - /// As the packed-buffer may hold onto a memory map, we avoid that to exist once per thread, multiplying system resources, cloning - /// it with every clone of the owning `Easy`. - /// This seems worth the cost of always going through an `Arc>>`. Note that `EasyArcExclusive` uses the same construct - /// but the reason we make this distinction at all is that there are other easy's that allows to chose exactly what you need in - /// your application. `State` is one size fits all with supporting single-threaded applications only. - packed_refs: Arc>, + /// As the packed-buffer may hold onto a memory map, so ideally this State is freed after use instead of keeping it around + /// for too long. At least `packed_refs` is lazily initialized. + packed_refs: RefCell, pack_cache: RefCell, buf: RefCell>, } diff --git a/git-repository/src/easy/reference/mod.rs b/git-repository/src/easy/reference/mod.rs index ed41c55d0db..87dd322bc80 100644 --- a/git-repository/src/easy/reference/mod.rs +++ b/git-repository/src/easy/reference/mod.rs @@ -51,6 +51,15 @@ pub mod peel_to_id_in_place { #[error("BUG: The repository could not be borrowed")] BorrowRepo(#[from] easy::borrow::repo::Error), } + + impl From for Error { + fn from(err: easy::reference::packed::Error) -> Self { + match err { + easy::reference::packed::Error::PackedRefsOpen(err) => Error::PackedRefsOpen(err), + easy::reference::packed::Error::BorrowState(err) => Error::BorrowState(err), + } + } + } } impl<'repo, A> Reference<'repo, A> { @@ -84,7 +93,7 @@ where let mut pack_cache = state.try_borrow_mut_pack_cache()?; let oid = self.inner.peel_to_id_in_place( &repo.refs, - state.assure_packed_refs_uptodate(&repo.refs)?.as_ref(), + state.assure_packed_refs_uptodate(&repo.refs)?.packed_refs.as_ref(), |oid, buf| { repo.odb .try_find(oid, buf, pack_cache.deref_mut()) @@ -97,6 +106,73 @@ where pub mod log; +pub(crate) mod packed { + use crate::easy; + use git_ref::file; + use std::cell::{BorrowError, BorrowMutError}; + use std::time::SystemTime; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + PackedRefsOpen(#[from] git_ref::packed::buffer::open::Error), + #[error("BUG: Part of interior state could not be borrowed.")] + BorrowState(#[from] easy::borrow::state::Error), + } + + impl From for Error { + fn from(err: BorrowError) -> Self { + Error::BorrowState(easy::borrow::state::Error::Borrow(err)) + } + } + impl From for Error { + fn from(err: BorrowMutError) -> Self { + Error::BorrowState(easy::borrow::state::Error::BorrowMut(err)) + } + } + + #[derive(Default)] + pub(crate) struct ModifieablePackedRefsBuffer { + pub(crate) packed_refs: Option, + modified: Option, + } + + impl ModifieablePackedRefsBuffer { + pub fn assure_packed_refs_uptodate( + &mut self, + file: &file::Store, + ) -> Result<(), git_ref::packed::buffer::open::Error> { + let packed_refs_modified_time = || file.packed_refs_path().metadata().and_then(|m| m.modified()).ok(); + if self.packed_refs.is_none() { + self.packed_refs = file.packed_buffer()?; + if self.packed_refs.is_some() { + self.modified = packed_refs_modified_time(); + } + } else { + let recent_modification = packed_refs_modified_time(); + match (&self.modified, recent_modification) { + (None, None) => {} + (Some(_), None) => { + self.packed_refs = None; + self.modified = None + } + (Some(cached_time), Some(modified_time)) => { + if *cached_time < modified_time { + self.packed_refs = file.packed_buffer()?; + self.modified = Some(modified_time); + } + } + (None, Some(modified_time)) => { + self.packed_refs = file.packed_buffer()?; + self.modified = Some(modified_time); + } + } + } + Ok(()) + } + } +} + pub mod find { use crate::easy; @@ -124,4 +200,13 @@ pub mod find { #[error("BUG: The repository could not be borrowed")] BorrowRepo(#[from] easy::borrow::repo::Error), } + + impl From for Error { + fn from(err: easy::reference::packed::Error) -> Self { + match err { + easy::reference::packed::Error::PackedRefsOpen(err) => Error::PackedRefsOpen(err), + easy::reference::packed::Error::BorrowState(err) => Error::BorrowState(err), + } + } + } } diff --git a/git-repository/src/easy/state.rs b/git-repository/src/easy/state.rs index fb87faea13a..07176f8b146 100644 --- a/git-repository/src/easy/state.rs +++ b/git-repository/src/easy/state.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] use std::cell::{Ref, RefMut}; -use git_ref::{file, packed}; +use git_ref::file; use crate::{ easy, @@ -10,42 +10,7 @@ use crate::{ impl Clone for easy::State { fn clone(&self) -> Self { - easy::State { - packed_refs: self.packed_refs.clone(), - ..Default::default() - } - } -} - -impl easy::ModifieablePackedRefsBuffer { - fn assure_packed_refs_uptodate(&mut self, file: &file::Store) -> Result<(), packed::buffer::open::Error> { - let packed_refs_modified_time = || file.packed_refs_path().metadata().and_then(|m| m.modified()).ok(); - if self.packed_refs.is_none() { - self.packed_refs = file.packed_buffer()?; - if self.packed_refs.is_some() { - self.modified = packed_refs_modified_time(); - } - } else { - let recent_modification = packed_refs_modified_time(); - match (&self.modified, recent_modification) { - (None, None) => {} - (Some(_), None) => { - self.packed_refs = None; - self.modified = None - } - (Some(cached_time), Some(modified_time)) => { - if *cached_time < modified_time { - self.packed_refs = file.packed_buffer()?; - self.modified = Some(modified_time); - } - } - (None, Some(modified_time)) => { - self.packed_refs = file.packed_buffer()?; - self.modified = Some(modified_time); - } - } - } - Ok(()) + easy::State { ..Default::default() } } } @@ -55,13 +20,11 @@ impl easy::State { pub(crate) fn assure_packed_refs_uptodate( &self, file: &file::Store, - ) -> Result>, packed::buffer::open::Error> { - let mut packed_refs = self.packed_refs.write(); + ) -> Result, easy::reference::packed::Error> { + let mut packed_refs = self.packed_refs.try_borrow_mut()?; packed_refs.assure_packed_refs_uptodate(file)?; - let packed_refs = parking_lot::RwLockWriteGuard::<'_, _>::downgrade(packed_refs); - Ok(parking_lot::RwLockReadGuard::<'_, _>::map(packed_refs, |buffer| { - &buffer.packed_refs - })) + drop(packed_refs); + Ok(self.packed_refs.try_borrow()?) } #[inline] From 2c0939a146b5973de26bd03987e075a34a84bc88 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 15:17:38 +0800 Subject: [PATCH 59/91] =?UTF-8?q?[repository=20#190]=20implementation=20of?= =?UTF-8?q?=20reference=20iteration=20(all()=20for=20now)=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …with an option to add 'prefixed' as well. --- git-repository/src/easy/ext/reference.rs | 17 +++--- git-repository/src/easy/iter.rs | 64 +++++++++++++++++++++- git-repository/src/easy/state.rs | 2 - git-repository/tests/easy/ext/reference.rs | 21 ++++++- 4 files changed, 89 insertions(+), 15 deletions(-) diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 52646989f01..f0950bd226f 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -145,15 +145,14 @@ pub trait ReferenceAccessExt: easy::Access + Sized { } fn iter_references(&self) -> Result, easy::iter::references::Error> { - let _state = self.state(); - todo!("") - // Ok(easy::iter::References { - // inner: self - // .repo()? - // .refs - // .iter(state.assure_packed_refs_uptodate(&repo.refs)?.as_ref())?, - // access: self, - // }) + let state = self.state(); + let repo = self.repo()?; + let packed_refs = state.assure_packed_refs_uptodate(&repo.refs)?; + Ok(easy::iter::references::State { + repo, + packed_refs, + access: self, + }) } fn try_find_reference<'a, Name, E>(&self, name: Name) -> Result>, reference::find::Error> diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index 00fc9f25020..6009dcfd184 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -1,21 +1,79 @@ #![allow(missing_docs)] pub mod references { + use crate::easy; + use std::cell::Ref; + /// An iterator over references - pub struct State<'r, A> { - // pub(crate) inner: git_ref::file::iter::LooseThenPacked<'r, 'r>, + pub struct State<'r, A> + where + A: easy::Access + Sized, + { + pub(crate) repo: A::RepoRef, + pub(crate) packed_refs: Ref<'r, easy::reference::packed::ModifieablePackedRefsBuffer>, pub(crate) access: &'r A, } + pub struct Iter<'r, A> { + inner: git_ref::file::iter::LooseThenPacked<'r, 'r>, + access: &'r A, + } + + impl<'r, A> State<'r, A> + where + A: easy::Access + Sized, + { + pub fn all(&self) -> Result, init::Error> { + Ok(Iter { + inner: self.repo.deref().refs.iter(self.packed_refs.packed_refs.as_ref())?, + access: self.access, + }) + } + } + + impl<'r, A> Iterator for Iter<'r, A> + where + A: easy::Access + Sized, + { + type Item = Result, Box>; + + fn next(&mut self) -> Option { + self.inner.next().map(|res| { + res.map_err(|err| Box::new(err) as Box) + .map(|r| easy::Reference::from_ref(r, self.access)) + }) + } + } + + pub mod init { + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + } + } + mod error { use crate::easy; #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] - Io(#[from] std::io::Error), + PackedRefsOpen(#[from] git_ref::packed::buffer::open::Error), + #[error("BUG: Part of interior state could not be borrowed.")] + BorrowState(#[from] easy::borrow::state::Error), #[error("BUG: The repository could not be borrowed")] BorrowRepo(#[from] easy::borrow::repo::Error), } + + impl From for Error { + fn from(err: easy::reference::packed::Error) -> Self { + match err { + easy::reference::packed::Error::PackedRefsOpen(err) => Error::PackedRefsOpen(err), + easy::reference::packed::Error::BorrowState(err) => Error::BorrowState(err), + } + } + } } pub use error::Error; + use std::ops::Deref; } diff --git a/git-repository/src/easy/state.rs b/git-repository/src/easy/state.rs index 07176f8b146..e3a29cdfe5a 100644 --- a/git-repository/src/easy/state.rs +++ b/git-repository/src/easy/state.rs @@ -15,8 +15,6 @@ impl Clone for easy::State { } impl easy::State { - // TODO: ~~this method should be on the Store itself, as one day there will be reftable support which lacks packed-refs~~ - // Actually make it a RefMut instead and don't share across threads. Iteration would have to hold the lock for too long pub(crate) fn assure_packed_refs_uptodate( &self, file: &file::Store, diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 9035722a4c6..daa53830a30 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -1,4 +1,23 @@ -pub mod head { +mod iter_references { + use git_repository::prelude::ReferenceAccessExt; + + #[test] + fn all() { + let repo = crate::basic_repo().unwrap(); + assert_eq!( + repo.iter_references() + .unwrap() + .all() + .unwrap() + .map(Result::unwrap) + .map(|r| r.name().as_bstr().to_owned()) + .collect::>(), + vec!["refs/heads/main"] + ); + } +} + +mod head { use git_ref::transaction::PreviousValue; use git_repository as git; From a6e19c9a49bdc6a7c5cabef0a8d93bfd48a74fcd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 22:32:07 +0800 Subject: [PATCH 60/91] [repository #190] prefixed reference iteration --- git-repository/src/easy/iter.rs | 13 ++++++ git-repository/src/easy/reference/log.rs | 1 + git-repository/tests/easy/ext/reference.rs | 54 ++++++++++++++++++---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index 6009dcfd184..876234f3def 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -4,6 +4,7 @@ pub mod references { use std::cell::Ref; /// An iterator over references + #[must_use] pub struct State<'r, A> where A: easy::Access + Sized, @@ -28,6 +29,17 @@ pub mod references { access: self.access, }) } + + pub fn prefixed(&self, prefix: impl AsRef) -> Result, init::Error> { + Ok(Iter { + inner: self + .repo + .deref() + .refs + .iter_prefixed(self.packed_refs.packed_refs.as_ref(), prefix)?, + access: self.access, + }) + } } impl<'r, A> Iterator for Iter<'r, A> @@ -76,4 +88,5 @@ pub mod references { } pub use error::Error; use std::ops::Deref; + use std::path::Path; } diff --git a/git-repository/src/easy/reference/log.rs b/git-repository/src/easy/reference/log.rs index 20a0e92b2f0..bbdf1e61eec 100644 --- a/git-repository/src/easy/reference/log.rs +++ b/git-repository/src/easy/reference/log.rs @@ -4,6 +4,7 @@ use git_ref::file::ReferenceExt; use crate::{easy, easy::Reference}; +#[must_use = "Iterators should be obtained from this log buffer"] pub struct State<'repo, A: 'repo, R> where R: Borrow>, diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index daa53830a30..92f63ef9955 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -1,19 +1,57 @@ mod iter_references { + use git_repository as git; use git_repository::prelude::ReferenceAccessExt; + fn repo() -> crate::Result { + crate::repo("make_references_repo.sh").map(|r| r.into_easy()) + } + + #[test] + fn all() -> crate::Result { + let repo = repo()?; + assert_eq!( + repo.iter_references()? + .all()? + .filter_map(Result::ok) + .map(|r| r.name().as_bstr().to_owned()) + .collect::>(), + vec![ + "refs/d1", + "refs/heads/d1", + "refs/heads/dt1", + "refs/heads/main", + "refs/heads/multi-link-target1", + "refs/loop-a", + "refs/loop-b", + "refs/multi-link", + "refs/remotes/origin/HEAD", + "refs/remotes/origin/main", + "refs/remotes/origin/multi-link-target3", + "refs/tags/dt1", + "refs/tags/multi-link-target2", + "refs/tags/t1" + ] + ); + Ok(()) + } + #[test] - fn all() { - let repo = crate::basic_repo().unwrap(); + fn prefixed() -> crate::Result { + let repo = repo()?; assert_eq!( - repo.iter_references() - .unwrap() - .all() - .unwrap() - .map(Result::unwrap) + repo.iter_references()? + .prefixed("refs/heads/")? + .filter_map(Result::ok) .map(|r| r.name().as_bstr().to_owned()) .collect::>(), - vec!["refs/heads/main"] + vec![ + "refs/heads/d1", + "refs/heads/dt1", + "refs/heads/main", + "refs/heads/multi-link-target1", + ] ); + Ok(()) } } From 1795a333c05c60a1a2f3164d5c4c78289eb7050c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 23:02:15 +0800 Subject: [PATCH 61/91] [ref #190] more conversion trait impls --- git-packetline/src/line/mod.rs | 2 +- git-ref/src/fullname.rs | 12 ++++++++++++ git-ref/src/target.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/git-packetline/src/line/mod.rs b/git-packetline/src/line/mod.rs index ffe3977d7b7..15604d891e2 100644 --- a/git-packetline/src/line/mod.rs +++ b/git-packetline/src/line/mod.rs @@ -52,7 +52,7 @@ impl<'a> PacketLineRef<'a> { }) } - /// Decode the band of this [`slice`][PacketLineRef::as_slice()], or panic if it is not actually a side-band line. + /// Decode the band of this [`slice`][PacketLineRef::as_slice()] pub fn decode_band(&self) -> Result, decode::band::Error> { let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?; Ok(match d[0] { diff --git a/git-ref/src/fullname.rs b/git-ref/src/fullname.rs index 3b8be4d4bf6..3bc41e81b20 100644 --- a/git-ref/src/fullname.rs +++ b/git-ref/src/fullname.rs @@ -38,6 +38,18 @@ impl TryFrom for FullName { } } +impl From for BString { + fn from(name: FullName) -> Self { + name.0 + } +} + +impl<'a> From> for &'a BStr { + fn from(name: FullNameRef<'a>) -> Self { + name.0 + } +} + impl<'a> From> for FullName { fn from(value: crate::FullNameRef<'a>) -> Self { FullName(value.as_bstr().into()) diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 241b90e0e88..c4358fd648d 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -4,6 +4,7 @@ use bstr::BStr; use git_hash::{oid, ObjectId}; use crate::{FullName, Kind, Target, TargetRef}; +use std::convert::TryFrom; impl<'a> TargetRef<'a> { /// Returns the kind of the target the ref is pointing to. @@ -119,6 +120,17 @@ impl From for Target { } } +impl TryFrom for ObjectId { + type Error = Target; + + fn try_from(value: Target) -> Result { + match value { + Target::Peeled(id) => Ok(id), + Target::Symbolic(_) => Err(value), + } + } +} + impl From for Target { fn from(name: FullName) -> Self { Target::Symbolic(name) From e4411ff43b24af79fefeaa4411e004dc504a4e2a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 23:16:59 +0800 Subject: [PATCH 62/91] =?UTF-8?q?[repository=20#190]=20cleanup=20usage=20o?= =?UTF-8?q?f=20bstr=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …so that it always comes from git-object if possible, and so that major users like git-ref re-export it. The re-export is really something to possibly change to put that onto git-repository level, as well. --- Cargo.lock | 2 -- git-ref/Cargo.toml | 3 +-- git-ref/src/fullname.rs | 2 +- git-ref/src/lib.rs | 5 +++-- git-ref/src/name.rs | 2 +- git-ref/src/namespace.rs | 2 +- git-ref/src/parse.rs | 2 +- git-ref/src/peel.rs | 2 +- git-ref/src/raw.rs | 2 +- git-ref/src/store/file/find.rs | 2 +- git-ref/src/store/file/log/iter.rs | 4 ++-- git-ref/src/store/file/log/line.rs | 8 ++++---- git-ref/src/store/file/log/mod.rs | 2 +- git-ref/src/store/file/loose/iter.rs | 2 +- git-ref/src/store/file/loose/reference/decode.rs | 2 +- git-ref/src/store/file/loose/reflog.rs | 2 +- .../src/store/file/loose/reflog/create_or_update/tests.rs | 2 +- git-ref/src/store/file/mod.rs | 4 ++-- git-ref/src/store/file/overlay.rs | 2 +- git-ref/src/store/file/transaction/commit.rs | 2 +- git-ref/src/store/file/transaction/mod.rs | 2 +- git-ref/src/store/file/transaction/prepare.rs | 2 +- git-ref/src/store/packed/decode.rs | 2 +- git-ref/src/store/packed/decode/tests.rs | 2 +- git-ref/src/store/packed/find.rs | 2 +- git-ref/src/store/packed/iter.rs | 4 ++-- git-ref/src/store/packed/mod.rs | 2 +- git-ref/src/target.rs | 2 +- git-ref/src/transaction/ext.rs | 2 +- git-ref/src/transaction/mod.rs | 2 +- git-ref/tests/file/log.rs | 4 ++-- git-ref/tests/file/reference.rs | 2 +- git-ref/tests/file/store/iter.rs | 4 ++-- git-ref/tests/file/transaction/mod.rs | 2 +- .../transaction/prepare_and_commit/create_or_update.rs | 2 +- git-ref/tests/packed/iter.rs | 2 +- git-ref/tests/transaction/mod.rs | 2 +- git-repository/Cargo.toml | 1 - git-repository/src/commit.rs | 2 +- git-repository/src/easy/ext/object.rs | 2 +- git-repository/src/easy/ext/reference.rs | 2 +- git-repository/src/lib.rs | 1 + git-repository/src/reference.rs | 2 +- git-repository/src/repository.rs | 2 +- 44 files changed, 52 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d636bb5c903..c655a83704b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1165,7 +1165,6 @@ dependencies = [ name = "git-ref" version = "0.7.0" dependencies = [ - "bstr", "filebuffer", "git-actor", "git-features", @@ -1188,7 +1187,6 @@ name = "git-repository" version = "0.8.1" dependencies = [ "anyhow", - "bstr", "git-actor", "git-config", "git-diff", diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 297ba88a553..864a2292d28 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -13,7 +13,7 @@ doctest = false test = true [features] -serde1 = ["serde", "bstr/serde1", "git-hash/serde1", "git-actor/serde1"] +serde1 = ["serde", "git-hash/serde1", "git-actor/serde1", "git-object/serde1"] internal-testing-git-features-parallel = ["git-features/parallel"] # test sorted parallel loose file traversal [[test]] @@ -33,7 +33,6 @@ git-lock = { version ="^1.0.0", path = "../git-lock" } git-tempfile = { version ="^1.0.0", path = "../git-tempfile" } quick-error = "2.0.0" -bstr = { version = "0.2.13", default-features = false, features = ["std"] } nom = { version = "7", default-features = false, features = ["std"]} serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} os_str_bytes = "3.1.0" diff --git a/git-ref/src/fullname.rs b/git-ref/src/fullname.rs index 3bc41e81b20..39fc590b02f 100644 --- a/git-ref/src/fullname.rs +++ b/git-ref/src/fullname.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, convert::TryFrom, path::Path}; -use bstr::{BStr, BString, ByteSlice}; +use git_object::bstr::{BStr, BString, ByteSlice}; use crate::{FullName, FullNameRef}; diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index e461be64701..a98cdcfbb80 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -18,8 +18,9 @@ //! * supersedes all of the above to allow handling hundreds of thousands of references. #![forbid(unsafe_code)] #![deny(missing_docs, rust_2018_idioms)] -use bstr::{BStr, BString}; use git_hash::{oid, ObjectId}; +pub use git_object::bstr; +use git_object::bstr::{BStr, BString}; mod store; pub use store::{file, packed}; @@ -41,8 +42,8 @@ mod target; /// pub mod log { - use bstr::BString; use git_hash::ObjectId; + use git_object::bstr::BString; /// A parsed ref log line that can be changed #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] diff --git a/git-ref/src/name.rs b/git-ref/src/name.rs index 4613335f8fe..dddded15d4c 100644 --- a/git-ref/src/name.rs +++ b/git-ref/src/name.rs @@ -4,7 +4,7 @@ use std::{ path::Path, }; -use bstr::{BStr, ByteSlice}; +use git_object::bstr::{BStr, ByteSlice}; use crate::{FullNameRef, PartialNameRef}; diff --git a/git-ref/src/namespace.rs b/git-ref/src/namespace.rs index 9cc9ec3d435..dbe0eba9de3 100644 --- a/git-ref/src/namespace.rs +++ b/git-ref/src/namespace.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, convert::TryInto, path::Path}; -use bstr::{BStr, BString, ByteSlice, ByteVec}; +use git_object::bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::{Namespace, PartialNameRef}; diff --git a/git-ref/src/parse.rs b/git-ref/src/parse.rs index fe3d058c7e1..f009f6fede9 100644 --- a/git-ref/src/parse.rs +++ b/git-ref/src/parse.rs @@ -1,4 +1,4 @@ -use bstr::{BStr, ByteSlice}; +use git_object::bstr::{BStr, ByteSlice}; use nom::{ branch::alt, bytes::complete::{tag, take_while_m_n}, diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs index 91477ae3ce7..02913d5ff79 100644 --- a/git-ref/src/peel.rs +++ b/git-ref/src/peel.rs @@ -10,7 +10,7 @@ pub fn none( pub mod to_id { use std::path::PathBuf; - use bstr::BString; + use git_object::bstr::BString; use quick_error::quick_error; use crate::file; diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index f375682ed97..11a4a74dac8 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -55,7 +55,7 @@ mod convert { } mod access { - use bstr::ByteSlice; + use git_object::bstr::ByteSlice; use crate::{raw::Reference, FullNameRef, Namespace}; diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 9e73d496306..6168fda7f55 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -4,8 +4,8 @@ use std::{ path::{Path, PathBuf}, }; -use bstr::ByteSlice; pub use error::Error; +use git_object::bstr::ByteSlice; use crate::{ file, diff --git a/git-ref/src/store/file/log/iter.rs b/git-ref/src/store/file/log/iter.rs index 5283875da54..28d4e0a1ecd 100644 --- a/git-ref/src/store/file/log/iter.rs +++ b/git-ref/src/store/file/log/iter.rs @@ -1,4 +1,4 @@ -use bstr::ByteSlice; +use git_object::bstr::ByteSlice; use crate::store::file::{log, log::iter::decode::LineNumber}; @@ -59,7 +59,7 @@ pub fn forward(lines: &[u8]) -> Forward<'_> { /// An iterator yielding parsed lines in a file from start to end, oldest to newest. pub struct Forward<'a> { - inner: std::iter::Enumerate>, + inner: std::iter::Enumerate>, } impl<'a> Iterator for Forward<'a> { diff --git a/git-ref/src/store/file/log/line.rs b/git-ref/src/store/file/log/line.rs index 21f6ee51c47..a9dfeb21f8e 100644 --- a/git-ref/src/store/file/log/line.rs +++ b/git-ref/src/store/file/log/line.rs @@ -12,7 +12,7 @@ impl<'a> LineRef<'a> { mod write { use std::io; - use bstr::{BStr, ByteSlice}; + use git_object::bstr::{BStr, ByteSlice}; use quick_error::quick_error; use crate::log::Line; @@ -77,7 +77,7 @@ impl<'a> From> for Line { /// pub mod decode { - use bstr::{BStr, ByteSlice}; + use git_object::bstr::{BStr, ByteSlice}; use nom::{ bytes::complete::{tag, take_while}, combinator::opt, @@ -90,7 +90,7 @@ pub mod decode { /// mod error { - use bstr::{BString, ByteSlice}; + use git_object::bstr::{BString, ByteSlice}; /// The error returned by [from_bytes(…)][super::Line::from_bytes()] #[derive(Debug)] @@ -172,9 +172,9 @@ pub mod decode { #[cfg(test)] mod test { - use bstr::ByteSlice; use git_actor::{Sign, Time}; use git_hash::ObjectId; + use git_object::bstr::ByteSlice; use super::*; diff --git a/git-ref/src/store/file/log/mod.rs b/git-ref/src/store/file/log/mod.rs index ab7ce165232..46d64c26b6d 100644 --- a/git-ref/src/store/file/log/mod.rs +++ b/git-ref/src/store/file/log/mod.rs @@ -1,4 +1,4 @@ -use bstr::BStr; +use git_object::bstr::BStr; pub use super::loose::reflog::{create_or_update, Error}; diff --git a/git-ref/src/store/file/loose/iter.rs b/git-ref/src/store/file/loose/iter.rs index 2e5bd6d9c1f..b24584d93dc 100644 --- a/git-ref/src/store/file/loose/iter.rs +++ b/git-ref/src/store/file/loose/iter.rs @@ -3,8 +3,8 @@ use std::{ path::{Path, PathBuf}, }; -use bstr::ByteSlice; use git_features::fs::walkdir::DirEntryIter; +use git_object::bstr::ByteSlice; use os_str_bytes::OsStrBytes; use crate::{ diff --git a/git-ref/src/store/file/loose/reference/decode.rs b/git-ref/src/store/file/loose/reference/decode.rs index c59c1f9802f..21db27cb2ac 100644 --- a/git-ref/src/store/file/loose/reference/decode.rs +++ b/git-ref/src/store/file/loose/reference/decode.rs @@ -1,7 +1,7 @@ use std::convert::{TryFrom, TryInto}; -use bstr::BString; use git_hash::ObjectId; +use git_object::bstr::BString; use nom::{ bytes::complete::{tag, take_while}, combinator::{map, opt}, diff --git a/git-ref/src/store/file/loose/reflog.rs b/git-ref/src/store/file/loose/reflog.rs index 07234716e4b..daff52c3456 100644 --- a/git-ref/src/store/file/loose/reflog.rs +++ b/git-ref/src/store/file/loose/reflog.rs @@ -89,8 +89,8 @@ pub mod create_or_update { path::{Path, PathBuf}, }; - use bstr::BStr; use git_hash::{oid, ObjectId}; + use git_object::bstr::BStr; use crate::store::{file, file::WriteReflog}; diff --git a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs index b91162b7044..3f7102b9c98 100644 --- a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -1,8 +1,8 @@ use super::*; use crate::{file::WriteReflog, FullNameRef}; -use bstr::ByteSlice; use git_actor::{Sign, Signature, Time}; use git_lock::acquire::Fail; +use git_object::bstr::ByteSlice; use git_testtools::hex_to_id; use std::{convert::TryInto, path::Path}; use tempfile::TempDir; diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 3ee93c30e9c..28bad2ab90d 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -38,12 +38,12 @@ pub struct Transaction<'s> { namespace: Option, } -pub(in crate::store::file) fn path_to_name(path: impl Into) -> bstr::BString { +pub(in crate::store::file) fn path_to_name(path: impl Into) -> git_object::bstr::BString { use os_str_bytes::OsStringBytes; let path = path.into().into_raw_vec(); #[cfg(windows)] let path = { - use bstr::ByteSlice; + use git_object::bstr::ByteSlice; path.replace(b"\\", b"/") }; path.into() diff --git a/git-ref/src/store/file/overlay.rs b/git-ref/src/store/file/overlay.rs index a8c8864d6c9..c54b36d6633 100644 --- a/git-ref/src/store/file/overlay.rs +++ b/git-ref/src/store/file/overlay.rs @@ -150,7 +150,7 @@ impl file::Store { mod error { use std::{io, path::PathBuf}; - use bstr::BString; + use git_object::bstr::BString; use quick_error::quick_error; use crate::store::file; diff --git a/git-ref/src/store/file/transaction/commit.rs b/git-ref/src/store/file/transaction/commit.rs index 4e84bab5975..75fe61bdb32 100644 --- a/git-ref/src/store/file/transaction/commit.rs +++ b/git-ref/src/store/file/transaction/commit.rs @@ -155,7 +155,7 @@ impl<'s> Transaction<'s> { } } mod error { - use bstr::BString; + use git_object::bstr::BString; use quick_error::quick_error; use crate::store::{file, packed}; diff --git a/git-ref/src/store/file/transaction/mod.rs b/git-ref/src/store/file/transaction/mod.rs index 9c8bb647b52..8eb6d3379d1 100644 --- a/git-ref/src/store/file/transaction/mod.rs +++ b/git-ref/src/store/file/transaction/mod.rs @@ -1,5 +1,5 @@ -use bstr::BString; use git_hash::ObjectId; +use git_object::bstr::BString; use crate::{ store::{file, file::Transaction}, diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 7b623f26552..68f1b4bff59 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -339,7 +339,7 @@ impl<'s> Transaction<'s> { } mod error { - use bstr::BString; + use git_object::bstr::BString; use quick_error::quick_error; use crate::{ diff --git a/git-ref/src/store/packed/decode.rs b/git-ref/src/store/packed/decode.rs index 10d91f154e0..5a90a57c230 100644 --- a/git-ref/src/store/packed/decode.rs +++ b/git-ref/src/store/packed/decode.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use bstr::{BStr, ByteSlice}; +use git_object::bstr::{BStr, ByteSlice}; use nom::{ bytes::complete::{tag, take_while}, combinator::{map, map_res, opt}, diff --git a/git-ref/src/store/packed/decode/tests.rs b/git-ref/src/store/packed/decode/tests.rs index 7fb4443a239..da48526e380 100644 --- a/git-ref/src/store/packed/decode/tests.rs +++ b/git-ref/src/store/packed/decode/tests.rs @@ -46,7 +46,7 @@ mod reference { } mod header { - use bstr::ByteSlice; + use git_object::bstr::ByteSlice; use git_testtools::to_bstr_err; use super::Result; diff --git a/git-ref/src/store/packed/find.rs b/git-ref/src/store/packed/find.rs index 4aa4218fdfc..1621e57ba36 100644 --- a/git-ref/src/store/packed/find.rs +++ b/git-ref/src/store/packed/find.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, convert::TryInto}; -use bstr::{BStr, BString, ByteSlice}; +use git_object::bstr::{BStr, BString, ByteSlice}; use crate::{store::packed, PartialNameRef}; diff --git a/git-ref/src/store/packed/iter.rs b/git-ref/src/store/packed/iter.rs index ce84ad4e5a4..ad43289977d 100644 --- a/git-ref/src/store/packed/iter.rs +++ b/git-ref/src/store/packed/iter.rs @@ -1,4 +1,4 @@ -use bstr::{BString, ByteSlice}; +use git_object::bstr::{BString, ByteSlice}; use crate::store::{packed, packed::decode}; @@ -94,7 +94,7 @@ impl<'a> packed::Iter<'a> { } mod error { - use bstr::BString; + use git_object::bstr::BString; use quick_error::quick_error; quick_error! { diff --git a/git-ref/src/store/packed/mod.rs b/git-ref/src/store/packed/mod.rs index fd7f3570504..630a22c7600 100644 --- a/git-ref/src/store/packed/mod.rs +++ b/git-ref/src/store/packed/mod.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use bstr::{BStr, BString}; use filebuffer::FileBuffer; use git_hash::ObjectId; +use git_object::bstr::{BStr, BString}; use crate::{transaction::RefEdit, FullNameRef}; diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index c4358fd648d..7d43eefdfe7 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,7 +1,7 @@ use std::fmt; -use bstr::BStr; use git_hash::{oid, ObjectId}; +use git_object::bstr::BStr; use crate::{FullName, Kind, Target, TargetRef}; use std::convert::TryFrom; diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index f33ce89c971..42cb400bca4 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -1,4 +1,4 @@ -use bstr::{BString, ByteVec}; +use git_object::bstr::{BString, ByteVec}; use crate::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target}, diff --git a/git-ref/src/transaction/mod.rs b/git-ref/src/transaction/mod.rs index a2641b6ddcd..06f6f3416d3 100644 --- a/git-ref/src/transaction/mod.rs +++ b/git-ref/src/transaction/mod.rs @@ -12,7 +12,7 @@ //! - errors during preparations will cause a perfect rollback //! * prepared transactions are committed to finalize the change //! - errors when committing while leave the ref store in an inconsistent, but operational state. -use bstr::BString; +use git_object::bstr::BString; use crate::{FullName, Target}; diff --git a/git-ref/tests/file/log.rs b/git-ref/tests/file/log.rs index a100027ffb7..be86fd304bf 100644 --- a/git-ref/tests/file/log.rs +++ b/git-ref/tests/file/log.rs @@ -1,6 +1,6 @@ mod line { mod write_to { - use bstr::ByteVec; + use git_object::bstr::ByteVec; use git_ref::file::log; #[test] @@ -159,8 +159,8 @@ mod iter { } } mod forward { - use bstr::B; use git_hash::ObjectId; + use git_object::bstr::B; use crate::file::log::iter::reflog; diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 13bd067ca80..1e0bd6f2c6f 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -191,7 +191,7 @@ mod parse { mktest!(ref_tag, b"reff: hello", "\"reff: hello\" could not be parsed"); } mod valid { - use bstr::ByteSlice; + use git_object::bstr::ByteSlice; use git_ref::file::loose::Reference; use git_testtools::hex_to_id; diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index d79a2e365a6..4e61a7d4864 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -1,12 +1,12 @@ use std::convert::TryInto; -use bstr::ByteSlice; +use git_object::bstr::ByteSlice; use git_testtools::hex_to_id; use crate::file::{store, store_at, store_with_packed_refs}; mod with_namespace { - use bstr::BString; + use git_object::bstr::BString; use git_object::bstr::ByteSlice; use crate::file::store_at; diff --git a/git-ref/tests/file/transaction/mod.rs b/git-ref/tests/file/transaction/mod.rs index 9f56ba84469..10b96a9bfef 100644 --- a/git-ref/tests/file/transaction/mod.rs +++ b/git-ref/tests/file/transaction/mod.rs @@ -1,7 +1,7 @@ mod prepare_and_commit { - use bstr::BString; use git_actor::{Sign, Time}; use git_hash::ObjectId; + use git_object::bstr::BString; use git_ref::file; fn reflog_lines(store: &file::Store, name: &str) -> crate::Result> { diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index dd84206ef72..f2ea9d60e33 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -2,10 +2,10 @@ use crate::file::{ store_with_packed_refs, store_writable, transaction::prepare_and_commit::{committer, empty_store, log_line, reflog_lines}, }; -use bstr::ByteSlice; use git_hash::ObjectId; use git_lock::acquire::Fail; use git_object::bstr::BString; +use git_object::bstr::ByteSlice; use git_ref::file::ReferenceExt; use git_ref::transaction::PreviousValue; use git_ref::{ diff --git a/git-ref/tests/packed/iter.rs b/git-ref/tests/packed/iter.rs index dbaf884352f..680cb94509c 100644 --- a/git-ref/tests/packed/iter.rs +++ b/git-ref/tests/packed/iter.rs @@ -1,6 +1,6 @@ use std::convert::TryInto; -use bstr::ByteSlice; +use git_object::bstr::ByteSlice; use git_ref::packed; use crate::file::{store_at, store_with_packed_refs}; diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index e51bfbfa200..2ffd276841b 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -1,7 +1,7 @@ mod refedit_ext { use std::{cell::RefCell, collections::BTreeMap, convert::TryInto}; - use bstr::{BString, ByteSlice}; + use git_object::bstr::{BString, ByteSlice}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit, RefEditsExt, RefLog}, PartialNameRef, Target, diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index b24ab16f3f2..929f91e2911 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -54,7 +54,6 @@ git-protocol = { version ="^0.10.0", path = "../git-protocol", optional = true } git-diff = { version ="^0.9.0", path = "../git-diff", optional = true } git-features = { version = "^0.16.0", path = "../git-features", features = ["progress"] } -bstr = "0.2.15" signal-hook = { version = "0.3.9", default-features = false } thiserror = "1.0.26" parking_lot = { version = "0.11.2", features = ["arc_lock"] } diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs index a3b545c948d..d9e408a8894 100644 --- a/git-repository/src/commit.rs +++ b/git-repository/src/commit.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use bstr::{BStr, BString, ByteSlice, ByteVec}; +use git_object::bstr::{BStr, BString, ByteSlice, ByteVec}; /// An empty array of a type usable with the `git::easy` API to help declaring no parents should be used pub const NO_PARENT_IDS: [git_hash::ObjectId; 0] = []; diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 5b1daa4a8d8..53c0070a576 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -1,7 +1,7 @@ use std::{convert::TryInto, ops::DerefMut}; -use bstr::BString; use git_hash::ObjectId; +use git_object::bstr::BString; use git_odb::{Find, FindExt}; use git_ref::{ transaction::{LogChange, PreviousValue, RefLog}, diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index f0950bd226f..3eada6dd071 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,9 +1,9 @@ use std::convert::TryInto; -use bstr::BString; use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; +use git_object::bstr::BString; use git_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, FullName, PartialNameRef, Target, diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 79b1f9fb11b..fed5e274849 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -69,6 +69,7 @@ //! * [`odb`] //! * [`pack`][odb::pack] //! * [`refs`] +//! * [`bstr`][refs::bstr] //! * [`interrupt`] //! * [`tempfile`] //! * [`lock`] diff --git a/git-repository/src/reference.rs b/git-repository/src/reference.rs index 3ce5bac5c27..196d15367f2 100644 --- a/git-repository/src/reference.rs +++ b/git-repository/src/reference.rs @@ -1,6 +1,6 @@ /// pub mod log { - use bstr::{BString, ByteSlice, ByteVec}; + use git_object::bstr::{BString, ByteSlice, ByteVec}; use git_object::Commit; use crate::commit; diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 81be37d8f98..346492623c3 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -50,7 +50,7 @@ pub mod open { #[error(transparent)] ObjectStoreInitialization(#[from] git_odb::linked::init::Error), #[error("Cannot handle objects formatted as {:?}", .name)] - UnsupportedObjectFormat { name: bstr::BString }, + UnsupportedObjectFormat { name: git_object::bstr::BString }, } impl Repository { From 3b7ffde385b1984393ee65a7505ad7221fecd0dc Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 23:20:59 +0800 Subject: [PATCH 63/91] [repository #190] public bstr re-export --- git-repository/src/lib.rs | 4 ++-- git-repository/tests/commit/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index fed5e274849..2d9863f6db7 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -64,12 +64,11 @@ //! * [`hash`] //! * [`url`] //! * [`actor`] +//! * [`bstr`][bstr] //! * [`objs`] -//! * [`bstr`][objs::bstr] //! * [`odb`] //! * [`pack`][odb::pack] //! * [`refs`] -//! * [`bstr`][refs::bstr] //! * [`interrupt`] //! * [`tempfile`] //! * [`lock`] @@ -108,6 +107,7 @@ pub use git_tempfile as tempfile; pub use git_traverse as traverse; #[cfg(all(feature = "unstable", feature = "git-url"))] pub use git_url as url; +pub use objs::bstr; pub mod interrupt; diff --git a/git-repository/tests/commit/mod.rs b/git-repository/tests/commit/mod.rs index 7860c268f62..78e1c03867f 100644 --- a/git-repository/tests/commit/mod.rs +++ b/git-repository/tests/commit/mod.rs @@ -1,7 +1,7 @@ pub mod summary { use std::borrow::Cow; - use bstr::ByteSlice; + use git::bstr::ByteSlice; use git_repository as git; #[test] From 3d8796e670f9bb5d2ed22fb3b75130a599737341 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 23:27:17 +0800 Subject: [PATCH 64/91] [repository #190] turns out we need bstr with unicode support --- Cargo.lock | 1 + git-repository/Cargo.toml | 1 + git-repository/src/commit.rs | 2 +- git-repository/src/easy/ext/object.rs | 2 +- git-repository/src/easy/ext/reference.rs | 2 +- git-repository/src/lib.rs | 2 +- git-repository/src/path/create.rs | 2 +- git-repository/src/path/is_git.rs | 2 +- git-repository/src/reference.rs | 2 +- git-repository/src/repository.rs | 2 +- 10 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c655a83704b..9e06d628f1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,6 +1187,7 @@ name = "git-repository" version = "0.8.1" dependencies = [ "anyhow", + "bstr", "git-actor", "git-config", "git-diff", diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 929f91e2911..8cfe236599d 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -54,6 +54,7 @@ git-protocol = { version ="^0.10.0", path = "../git-protocol", optional = true } git-diff = { version ="^0.9.0", path = "../git-diff", optional = true } git-features = { version = "^0.16.0", path = "../git-features", features = ["progress"] } +bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"]} signal-hook = { version = "0.3.9", default-features = false } thiserror = "1.0.26" parking_lot = { version = "0.11.2", features = ["arc_lock"] } diff --git a/git-repository/src/commit.rs b/git-repository/src/commit.rs index d9e408a8894..a3b545c948d 100644 --- a/git-repository/src/commit.rs +++ b/git-repository/src/commit.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use git_object::bstr::{BStr, BString, ByteSlice, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; /// An empty array of a type usable with the `git::easy` API to help declaring no parents should be used pub const NO_PARENT_IDS: [git_hash::ObjectId; 0] = []; diff --git a/git-repository/src/easy/ext/object.rs b/git-repository/src/easy/ext/object.rs index 53c0070a576..5b1daa4a8d8 100644 --- a/git-repository/src/easy/ext/object.rs +++ b/git-repository/src/easy/ext/object.rs @@ -1,7 +1,7 @@ use std::{convert::TryInto, ops::DerefMut}; +use bstr::BString; use git_hash::ObjectId; -use git_object::bstr::BString; use git_odb::{Find, FindExt}; use git_ref::{ transaction::{LogChange, PreviousValue, RefLog}, diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 3eada6dd071..f0950bd226f 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,9 +1,9 @@ use std::convert::TryInto; +use bstr::BString; use git_actor as actor; use git_hash::ObjectId; use git_lock as lock; -use git_object::bstr::BString; use git_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, FullName, PartialNameRef, Target, diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 2d9863f6db7..3a84ce3174c 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -88,6 +88,7 @@ use std::{path::PathBuf, rc::Rc, sync::Arc}; // Re-exports to make this a potential one-stop shop crate avoiding people from having to reference various crates themselves. // This also means that their major version changes affect our major version, but that's alright as we directly expose their // APIs/instances anyway. +pub use bstr; pub use git_actor as actor; #[cfg(all(feature = "unstable", feature = "git-diff"))] pub use git_diff as diff; @@ -107,7 +108,6 @@ pub use git_tempfile as tempfile; pub use git_traverse as traverse; #[cfg(all(feature = "unstable", feature = "git-url"))] pub use git_url as url; -pub use objs::bstr; pub mod interrupt; diff --git a/git-repository/src/path/create.rs b/git-repository/src/path/create.rs index 2aa5f6e4d98..d414faa77e3 100644 --- a/git-repository/src/path/create.rs +++ b/git-repository/src/path/create.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use git_object::bstr::ByteSlice; +use bstr::ByteSlice; /// The error used in [`into()`]. #[derive(Debug, thiserror::Error)] diff --git a/git-repository/src/path/is_git.rs b/git-repository/src/path/is_git.rs index d643587c1f1..1c0f0ceb51f 100644 --- a/git-repository/src/path/is_git.rs +++ b/git-repository/src/path/is_git.rs @@ -5,7 +5,7 @@ pub enum Error { #[error("Could not find a valid HEAD reference")] FindHeadRef(#[from] git_ref::file::find::existing::Error), #[error("Expected HEAD at '.git/HEAD', got '.git/{}'", .name)] - MisplacedHead { name: git_object::bstr::BString }, + MisplacedHead { name: bstr::BString }, #[error("Expected an objects directory at '{}'", .missing.display())] MissingObjectsDirectory { missing: PathBuf }, #[error("Expected a refs directory at '{}'", .missing.display())] diff --git a/git-repository/src/reference.rs b/git-repository/src/reference.rs index 196d15367f2..3ce5bac5c27 100644 --- a/git-repository/src/reference.rs +++ b/git-repository/src/reference.rs @@ -1,6 +1,6 @@ /// pub mod log { - use git_object::bstr::{BString, ByteSlice, ByteVec}; + use bstr::{BString, ByteSlice, ByteVec}; use git_object::Commit; use crate::commit; diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 346492623c3..81be37d8f98 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -50,7 +50,7 @@ pub mod open { #[error(transparent)] ObjectStoreInitialization(#[from] git_odb::linked::init::Error), #[error("Cannot handle objects formatted as {:?}", .name)] - UnsupportedObjectFormat { name: git_object::bstr::BString }, + UnsupportedObjectFormat { name: bstr::BString }, } impl Repository { From 16b2232c70ad331e17e76ccca3b950963906aa81 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Sep 2021 23:29:25 +0800 Subject: [PATCH 65/91] [ref #190] Make References sortable --- git-ref/src/lib.rs | 19 +------------------ git-ref/src/log.rs | 16 ++++++++++++++++ git-ref/src/raw.rs | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) create mode 100644 git-ref/src/log.rs diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index a98cdcfbb80..035da08a78b 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -41,24 +41,7 @@ pub use raw::Reference; mod target; /// -pub mod log { - use git_hash::ObjectId; - use git_object::bstr::BString; - - /// A parsed ref log line that can be changed - #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] - #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] - pub struct Line { - /// The previous object id. Can be a null-sha to indicate this is a line for a new ref. - pub previous_oid: ObjectId, - /// The new object id. Can be a null-sha to indicate this ref is being deleted. - pub new_oid: ObjectId, - /// The signature of the currently configured committer. - pub signature: git_actor::Signature, - /// The message providing details about the operation performed in this log line. - pub message: BString, - } -} +pub mod log; /// pub mod peel; diff --git a/git-ref/src/log.rs b/git-ref/src/log.rs new file mode 100644 index 00000000000..78b2673f40a --- /dev/null +++ b/git-ref/src/log.rs @@ -0,0 +1,16 @@ +use git_hash::ObjectId; +use git_object::bstr::BString; + +/// A parsed ref log line that can be changed +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Line { + /// The previous object id. Can be a null-sha to indicate this is a line for a new ref. + pub previous_oid: ObjectId, + /// The new object id. Can be a null-sha to indicate this ref is being deleted. + pub new_oid: ObjectId, + /// The signature of the currently configured committer. + pub signature: git_actor::Signature, + /// The message providing details about the operation performed in this log line. + pub message: BString, +} diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 11a4a74dac8..c9e070413a5 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -3,7 +3,7 @@ use git_hash::ObjectId; use crate::{FullName, Target}; /// A fully owned backend agnostic reference -#[derive(Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Reference { /// The path to uniquely identify this ref within its store. pub name: FullName, From 0b0fea4f6315373f1c1c103fa50ef6f798e9d7fd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 07:55:42 +0800 Subject: [PATCH 66/91] [#190] faster builds with debug=false and dependency caching --- .cargo/config.toml | 3 +++ .github/workflows/rust.yml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 7f9f65305a7..05bf8918570 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ +[profile.dev] +debug = false + [build] rustflags = "--cfg unsound_local_offset -C target-cpu=native" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e6bb6c6e355..3525bd504e1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: clippy run: cargo clippy --all - name: fmt @@ -44,6 +45,7 @@ jobs: profile: default toolchain: stable override: true + - uses: Swatinem/rust-cache@v1 - name: "Check default features build on windows" uses: actions-rs/cargo@v1 with: @@ -75,4 +77,4 @@ jobs: - uses: actions/checkout@v2 - uses: EmbarkStudios/cargo-deny-action@v1 with: - command: check ${{ matrix.checks }} \ No newline at end of file + command: check ${{ matrix.checks }} From a22c95bac4947c849ad1372a9588ea945d14fd49 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 08:20:22 +0800 Subject: [PATCH 67/91] [#190] run tests faster (at the cost of compile time) --- Cargo.toml | 11 +++++++++++ etc/check-package-size.sh | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4d906481fdb..bc68ba1dfc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,17 @@ env_logger = { version = "0.9.0", optional = true, default-features = false, fea crosstermion = { version = "0.8.0", optional = true, default-features = false } futures-lite = { version = "1.12.0", optional = true, default-features = false, features = ["std"] } +[profile.dev.package] +git-object.opt-level = 3 +git-ref.opt-level = 3 +git-pack.opt-level = 3 +git-hash.opt-level = 3 +git-actor.opt-level = 3 +git-config.opt-level = 3 +miniz_oxide.opt-level = 3 +sha-1.opt-level = 3 +sha1.opt-level = 3 + [profile.release] overflow-checks = false lto = "fat" diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 0eea015adbe..8022b9adcb5 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -15,7 +15,7 @@ function indent () { } echo "in root: gitoxide CLI" -indent cargo diet -n --package-size-limit 25KB +#indent cargo diet -n --package-size-limit 25KB - fails right now because of dotted profile.dev.package (enter cargo-smart-release && indent cargo diet -n --package-size-limit 15KB) (enter git-actor && indent cargo diet -n --package-size-limit 5KB) (enter git-tempfile && indent cargo diet -n --package-size-limit 20KB) From d14f073707c2f4641a271ba7965ec8281638e8df Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 12:29:31 +0800 Subject: [PATCH 68/91] [repository #190] transparent namespace support --- git-ref/src/fullname.rs | 21 +++++- git-ref/src/lib.rs | 2 +- git-ref/src/namespace.rs | 16 +++- git-ref/src/target.rs | 3 +- git-ref/tests/file/store/iter.rs | 3 +- git-ref/tests/fullname/mod.rs | 22 ++++++ git-ref/tests/namespace/mod.rs | 12 +++ git-ref/tests/refs.rs | 1 + git-repository/src/easy/ext/reference.rs | 55 ++++++++++---- git-repository/src/easy/head.rs | 11 +-- git-repository/src/easy/iter.rs | 38 +++++++--- git-repository/src/easy/oid.rs | 14 ++++ git-repository/src/easy/reference/mod.rs | 32 ++++++-- git-repository/src/lib.rs | 1 + git-repository/src/reference.rs | 28 ------- git-repository/src/reference/log.rs | 25 +++++++ git-repository/src/reference/mod.rs | 2 + git-repository/src/repository.rs | 1 + git-repository/tests/easy/ext/reference.rs | 87 ++++++++++++++++++++++ git-repository/tests/easy/reference.rs | 2 +- git-repository/tests/repo.rs | 17 +++-- 21 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 git-ref/tests/fullname/mod.rs delete mode 100644 git-repository/src/reference.rs create mode 100644 git-repository/src/reference/log.rs create mode 100644 git-repository/src/reference/mod.rs diff --git a/git-ref/src/fullname.rs b/git-ref/src/fullname.rs index 39fc590b02f..b11048ebf54 100644 --- a/git-ref/src/fullname.rs +++ b/git-ref/src/fullname.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, convert::TryFrom, path::Path}; use git_object::bstr::{BStr, BString, ByteSlice}; -use crate::{FullName, FullNameRef}; +use crate::{bstr::ByteVec, FullName, FullNameRef, Namespace}; impl TryFrom<&str> for FullName { type Error = git_validate::refname::Error; @@ -76,10 +76,29 @@ impl FullName { pub fn into_inner(self) -> BString { self.0 } + /// Return ourselves as byte string which is a valid refname pub fn as_bstr(&self) -> &BStr { self.0.as_bstr() } + + /// Modify ourself so that we use `namespace` as prefix, if it is not yet in the `namespace` + pub fn prefix_with_namespace(&mut self, namespace: &Namespace) -> &mut Self { + if !self.0.starts_with_str(&namespace.0) { + self.0.insert_str(0, &namespace.0); + } + self + } + + /// Strip the given `namespace` off the beginning of this name, if it is in this namespace. + pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self { + if self.0.starts_with_str(&namespace.0) { + let prev_len = self.0.len(); + self.0.copy_within(namespace.0.len().., 0); + self.0.resize(prev_len - namespace.0.len(), 0); + } + self + } } impl<'a> FullNameRef<'a> { diff --git a/git-ref/src/lib.rs b/git-ref/src/lib.rs index 035da08a78b..34b8c98acab 100644 --- a/git-ref/src/lib.rs +++ b/git-ref/src/lib.rs @@ -58,7 +58,7 @@ pub struct FullNameRef<'a>(&'a BStr); #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] pub struct PartialNameRef<'a>(&'a BStr); -/// A validated prefix for references to act as a namespace. +/// A _validated_ prefix for references to act as a namespace. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Namespace(BString); diff --git a/git-ref/src/namespace.rs b/git-ref/src/namespace.rs index dbe0eba9de3..c7286f1340f 100644 --- a/git-ref/src/namespace.rs +++ b/git-ref/src/namespace.rs @@ -1,6 +1,11 @@ -use std::{borrow::Cow, convert::TryInto, path::Path}; +use std::{ + borrow::Cow, + convert::TryInto, + path::{Path, PathBuf}, +}; use git_object::bstr::{BStr, BString, ByteSlice, ByteVec}; +use os_str_bytes::OsStrBytes; use crate::{Namespace, PartialNameRef}; @@ -17,6 +22,15 @@ impl Namespace { pub fn to_path(&self) -> Cow<'_, Path> { self.0.to_path().expect("UTF-8 conversion succeeds").into() } + /// Append the given `prefix` to this namespace so it becomes usable for prefixed iteration. + pub fn into_namespaced_prefix(mut self, prefix: impl AsRef) -> PathBuf { + self.0.push_str(prefix.as_ref().to_raw_bytes()); + #[cfg(windows)] + let path = self.0.replace(b"/", b"\\").into_path_buf(); + #[cfg(not(windows))] + let path = self.0.replace(b"\\", b"/").into_path_buf(); + path.expect("UTF-8 conversion succeeds") + } } /// Given a `namespace` 'foo we output 'refs/namespaces/foo', and given 'foo/bar' we output 'refs/namespaces/foo/refs/namespaces/bar'. diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index 7d43eefdfe7..af5a8f9c5dc 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,10 +1,9 @@ -use std::fmt; +use std::{convert::TryFrom, fmt}; use git_hash::{oid, ObjectId}; use git_object::bstr::BStr; use crate::{FullName, Kind, Target, TargetRef}; -use std::convert::TryFrom; impl<'a> TargetRef<'a> { /// Returns the kind of the target the ref is pointing to. diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 4e61a7d4864..ee55e144ccc 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -6,8 +6,7 @@ use git_testtools::hex_to_id; use crate::file::{store, store_at, store_with_packed_refs}; mod with_namespace { - use git_object::bstr::BString; - use git_object::bstr::ByteSlice; + use git_object::bstr::{BString, ByteSlice}; use crate::file::store_at; diff --git a/git-ref/tests/fullname/mod.rs b/git-ref/tests/fullname/mod.rs new file mode 100644 index 00000000000..2638361997d --- /dev/null +++ b/git-ref/tests/fullname/mod.rs @@ -0,0 +1,22 @@ +use std::convert::TryInto; + +#[test] +fn prefix_with_namespace_and_stripping() { + let ns = git_ref::namespace::expand("foo").unwrap(); + let mut name: git_ref::FullName = "refs/heads/main".try_into().unwrap(); + assert_eq!( + name.prefix_with_namespace(&ns).as_bstr(), + "refs/namespaces/foo/refs/heads/main" + ); + assert_eq!( + name.prefix_with_namespace(&ns).as_bstr(), + "refs/namespaces/foo/refs/heads/main", + "idempotent prefixing" + ); + assert_eq!(name.strip_namespace(&ns).as_bstr(), "refs/heads/main"); + assert_eq!( + name.strip_namespace(&ns).as_bstr(), + "refs/heads/main", + "idempotent stripping" + ); +} diff --git a/git-ref/tests/namespace/mod.rs b/git-ref/tests/namespace/mod.rs index 45d83136d7b..a785ab64696 100644 --- a/git-ref/tests/namespace/mod.rs +++ b/git-ref/tests/namespace/mod.rs @@ -1,3 +1,15 @@ +use std::path::Path; + +#[test] +fn into_namespaced_prefix() { + assert_eq!( + git_ref::namespace::expand("foo") + .unwrap() + .into_namespaced_prefix("prefix"), + Path::new("refs").join("namespaces").join("foo").join("prefix") + ) +} + mod expand { #[test] fn components_end_with_trailing_slash_to_help_with_prefix_stripping() { diff --git a/git-ref/tests/refs.rs b/git-ref/tests/refs.rs index f6e7421a809..ad29b82dff6 100644 --- a/git-ref/tests/refs.rs +++ b/git-ref/tests/refs.rs @@ -1,6 +1,7 @@ type Result = std::result::Result>; mod file; +mod fullname; mod namespace; mod packed; mod transaction; diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index f0950bd226f..2052a6cb7c3 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -1,4 +1,7 @@ -use std::convert::TryInto; +use std::{ + convert::TryInto, + ops::{Deref, DerefMut}, +}; use bstr::BString; use git_actor as actor; @@ -15,34 +18,51 @@ use crate::{ ext::ReferenceExt, }; +const DEFAULT_LOCK_MODE: git_lock::acquire::Fail = git_lock::acquire::Fail::Immediately; + /// Obtain and alter references comfortably pub trait ReferenceAccessExt: easy::Access + Sized { fn tag( &self, name: impl AsRef, target: impl Into, - lock_mode: lock::acquire::Fail, - force: bool, + constraint: PreviousValue, ) -> Result, reference::edit::Error> { self.edit_reference( RefEdit { change: Change::Update { log: Default::default(), - expected: if force { - PreviousValue::Any - } else { - PreviousValue::MustNotExist - }, + expected: constraint, new: Target::Peeled(target.into()), }, name: format!("refs/tags/{}", name.as_ref()).try_into()?, deref: false, }, - lock_mode, + DEFAULT_LOCK_MODE, None, ) } + fn namespace(&self) -> Result, easy::borrow::repo::Error> { + self.repo().map(|repo| repo.deref().namespace.clone()) + } + + fn clear_namespace(&mut self) -> Result, easy::borrow::repo::Error> { + self.repo_mut().map(|mut repo| repo.deref_mut().namespace.take()) + } + + fn set_namespace<'a, Name, E>( + &mut self, + namespace: Name, + ) -> Result, easy::reference::namespace::set::Error> + where + Name: TryInto, Error = E>, + git_validate::refname::Error: From, + { + let namespace = git_ref::namespace::expand(namespace)?; + Ok(self.repo_mut()?.deref_mut().namespace.replace(namespace)) + } + // TODO: more tests or usage fn reference( &self, @@ -71,7 +91,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { name, deref: false, }, - git_lock::acquire::Fail::Immediately, + DEFAULT_LOCK_MODE, None, )?; assert_eq!( @@ -111,10 +131,19 @@ pub trait ReferenceAccessExt: easy::Access + Sized { &committer_storage } }; - self.repo()? - .refs + let repo = self.repo()?; + repo.refs .transaction() - .prepare(edits, lock_mode)? + .prepare( + edits.into_iter().map(|mut edit| match repo.namespace { + None => edit, + Some(ref namespace) => { + edit.name.prefix_with_namespace(namespace); + edit + } + }), + lock_mode, + )? .commit(committer) .map_err(Into::into) } diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index 3227f17a7f9..31ee8c02735 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -81,6 +81,7 @@ pub mod peel { use git_hash::ObjectId; use crate::{ + easy, easy::{head::Kind, Access, Head}, ext::{ObjectIdExt, ReferenceExt}, }; @@ -92,7 +93,7 @@ pub mod peel { #[error(transparent)] FindExistingObject(#[from] object::find::existing::Error), #[error(transparent)] - PeelReference(#[from] reference::peel_to_id_in_place::Error), + PeelReference(#[from] reference::peel::Error), } } pub use error::Error; @@ -102,12 +103,12 @@ pub mod peel { A: Access + Sized, { // TODO: tests - pub fn peel_to_id_in_place(&mut self) -> Option> { + pub fn peel_to_id_in_place(&mut self) -> Option, Error>> { Some(match &mut self.kind { Kind::Unborn(_name) => return None, Kind::Detached { peeled: Some(peeled), .. - } => Ok(*peeled), + } => Ok((*peeled).attach(self.access)), Kind::Detached { peeled: None, target } => { match target .attach(self.access) @@ -121,14 +122,14 @@ pub mod peel { peeled: Some(peeled), target: *target, }; - Ok(peeled) + Ok(peeled.attach(self.access)) } Err(err) => Err(err), } } Kind::Symbolic(r) => { let mut nr = r.clone().attach(self.access); - let peeled = nr.peel_to_id_in_place().map_err(Into::into).map(|id| id.detach()); + let peeled = nr.peel_to_id_in_place().map_err(Into::into); *r = nr.detach(); peeled } diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index 876234f3def..8fec2d1126d 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -1,8 +1,9 @@ #![allow(missing_docs)] pub mod references { - use crate::easy; use std::cell::Ref; + use crate::easy; + /// An iterator over references #[must_use] pub struct State<'r, A> @@ -16,6 +17,7 @@ pub mod references { pub struct Iter<'r, A> { inner: git_ref::file::iter::LooseThenPacked<'r, 'r>, + namespace: Option<&'r git_ref::Namespace>, access: &'r A, } @@ -24,19 +26,30 @@ pub mod references { A: easy::Access + Sized, { pub fn all(&self) -> Result, init::Error> { + let repo = self.repo.deref(); Ok(Iter { - inner: self.repo.deref().refs.iter(self.packed_refs.packed_refs.as_ref())?, + inner: match repo.namespace { + None => repo.refs.iter(self.packed_refs.packed_refs.as_ref())?, + Some(ref namespace) => repo + .refs + .iter_prefixed(self.packed_refs.packed_refs.as_ref(), namespace.to_path())?, + }, + namespace: repo.namespace.as_ref(), access: self.access, }) } pub fn prefixed(&self, prefix: impl AsRef) -> Result, init::Error> { + let repo = self.repo.deref(); Ok(Iter { - inner: self - .repo - .deref() - .refs - .iter_prefixed(self.packed_refs.packed_refs.as_ref(), prefix)?, + inner: match repo.namespace { + None => repo.refs.iter_prefixed(self.packed_refs.packed_refs.as_ref(), prefix)?, + Some(ref namespace) => repo.refs.iter_prefixed( + self.packed_refs.packed_refs.as_ref(), + namespace.to_owned().into_namespaced_prefix(prefix), + )?, + }, + namespace: repo.namespace.as_ref(), access: self.access, }) } @@ -51,7 +64,12 @@ pub mod references { fn next(&mut self) -> Option { self.inner.next().map(|res| { res.map_err(|err| Box::new(err) as Box) - .map(|r| easy::Reference::from_ref(r, self.access)) + .map(|mut r| { + if let Some(ref namespace) = self.namespace { + r.name.strip_namespace(namespace); + }; + easy::Reference::from_ref(r, self.access) + }) }) } } @@ -86,7 +104,7 @@ pub mod references { } } } + use std::{ops::Deref, path::Path}; + pub use error::Error; - use std::ops::Deref; - use std::path::Path; } diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 1a74f8ee1df..5d48b1ac4f9 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -96,3 +96,17 @@ where self.inner } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn size_of_oid() { + assert_eq!( + std::mem::size_of::>(), + 32, + "size of oid shouldn't change without notice" + ) + } +} diff --git a/git-repository/src/easy/reference/mod.rs b/git-repository/src/easy/reference/mod.rs index 87dd322bc80..25f433e6c26 100644 --- a/git-repository/src/easy/reference/mod.rs +++ b/git-repository/src/easy/reference/mod.rs @@ -9,6 +9,20 @@ use crate::{ easy::{Oid, Reference}, }; +pub mod namespace { + pub mod set { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + BorrowRepoMut(#[from] easy::borrow::repo::Error), + #[error(transparent)] + NameValidation(#[from] git_validate::refname::Error), + } + } +} + pub mod create { use crate::easy; @@ -37,7 +51,7 @@ pub mod edit { } } -pub mod peel_to_id_in_place { +pub mod peel { use crate::easy; #[derive(Debug, thiserror::Error)] @@ -87,7 +101,7 @@ where } } - pub fn peel_to_id_in_place(&mut self) -> Result, peel_to_id_in_place::Error> { + pub fn peel_to_id_in_place(&mut self) -> Result, peel::Error> { let repo = self.access.repo()?; let state = self.access.state(); let mut pack_cache = state.try_borrow_mut_pack_cache()?; @@ -102,15 +116,23 @@ where )?; Ok(Oid::from_id(oid, self.access)) } + + pub fn into_fully_peeled_id(mut self) -> Result, peel::Error> { + self.peel_to_id_in_place() + } } pub mod log; pub(crate) mod packed { - use crate::easy; + use std::{ + cell::{BorrowError, BorrowMutError}, + time::SystemTime, + }; + use git_ref::file; - use std::cell::{BorrowError, BorrowMutError}; - use std::time::SystemTime; + + use crate::easy; #[derive(Debug, thiserror::Error)] pub enum Error { diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 3a84ce3174c..24003baee18 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -154,6 +154,7 @@ pub struct Repository { /// The path to the worktree at which to find checked out files pub work_tree: Option, pub(crate) hash_kind: git_hash::Kind, + pub(crate) namespace: Option, // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how // packs are refreshed. This would be `git_config::fs::Config` when ready. diff --git a/git-repository/src/reference.rs b/git-repository/src/reference.rs deleted file mode 100644 index 3ce5bac5c27..00000000000 --- a/git-repository/src/reference.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// -pub mod log { - use bstr::{BString, ByteSlice, ByteVec}; - use git_object::Commit; - - use crate::commit; - - /// Generate a message typical for git commit logs based on the given `operation` - pub fn message(operation: &str, commit: &Commit) -> BString { - let mut out = BString::from(operation); - if let Some(commit_type) = commit_type_by_parents(commit.parents.len()) { - out.push_str(b" ("); - out.extend_from_slice(commit_type.as_bytes()); - out.push_byte(b')'); - } - out.push_str(b": "); - out.extend_from_slice(&commit::summary(commit.message.as_bstr())); - out - } - - pub(crate) fn commit_type_by_parents(count: usize) -> Option<&'static str> { - Some(match count { - 0 => "initial", - 1 => return None, - _two_or_more => "merge", - }) - } -} diff --git a/git-repository/src/reference/log.rs b/git-repository/src/reference/log.rs new file mode 100644 index 00000000000..a0d95f3e4a9 --- /dev/null +++ b/git-repository/src/reference/log.rs @@ -0,0 +1,25 @@ +use bstr::{BString, ByteSlice, ByteVec}; +use git_object::Commit; + +use crate::commit; + +/// Generate a message typical for git commit logs based on the given `operation` +pub fn message(operation: &str, commit: &Commit) -> BString { + let mut out = BString::from(operation); + if let Some(commit_type) = commit_type_by_parents(commit.parents.len()) { + out.push_str(b" ("); + out.extend_from_slice(commit_type.as_bytes()); + out.push_byte(b')'); + } + out.push_str(b": "); + out.extend_from_slice(&commit::summary(commit.message.as_bstr())); + out +} + +pub(crate) fn commit_type_by_parents(count: usize) -> Option<&'static str> { + Some(match count { + 0 => "initial", + 1 => return None, + _two_or_more => "merge", + }) +} diff --git a/git-repository/src/reference/mod.rs b/git-repository/src/reference/mod.rs new file mode 100644 index 00000000000..fb8abd6b1ab --- /dev/null +++ b/git-repository/src/reference/mod.rs @@ -0,0 +1,2 @@ +/// +pub mod log; diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 81be37d8f98..044ab596abd 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -114,6 +114,7 @@ pub mod open { ), work_tree: worktree_dir, hash_kind, + namespace: None, }) } } diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 92f63ef9955..7c640ec2344 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -1,3 +1,90 @@ +mod set_namespace { + use git_repository as git; + use git_repository::{prelude::ReferenceAccessExt, refs::transaction::PreviousValue}; + + fn easy_repo_rw() -> crate::Result<(git::EasyArcExclusive, tempfile::TempDir)> { + crate::repo_rw("make_references_repo.sh").map(|(r, d)| (r.into_easy_arc_exclusive(), d)) + } + + #[test] + fn affects_edits_and_iteration() { + let (mut repo, _keep) = easy_repo_rw().unwrap(); + assert_eq!( + repo.iter_references().unwrap().all().unwrap().count(), + 15, + "there are plenty of references in the default namespace" + ); + assert!(repo.namespace().unwrap().is_none(), "no namespace is set initially"); + assert!( + repo.set_namespace("foo").unwrap().is_none(), + "there is no previous namespace" + ); + + assert_eq!( + repo.iter_references() + .unwrap() + .all() + .unwrap() + .filter_map(Result::ok) + .count(), + 0, + "no references are in the namespace yet" + ); + + repo.tag( + "new-tag", + git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1), + PreviousValue::MustNotExist, + ) + .unwrap(); + + repo.reference( + "refs/heads/new-branch", + git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1), + PreviousValue::MustNotExist, + "message", + ) + .unwrap(); + + assert_eq!( + repo.iter_references() + .unwrap() + .all() + .unwrap() + .filter_map(Result::ok) + .map(|r| r.name().as_bstr().to_owned()) + .collect::>(), + vec!["refs/heads/new-branch", "refs/tags/new-tag"], + "namespaced references appear like normal ones" + ); + + assert_eq!( + repo.iter_references() + .unwrap() + .prefixed("refs/tags/") + .unwrap() + .filter_map(Result::ok) + .map(|r| r.name().as_bstr().to_owned()) + .collect::>(), + vec!["refs/tags/new-tag"], + "namespaced references appear like normal ones" + ); + + let previous_ns = repo.clear_namespace().unwrap().expect("namespace set"); + assert_eq!(previous_ns.as_bstr(), "refs/namespaces/foo/"); + assert!( + repo.clear_namespace().unwrap().is_none(), + "it doesn't invent namespaces" + ); + + assert_eq!( + repo.iter_references().unwrap().all().unwrap().count(), + 17, + "it lists all references, also the ones in namespaces" + ); + } +} + mod iter_references { use git_repository as git; use git_repository::prelude::ReferenceAccessExt; diff --git a/git-repository/tests/easy/reference.rs b/git-repository/tests/easy/reference.rs index c4034b9614f..1000f216f88 100644 --- a/git-repository/tests/easy/reference.rs +++ b/git-repository/tests/easy/reference.rs @@ -38,6 +38,6 @@ mod find { "refs/remotes/origin/multi-link-target3".try_into().unwrap(), "it follows symbolic refs, too" ); - assert_eq!(symbolic_ref.peel_to_id_in_place().unwrap(), the_commit, "idempotency"); + assert_eq!(symbolic_ref.into_fully_peeled_id().unwrap(), the_commit, "idempotency"); } } diff --git a/git-repository/tests/repo.rs b/git-repository/tests/repo.rs index 8025fc15f65..750942d2d74 100644 --- a/git-repository/tests/repo.rs +++ b/git-repository/tests/repo.rs @@ -4,17 +4,24 @@ type Result = std::result::Result>; fn repo(name: &str) -> crate::Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; - Ok(Repository::discover(repo_path)?) + Ok(Repository::open(repo_path)?) +} + +fn repo_rw(name: &str) -> crate::Result<(Repository, tempfile::TempDir)> { + let repo_path = git_testtools::scripted_fixture_repo_writable(name)?; + Ok((Repository::discover(repo_path.path())?, repo_path)) +} + +fn easy_repo_rw(name: &str) -> crate::Result<(Easy, tempfile::TempDir)> { + repo_rw(name).map(|(repo, dir)| (repo.into(), dir)) } fn basic_repo() -> crate::Result { - let repo_path = git_testtools::scripted_fixture_repo_read_only("make_basic_repo.sh")?; - Ok(Repository::open(repo_path)?.into()) + repo("make_basic_repo.sh").map(|r| r.into_easy()) } fn basic_rw_repo() -> crate::Result<(Easy, tempfile::TempDir)> { - let repo_path = git_testtools::scripted_fixture_repo_writable("make_basic_repo.sh")?; - Ok((Repository::open(repo_path.path())?.into(), repo_path)) + easy_repo_rw("make_basic_repo.sh") } mod commit; From f58a0ff8be6144d1dcb97f9b8030e1ee36ce41de Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 14:18:02 +0800 Subject: [PATCH 69/91] =?UTF-8?q?[ref=20#190]=20more=20assetions=20to=20un?= =?UTF-8?q?derstand=20'find(=E2=80=A6)'=20for=20namespaced=20refs=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and it works like it should. This shows that finding needs namespace support if it is even something that we need. --- git-ref/tests/file/store/iter.rs | 55 +++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index ee55e144ccc..86ccd173d2e 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -11,18 +11,18 @@ mod with_namespace { use crate::file::store_at; #[test] - fn general_iteration_can_trivially_use_namespaces_as_prefixes() { - let store = store_at("make_namespaced_packed_ref_repository.sh").unwrap(); - let packed = store.packed_buffer().unwrap(); + fn general_iteration_can_trivially_use_namespaces_as_prefixes() -> crate::Result { + let store = store_at("make_namespaced_packed_ref_repository.sh")?; + let packed = store.packed_buffer()?; - let ns_two = git_ref::namespace::expand("bar").unwrap(); + let ns_two = git_ref::namespace::expand("bar")?; + let namespaced_packed_refs = store + .iter_prefixed(packed.as_ref(), ns_two.to_path())? + .map(Result::unwrap) + .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) + .collect::>(); assert_eq!( - store - .iter_prefixed(packed.as_ref(), ns_two.to_path()) - .unwrap() - .map(Result::unwrap) - .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) - .collect::>(), + namespaced_packed_refs, vec![ "refs/namespaces/bar/refs/heads/multi-link-target1", "refs/namespaces/bar/refs/multi-link", @@ -30,12 +30,37 @@ mod with_namespace { "refs/namespaces/bar/refs/tags/multi-link-target2" ] ); + for fullname in namespaced_packed_refs { + let reference = store.find(fullname.as_bstr(), packed.as_ref())?; + assert_eq!( + reference.name.as_bstr(), + fullname, + "it finds namespaced items by fully qualified name" + ); + assert!( + store + .try_find( + fullname.rsplit_str(b"/").next().expect("name").as_bstr(), + packed.as_ref() + )? + .is_none(), + "it won't find namespaced items just by their shortest name" + ); + assert!( + store + .try_find( + reference.name_without_namespace(&ns_two).expect("namespaced"), + packed.as_ref() + )? + .is_none(), + "it won't find namespaced items by their full name without namespace" + ); + } - let ns_one = git_ref::namespace::expand("foo").unwrap(); + let ns_one = git_ref::namespace::expand("foo")?; assert_eq!( store - .iter_prefixed(packed.as_ref(), ns_one.to_path()) - .unwrap() + .iter_prefixed(packed.as_ref(), ns_one.to_path())? .map(Result::unwrap) .map(|r: git_ref::Reference| ( r.name.as_bstr().to_owned(), @@ -60,8 +85,7 @@ mod with_namespace { assert_eq!( store - .iter(packed.as_ref()) - .unwrap() + .iter(packed.as_ref())? .map(Result::unwrap) .filter_map( |r: git_ref::Reference| if r.name.as_bstr().starts_with_str("refs/namespaces") { @@ -80,6 +104,7 @@ mod with_namespace { ], "we can find refs without namespace by manual filter, really just for testing purposes" ); + Ok(()) } } From d3357318cf100fc3e0751e5b6de3922b1c209ddb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 14:32:35 +0800 Subject: [PATCH 70/91] =?UTF-8?q?[repository=20#190]=20note=20a=20known=20?= =?UTF-8?q?limitation=20about=20finding=20references=20in=20namespaces?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …which doesn't work unless the fully qualified namespaced name is provided. This is correct behaviour. To fix this, one would have to search in a known namespace, which can be done with some refactoring. This would allow refs to be found by partial name. However, doing this is only useful for client applications who want to namespace a repository and allow the user to search by partial name. --- git-ref/src/store/file/find.rs | 4 +++- git-repository/tests/easy/ext/reference.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 6168fda7f55..dae2706e4cb 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -31,7 +31,9 @@ impl file::Store { /// /// ### Note /// - /// The lookup algorithm follows the one in [the git documentation][git-lookup-docs]. + /// * The lookup algorithm follows the one in [the git documentation][git-lookup-docs]. + /// * Namespaced names will only be found if they are fully qualified. They can, however, be found during iteration. + /// This shortcoming can be fixed if there is demand by introducing `try_find_in_namespace(…)`. /// /// [git-lookup-docs]: https://github.com/git/git/blob/5d5b1473453400224ebb126bf3947e0a3276bdf5/Documentation/revisions.txt#L34-L46 pub fn try_find<'a, Name, E>( diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 7c640ec2344..c5aede68f9b 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -69,6 +69,16 @@ mod set_namespace { vec!["refs/tags/new-tag"], "namespaced references appear like normal ones" ); + let fully_qualified_tag_name = "refs/namespaces/foo/refs/tags/new-tag"; + assert_eq!( + repo.find_reference(fully_qualified_tag_name).unwrap().name().as_bstr(), + fully_qualified_tag_name, + "namespaced names can only be found by fully qualified name - a known limitation that could be lifted." + ); + assert!( + repo.try_find_reference("refs/tags/new-tag").unwrap().is_none(), + "namespaces aren't currently respected when finding references. This can be lifted if there is a non-server application for it." + ); let previous_ns = repo.clear_namespace().unwrap().expect("namespace set"); assert_eq!(previous_ns.as_bstr(), "refs/namespaces/foo/"); From f5e118c8871e45ed3db9da9cd6bc63a5ea99621e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 14:39:16 +0800 Subject: [PATCH 71/91] [repository #190] fix build --- cargo-smart-release/src/command/release/git.rs | 10 ++++------ git-ref/src/store/file/find.rs | 3 ++- git-repository/src/easy/iter.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cargo-smart-release/src/command/release/git.rs b/cargo-smart-release/src/command/release/git.rs index 5c623012812..fcb8635b558 100644 --- a/cargo-smart-release/src/command/release/git.rs +++ b/cargo-smart-release/src/command/release/git.rs @@ -9,6 +9,7 @@ use cargo_metadata::{ use git_repository::{easy::object, prelude::ReferenceAccessExt, refs}; use super::{tag_name_for, utils::will, Context, Oid, Options}; +use git_repository::refs::transaction::PreviousValue; fn is_top_level_package(manifest_path: &Utf8Path, shared: &git_repository::Easy) -> bool { manifest_path @@ -155,12 +156,9 @@ pub(in crate::command::release_impl) fn create_version_tag<'repo>( } Ok(Some(format!("refs/tags/{}", tag_name).try_into()?)) } else { - let edits = ctx.git_easy.tag( - tag_name, - commit_id.expect("set in --execute mode"), - git_lock::acquire::Fail::Immediately, - false, - )?; + let edits = ctx + .git_easy + .tag(tag_name, commit_id.expect("set in --execute mode"), PreviousValue::Any)?; assert_eq!(edits.len(), 1, "We create only one tag and there is no expansion"); let tag = edits.into_iter().next().expect("the promised tag"); log::info!("Created tag {}", tag.name.as_bstr()); diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index dae2706e4cb..04ebea1798c 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -33,7 +33,8 @@ impl file::Store { /// /// * The lookup algorithm follows the one in [the git documentation][git-lookup-docs]. /// * Namespaced names will only be found if they are fully qualified. They can, however, be found during iteration. - /// This shortcoming can be fixed if there is demand by introducing `try_find_in_namespace(…)`. + /// This shortcoming can be fixed if there is demand by introducing `try_find_in_namespace(…)` or by letting `PartialNameRef` have + /// a namespace reference, too. /// /// [git-lookup-docs]: https://github.com/git/git/blob/5d5b1473453400224ebb126bf3947e0a3276bdf5/Documentation/revisions.txt#L34-L46 pub fn try_find<'a, Name, E>( diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index 8fec2d1126d..a4c7acc8f03 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -65,7 +65,7 @@ pub mod references { self.inner.next().map(|res| { res.map_err(|err| Box::new(err) as Box) .map(|mut r| { - if let Some(ref namespace) = self.namespace { + if let Some(namespace) = self.namespace { r.name.strip_namespace(namespace); }; easy::Reference::from_ref(r, self.access) From 1ef6cb344176aeafcc61a1f1af503a3f8afd1f77 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 14:52:31 +0800 Subject: [PATCH 72/91] [ref #190] refactor --- git-ref/src/transaction/ext.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index 42cb400bca4..d366ae8d52d 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -1,4 +1,4 @@ -use git_object::bstr::{BString, ByteVec}; +use git_object::bstr::BString; use crate::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target}, @@ -144,21 +144,13 @@ where if let Some(namespace) = namespace { for entry in self.iter_mut() { let entry = entry.borrow_mut(); - entry.name.0 = { - let mut new_name = namespace.0.clone(); - new_name.push_str(&entry.name.0); - new_name - }; + entry.name.prefix_with_namespace(&namespace); if let Change::Update { new: Target::Symbolic(ref mut name), .. } = entry.change { - name.0 = { - let mut new_name = namespace.0.clone(); - new_name.push_str(&name.0); - new_name - }; + name.prefix_with_namespace(&namespace); } } } From 609c249916ca64f4beecdab86eb4562adbd1ca4f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 15:36:14 +0800 Subject: [PATCH 73/91] [repository #190] refactor --- git-ref/src/fullname.rs | 2 +- git-ref/src/raw.rs | 8 ++++++++ git-ref/src/transaction/ext.rs | 4 ++-- git-ref/tests/fullname/mod.rs | 4 ++-- git-ref/tests/reference/mod.rs | 23 +++++++++++++++++++++++ git-ref/tests/refs.rs | 1 + git-repository/src/easy/ext/reference.rs | 23 +++++++++-------------- git-repository/src/easy/iter.rs | 2 +- 8 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 git-ref/tests/reference/mod.rs diff --git a/git-ref/src/fullname.rs b/git-ref/src/fullname.rs index b11048ebf54..bd213f06adc 100644 --- a/git-ref/src/fullname.rs +++ b/git-ref/src/fullname.rs @@ -83,7 +83,7 @@ impl FullName { } /// Modify ourself so that we use `namespace` as prefix, if it is not yet in the `namespace` - pub fn prefix_with_namespace(&mut self, namespace: &Namespace) -> &mut Self { + pub fn prefix_namespace(&mut self, namespace: &Namespace) -> &mut Self { if !self.0.starts_with_str(&namespace.0) { self.0.insert_str(0, &namespace.0); } diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index c9e070413a5..9c8253d78f8 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -75,6 +75,14 @@ mod access { .strip_prefix(namespace.0.as_bstr().as_ref()) .map(|stripped| FullNameRef(stripped.as_bstr())) } + + /// Strip the given namespace from our name as well as the name, but not the reference we point to. + /// + /// Symbolic link targets must remain as is or else the reference cannot be peeled without knowing the namespace. + pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self { + self.name.strip_namespace(namespace); + self + } } } diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index d366ae8d52d..7a2e97432c4 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -144,13 +144,13 @@ where if let Some(namespace) = namespace { for entry in self.iter_mut() { let entry = entry.borrow_mut(); - entry.name.prefix_with_namespace(&namespace); + entry.name.prefix_namespace(&namespace); if let Change::Update { new: Target::Symbolic(ref mut name), .. } = entry.change { - name.prefix_with_namespace(&namespace); + name.prefix_namespace(&namespace); } } } diff --git a/git-ref/tests/fullname/mod.rs b/git-ref/tests/fullname/mod.rs index 2638361997d..97b0283aee2 100644 --- a/git-ref/tests/fullname/mod.rs +++ b/git-ref/tests/fullname/mod.rs @@ -5,11 +5,11 @@ fn prefix_with_namespace_and_stripping() { let ns = git_ref::namespace::expand("foo").unwrap(); let mut name: git_ref::FullName = "refs/heads/main".try_into().unwrap(); assert_eq!( - name.prefix_with_namespace(&ns).as_bstr(), + name.prefix_namespace(&ns).as_bstr(), "refs/namespaces/foo/refs/heads/main" ); assert_eq!( - name.prefix_with_namespace(&ns).as_bstr(), + name.prefix_namespace(&ns).as_bstr(), "refs/namespaces/foo/refs/heads/main", "idempotent prefixing" ); diff --git a/git-ref/tests/reference/mod.rs b/git-ref/tests/reference/mod.rs new file mode 100644 index 00000000000..63c4df41bec --- /dev/null +++ b/git-ref/tests/reference/mod.rs @@ -0,0 +1,23 @@ +use git_ref::{FullName, Target}; +use std::convert::TryInto; + +#[test] +fn strip_namespace() { + let ns = git_ref::namespace::expand("ns").unwrap(); + let mut r = git_ref::Reference { + name: { + let mut n: FullName = "refs/heads/main".try_into().unwrap(); + n.prefix_namespace(&ns); + n + }, + target: Target::Symbolic({ + let mut n: FullName = "refs/tags/foo".try_into().unwrap(); + n.prefix_namespace(&ns); + n + }), + peeled: None, + }; + r.strip_namespace(&ns); + assert_eq!(r.name.as_bstr(), "refs/heads/main"); + assert!(matches!(r.target, Target::Symbolic(n) if n.as_bstr() == "refs/namespaces/ns/refs/tags/foo")); +} diff --git a/git-ref/tests/refs.rs b/git-ref/tests/refs.rs index ad29b82dff6..6d1ab45a52d 100644 --- a/git-ref/tests/refs.rs +++ b/git-ref/tests/refs.rs @@ -4,4 +4,5 @@ mod file; mod fullname; mod namespace; mod packed; +mod reference; mod transaction; diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 2052a6cb7c3..2bc5f1c63d4 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -117,6 +117,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { self.edit_references(Some(edit), lock_mode, log_committer) } + // NOTE: Returned edits don't hide the namespace. fn edit_references( &self, edits: impl IntoIterator, @@ -132,20 +133,14 @@ pub trait ReferenceAccessExt: easy::Access + Sized { } }; let repo = self.repo()?; - repo.refs - .transaction() - .prepare( - edits.into_iter().map(|mut edit| match repo.namespace { - None => edit, - Some(ref namespace) => { - edit.name.prefix_with_namespace(namespace); - edit - } - }), - lock_mode, - )? - .commit(committer) - .map_err(Into::into) + let transaction = repo.refs.transaction(); + match &repo.namespace { + Some(namespace) => transaction.namespace(namespace.to_owned()), + None => transaction, + } + .prepare(edits, lock_mode)? + .commit(committer) + .map_err(Into::into) } fn head(&self) -> Result, reference::find::existing::Error> { diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index a4c7acc8f03..b62e6d66d00 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -66,7 +66,7 @@ pub mod references { res.map_err(|err| Box::new(err) as Box) .map(|mut r| { if let Some(namespace) = self.namespace { - r.name.strip_namespace(namespace); + r.strip_namespace(namespace); }; easy::Reference::from_ref(r, self.access) }) From d2d1db647e6ad0dd92b88ce57df866f5195b8dd6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 15:42:12 +0800 Subject: [PATCH 74/91] [repository #190] prepare for namespacing support on file store level --- git-ref/src/store/file/loose/mod.rs | 1 + git-ref/src/store/file/mod.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/git-ref/src/store/file/loose/mod.rs b/git-ref/src/store/file/loose/mod.rs index 1278830e24b..c07a50c2f94 100644 --- a/git-ref/src/store/file/loose/mod.rs +++ b/git-ref/src/store/file/loose/mod.rs @@ -36,6 +36,7 @@ mod init { file::Store { base: git_dir.into(), write_reflog, + namespace: None, } } } diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 28bad2ab90d..239546ec58d 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -27,6 +27,8 @@ pub struct Store { pub base: PathBuf, /// The way to handle reflog edits pub write_reflog: WriteReflog, + /// The namespace to use for edits and reads + pub namespace: Option, } /// A transaction on a file store @@ -79,4 +81,5 @@ pub mod transaction; pub mod packed; mod raw_ext; +use crate::Namespace; pub use raw_ext::ReferenceExt; From 0660a6f8fcb5a51a4661dd8b3e2e43a07b5e1d3a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 15:44:41 +0800 Subject: [PATCH 75/91] [#190] disable caching to see if this fixes windows --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3525bd504e1..1d06c5f263c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: Swatinem/rust-cache@v1 +# - uses: Swatinem/rust-cache@v1 - todo: figure out why windows doesn't seem to get its own caches, maybe? - name: clippy run: cargo clippy --all - name: fmt From 3c7968c7fe8ac166b01f5338b23f817899dc085e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 15:54:18 +0800 Subject: [PATCH 76/91] [ref #190] refactor --- git-ref/src/store/file/mod.rs | 6 +++--- git-ref/src/store/file/{overlay.rs => overlay_iter.rs} | 0 git-ref/src/store/file/transaction/mod.rs | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) rename git-ref/src/store/file/{overlay.rs => overlay_iter.rs} (100%) diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index 239546ec58d..cfe220c2479 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -53,18 +53,18 @@ pub(in crate::store::file) fn path_to_name(path: impl Into) -> git_obje /// pub mod loose; -mod overlay; +mod overlay_iter; /// pub mod iter { pub use super::{ loose::iter::{loose, Loose}, - overlay::LooseThenPacked, + overlay_iter::LooseThenPacked, }; /// pub mod loose_then_packed { - pub use super::super::overlay::Error; + pub use super::super::overlay_iter::Error; } } diff --git a/git-ref/src/store/file/overlay.rs b/git-ref/src/store/file/overlay_iter.rs similarity index 100% rename from git-ref/src/store/file/overlay.rs rename to git-ref/src/store/file/overlay_iter.rs diff --git a/git-ref/src/store/file/transaction/mod.rs b/git-ref/src/store/file/transaction/mod.rs index 8eb6d3379d1..8b00972e9de 100644 --- a/git-ref/src/store/file/transaction/mod.rs +++ b/git-ref/src/store/file/transaction/mod.rs @@ -69,13 +69,15 @@ impl file::Store { /// A snapshot of packed references will be obtained automatically if needed to fulfill this transaction /// and will be provided as result of a successful transaction. Note that upon transaction failure, packed-refs /// will never have been altered. + /// + /// The transaction inherits the parent namespace. pub fn transaction(&self) -> Transaction<'_> { Transaction { store: self, packed_transaction: None, updates: None, packed_refs: PackedRefs::default(), - namespace: None, + namespace: self.namespace.clone(), } } } From d5987d41753cf083573d86e8d5bc86c7a825605c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 19:12:13 +0800 Subject: [PATCH 77/91] [ref #190] iteration with namespace support --- git-ref/src/store/file/overlay_iter.rs | 48 ++++++++++++++++++-------- git-ref/tests/file/store/iter.rs | 30 +++++++++++----- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/git-ref/src/store/file/overlay_iter.rs b/git-ref/src/store/file/overlay_iter.rs index c54b36d6633..03fab1c95cf 100644 --- a/git-ref/src/store/file/overlay_iter.rs +++ b/git-ref/src/store/file/overlay_iter.rs @@ -104,20 +104,24 @@ impl file::Store { /// /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themeselves. pub fn iter<'p, 's>(&'s self, packed: Option<&'p packed::Buffer>) -> std::io::Result> { - Ok(LooseThenPacked { - base: &self.base, - packed: match packed { - Some(packed) => Some( - packed - .iter() - .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? - .peekable(), - ), - None => None, - }, - loose: loose::iter::SortedLoosePaths::at_root_with_names(self.refs_dir(), self.base.to_owned()).peekable(), - buf: Vec::new(), - }) + match &self.namespace { + Some(namespace) => self.iter_prefixed_unvalidated(packed, namespace.to_path()), + None => Ok(LooseThenPacked { + base: &self.base, + packed: match packed { + Some(packed) => Some( + packed + .iter() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? + .peekable(), + ), + None => None, + }, + loose: loose::iter::SortedLoosePaths::at_root_with_names(self.refs_dir(), self.base.to_owned()) + .peekable(), + buf: Vec::new(), + }), + } } /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads". @@ -128,7 +132,21 @@ impl file::Store { packed: Option<&'p packed::Buffer>, prefix: impl AsRef, ) -> std::io::Result> { - let packed_prefix = path_to_name(self.validate_prefix(prefix.as_ref())?); + self.validate_prefix(prefix.as_ref())?; + match &self.namespace { + None => self.iter_prefixed_unvalidated(packed, prefix), + Some(namespace) => { + self.iter_prefixed_unvalidated(packed, namespace.to_owned().into_namespaced_prefix(prefix)) + } + } + } + + fn iter_prefixed_unvalidated<'p, 's>( + &'s self, + packed: Option<&'p packed::Buffer>, + prefix: impl AsRef, + ) -> std::io::Result> { + let packed_prefix = path_to_name(prefix.as_ref()); Ok(LooseThenPacked { base: &self.base, packed: match packed { diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 86ccd173d2e..b7729988a0b 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -21,15 +21,13 @@ mod with_namespace { .map(Result::unwrap) .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) .collect::>(); - assert_eq!( - namespaced_packed_refs, - vec![ - "refs/namespaces/bar/refs/heads/multi-link-target1", - "refs/namespaces/bar/refs/multi-link", - "refs/namespaces/bar/refs/remotes/origin/multi-link-target3", - "refs/namespaces/bar/refs/tags/multi-link-target2" - ] - ); + let expected_namespaced_refs = vec![ + "refs/namespaces/bar/refs/heads/multi-link-target1", + "refs/namespaces/bar/refs/multi-link", + "refs/namespaces/bar/refs/remotes/origin/multi-link-target3", + "refs/namespaces/bar/refs/tags/multi-link-target2", + ]; + assert_eq!(namespaced_packed_refs, expected_namespaced_refs); for fullname in namespaced_packed_refs { let reference = store.find(fullname.as_bstr(), packed.as_ref())?; assert_eq!( @@ -57,6 +55,20 @@ mod with_namespace { ); } + let ns_store = { + let mut s = store.clone(); + s.namespace = ns_two.clone().into(); + s + }; + assert_eq!( + ns_store + .iter(packed.as_ref())? + .map(Result::unwrap) + .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) + .collect::>(), + expected_namespaced_refs + ); + let ns_one = git_ref::namespace::expand("foo")?; assert_eq!( store From 1aa9c113488175f03758f8a64338a33b3417dd87 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 19:29:31 +0800 Subject: [PATCH 78/91] [repository #190] leverage git-ref namespace support --- git-ref/src/store/file/overlay_iter.rs | 36 ++++++++++++++++-------- git-ref/tests/file/store/iter.rs | 27 ++++++++++++------ git-repository/src/easy/ext/reference.rs | 19 ++++++------- git-repository/src/easy/iter.rs | 25 ++-------------- git-repository/src/lib.rs | 1 - git-repository/src/repository.rs | 1 - 6 files changed, 55 insertions(+), 54 deletions(-) diff --git a/git-ref/src/store/file/overlay_iter.rs b/git-ref/src/store/file/overlay_iter.rs index 03fab1c95cf..21fb2c18052 100644 --- a/git-ref/src/store/file/overlay_iter.rs +++ b/git-ref/src/store/file/overlay_iter.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ file::{loose, path_to_name}, store::{file, packed}, - FullName, Reference, + FullName, Namespace, Reference, }; /// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise @@ -20,23 +20,34 @@ pub struct LooseThenPacked<'p, 's> { packed: Option>>, loose: Peekable, buf: Vec, + namespace: Option<&'s Namespace>, } impl<'p, 's> LooseThenPacked<'p, 's> { + fn strip_namespace(&self, mut r: Reference) -> Reference { + if let Some(namespace) = &self.namespace { + r.strip_namespace(namespace); + } + r + } + fn convert_packed( &mut self, packed: Result, packed::iter::Error>, ) -> Result { - packed.map(Into::into).map_err(|err| match err { - packed::iter::Error::Reference { - invalid_line, - line_number, - } => Error::PackedReference { - invalid_line, - line_number, - }, - packed::iter::Error::Header { .. } => unreachable!("this one only happens on iteration creation"), - }) + packed + .map(Into::into) + .map(|r| self.strip_namespace(r)) + .map_err(|err| match err { + packed::iter::Error::Reference { + invalid_line, + line_number, + } => Error::PackedReference { + invalid_line, + line_number, + }, + packed::iter::Error::Header { .. } => unreachable!("this one only happens on iteration creation"), + }) } fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result { @@ -53,6 +64,7 @@ impl<'p, 's> LooseThenPacked<'p, 's> { relative_path: refpath.strip_prefix(&self.base).expect("base contains path").into(), }) .map(Into::into) + .map(|r| self.strip_namespace(r)) } } @@ -120,6 +132,7 @@ impl file::Store { loose: loose::iter::SortedLoosePaths::at_root_with_names(self.refs_dir(), self.base.to_owned()) .peekable(), buf: Vec::new(), + namespace: None, }), } } @@ -161,6 +174,7 @@ impl file::Store { loose: loose::iter::SortedLoosePaths::at_root_with_names(self.base.join(prefix), self.base.to_owned()) .peekable(), buf: Vec::new(), + namespace: self.namespace.as_ref(), }) } } diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index b7729988a0b..9e189648d83 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -7,8 +7,10 @@ use crate::file::{store, store_at, store_with_packed_refs}; mod with_namespace { use git_object::bstr::{BString, ByteSlice}; + use git_ref::FullName; use crate::file::store_at; + use std::convert::TryFrom; #[test] fn general_iteration_can_trivially_use_namespaces_as_prefixes() -> crate::Result { @@ -16,7 +18,7 @@ mod with_namespace { let packed = store.packed_buffer()?; let ns_two = git_ref::namespace::expand("bar")?; - let namespaced_packed_refs = store + let namespaced_refs = store .iter_prefixed(packed.as_ref(), ns_two.to_path())? .map(Result::unwrap) .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) @@ -27,8 +29,8 @@ mod with_namespace { "refs/namespaces/bar/refs/remotes/origin/multi-link-target3", "refs/namespaces/bar/refs/tags/multi-link-target2", ]; - assert_eq!(namespaced_packed_refs, expected_namespaced_refs); - for fullname in namespaced_packed_refs { + assert_eq!(namespaced_refs, expected_namespaced_refs); + for fullname in namespaced_refs { let reference = store.find(fullname.as_bstr(), packed.as_ref())?; assert_eq!( reference.name.as_bstr(), @@ -60,13 +62,22 @@ mod with_namespace { s.namespace = ns_two.clone().into(); s }; + + let namespaced_refs = ns_store + .iter(packed.as_ref())? + .map(Result::unwrap) + .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) + .collect::>(); assert_eq!( - ns_store - .iter(packed.as_ref())? - .map(Result::unwrap) - .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) - .collect::>(), + namespaced_refs, expected_namespaced_refs + .into_iter() + .map(|name| FullName::try_from(name) + .expect("valid full name") + .strip_namespace(&ns_two) + .as_bstr() + .to_owned()) + .collect::>() ); let ns_one = git_ref::namespace::expand("foo")?; diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 2bc5f1c63d4..f675712924f 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -44,11 +44,11 @@ pub trait ReferenceAccessExt: easy::Access + Sized { } fn namespace(&self) -> Result, easy::borrow::repo::Error> { - self.repo().map(|repo| repo.deref().namespace.clone()) + self.repo().map(|repo| repo.deref().refs.namespace.clone()) } fn clear_namespace(&mut self) -> Result, easy::borrow::repo::Error> { - self.repo_mut().map(|mut repo| repo.deref_mut().namespace.take()) + self.repo_mut().map(|mut repo| repo.deref_mut().refs.namespace.take()) } fn set_namespace<'a, Name, E>( @@ -60,7 +60,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { git_validate::refname::Error: From, { let namespace = git_ref::namespace::expand(namespace)?; - Ok(self.repo_mut()?.deref_mut().namespace.replace(namespace)) + Ok(self.repo_mut()?.deref_mut().refs.namespace.replace(namespace)) } // TODO: more tests or usage @@ -133,14 +133,11 @@ pub trait ReferenceAccessExt: easy::Access + Sized { } }; let repo = self.repo()?; - let transaction = repo.refs.transaction(); - match &repo.namespace { - Some(namespace) => transaction.namespace(namespace.to_owned()), - None => transaction, - } - .prepare(edits, lock_mode)? - .commit(committer) - .map_err(Into::into) + repo.refs + .transaction() + .prepare(edits, lock_mode)? + .commit(committer) + .map_err(Into::into) } fn head(&self) -> Result, reference::find::existing::Error> { diff --git a/git-repository/src/easy/iter.rs b/git-repository/src/easy/iter.rs index b62e6d66d00..28181b52e9f 100644 --- a/git-repository/src/easy/iter.rs +++ b/git-repository/src/easy/iter.rs @@ -17,7 +17,6 @@ pub mod references { pub struct Iter<'r, A> { inner: git_ref::file::iter::LooseThenPacked<'r, 'r>, - namespace: Option<&'r git_ref::Namespace>, access: &'r A, } @@ -28,13 +27,7 @@ pub mod references { pub fn all(&self) -> Result, init::Error> { let repo = self.repo.deref(); Ok(Iter { - inner: match repo.namespace { - None => repo.refs.iter(self.packed_refs.packed_refs.as_ref())?, - Some(ref namespace) => repo - .refs - .iter_prefixed(self.packed_refs.packed_refs.as_ref(), namespace.to_path())?, - }, - namespace: repo.namespace.as_ref(), + inner: repo.refs.iter(self.packed_refs.packed_refs.as_ref())?, access: self.access, }) } @@ -42,14 +35,7 @@ pub mod references { pub fn prefixed(&self, prefix: impl AsRef) -> Result, init::Error> { let repo = self.repo.deref(); Ok(Iter { - inner: match repo.namespace { - None => repo.refs.iter_prefixed(self.packed_refs.packed_refs.as_ref(), prefix)?, - Some(ref namespace) => repo.refs.iter_prefixed( - self.packed_refs.packed_refs.as_ref(), - namespace.to_owned().into_namespaced_prefix(prefix), - )?, - }, - namespace: repo.namespace.as_ref(), + inner: repo.refs.iter_prefixed(self.packed_refs.packed_refs.as_ref(), prefix)?, access: self.access, }) } @@ -64,12 +50,7 @@ pub mod references { fn next(&mut self) -> Option { self.inner.next().map(|res| { res.map_err(|err| Box::new(err) as Box) - .map(|mut r| { - if let Some(namespace) = self.namespace { - r.strip_namespace(namespace); - }; - easy::Reference::from_ref(r, self.access) - }) + .map(|r| easy::Reference::from_ref(r, self.access)) }) } } diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 24003baee18..3a84ce3174c 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -154,7 +154,6 @@ pub struct Repository { /// The path to the worktree at which to find checked out files pub work_tree: Option, pub(crate) hash_kind: git_hash::Kind, - pub(crate) namespace: Option, // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how // packs are refreshed. This would be `git_config::fs::Config` when ready. diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 044ab596abd..81be37d8f98 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -114,7 +114,6 @@ pub mod open { ), work_tree: worktree_dir, hash_kind, - namespace: None, }) } } From 5fcd0e4c3c803a372360ef4cc2a7663b17ccebdb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 19:44:46 +0800 Subject: [PATCH 79/91] =?UTF-8?q?[ref=20#190]=20prepare=20test=20for=20nam?= =?UTF-8?q?espaced=20find(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- git-ref/tests/file/store/iter.rs | 104 ++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 9e189648d83..f1a520c3577 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -7,13 +7,11 @@ use crate::file::{store, store_at, store_with_packed_refs}; mod with_namespace { use git_object::bstr::{BString, ByteSlice}; - use git_ref::FullName; use crate::file::store_at; - use std::convert::TryFrom; #[test] - fn general_iteration_can_trivially_use_namespaces_as_prefixes() -> crate::Result { + fn iteration_can_trivially_use_namespaces_as_prefixes() -> crate::Result { let store = store_at("make_namespaced_packed_ref_repository.sh")?; let packed = store.packed_buffer()?; @@ -21,7 +19,7 @@ mod with_namespace { let namespaced_refs = store .iter_prefixed(packed.as_ref(), ns_two.to_path())? .map(Result::unwrap) - .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) + .map(|r: git_ref::Reference| r.name) .collect::>(); let expected_namespaced_refs = vec![ "refs/namespaces/bar/refs/heads/multi-link-target1", @@ -29,18 +27,20 @@ mod with_namespace { "refs/namespaces/bar/refs/remotes/origin/multi-link-target3", "refs/namespaces/bar/refs/tags/multi-link-target2", ]; - assert_eq!(namespaced_refs, expected_namespaced_refs); + assert_eq!( + namespaced_refs.iter().map(|n| n.as_bstr()).collect::>(), + expected_namespaced_refs + ); for fullname in namespaced_refs { let reference = store.find(fullname.as_bstr(), packed.as_ref())?; assert_eq!( - reference.name.as_bstr(), - fullname, + reference.name, fullname, "it finds namespaced items by fully qualified name" ); assert!( store .try_find( - fullname.rsplit_str(b"/").next().expect("name").as_bstr(), + fullname.as_bstr().rsplit_str(b"/").next().expect("name").as_bstr(), packed.as_ref() )? .is_none(), @@ -57,29 +57,6 @@ mod with_namespace { ); } - let ns_store = { - let mut s = store.clone(); - s.namespace = ns_two.clone().into(); - s - }; - - let namespaced_refs = ns_store - .iter(packed.as_ref())? - .map(Result::unwrap) - .map(|r: git_ref::Reference| r.name.as_bstr().to_owned()) - .collect::>(); - assert_eq!( - namespaced_refs, - expected_namespaced_refs - .into_iter() - .map(|name| FullName::try_from(name) - .expect("valid full name") - .strip_namespace(&ns_two) - .as_bstr() - .to_owned()) - .collect::>() - ); - let ns_one = git_ref::namespace::expand("foo")?; assert_eq!( store @@ -129,6 +106,71 @@ mod with_namespace { ); Ok(()) } + + #[test] + #[ignore] + fn iteration_on_store_with_namespace_makes_namespace_transparent() { + let ns_two = git_ref::namespace::expand("bar").unwrap(); + let mut ns_store = { + let mut s = store_at("make_namespaced_packed_ref_repository.sh").unwrap(); + s.namespace = ns_two.clone().into(); + s + }; + let packed = ns_store.packed_buffer().unwrap(); + + let expected_refs = vec![ + "refs/heads/multi-link-target1", + "refs/multi-link", + "refs/remotes/origin/multi-link-target3", + "refs/tags/multi-link-target2", + ]; + let ref_names = ns_store + .iter(packed.as_ref()) + .unwrap() + .map(Result::unwrap) + .map(|r: git_ref::Reference| r.name) + .collect::>(); + assert_eq!(ref_names.iter().map(|n| n.as_bstr()).collect::>(), expected_refs); + + for fullname in ref_names { + assert_eq!( + ns_store.find(fullname.as_bstr(), packed.as_ref()).unwrap().name, + fullname, + "it finds namespaced items by fully qualified name, excluding namespace" + ); + assert!( + ns_store + .try_find(fullname.clone().prefix_namespace(&ns_two).to_partial(), packed.as_ref()) + .unwrap() + .is_none(), + "it won't find namespaced items by their store-relative name with namespace" + ); + assert_eq!( + ns_store + .find( + fullname.as_bstr().rsplit_str(b"/").next().expect("name").as_bstr(), + packed.as_ref() + ) + .unwrap() + .name, + fullname, + "it finds partial names within the namespace" + ); + } + + let ns_one = git_ref::namespace::expand("foo").unwrap(); + ns_store.namespace = ns_one.into(); + + assert_eq!( + ns_store + .iter(packed.as_ref()) + .unwrap() + .map(Result::unwrap) + .map(|r: git_ref::Reference| r.name.into_inner()) + .collect::>(), + vec!["refs/d1", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"], + ); + } } #[test] From 1240c21a353c7df736f40b6639076af94eae0f15 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 20:14:54 +0800 Subject: [PATCH 80/91] [ref #190] find() with namespace support --- git-ref/src/store/file/find.rs | 22 +++++++++++++++++++--- git-ref/tests/file/store/iter.rs | 14 +++++++------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 04ebea1798c..c66b5a34a7c 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -121,10 +121,17 @@ impl file::Store { None => { if is_definitely_absolute { if let Some(packed) = packed { - let full_name = path_to_name(relative_path); + let full_name = path_to_name(match &self.namespace { + None => relative_path, + Some(namespace) => namespace.to_owned().into_namespaced_prefix(relative_path), + }); let full_name = PartialNameRef((*full_name).as_bstr()); if let Some(packed_ref) = packed.try_find(full_name)? { - return Ok(Some(packed_ref.into())); + let mut res: Reference = packed_ref.into(); + if let Some(namespace) = &self.namespace { + res.strip_namespace(namespace); + } + return Ok(Some(res)); }; } } @@ -136,6 +143,12 @@ impl file::Store { let full_name = path_to_name(&relative_path); loose::Reference::try_from_path(FullName(full_name), &contents) .map(Into::into) + .map(|mut r: Reference| { + if let Some(namespace) = &self.namespace { + r.strip_namespace(namespace); + } + r + }) .map_err(|err| Error::ReferenceCreation { err, relative_path })? })) } @@ -144,7 +157,10 @@ impl file::Store { impl file::Store { /// Implements the logic required to transform a fully qualified refname into a filesystem path pub(crate) fn reference_path(&self, name: &Path) -> PathBuf { - self.base.join(name) + match &self.namespace { + None => self.base.join(name), + Some(namespace) => self.base.join(namespace.to_path()).join(name), + } } /// Read the file contents with a verified full reference path and return it in the given vector if possible. diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index f1a520c3577..9261d26313a 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -37,14 +37,15 @@ mod with_namespace { reference.name, fullname, "it finds namespaced items by fully qualified name" ); - assert!( + assert_eq!( store - .try_find( - fullname.as_bstr().rsplit_str(b"/").next().expect("name").as_bstr(), + .find( + fullname.as_bstr().splitn_str(2, b"/").nth(1).expect("name").as_bstr(), packed.as_ref() )? - .is_none(), - "it won't find namespaced items just by their shortest name" + .name, + fullname, + "it will find namespaced items just by their shortened (but not shortest) name" ); assert!( store @@ -108,7 +109,6 @@ mod with_namespace { } #[test] - #[ignore] fn iteration_on_store_with_namespace_makes_namespace_transparent() { let ns_two = git_ref::namespace::expand("bar").unwrap(); let mut ns_store = { @@ -148,7 +148,7 @@ mod with_namespace { assert_eq!( ns_store .find( - fullname.as_bstr().rsplit_str(b"/").next().expect("name").as_bstr(), + fullname.as_bstr().splitn_str(2, b"/").nth(1).expect("name").as_bstr(), packed.as_ref() ) .unwrap() From c663da16646bc3371e5a31f5c488a775aac4f795 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 20:53:26 +0800 Subject: [PATCH 81/91] =?UTF-8?q?[ref=20#190]=20don't=20provide=20namespac?= =?UTF-8?q?e=20support=20for=20loose=20and=20packed=20refs=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …as nobody should really need this. It's available for the top-level overlay iterator though. --- git-ref/src/raw.rs | 7 +-- git-ref/src/store/file/loose/iter.rs | 4 ++ git-ref/src/store/packed/iter.rs | 4 ++ git-ref/tests/file/store/iter.rs | 67 ++++++++++++++++++++++------ git-ref/tests/reference/mod.rs | 7 ++- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/git-ref/src/raw.rs b/git-ref/src/raw.rs index 9c8253d78f8..7d692e653b7 100644 --- a/git-ref/src/raw.rs +++ b/git-ref/src/raw.rs @@ -57,7 +57,7 @@ mod convert { mod access { use git_object::bstr::ByteSlice; - use crate::{raw::Reference, FullNameRef, Namespace}; + use crate::{raw::Reference, FullNameRef, Namespace, Target}; impl Reference { /// Returns the kind of reference based on its target @@ -77,10 +77,11 @@ mod access { } /// Strip the given namespace from our name as well as the name, but not the reference we point to. - /// - /// Symbolic link targets must remain as is or else the reference cannot be peeled without knowing the namespace. pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self { self.name.strip_namespace(namespace); + if let Target::Symbolic(name) = &mut self.target { + name.strip_namespace(namespace); + } self } } diff --git a/git-ref/src/store/file/loose/iter.rs b/git-ref/src/store/file/loose/iter.rs index b24584d93dc..0377b517306 100644 --- a/git-ref/src/store/file/loose/iter.rs +++ b/git-ref/src/store/file/loose/iter.rs @@ -113,6 +113,10 @@ impl file::Store { /// /// Reference files that do not constitute valid names will be silently ignored. /// + /// # Note + /// + /// There is no namespace support in loose file iterators. It can be emulated using `loose_iter_prefixed(…)`. + /// /// See [`Store::packed()`][file::Store::packed_buffer()] for interacting with packed references. pub fn loose_iter(&self) -> std::io::Result { let refs = self.refs_dir(); diff --git a/git-ref/src/store/packed/iter.rs b/git-ref/src/store/packed/iter.rs index ad43289977d..7e423472189 100644 --- a/git-ref/src/store/packed/iter.rs +++ b/git-ref/src/store/packed/iter.rs @@ -5,6 +5,10 @@ use crate::store::{packed, packed::decode}; /// packed-refs specific functionality impl packed::Buffer { /// Return an iterator of references stored in this packed refs buffer, ordered by reference name. + /// + /// # Note + /// + /// There is no namespace support in packed iterators. It can be emulated using `iter_prefixed(…)`. pub fn iter(&self) -> Result, packed::iter::Error> { packed::Iter::new(self.as_ref()) } diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 9261d26313a..ed75186964c 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -31,6 +31,28 @@ mod with_namespace { namespaced_refs.iter().map(|n| n.as_bstr()).collect::>(), expected_namespaced_refs ); + assert_eq!( + store + .loose_iter_prefixed(ns_two.to_path())? + .map(Result::unwrap) + .map(|r| r.name.into_inner()) + .collect::>(), + [ + "refs/namespaces/bar/refs/heads/multi-link-target1", + "refs/namespaces/bar/refs/multi-link", + "refs/namespaces/bar/refs/tags/multi-link-target2" + ] + ); + assert_eq!( + packed + .as_ref() + .expect("present") + .iter_prefixed(ns_two.as_bstr())? + .map(Result::unwrap) + .map(|r| r.name.to_owned().into_inner()) + .collect::>(), + ["refs/namespaces/bar/refs/remotes/origin/multi-link-target3"] + ); for fullname in namespaced_refs { let reference = store.find(fullname.as_bstr(), packed.as_ref())?; assert_eq!( @@ -109,14 +131,14 @@ mod with_namespace { } #[test] - fn iteration_on_store_with_namespace_makes_namespace_transparent() { - let ns_two = git_ref::namespace::expand("bar").unwrap(); + fn iteration_on_store_with_namespace_makes_namespace_transparent() -> crate::Result { + let ns_two = git_ref::namespace::expand("bar")?; let mut ns_store = { - let mut s = store_at("make_namespaced_packed_ref_repository.sh").unwrap(); + let mut s = store_at("make_namespaced_packed_ref_repository.sh")?; s.namespace = ns_two.clone().into(); s }; - let packed = ns_store.packed_buffer().unwrap(); + let packed = ns_store.packed_buffer()?; let expected_refs = vec![ "refs/heads/multi-link-target1", @@ -125,8 +147,7 @@ mod with_namespace { "refs/tags/multi-link-target2", ]; let ref_names = ns_store - .iter(packed.as_ref()) - .unwrap() + .iter(packed.as_ref())? .map(Result::unwrap) .map(|r: git_ref::Reference| r.name) .collect::>(); @@ -134,14 +155,13 @@ mod with_namespace { for fullname in ref_names { assert_eq!( - ns_store.find(fullname.as_bstr(), packed.as_ref()).unwrap().name, + ns_store.find(fullname.as_bstr(), packed.as_ref())?.name, fullname, "it finds namespaced items by fully qualified name, excluding namespace" ); assert!( ns_store - .try_find(fullname.clone().prefix_namespace(&ns_two).to_partial(), packed.as_ref()) - .unwrap() + .try_find(fullname.clone().prefix_namespace(&ns_two).to_partial(), packed.as_ref())? .is_none(), "it won't find namespaced items by their store-relative name with namespace" ); @@ -150,26 +170,45 @@ mod with_namespace { .find( fullname.as_bstr().splitn_str(2, b"/").nth(1).expect("name").as_bstr(), packed.as_ref() - ) - .unwrap() + )? .name, fullname, "it finds partial names within the namespace" ); } - let ns_one = git_ref::namespace::expand("foo").unwrap(); + assert_eq!( + packed.as_ref().expect("present").iter()?.map(Result::unwrap).count(), + 8, + "packed refs have no namespace support at all" + ); + assert_eq!( + ns_store + .loose_iter()? + .map(Result::unwrap) + .map(|r| r.name.into_inner()) + .collect::>(), + [ + "refs/namespaces/bar/refs/heads/multi-link-target1", + "refs/namespaces/bar/refs/multi-link", + "refs/namespaces/bar/refs/tags/multi-link-target2", + "refs/namespaces/foo/refs/remotes/origin/HEAD" + ], + "loose iterators have no namespace support at all" + ); + + let ns_one = git_ref::namespace::expand("foo")?; ns_store.namespace = ns_one.into(); assert_eq!( ns_store - .iter(packed.as_ref()) - .unwrap() + .iter(packed.as_ref())? .map(Result::unwrap) .map(|r: git_ref::Reference| r.name.into_inner()) .collect::>(), vec!["refs/d1", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"], ); + Ok(()) } } diff --git a/git-ref/tests/reference/mod.rs b/git-ref/tests/reference/mod.rs index 63c4df41bec..1c1ae0983f9 100644 --- a/git-ref/tests/reference/mod.rs +++ b/git-ref/tests/reference/mod.rs @@ -18,6 +18,9 @@ fn strip_namespace() { peeled: None, }; r.strip_namespace(&ns); - assert_eq!(r.name.as_bstr(), "refs/heads/main"); - assert!(matches!(r.target, Target::Symbolic(n) if n.as_bstr() == "refs/namespaces/ns/refs/tags/foo")); + assert_eq!(r.name.as_bstr(), "refs/heads/main", "name is stripped"); + assert!( + matches!(r.target, Target::Symbolic(n) if n.as_bstr() == "refs/tags/foo"), + "and the symbolic target as well" + ); } From e5a5c09bb108741fff416672566e381f50f02b38 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 21:02:28 +0800 Subject: [PATCH 82/91] =?UTF-8?q?[repository=20#190]=20fix=20tests;=20need?= =?UTF-8?q?s=20inbound=20transaction=20handling=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …now that namespaces are rooted in the store transactions don't have to handle them anymore. --- git-repository/tests/easy/ext/reference.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index c5aede68f9b..15dbc0034d1 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -69,15 +69,16 @@ mod set_namespace { vec!["refs/tags/new-tag"], "namespaced references appear like normal ones" ); - let fully_qualified_tag_name = "refs/namespaces/foo/refs/tags/new-tag"; + let fully_qualified_tag_name = "refs/tags/new-tag"; assert_eq!( repo.find_reference(fully_qualified_tag_name).unwrap().name().as_bstr(), fully_qualified_tag_name, - "namespaced names can only be found by fully qualified name - a known limitation that could be lifted." + "fully qualified (yet namespaced) names work" ); - assert!( - repo.try_find_reference("refs/tags/new-tag").unwrap().is_none(), - "namespaces aren't currently respected when finding references. This can be lifted if there is a non-server application for it." + assert_eq!( + repo.find_reference("new-tag").unwrap().name().as_bstr(), + fully_qualified_tag_name, + "namespaces are transparent" ); let previous_ns = repo.clear_namespace().unwrap().expect("namespace set"); From e426e15188d8ec38ee0029f1d080dbab9afd8642 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 21:06:51 +0800 Subject: [PATCH 83/91] [ref #190] fix tests --- git-ref/src/store/file/mod.rs | 1 - git-ref/src/store/file/transaction/mod.rs | 13 ------------- git-ref/src/store/file/transaction/prepare.rs | 2 +- .../prepare_and_commit/create_or_update.rs | 14 ++++++-------- git-repository/src/easy/ext/reference.rs | 1 - 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index cfe220c2479..f753e03165b 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -37,7 +37,6 @@ pub struct Transaction<'s> { packed_transaction: Option, updates: Option>, packed_refs: transaction::PackedRefs, - namespace: Option, } pub(in crate::store::file) fn path_to_name(path: impl Into) -> git_object::bstr::BString { diff --git a/git-ref/src/store/file/transaction/mod.rs b/git-ref/src/store/file/transaction/mod.rs index 8b00972e9de..92b085b329f 100644 --- a/git-ref/src/store/file/transaction/mod.rs +++ b/git-ref/src/store/file/transaction/mod.rs @@ -4,7 +4,6 @@ use git_object::bstr::BString; use crate::{ store::{file, file::Transaction}, transaction::RefEdit, - Namespace, }; /// A function receiving an object id to resolve, returning its decompressed bytes. @@ -77,7 +76,6 @@ impl file::Store { packed_transaction: None, updates: None, packed_refs: PackedRefs::default(), - namespace: self.namespace.clone(), } } } @@ -88,17 +86,6 @@ impl<'s> Transaction<'s> { self.packed_refs = packed_refs; self } - - /// Configure the namespace within which all edits should take place. - /// For example, with namespace `foo`, edits destined for `HEAD` will affect `refs/namespaces/foo/HEAD` instead. - /// Set `None` to not use any namespace, which also is the default. - /// - /// This also means that edits returned when [`commit(…)`ing](Transaction::commit()) will have their name altered to include - /// the namespace automatically, so it must be stripped when returning them to the user to keep them 'invisible'. - pub fn namespace(mut self, namespace: impl Into>) -> Self { - self.namespace = namespace.into(); - self - } } /// diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 68f1b4bff59..3ee7b06154d 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -202,7 +202,7 @@ impl<'s> Transaction<'s> { parent_index: Some(idx), leaf_referent_previous_oid: None, }, - self.namespace.take(), + None, ) .map_err(Error::PreprocessingFailed)?; diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index f2ea9d60e33..df4024de4cb 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -218,13 +218,11 @@ fn reference_with_must_not_exist_constraint_cannot_be_created_if_it_exists_alrea } #[test] -fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_observable_in_the_output() -> crate::Result -{ - let (_keep, store) = empty_store()?; - +fn namespaced_updates_or_deletions_are_transparent_and_not_observable() -> crate::Result { + let (_keep, mut store) = empty_store()?; + store.namespace = git_ref::namespace::expand("foo")?.into(); let edits = store .transaction() - .namespace(git_ref::namespace::expand("foo")?) .prepare( vec![ RefEdit { @@ -257,16 +255,16 @@ fn namespaced_updates_or_deletions_cause_reference_names_to_be_rewritten_and_obs expected: PreviousValue::Any, log: RefLog::AndReference, }, - name: "refs/namespaces/foo/refs/for/deletion".try_into()?, + name: "refs/for/deletion".try_into()?, deref: false, }, RefEdit { change: Change::Update { log: LogChange::default(), - new: Target::Symbolic("refs/namespaces/foo/refs/heads/hello".try_into()?), + new: Target::Symbolic("refs/heads/hello".try_into()?), expected: PreviousValue::MustNotExist, }, - name: "refs/namespaces/foo/HEAD".try_into()?, + name: "HEAD".try_into()?, deref: false, } ] diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index f675712924f..108e44552b2 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -117,7 +117,6 @@ pub trait ReferenceAccessExt: easy::Access + Sized { self.edit_references(Some(edit), lock_mode, log_committer) } - // NOTE: Returned edits don't hide the namespace. fn edit_references( &self, edits: impl IntoIterator, From 010be48d2cd2dfebf7a1b6302e94b5f2e95fedc6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 21:36:11 +0800 Subject: [PATCH 84/91] [ref #190] refactor --- git-ref/CHANGELOG.md | 1 + git-ref/src/store/file/transaction/prepare.rs | 1 - git-ref/src/transaction/ext.rs | 24 +-------- git-ref/tests/transaction/mod.rs | 50 +------------------ 4 files changed, 3 insertions(+), 73 deletions(-) diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index b9575b397f7..a126f75f0e6 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -6,6 +6,7 @@ * Remove `file::Reference` in favor of `Reference` * Move `file::log::Line` to `log::Line` * `TargetRef::Symbolic(&BStr)` -> `TargetRef::Symbolic(FullNameRef)` +* replace `Transaction::namespacce()` with `file::Store::namespace` ### v0.6.1 diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index 3ee7b06154d..0d56b09faa2 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -202,7 +202,6 @@ impl<'s> Transaction<'s> { parent_index: Some(idx), leaf_referent_previous_oid: None, }, - None, ) .map_err(Error::PreprocessingFailed)?; diff --git a/git-ref/src/transaction/ext.rs b/git-ref/src/transaction/ext.rs index 7a2e97432c4..1ff2d642b21 100644 --- a/git-ref/src/transaction/ext.rs +++ b/git-ref/src/transaction/ext.rs @@ -2,7 +2,7 @@ use git_object::bstr::BString; use crate::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target}, - Namespace, PartialNameRef, + PartialNameRef, }; /// An extension trait to perform commonly used operations on edits across different ref stores. @@ -22,10 +22,6 @@ where make_entry: impl FnMut(usize, RefEdit) -> T, ) -> Result<(), std::io::Error>; - /// If `namespace` is not `None`, alter all edit names by prefixing them with the given namespace. - /// Note that symbolic reference targets will also be rewritten to point into the namespace instead. - fn adjust_namespace(&mut self, namespace: Option); - /// All processing steps in one and in the correct order. /// /// Users call this to assure derefs are honored and duplicate checks are done. @@ -33,9 +29,7 @@ where &mut self, find: impl FnMut(PartialNameRef<'_>) -> Option, make_entry: impl FnMut(usize, RefEdit) -> T, - namespace: impl Into>, ) -> Result<(), std::io::Error> { - self.adjust_namespace(namespace.into()); self.extend_with_splits_of_symbolic_refs(find, make_entry)?; self.assure_one_name_has_one_edit().map_err(|name| { std::io::Error::new( @@ -139,20 +133,4 @@ where self.extend(new_edits.drain(..)); } } - - fn adjust_namespace(&mut self, namespace: Option) { - if let Some(namespace) = namespace { - for entry in self.iter_mut() { - let entry = entry.borrow_mut(); - entry.name.prefix_namespace(&namespace); - if let Change::Update { - new: Target::Symbolic(ref mut name), - .. - } = entry.change - { - name.prefix_namespace(&namespace); - } - } - } - } } diff --git a/git-ref/tests/transaction/mod.rs b/git-ref/tests/transaction/mod.rs index 2ffd276841b..c03f9a03748 100644 --- a/git-ref/tests/transaction/mod.rs +++ b/git-ref/tests/transaction/mod.rs @@ -65,7 +65,7 @@ mod refedit_ext { ]; let err = edits - .pre_process(|n| store.find_existing(n), |_, e| e, None) + .pre_process(|n| store.find_existing(n), |_, e| e) .expect_err("duplicate detected"); assert_eq!( err.to_string(), @@ -95,54 +95,6 @@ mod refedit_ext { ); } - #[test] - fn namespaces_are_rewriting_names_and_symbolic_ref_targets_when_provided() -> crate::Result { - let mut edits = vec![ - RefEdit { - change: Change::Delete { - expected: PreviousValue::Any, - log: RefLog::AndReference, - }, - name: "refs/tags/deleted".try_into()?, - deref: false, - }, - RefEdit { - change: Change::Update { - log: Default::default(), - expected: PreviousValue::MustNotExist, - new: Target::Symbolic("refs/heads/main".try_into()?), - }, - name: "HEAD".try_into()?, - deref: false, - }, - ]; - edits.adjust_namespace(git_ref::namespace::expand("foo")?.into()); - assert_eq!( - edits, - vec![ - RefEdit { - change: Change::Delete { - expected: PreviousValue::Any, - log: RefLog::AndReference, - }, - name: "refs/namespaces/foo/refs/tags/deleted".try_into()?, - deref: false, - }, - RefEdit { - change: Change::Update { - log: Default::default(), - expected: PreviousValue::MustNotExist, - new: Target::Symbolic("refs/namespaces/foo/refs/heads/main".try_into()?), - }, - name: "refs/namespaces/foo/HEAD".try_into()?, - deref: false, - } - ], - "it rewrites both names as well as symbolic ref targets" - ); - Ok(()) - } - mod splitting { use std::{cell::Cell, convert::TryInto}; From e7188e047529cb0f4b20b3876f36b4592e9d2dc4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 21:47:46 +0800 Subject: [PATCH 85/91] [repository #190] refactor --- .../src/command/release/git.rs | 3 +- git-ref/src/store/file/mod.rs | 3 +- git-ref/tests/reference/mod.rs | 3 +- git-repository/src/easy/ext/reference.rs | 2 +- git-repository/src/easy/oid.rs | 104 +++++++++--------- git-repository/tests/easy/ext/reference.rs | 19 ++-- 6 files changed, 68 insertions(+), 66 deletions(-) diff --git a/cargo-smart-release/src/command/release/git.rs b/cargo-smart-release/src/command/release/git.rs index fcb8635b558..89c38e024f7 100644 --- a/cargo-smart-release/src/command/release/git.rs +++ b/cargo-smart-release/src/command/release/git.rs @@ -6,10 +6,9 @@ use cargo_metadata::{ camino::{Utf8Component, Utf8Path}, Package, }; -use git_repository::{easy::object, prelude::ReferenceAccessExt, refs}; +use git_repository::{easy::object, prelude::ReferenceAccessExt, refs, refs::transaction::PreviousValue}; use super::{tag_name_for, utils::will, Context, Oid, Options}; -use git_repository::refs::transaction::PreviousValue; fn is_top_level_package(manifest_path: &Utf8Path, shared: &git_repository::Easy) -> bool { manifest_path diff --git a/git-ref/src/store/file/mod.rs b/git-ref/src/store/file/mod.rs index f753e03165b..892fc74004e 100644 --- a/git-ref/src/store/file/mod.rs +++ b/git-ref/src/store/file/mod.rs @@ -80,5 +80,6 @@ pub mod transaction; pub mod packed; mod raw_ext; -use crate::Namespace; pub use raw_ext::ReferenceExt; + +use crate::Namespace; diff --git a/git-ref/tests/reference/mod.rs b/git-ref/tests/reference/mod.rs index 1c1ae0983f9..a519653be77 100644 --- a/git-ref/tests/reference/mod.rs +++ b/git-ref/tests/reference/mod.rs @@ -1,6 +1,7 @@ -use git_ref::{FullName, Target}; use std::convert::TryInto; +use git_ref::{FullName, Target}; + #[test] fn strip_namespace() { let ns = git_ref::namespace::expand("ns").unwrap(); diff --git a/git-repository/src/easy/ext/reference.rs b/git-repository/src/easy/ext/reference.rs index 108e44552b2..0cb6394eebd 100644 --- a/git-repository/src/easy/ext/reference.rs +++ b/git-repository/src/easy/ext/reference.rs @@ -164,7 +164,7 @@ pub trait ReferenceAccessExt: easy::Access + Sized { .ok_or(reference::find::existing::Error::NotFound) } - fn iter_references(&self) -> Result, easy::iter::references::Error> { + fn references(&self) -> Result, easy::iter::references::Error> { let state = self.state(); let repo = self.repo()?; let packed_refs = state.assure_packed_refs_uptodate(&repo.refs)?; diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 5d48b1ac4f9..448e4229c49 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -4,57 +4,9 @@ use git_hash::{oid, ObjectId}; use crate::{ easy, - easy::{ext::ObjectAccessExt, object::find, Object, ObjectRef, Oid}, + easy::{ext::ObjectAccessExt, object::find, ObjectRef, Oid}, }; -impl<'repo, A, B> PartialEq> for Oid<'repo, B> { - fn eq(&self, other: &Oid<'repo, A>) -> bool { - self.inner == other.inner - } -} - -impl<'repo, A> PartialEq for Oid<'repo, A> { - fn eq(&self, other: &ObjectId) -> bool { - &self.inner == other - } -} - -impl<'repo, A> PartialEq for Oid<'repo, A> { - fn eq(&self, other: &oid) -> bool { - self.inner == other - } -} - -impl<'repo, A, B> PartialEq> for Oid<'repo, B> { - fn eq(&self, other: &ObjectRef<'repo, A>) -> bool { - self.inner == other.id - } -} - -impl<'repo, A> PartialEq for Oid<'repo, A> { - fn eq(&self, other: &Object) -> bool { - self.inner == other.id - } -} - -impl<'repo, A> std::fmt::Debug for Oid<'repo, A> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.inner.fmt(f) - } -} - -impl<'repo, A> AsRef for Oid<'repo, A> { - fn as_ref(&self) -> &oid { - &self.inner - } -} - -impl<'repo, A> From> for ObjectId { - fn from(v: Oid<'repo, A>) -> Self { - v.inner - } -} - impl<'repo, A> Oid<'repo, A> where A: easy::Access + Sized, @@ -97,6 +49,60 @@ where } } +mod impls { + use git_hash::{oid, ObjectId}; + + use crate::easy::{Object, ObjectRef, Oid}; + + impl<'repo, A, B> PartialEq> for Oid<'repo, B> { + fn eq(&self, other: &Oid<'repo, A>) -> bool { + self.inner == other.inner + } + } + + impl<'repo, A> PartialEq for Oid<'repo, A> { + fn eq(&self, other: &ObjectId) -> bool { + &self.inner == other + } + } + + impl<'repo, A> PartialEq for Oid<'repo, A> { + fn eq(&self, other: &oid) -> bool { + self.inner == other + } + } + + impl<'repo, A, B> PartialEq> for Oid<'repo, B> { + fn eq(&self, other: &ObjectRef<'repo, A>) -> bool { + self.inner == other.id + } + } + + impl<'repo, A> PartialEq for Oid<'repo, A> { + fn eq(&self, other: &Object) -> bool { + self.inner == other.id + } + } + + impl<'repo, A> std::fmt::Debug for Oid<'repo, A> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } + } + + impl<'repo, A> AsRef for Oid<'repo, A> { + fn as_ref(&self) -> &oid { + &self.inner + } + } + + impl<'repo, A> From> for ObjectId { + fn from(v: Oid<'repo, A>) -> Self { + v.inner + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/git-repository/tests/easy/ext/reference.rs b/git-repository/tests/easy/ext/reference.rs index 15dbc0034d1..04e9795d959 100644 --- a/git-repository/tests/easy/ext/reference.rs +++ b/git-repository/tests/easy/ext/reference.rs @@ -10,7 +10,7 @@ mod set_namespace { fn affects_edits_and_iteration() { let (mut repo, _keep) = easy_repo_rw().unwrap(); assert_eq!( - repo.iter_references().unwrap().all().unwrap().count(), + repo.references().unwrap().all().unwrap().count(), 15, "there are plenty of references in the default namespace" ); @@ -21,12 +21,7 @@ mod set_namespace { ); assert_eq!( - repo.iter_references() - .unwrap() - .all() - .unwrap() - .filter_map(Result::ok) - .count(), + repo.references().unwrap().all().unwrap().filter_map(Result::ok).count(), 0, "no references are in the namespace yet" ); @@ -47,7 +42,7 @@ mod set_namespace { .unwrap(); assert_eq!( - repo.iter_references() + repo.references() .unwrap() .all() .unwrap() @@ -59,7 +54,7 @@ mod set_namespace { ); assert_eq!( - repo.iter_references() + repo.references() .unwrap() .prefixed("refs/tags/") .unwrap() @@ -89,7 +84,7 @@ mod set_namespace { ); assert_eq!( - repo.iter_references().unwrap().all().unwrap().count(), + repo.references().unwrap().all().unwrap().count(), 17, "it lists all references, also the ones in namespaces" ); @@ -108,7 +103,7 @@ mod iter_references { fn all() -> crate::Result { let repo = repo()?; assert_eq!( - repo.iter_references()? + repo.references()? .all()? .filter_map(Result::ok) .map(|r| r.name().as_bstr().to_owned()) @@ -137,7 +132,7 @@ mod iter_references { fn prefixed() -> crate::Result { let repo = repo()?; assert_eq!( - repo.iter_references()? + repo.references()? .prefixed("refs/heads/")? .filter_map(Result::ok) .map(|r| r.name().as_bstr().to_owned()) From 85f1a48ea39f3b224e8d0ba3728dd75e03a6edc3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 22:30:06 +0800 Subject: [PATCH 86/91] =?UTF-8?q?[repository=20#190]=20first=20shot=20at?= =?UTF-8?q?=20ancestor=20iteration=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …which surprisingly enough works with our kind of caching system. Amazing. Does it really work though? --- Cargo.toml | 21 ++--- experiments/diffing/src/main.rs | 4 +- experiments/traversal/src/main.rs | 4 +- git-diff/tests/visit/mod.rs | 12 +-- git-pack/CHANGELOG.md | 7 ++ git-pack/src/data/object.rs | 6 +- git-pack/src/data/output/count/objects.rs | 4 +- git-pack/src/find_traits.rs | 6 +- git-repository/src/easy/mod.rs | 2 +- git-repository/src/easy/object/mod.rs | 6 +- git-repository/src/easy/oid.rs | 94 +++++++++++++++++++++++ 11 files changed, 134 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc68ba1dfc3..6adc947235e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,16 +80,17 @@ env_logger = { version = "0.9.0", optional = true, default-features = false, fea crosstermion = { version = "0.8.0", optional = true, default-features = false } futures-lite = { version = "1.12.0", optional = true, default-features = false, features = ["std"] } -[profile.dev.package] -git-object.opt-level = 3 -git-ref.opt-level = 3 -git-pack.opt-level = 3 -git-hash.opt-level = 3 -git-actor.opt-level = 3 -git-config.opt-level = 3 -miniz_oxide.opt-level = 3 -sha-1.opt-level = 3 -sha1.opt-level = 3 +# toml_edit can't parse this :( +#[profile.dev.package] +#git-object.opt-level = 3 +#git-ref.opt-level = 3 +#git-pack.opt-level = 3 +#git-hash.opt-level = 3 +#git-actor.opt-level = 3 +#git-config.opt-level = 3 +#miniz_oxide.opt-level = 3 +#sha-1.opt-level = 3 +#sha1.opt-level = 3 [profile.release] overflow-checks = false diff --git a/experiments/diffing/src/main.rs b/experiments/diffing/src/main.rs index 8ecae90c2c7..c8201c7081e 100644 --- a/experiments/diffing/src/main.rs +++ b/experiments/diffing/src/main.rs @@ -255,7 +255,7 @@ where where L: for<'a> FnMut(&oid, &'a mut Vec) -> Option>, { - find(id, buf).and_then(|o| o.into_tree_iter()) + find(id, buf).and_then(|o| o.try_into_tree_iter()) } fn tree_iter_by_commit<'b, L>(id: &oid, buf: &'b mut Vec, mut find: L) -> TreeRefIter<'b> @@ -264,7 +264,7 @@ where { let tid = find(id, buf) .expect("commit present") - .into_commit_iter() + .try_into_commit_iter() .expect("a commit") .tree_id() .expect("tree id present and decodable"); diff --git a/experiments/traversal/src/main.rs b/experiments/traversal/src/main.rs index 20fd4a033c1..e2867998c6e 100644 --- a/experiments/traversal/src/main.rs +++ b/experiments/traversal/src/main.rs @@ -209,13 +209,13 @@ where for commit in commits { let tree_id = db .try_find(commit, &mut buf, &mut cache)? - .and_then(|o| o.into_commit_iter().and_then(|mut c| c.tree_id())) + .and_then(|o| o.try_into_commit_iter().and_then(|mut c| c.tree_id())) .expect("commit as starting point"); let mut count = Count { entries: 0, seen }; db.find_tree_iter(tree_id, &mut buf2, &mut cache)?.traverse( &mut state, - |oid, buf| db.find(oid, buf, &mut cache).ok().and_then(|o| o.into_tree_iter()), + |oid, buf| db.find(oid, buf, &mut cache).ok().and_then(|o| o.try_into_tree_iter()), &mut count, )?; entries += count.entries as u64; diff --git a/git-diff/tests/visit/mod.rs b/git-diff/tests/visit/mod.rs index 23faff0f47f..de705a662d2 100644 --- a/git-diff/tests/visit/mod.rs +++ b/git-diff/tests/visit/mod.rs @@ -34,7 +34,7 @@ mod changes { Ok(db .try_find(tree_id, buf, &mut pack::cache::Never)? .expect("main tree present") - .into_tree_iter() + .try_into_tree_iter() .expect("id to be a tree")) } @@ -53,7 +53,7 @@ mod changes { db.try_find(oid, buf, &mut pack::cache::Never) .ok() .flatten() - .and_then(|obj| obj.into_tree_iter()) + .and_then(|obj| obj.try_into_tree_iter()) }, &mut recorder, )?; @@ -78,7 +78,7 @@ mod changes { let current_tree = db .try_find(main_tree_id, &mut buf, &mut pack::cache::Never)? .expect("main tree present") - .into_tree_iter() + .try_into_tree_iter() .expect("id to be a tree"); let mut buf2 = Vec::new(); let previous_tree: Option<_> = { @@ -88,7 +88,7 @@ mod changes { .and_then(|c| c.into_commit()) .map(|c| c.tree()) .and_then(|tree| db.try_find(tree, &mut buf2, &mut pack::cache::Never).ok().flatten()) - .and_then(|tree| tree.into_tree_iter()) + .and_then(|tree| tree.try_into_tree_iter()) }; let mut recorder = git_diff::tree::Recorder::default(); @@ -99,7 +99,7 @@ mod changes { db.try_find(oid, buf, &mut pack::cache::Never) .ok() .flatten() - .and_then(|obj| obj.into_tree_iter()) + .and_then(|obj| obj.try_into_tree_iter()) }, &mut recorder, )?; @@ -133,7 +133,7 @@ mod changes { db.try_find(oid, buf, &mut pack::cache::Never) .ok() .flatten() - .and_then(|o| o.into_commit_iter()) + .and_then(|o| o.try_into_commit_iter()) }) .collect::>() .into_iter() diff --git a/git-pack/CHANGELOG.md b/git-pack/CHANGELOG.md index 651f6dc6483..7bc45a7fc70 100644 --- a/git-pack/CHANGELOG.md +++ b/git-pack/CHANGELOG.md @@ -1,3 +1,10 @@ +### 0.10.0 (2021-08-??) + +- **renames** + - `data::Object::into_commit_iter()` -> `data::Object::try_into_commit_iter()` + - `data::Object::into_tree_iter()` -> `data::Object::try_into_tree_iter()` + - `data::Object::into_tag_iter()` -> `data::Object::try_into_tag_iter()` + ### 0.9.0 (2021-08-27) - **renames / moves / visibility** diff --git a/git-pack/src/data/object.rs b/git-pack/src/data/object.rs index 580e3812501..4de8ae9baf4 100644 --- a/git-pack/src/data/object.rs +++ b/git-pack/src/data/object.rs @@ -29,7 +29,7 @@ impl<'a> Object<'a> { /// Returns this object as tree iterator to parse entries one at a time to avoid allocations, or /// `None` if this is not a tree object. - pub fn into_tree_iter(self) -> Option> { + pub fn try_into_tree_iter(self) -> Option> { match self.kind { git_object::Kind::Tree => Some(TreeRefIter::from_bytes(self.data)), _ => None, @@ -38,7 +38,7 @@ impl<'a> Object<'a> { /// Returns this object as commit iterator to parse tokens one at a time to avoid allocations, or /// `None` if this is not a commit object. - pub fn into_commit_iter(self) -> Option> { + pub fn try_into_commit_iter(self) -> Option> { match self.kind { git_object::Kind::Commit => Some(CommitRefIter::from_bytes(self.data)), _ => None, @@ -47,7 +47,7 @@ impl<'a> Object<'a> { /// Returns this object as tag iterator to parse tokens one at a time to avoid allocations, or /// `None` if this is not a tag object. - pub fn into_tag_iter(self) -> Option> { + pub fn try_into_tag_iter(self) -> Option> { match self.kind { git_object::Kind::Tag => Some(TagRefIter::from_bytes(self.data)), _ => None, diff --git a/git-pack/src/data/output/count/objects.rs b/git-pack/src/data/output/count/objects.rs index c358533261d..2cf58e101b4 100644 --- a/git-pack/src/data/output/count/objects.rs +++ b/git-pack/src/data/output/count/objects.rs @@ -217,7 +217,7 @@ where progress.inc(); stats.expanded_objects += 1; out.push(output::Count::from_data(oid, &obj)); - obj.into_tree_iter() + obj.try_into_tree_iter() } None => None, } @@ -300,7 +300,7 @@ where progress.inc(); stats.expanded_objects += 1; out.push(output::Count::from_data(oid, &obj)); - obj.into_tree_iter() + obj.try_into_tree_iter() } None => None, } diff --git a/git-pack/src/find_traits.rs b/git-pack/src/find_traits.rs index 3c766b106cf..aeb2c28efbd 100644 --- a/git-pack/src/find_traits.rs +++ b/git-pack/src/find_traits.rs @@ -127,9 +127,9 @@ mod ext { make_obj_lookup!(find_tree, ObjectRef::Tree, Kind::Tree, TreeRef<'a>); make_obj_lookup!(find_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>); make_obj_lookup!(find_blob, ObjectRef::Blob, Kind::Blob, BlobRef<'a>); - make_iter_lookup!(find_commit_iter, Kind::Blob, CommitRefIter<'a>, into_commit_iter); - make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, into_tree_iter); - make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, into_tag_iter); + make_iter_lookup!(find_commit_iter, Kind::Blob, CommitRefIter<'a>, try_into_commit_iter); + make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, try_into_tree_iter); + make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, try_into_tag_iter); } impl FindExt for T {} diff --git a/git-repository/src/easy/mod.rs b/git-repository/src/easy/mod.rs index 1f6a5918660..a0b54d91253 100644 --- a/git-repository/src/easy/mod.rs +++ b/git-repository/src/easy/mod.rs @@ -29,7 +29,7 @@ pub mod commit; pub mod head; pub mod iter; pub mod object; -mod oid; +pub mod oid; pub mod reference; pub mod state; diff --git a/git-repository/src/easy/object/mod.rs b/git-repository/src/easy/object/mod.rs index 129ecd413da..19f7e7617de 100644 --- a/git-repository/src/easy/object/mod.rs +++ b/git-repository/src/easy/object/mod.rs @@ -131,16 +131,16 @@ where /// As [`to_commit_iter()`][ObjectRef::to_commit_iter()] but panics if this is not a commit pub fn commit_iter(&self) -> CommitRefIter<'_> { git_odb::data::Object::new(self.kind, &self.data) - .into_commit_iter() + .try_into_commit_iter() .expect("BUG: This object must be a commit") } pub fn to_commit_iter(&self) -> Option> { - git_odb::data::Object::new(self.kind, &self.data).into_commit_iter() + git_odb::data::Object::new(self.kind, &self.data).try_into_commit_iter() } pub fn to_tag_iter(&self) -> Option> { - git_odb::data::Object::new(self.kind, &self.data).into_tag_iter() + git_odb::data::Object::new(self.kind, &self.data).try_into_tag_iter() } } diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index 448e4229c49..d89e785214e 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use std::ops::Deref; use git_hash::{oid, ObjectId}; @@ -6,6 +7,7 @@ use crate::{ easy, easy::{ext::ObjectAccessExt, object::find, ObjectRef, Oid}, }; +use std::cell::RefMut; impl<'repo, A> Oid<'repo, A> where @@ -49,6 +51,98 @@ where } } +pub struct Ancestors<'repo, A> +where + A: easy::Access + Sized, +{ + repo: A::RepoRef, + pack_cache: RefMut<'repo, easy::PackCache>, + access: &'repo A, + tip: ObjectId, +} + +pub mod ancestors { + use crate::easy; + use crate::easy::oid::Ancestors; + use crate::easy::Oid; + use git_odb::Find; + use std::ops::{Deref, DerefMut}; + + impl<'repo, A> Oid<'repo, A> + where + A: easy::Access + Sized, + { + pub fn ancestors(&self) -> Result, Error> { + let pack_cache = self.access.state().try_borrow_mut_pack_cache()?; + let repo = self.access.repo()?; + Ok(Ancestors { + pack_cache, + repo, + access: self.access, + tip: self.inner, + }) + } + } + + pub struct Iter<'a, 'repo, A> + where + A: easy::Access + Sized, + { + access: &'repo A, + inner: Box> + 'a>, + } + + impl<'repo, A> Ancestors<'repo, A> + where + A: easy::Access + Sized, + { + // TODO: tests + pub fn all(&mut self) -> Iter<'_, 'repo, A> { + Iter { + access: self.access, + inner: Box::new(git_traverse::commit::Ancestors::new( + Some(self.tip), + git_traverse::commit::ancestors::State::default(), + move |oid, buf| { + self.repo + .deref() + .odb + .try_find(oid, buf, self.pack_cache.deref_mut()) + .ok() + .flatten() + .and_then(|obj| obj.try_into_commit_iter()) + }, + )), + } + } + } + + impl<'a, 'repo, A> Iterator for Iter<'a, 'repo, A> + where + A: easy::Access + Sized, + { + type Item = Result, git_traverse::commit::ancestors::Error>; + + fn next(&mut self) -> Option { + self.inner.next().map(|res| res.map(|oid| oid.attach(self.access))) + } + } + + mod error { + use crate::easy; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + BorrowRepo(#[from] easy::borrow::repo::Error), + #[error(transparent)] + BorrowBufMut(#[from] easy::borrow::state::Error), + } + } + use crate::ext::ObjectIdExt; + use error::Error; +} + mod impls { use git_hash::{oid, ObjectId}; From f4d1ec4ac0be8aa46d97eb92fb8a8f3fb8da94fb Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 22:39:07 +0800 Subject: [PATCH 87/91] [repository #190] access to repository directories --- git-repository/src/repository.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/git-repository/src/repository.rs b/git-repository/src/repository.rs index 81be37d8f98..a61283eca0d 100644 --- a/git-repository/src/repository.rs +++ b/git-repository/src/repository.rs @@ -144,6 +144,22 @@ pub mod init { } } +mod location { + use crate::Repository; + + impl Repository { + /// The path to the `.git` directory itself, or equivalent if this is a bare repository. + pub fn path(&self) -> &std::path::Path { + &self.refs.base + } + + /// Return the path to the working directory if this is not a bare repository. + pub fn workdir(&self) -> Option<&std::path::Path> { + self.work_tree.as_deref() + } + } +} + pub mod discover { use std::{convert::TryInto, path::Path}; From e5e3c8024e1c2e5e90cee83abbdae41d58eee156 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 22:40:37 +0800 Subject: [PATCH 88/91] Bump git-pack v0.10.0 --- Cargo.lock | 2 +- git-odb/Cargo.toml | 2 +- git-pack/Cargo.toml | 2 +- git-repository/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e06d628f1c..bfde5c10ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1096,7 +1096,7 @@ dependencies = [ [[package]] name = "git-pack" -version = "0.9.0" +version = "0.10.0" dependencies = [ "bstr", "btoi", diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index 340658947a2..3b670a49855 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -31,7 +31,7 @@ all-features = true git-features = { version = "^0.16.0", path = "../git-features", features = ["rustsha1", "walkdir", "zlib"] } git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } -git-pack = { version ="^0.9.0", path = "../git-pack" } +git-pack = { version ="^0.10.0", path = "../git-pack" } btoi = "0.4.2" tempfile = "3.1.0" diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index 31b96eac2cc..442c4bea0e6 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-pack" -version = "0.9.0" +version = "0.10.0" repository = "https://github.com/Byron/gitoxide" authors = ["Sebastian Thiel "] license = "MIT/Apache-2.0" diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 8cfe236599d..e85303454f1 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -46,7 +46,7 @@ git-odb = { version ="^0.21.0", path = "../git-odb" } git-hash = { version ="^0.6.0", path = "../git-hash" } git-object = { version ="^0.13.0", path = "../git-object" } git-actor = { version ="^0.5.0", path = "../git-actor" } -git-pack = { version ="^0.9.0", path = "../git-pack" } +git-pack = { version ="^0.10.0", path = "../git-pack" } git-url = { version = "0.3.0", path = "../git-url", optional = true } git-traverse = { version ="^0.8.0", path = "../git-traverse", optional = true } From 6da35994cf2a3c9ab741733af53761c9a2cebeed Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 6 Sep 2021 23:48:16 +0800 Subject: [PATCH 89/91] [repository #190] fix build, lets just make traversal available by default --- git-repository/Cargo.toml | 3 +-- git-repository/src/ext/object_id.rs | 3 --- git-repository/src/ext/tree.rs | 5 ----- git-repository/src/lib.rs | 2 +- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index e85303454f1..910985b9293 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -20,7 +20,6 @@ max-performance = ["git-features/zlib-ng-compat", "git-features/fast-sha1"] local-time-support = ["git-actor/local-time-support"] local = [ "git-url", - "git-traverse", "git-diff", "git-pack/pack-cache-lru-dynamic", "git-pack/pack-cache-lru-static", @@ -49,7 +48,7 @@ git-actor = { version ="^0.5.0", path = "../git-actor" } git-pack = { version ="^0.10.0", path = "../git-pack" } git-url = { version = "0.3.0", path = "../git-url", optional = true } -git-traverse = { version ="^0.8.0", path = "../git-traverse", optional = true } +git-traverse = { version ="^0.8.0", path = "../git-traverse" } git-protocol = { version ="^0.10.0", path = "../git-protocol", optional = true } git-diff = { version ="^0.9.0", path = "../git-diff", optional = true } git-features = { version = "^0.16.0", path = "../git-features", features = ["progress"] } diff --git a/git-repository/src/ext/object_id.rs b/git-repository/src/ext/object_id.rs index c7e85e792ab..232501440b2 100644 --- a/git-repository/src/ext/object_id.rs +++ b/git-repository/src/ext/object_id.rs @@ -1,6 +1,5 @@ #![allow(missing_docs)] use git_hash::ObjectId; -#[cfg(feature = "git-traverse")] use git_traverse::commit::ancestors::{Ancestors, State}; use crate::easy; @@ -8,7 +7,6 @@ use crate::easy; pub trait Sealed {} pub trait ObjectIdExt: Sealed { - #[cfg(feature = "git-traverse")] fn ancestors_iter(self, find: Find) -> Ancestors bool, State> where Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>; @@ -19,7 +17,6 @@ pub trait ObjectIdExt: Sealed { impl Sealed for ObjectId {} impl ObjectIdExt for ObjectId { - #[cfg(feature = "git-traverse")] fn ancestors_iter(self, find: Find) -> Ancestors bool, State> where Find: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>, diff --git a/git-repository/src/ext/tree.rs b/git-repository/src/ext/tree.rs index 0615201ce59..865f6c8a9f3 100644 --- a/git-repository/src/ext/tree.rs +++ b/git-repository/src/ext/tree.rs @@ -1,11 +1,8 @@ #![allow(missing_docs)] -#[cfg(feature = "git-diff")] use std::borrow::BorrowMut; -#[cfg(feature = "git-diff")] use git_hash::oid; use git_object::TreeRefIter; -#[cfg(feature = "git-traverse")] use git_traverse::tree::breadthfirst; pub trait Sealed {} @@ -25,7 +22,6 @@ pub trait TreeIterExt: Sealed { StateMut: BorrowMut; /// Use this for squeezing out the last bits of performance. - #[cfg(feature = "git-traverse")] fn traverse( &self, state: StateMut, @@ -57,7 +53,6 @@ impl<'d> TreeIterExt for TreeRefIter<'d> { git_diff::tree::Changes::from(Some(self.clone())).needed_to_obtain(other, state, find, delegate) } - #[cfg(feature = "git-traverse")] fn traverse( &self, state: StateMut, diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 3a84ce3174c..b1842d81e79 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -104,7 +104,7 @@ pub use git_protocol as protocol; pub use git_ref as refs; #[cfg(feature = "unstable")] pub use git_tempfile as tempfile; -#[cfg(all(feature = "unstable", feature = "git-traverse"))] +#[cfg(feature = "unstable")] pub use git_traverse as traverse; #[cfg(all(feature = "unstable", feature = "git-url"))] pub use git_url as url; From fdc3678c63fa128ac754b3fa9ae3d88a4a221d0d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 7 Sep 2021 07:42:10 +0800 Subject: [PATCH 90/91] [repository #190] test for oid.ancestors().all() --- git-repository/src/easy/head.rs | 14 ++++---------- git-repository/src/easy/oid.rs | 18 ++++++++++-------- git-repository/tests/easy/ext/object.rs | 2 +- git-repository/tests/easy/mod.rs | 1 + git-repository/tests/easy/oid.rs | 19 +++++++++++++++++++ 5 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 git-repository/tests/easy/oid.rs diff --git a/git-repository/src/easy/head.rs b/git-repository/src/easy/head.rs index 31ee8c02735..a8ba4e9ff33 100644 --- a/git-repository/src/easy/head.rs +++ b/git-repository/src/easy/head.rs @@ -78,8 +78,6 @@ pub mod log { } pub mod peel { - use git_hash::ObjectId; - use crate::{ easy, easy::{head::Kind, Access, Head}, @@ -136,23 +134,19 @@ pub mod peel { }) } - pub fn into_fully_peeled_id(self) -> Option> { + pub fn into_fully_peeled_id(self) -> Option, Error>> { Some(match self.kind { Kind::Unborn(_name) => return None, Kind::Detached { peeled: Some(peeled), .. - } => Ok(peeled), + } => Ok(peeled.attach(self.access)), Kind::Detached { peeled: None, target } => target .attach(self.access) .object() .map_err(Into::into) .and_then(|obj| obj.peel_to_end().map_err(Into::into)) - .map(|peeled| peeled.id), - Kind::Symbolic(r) => r - .attach(self.access) - .peel_to_id_in_place() - .map_err(Into::into) - .map(|id| id.detach()), + .map(|obj| obj.id.attach(self.access)), + Kind::Symbolic(r) => r.attach(self.access).peel_to_id_in_place().map_err(Into::into), }) } } diff --git a/git-repository/src/easy/oid.rs b/git-repository/src/easy/oid.rs index d89e785214e..92301b905dc 100644 --- a/git-repository/src/easy/oid.rs +++ b/git-repository/src/easy/oid.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use std::ops::Deref; +use std::{cell::RefMut, ops::Deref}; use git_hash::{oid, ObjectId}; @@ -7,7 +7,6 @@ use crate::{ easy, easy::{ext::ObjectAccessExt, object::find, ObjectRef, Oid}, }; -use std::cell::RefMut; impl<'repo, A> Oid<'repo, A> where @@ -62,12 +61,15 @@ where } pub mod ancestors { - use crate::easy; - use crate::easy::oid::Ancestors; - use crate::easy::Oid; - use git_odb::Find; use std::ops::{Deref, DerefMut}; + use git_odb::Find; + + use crate::{ + easy, + easy::{oid::Ancestors, Oid}, + }; + impl<'repo, A> Oid<'repo, A> where A: easy::Access + Sized, @@ -96,7 +98,6 @@ pub mod ancestors { where A: easy::Access + Sized, { - // TODO: tests pub fn all(&mut self) -> Iter<'_, 'repo, A> { Iter { access: self.access, @@ -139,8 +140,9 @@ pub mod ancestors { BorrowBufMut(#[from] easy::borrow::state::Error), } } - use crate::ext::ObjectIdExt; use error::Error; + + use crate::ext::ObjectIdExt; } mod impls { diff --git a/git-repository/tests/easy/ext/object.rs b/git-repository/tests/easy/ext/object.rs index dd8fdefb523..0706313192e 100644 --- a/git-repository/tests/easy/ext/object.rs +++ b/git-repository/tests/easy/ext/object.rs @@ -87,7 +87,7 @@ mod commit { "we get the actual HEAD log, not the log of some reference" ); let current_commit = repo.head()?.into_fully_peeled_id().expect("born")?; - assert_eq!(current_commit, &*first_commit_id, "the commit was set"); + assert_eq!(current_commit, first_commit_id, "the commit was set"); let second_commit_id = repo.commit( "refs/heads/new-branch", diff --git a/git-repository/tests/easy/mod.rs b/git-repository/tests/easy/mod.rs index 73647542bbf..03076392ef3 100644 --- a/git-repository/tests/easy/mod.rs +++ b/git-repository/tests/easy/mod.rs @@ -1,4 +1,5 @@ mod access; mod ext; mod object; +mod oid; mod reference; diff --git a/git-repository/tests/easy/oid.rs b/git-repository/tests/easy/oid.rs new file mode 100644 index 00000000000..7036b423e05 --- /dev/null +++ b/git-repository/tests/easy/oid.rs @@ -0,0 +1,19 @@ +mod ancestors { + use git_repository::prelude::ReferenceAccessExt; + + #[test] + fn all() -> crate::Result { + let repo = crate::basic_repo()?; + assert_eq!( + repo.head()? + .into_fully_peeled_id() + .expect("born")? + .ancestors()? + .all() + .count(), + 2, + "need a specific amount of commits" + ); + Ok(()) + } +} From ab250f76b356c0937ada959591dc4df3872acf8f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 7 Sep 2021 08:04:51 +0800 Subject: [PATCH 91/91] [odb #190] Read all eligble packed refs, no "pack-" prefix needed This allows to read packs which are differently named which is what git and libgit2 do, too. --- git-odb/src/store/compound/init.rs | 5 +--- .../tests/fixtures/repos/small-packs.git/HEAD | 1 + .../fixtures/repos/small-packs.git/config | 7 ++++++ .../repos/small-packs.git/description | 1 + .../repos/small-packs.git/info/exclude | 2 ++ ...a90be3fe6db2b49e35858c529e4b9c687b9a08.idx | Bin 0 -> 1100 bytes ...90be3fe6db2b49e35858c529e4b9c687b9a08.pack | Bin 0 -> 169 bytes ...6edf2f7a671742705e6ca8a639daacfcf91217.idx | Bin 0 -> 1128 bytes ...edf2f7a671742705e6ca8a639daacfcf91217.pack | Bin 0 -> 150 bytes .../repos/small-packs.git/refs/heads/next | 1 + git-odb/tests/odb/mod.rs | 1 + git-odb/tests/odb/regression/mod.rs | 23 ++++++++++++++++++ 12 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/HEAD create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/config create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/description create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/info/exclude create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/objects/pack/09a90be3fe6db2b49e35858c529e4b9c687b9a08.idx create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/objects/pack/09a90be3fe6db2b49e35858c529e4b9c687b9a08.pack create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.idx create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.pack create mode 100644 git-odb/tests/fixtures/repos/small-packs.git/refs/heads/next create mode 100644 git-odb/tests/odb/regression/mod.rs diff --git a/git-odb/src/store/compound/init.rs b/git-odb/src/store/compound/init.rs index dfc09e1bf64..a02adf0e402 100644 --- a/git-odb/src/store/compound/init.rs +++ b/git-odb/src/store/compound/init.rs @@ -34,10 +34,7 @@ impl compound::Store { .filter_map(Result::ok) .filter_map(|e| e.metadata().map(|md| (e.path(), md)).ok()) .filter(|(_, md)| md.file_type().is_file()) - .filter(|(p, _)| { - p.extension().unwrap_or_default() == "idx" - && p.file_name().unwrap_or_default().to_string_lossy().starts_with("pack-") - }) + .filter(|(p, _)| p.extension().unwrap_or_default() == "idx") // TODO: make this configurable, git for instance sorts by modification date // https://github.com/libgit2/libgit2/blob/main/src/odb_pack.c#L41-L158 .map(|(p, md)| pack::Bundle::at(p).map(|b| (b, md.len()))) diff --git a/git-odb/tests/fixtures/repos/small-packs.git/HEAD b/git-odb/tests/fixtures/repos/small-packs.git/HEAD new file mode 100644 index 00000000000..b870d82622c --- /dev/null +++ b/git-odb/tests/fixtures/repos/small-packs.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/git-odb/tests/fixtures/repos/small-packs.git/config b/git-odb/tests/fixtures/repos/small-packs.git/config new file mode 100644 index 00000000000..c911c67ba6f --- /dev/null +++ b/git-odb/tests/fixtures/repos/small-packs.git/config @@ -0,0 +1,7 @@ +[core] + bare = true + repositoryformatversion = 0 + filemode = true + ignorecase = true + precomposeunicode = true + logallrefupdates = true diff --git a/git-odb/tests/fixtures/repos/small-packs.git/description b/git-odb/tests/fixtures/repos/small-packs.git/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/git-odb/tests/fixtures/repos/small-packs.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/git-odb/tests/fixtures/repos/small-packs.git/info/exclude b/git-odb/tests/fixtures/repos/small-packs.git/info/exclude new file mode 100644 index 00000000000..6d05881d3a0 --- /dev/null +++ b/git-odb/tests/fixtures/repos/small-packs.git/info/exclude @@ -0,0 +1,2 @@ +# File patterns to ignore; see `git help ignore` for more information. +# Lines that start with '#' are comments. diff --git a/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/09a90be3fe6db2b49e35858c529e4b9c687b9a08.idx b/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/09a90be3fe6db2b49e35858c529e4b9c687b9a08.idx new file mode 100644 index 0000000000000000000000000000000000000000..c8aaedf764bd7a6141df04c4447a7f47a6f706a1 GIT binary patch literal 1100 zcmexg;-AdGz`z8=qhK@yMnhoega9MZsZ_&njx{o9*8gv@s(au2;ll5RgC}+!HL26P y2DF2Rb0zoVf4Q5s%rkB637Y3UC!=~6hhScw&dr`JRSoM_Nx02>a%o1U-CY0=eAmUj^J4G986w^E` za}h2RWnzM?pdo+ytG1Yrx~?_eeteYk^J;#N1C1b<1UX*dTqt$h-o5)DN!XuXp7}Z) X38@R?{%x|fo;8JxQl3kkXnUFn{EJO+ literal 0 HcmV?d00001 diff --git a/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.idx b/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.idx new file mode 100644 index 0000000000000000000000000000000000000000..06709bce7cf6f31e49372cf79be52311df07b9ba GIT binary patch literal 1128 zcmexg;-AdGz`z8=qk#AjU<4{1#iLUy+e{BKn5i&yn literal 0 HcmV?d00001 diff --git a/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.pack b/git-odb/tests/fixtures/repos/small-packs.git/objects/pack/216edf2f7a671742705e6ca8a639daacfcf91217.pack new file mode 100644 index 0000000000000000000000000000000000000000..9bcf521aea36f6643eb3d4c0eafa573e970895bc GIT binary patch literal 150 zcmV;H0BQeFK|@Ob000620007+33!~9j6n(lF$@6jeMRnq#F`BWi1?MJ&0?WzWr_cH z!527}InBiG4rG#-R+(}&cGH54zL2fbShL7#?xaYmeBwEGDDOGOgxk-LdOXg}?{MKO zDrF^s6CR{Gx2;F`A4%%39=(h&3?eQlAb6Yu000000U>VRFM4MeLU3MesHQpEto->B E7qeVI`v3p{ literal 0 HcmV?d00001 diff --git a/git-odb/tests/fixtures/repos/small-packs.git/refs/heads/next b/git-odb/tests/fixtures/repos/small-packs.git/refs/heads/next new file mode 100644 index 00000000000..541dba416ac --- /dev/null +++ b/git-odb/tests/fixtures/repos/small-packs.git/refs/heads/next @@ -0,0 +1 @@ +ecc68100297fff843a7eef8df0d0fb80c1c8bac5 diff --git a/git-odb/tests/odb/mod.rs b/git-odb/tests/odb/mod.rs index 906571e70c4..dd71d689379 100644 --- a/git-odb/tests/odb/mod.rs +++ b/git-odb/tests/odb/mod.rs @@ -3,4 +3,5 @@ pub use git_testtools::{fixture_path, hex_to_id, scripted_fixture_repo_read_only pub type Result = std::result::Result>; pub mod alternate; +pub mod regression; pub mod store; diff --git a/git-odb/tests/odb/regression/mod.rs b/git-odb/tests/odb/regression/mod.rs new file mode 100644 index 00000000000..6018ade261d --- /dev/null +++ b/git-odb/tests/odb/regression/mod.rs @@ -0,0 +1,23 @@ +mod repo_with_small_packs { + use crate::odb::{fixture_path, hex_to_id}; + use git_odb::pack; + + #[test] + fn all_packed_objects_can_be_found() { + let store = git_odb::linked::Store::at(fixture_path("repos/small-packs.git/objects")).unwrap(); + assert_eq!(store.dbs.len(), 1, "a simple repo"); + let db = &store.dbs[0]; + assert_eq!(db.bundles.len(), 2, "small packs"); + let mut buf = Vec::new(); + assert!( + db.try_find( + hex_to_id("ecc68100297fff843a7eef8df0d0fb80c1c8bac5"), + &mut buf, + &mut pack::cache::Never + ) + .unwrap() + .is_some(), + "object is present and available" + ); + } +}