Skip to content

Commit

Permalink
Allow RevSpec to be serialized and deserialized with serde. (#427)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Jun 15, 2022
1 parent f3c609f commit 0660588
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 23 deletions.
19 changes: 15 additions & 4 deletions git-repository/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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"]
Expand Down Expand Up @@ -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 }

Expand Down
3 changes: 2 additions & 1 deletion git-repository/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ pub(crate) type Config = OwnShared<git_config::File<'static>>;
///
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;
Expand Down
105 changes: 88 additions & 17 deletions git-repository/src/rev_spec.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
},
};
Expand Down Expand Up @@ -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
}
}

Expand All @@ -221,18 +227,25 @@ 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,
}
}
}

/// 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`).
///
Expand All @@ -241,28 +254,30 @@ impl<'repo> RevSpec<'repo> {
pub fn into_names(self) -> (Option<Reference<'repo>>, Option<Reference<'repo>>) {
// 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)),
)
}

/// 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<Id<'repo>> {
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<Id<'repo>> {
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<Id<'repo>> {
self.from
self.inner
.from
.and_then(|id| matches!(self.kind(), git_revision::spec::Kind::Single).then(|| Id::from_id(id, self.repo)))
}

Expand All @@ -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<git_ref::Reference>, Option<git_ref::Reference>) {
// 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<git_hash::ObjectId> {
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<git_hash::ObjectId> {
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<git_hash::ObjectId> {
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)
}
Expand Down
9 changes: 8 additions & 1 deletion git-repository/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<git_ref::Reference>,
pub(crate) from: Option<git_hash::ObjectId>,
pub(crate) to_ref: Option<git_ref::Reference>,
pub(crate) to: Option<git_hash::ObjectId>,
pub(crate) kind: Option<git_revision::spec::Kind>,
pub(crate) repo: &'repo Repository,
}

0 comments on commit 0660588

Please sign in to comment.