From 0660588b64f8d68ffa2f585ad49b384e86e3caec Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 15 Jun 2022 20:55:04 +0800 Subject: [PATCH] Allow `RevSpec` to be serialized and deserialized with `serde`. (#427) --- git-repository/Cargo.toml | 19 ++++-- git-repository/src/lib.rs | 3 +- git-repository/src/rev_spec.rs | 105 +++++++++++++++++++++++++++------ git-repository/src/types.rs | 9 ++- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index b6f04c223f2..230ba229301 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -17,9 +17,6 @@ test = true default = ["max-performance", "one-stop-shop"] -# enable when https://github.com/RustCrypto/asm-hashes/issues/17 is fixed -# max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-features/fast-sha1", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] - #! ### Mutually Exclusive Client #! Either `async-*` or `blocking-*` versions of these toggles may be enabled at a time. @@ -41,9 +38,22 @@ one-stop-shop = [ "local", "local-time-support" ] #! ### Other ## Data structures implement `serde::Serialize` and `serde::Deserialize`. -serde1 = ["git-pack/serde1", "git-object/serde1", "git-protocol/serde1", "git-transport/serde1", "git-ref/serde1", "git-odb/serde1", "git-index/serde1", "git-mailmap/serde1", "git-attributes/serde1"] +serde1 = [ "serde", + "git-pack/serde1", + "git-object/serde1", + "git-protocol/serde1", + "git-transport/serde1", + "git-ref/serde1", + "git-odb/serde1", + "git-index/serde1", + "git-mailmap/serde1", + "git-attributes/serde1", + "git-revision/serde1"] ## Activate other features that maximize performance, like usage of threads, `zlib-ng` and access to caching in object databases. ## **Note** that + +# enable when https://github.com/RustCrypto/asm-hashes/issues/17 is fixed +# max-performance = ["git-features/fast-sha1", "git-features/parallel", "git-features/zlib-ng-compat", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] ## Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. local-time-support = ["git-actor/local-time-support"] @@ -92,6 +102,7 @@ thiserror = "1.0.26" clru = "0.5.0" byte-unit = "4.0" log = "0.4.14" +serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} document-features = { version = "0.2.0", optional = true } diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index 7047a11325e..312f129be22 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -185,7 +185,8 @@ pub(crate) type Config = OwnShared>; /// mod types; pub use types::{ - Commit, DetachedObject, Head, Id, Object, Reference, Repository, RevSpec, Tag, ThreadSafeRepository, Tree, Worktree, + Commit, DetachedObject, Head, Id, Object, Reference, Repository, RevSpec, RevSpecDetached, Tag, + ThreadSafeRepository, Tree, Worktree, }; pub mod commit; diff --git a/git-repository/src/rev_spec.rs b/git-repository/src/rev_spec.rs index 9fd1a84f31d..e4471299334 100644 --- a/git-repository/src/rev_spec.rs +++ b/git-repository/src/rev_spec.rs @@ -1,10 +1,12 @@ #![allow(missing_docs)] use crate::ext::ReferenceExt; -use crate::{Id, Reference, RevSpec}; +use crate::types::RevSpecDetached; +use crate::{Id, Reference, Repository, RevSpec}; /// pub mod parse { use crate::bstr::{BStr, ByteSlice}; + use crate::types::RevSpecDetached; use crate::Repository; use crate::RevSpec; use git_revision::spec::parse; @@ -61,11 +63,13 @@ pub mod parse { } Err(err) => return Err(err.into()), Ok(()) => RevSpec { - from_ref: delegate.refs[0].take(), - from: delegate.objs[0], - to_ref: delegate.refs[1].take(), - to: delegate.objs[1], - kind: delegate.kind, + inner: RevSpecDetached { + from_ref: delegate.refs[0].take(), + from: delegate.objs[0], + to_ref: delegate.refs[1].take(), + to: delegate.objs[1], + kind: delegate.kind, + }, repo, }, }; @@ -209,7 +213,9 @@ mod impls { impl<'repo> PartialEq for RevSpec<'repo> { fn eq(&self, other: &Self) -> bool { - self.kind == other.kind && self.from == other.from && self.to == other.to + self.inner.kind == other.inner.kind + && self.inner.from == other.inner.from + && self.inner.to == other.inner.to } } @@ -221,11 +227,13 @@ impl<'repo> RevSpec<'repo> { /// Create a single specification which points to `id`. pub fn from_id(id: Id<'repo>) -> Self { RevSpec { - from_ref: None, - from: Some(id.inner), - to: None, - to_ref: None, - kind: None, + inner: RevSpecDetached { + from_ref: None, + from: Some(id.inner), + to: None, + to_ref: None, + kind: None, + }, repo: id.repo, } } @@ -233,6 +241,11 @@ impl<'repo> RevSpec<'repo> { /// Access impl<'repo> RevSpec<'repo> { + /// Detach the `Repository` from this instance, leaving only plain data that can be moved freely and serialized. + pub fn detach(self) -> RevSpecDetached { + self.inner + } + /// Some revision specifications leave information about reference names which are returned as `(from-ref, to-ref)` here, e.g. /// `HEAD@{-1}..main` might be (`refs/heads/previous-branch`, `refs/heads/main`). /// @@ -241,9 +254,10 @@ impl<'repo> RevSpec<'repo> { pub fn into_names(self) -> (Option>, Option>) { // TODO: assure we can set the reference also when it is only implied, like with `@{1}`. let repo = self.repo; + let this = self.inner; ( - self.from_ref.map(|r| r.attach(repo)), - self.to_ref.map(|r| r.attach(repo)), + this.from_ref.map(|r| r.attach(repo)), + this.to_ref.map(|r| r.attach(repo)), ) } @@ -251,18 +265,19 @@ impl<'repo> RevSpec<'repo> { /// /// Note that this can be `None` for ranges like e.g. `^HEAD`, `..@`, `...v1.0` or similar. pub fn from(&self) -> Option> { - self.from.map(|id| Id::from_id(id, self.repo)) + self.inner.from.map(|id| Id::from_id(id, self.repo)) } /// The object at which the range ends, as in e.g. `...HEAD` or `...feature`. /// /// Note that this can be `None` in case of single revisions like `HEAD@{1}` or `HEAD...`. pub fn to(&self) -> Option> { - self.to.map(|id| Id::from_id(id, self.repo)) + self.inner.to.map(|id| Id::from_id(id, self.repo)) } /// Return the single object represented by this instance, or `None` if it is a range of any kind. pub fn single(&self) -> Option> { - self.from + self.inner + .from .and_then(|id| matches!(self.kind(), git_revision::spec::Kind::Single).then(|| Id::from_id(id, self.repo))) } @@ -282,6 +297,62 @@ impl<'repo> RevSpec<'repo> { }) } + pub fn kind(&self) -> git_revision::spec::Kind { + self.inner.kind.unwrap_or(git_revision::spec::Kind::Single) + } +} + +/// Access +impl RevSpecDetached { + /// Attach `repo` to ourselves for more convenient types. + pub fn attach(self, repo: &Repository) -> RevSpec<'_> { + RevSpec { inner: self, repo } + } + /// Some revision specifications leave information about reference names which are returned as `(from-ref, to-ref)` here, e.g. + /// `HEAD@{-1}..main` might be (`refs/heads/previous-branch`, `refs/heads/main`). + /// + /// Note that no reference name is known when revisions are specified by prefix as with `v1.2.3-10-gabcd1234`. + // TODO: tests + pub fn into_names(self) -> (Option, Option) { + // TODO: assure we can set the reference also when it is only implied, like with `@{1}`. + (self.from_ref, self.to_ref) + } + + /// The object from which to start a range, or the only revision as specified by e.g. `@` or `HEAD`. + /// + /// Note that this can be `None` for ranges like e.g. `^HEAD`, `..@`, `...v1.0` or similar. + pub fn from(&self) -> Option { + self.from + } + /// The object at which the range ends, as in e.g. `...HEAD` or `...feature`. + /// + /// Note that this can be `None` in case of single revisions like `HEAD@{1}` or `HEAD...`. + pub fn to(&self) -> Option { + self.to + } + + /// Return the single object represented by this instance, or `None` if it is a range of any kind. + pub fn single(&self) -> Option { + self.from + .and_then(|id| matches!(self.kind(), git_revision::spec::Kind::Single).then(|| id)) + } + + /// Return `(kind being merge-base or range, from-id, to-id)` if our `kind` is not describing a single revision. + /// + /// Note that `...HEAD` is equivalent to `HEAD...HEAD` and `HEAD..` is equivalent to `HEAD..HEAD`. If this is not desirable, + /// access [`from()`][RevSpec::from()] and [`to()`][RevSpec::to()] individually after validating that [`kind()`][RevSpec::kind()] + /// is indeed not a single revision. + // TODO: test + pub fn range(&self) -> Option<(git_revision::spec::Kind, git_hash::ObjectId, git_hash::ObjectId)> { + (!matches!(self.kind(), git_revision::spec::Kind::Single)).then(|| { + ( + self.kind(), + self.from().or_else(|| self.to()).expect("at least one id is set"), + self.to().or_else(|| self.from()).expect("at least one id is set"), + ) + }) + } + pub fn kind(&self) -> git_revision::spec::Kind { self.kind.unwrap_or(git_revision::spec::Kind::Single) } diff --git a/git-repository/src/types.rs b/git-repository/src/types.rs index 1651344eafa..aa18d1c81ad 100644 --- a/git-repository/src/types.rs +++ b/git-repository/src/types.rs @@ -169,10 +169,17 @@ pub struct ThreadSafeRepository { /// to specify revisions and revision ranges. #[derive(Clone, Debug)] pub struct RevSpec<'repo> { + pub(crate) inner: RevSpecDetached, + pub(crate) repo: &'repo Repository, +} + +/// A revision specification without any bindings to a repository, useful for serialization or movement over thread boundaries. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct RevSpecDetached { pub(crate) from_ref: Option, pub(crate) from: Option, pub(crate) to_ref: Option, pub(crate) to: Option, pub(crate) kind: Option, - pub(crate) repo: &'repo Repository, }