mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-31 08:50:45 +08:00
Rust 1.67 was announced[0]. Update the nix sources to be on par with the latest rustc version. This required fixes to clippy warnings for using variable interpolation for format strings. There was also an advisory error related to libgit2-sys[1], which is transitively depended on via git2. The fix is included in the 0.16.1 version of git2. [0]: https://blog.rust-lang.org/2023/01/26/Rust-1.67.0.html [1]: https://rustsec.org/advisories/RUSTSEC-2023-0003 Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
551 lines
16 KiB
Rust
551 lines
16 KiB
Rust
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
|
//
|
|
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
|
|
// Linking Exception. For full terms see the included LICENSE file.
|
|
|
|
use std::{
|
|
convert::TryFrom,
|
|
fmt::{self, Display},
|
|
str::FromStr,
|
|
};
|
|
|
|
use git_ext as ext;
|
|
|
|
use super::{AsNamespace, Force};
|
|
|
|
/// Type witness for a [`Reference`] that should point to a single reference.
|
|
pub type One = ext::RefLike;
|
|
|
|
/// Alias for [`One`].
|
|
pub type Single = One;
|
|
|
|
/// Type witness for a [`Reference`] that should point to multiple references.
|
|
pub type Many = ext::RefspecPattern;
|
|
|
|
/// Alias for [`Many`].
|
|
pub type Multiple = Many;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum RefsCategory {
|
|
Heads,
|
|
Rad,
|
|
Tags,
|
|
Notes,
|
|
/// Collaborative objects
|
|
Cobs,
|
|
Unknown(ext::RefLike),
|
|
}
|
|
|
|
impl RefsCategory {
|
|
/// The categories that are present in a default git repository
|
|
pub const fn default_categories() -> [RefsCategory; 3] {
|
|
[Self::Heads, Self::Tags, Self::Notes]
|
|
}
|
|
}
|
|
|
|
impl FromStr for RefsCategory {
|
|
type Err = ext::reference::name::Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Ok(match s {
|
|
"heads" => Self::Heads,
|
|
"rad" => Self::Rad,
|
|
"tags" => Self::Tags,
|
|
"notes" => Self::Notes,
|
|
"cobs" => Self::Cobs,
|
|
other => {
|
|
let reflike = ext::RefLike::try_from(other)?;
|
|
Self::Unknown(reflike)
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Display for RefsCategory {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Heads => f.write_str("heads"),
|
|
Self::Rad => f.write_str("rad"),
|
|
Self::Tags => f.write_str("tags"),
|
|
Self::Notes => f.write_str("notes"),
|
|
Self::Cobs => f.write_str("cobs"),
|
|
Self::Unknown(cat) => f.write_str(cat),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RefsCategory> for ext::RefLike {
|
|
fn from(cat: RefsCategory) -> Self {
|
|
ext::RefLike::try_from(cat.to_string()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl From<&RefsCategory> for ext::RefLike {
|
|
fn from(cat: &RefsCategory) -> Self {
|
|
ext::RefLike::try_from(cat.to_string()).unwrap()
|
|
}
|
|
}
|
|
|
|
impl From<ext::RefLike> for RefsCategory {
|
|
fn from(r: ext::RefLike) -> Self {
|
|
(&r).into()
|
|
}
|
|
}
|
|
|
|
impl From<&ext::RefLike> for RefsCategory {
|
|
fn from(r: &ext::RefLike) -> Self {
|
|
match r.as_str() {
|
|
"heads" => Self::Heads,
|
|
"rad" => Self::Rad,
|
|
"tags" => Self::Tags,
|
|
"notes" => Self::Notes,
|
|
"cobs" => Self::Cobs,
|
|
_ => Self::Unknown(r.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ad-hoc trait to prevent the typechecker from recursing.
|
|
///
|
|
/// Morally, we can convert `Reference<N, R, C>` into `ext::RefLike` for any `R:
|
|
/// Into<ext::RefLike>`. However, the typechecker may then attempt to unify `R`
|
|
/// with `Reference<_, Reference<_, ...` recursively, leading to
|
|
/// non-termination. Hence, we restrict the types which can be used as
|
|
/// `Reference::remote` artificially.
|
|
pub trait AsRemote: Into<ext::RefLike> {}
|
|
|
|
impl AsRemote for ext::RefLike {}
|
|
impl AsRemote for &ext::RefLike {}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Reference<Namespace, Remote, Cardinality> {
|
|
/// The remote portion of this reference.
|
|
pub remote: Option<Remote>,
|
|
/// Where this reference falls under, i.e. `heads`, `tags`, `cob`, or`rad`.
|
|
pub category: RefsCategory,
|
|
/// The path of the reference, e.g. `feature/123`, `dev`, `heads/*`.
|
|
pub name: Cardinality,
|
|
/// The namespace of this reference.
|
|
pub namespace: Option<Namespace>,
|
|
}
|
|
|
|
// Polymorphic definitions
|
|
impl<N, R, C> Reference<N, R, C>
|
|
where
|
|
N: Clone,
|
|
R: Clone,
|
|
C: Clone,
|
|
{
|
|
pub fn with_remote(self, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub fn set_remote(&mut self, remote: impl Into<Option<R>>) {
|
|
self.remote = remote.into();
|
|
}
|
|
|
|
pub fn remote(&mut self, remote: impl Into<Option<R>>) -> &mut Self {
|
|
self.set_remote(remote);
|
|
self
|
|
}
|
|
|
|
/// Set the namespace of this reference to another one. Note that the
|
|
/// namespace does not have to be of the original namespace's type.
|
|
pub fn with_namespace<NN, Other>(self, namespace: NN) -> Reference<Other, R, C>
|
|
where
|
|
NN: Into<Option<Other>>,
|
|
Other: AsNamespace,
|
|
{
|
|
Reference {
|
|
name: self.name,
|
|
remote: self.remote,
|
|
category: self.category,
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Set the named portion of this path.
|
|
pub fn with_name<S: Into<C>>(self, name: S) -> Self {
|
|
Self {
|
|
name: name.into(),
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// Set the named portion of this path.
|
|
pub fn set_name<S: Into<C>>(&mut self, name: S) {
|
|
self.name = name.into();
|
|
}
|
|
|
|
pub fn name<S: Into<C>>(&mut self, name: S) -> &mut Self {
|
|
self.set_name(name);
|
|
self
|
|
}
|
|
}
|
|
|
|
// References with a `One` cardinality
|
|
impl<N, R> Reference<N, R, One> {
|
|
/// Find this particular reference.
|
|
pub fn find<'a>(&self, repo: &'a git2::Repository) -> Result<git2::Reference<'a>, git2::Error>
|
|
where
|
|
Self: ToString,
|
|
{
|
|
repo.find_reference(&self.to_string())
|
|
}
|
|
|
|
/// Resolve the [`git2::Oid`] the reference points to (if it exists).
|
|
///
|
|
/// Avoids allocating a [`git2::Reference`].
|
|
pub fn oid(&self, repo: &git2::Repository) -> Result<git2::Oid, git2::Error>
|
|
where
|
|
Self: ToString,
|
|
{
|
|
repo.refname_to_id(&self.to_string())
|
|
}
|
|
|
|
pub fn create<'a>(
|
|
&self,
|
|
repo: &'a git2::Repository,
|
|
target: git2::Oid,
|
|
force: super::Force,
|
|
log_message: &str,
|
|
) -> Result<git2::Reference<'a>, git2::Error>
|
|
where
|
|
Self: ToString,
|
|
{
|
|
tracing::debug!(
|
|
"creating direct reference {} -> {} (force: {}, reflog: '{}')",
|
|
self.to_string(),
|
|
target,
|
|
force.as_bool(),
|
|
log_message
|
|
);
|
|
let name = self.to_string();
|
|
repo.reference_ensure_log(&name)?;
|
|
repo.reference(&name, target, force.as_bool(), log_message)
|
|
}
|
|
|
|
/// Create a [`SymbolicRef`] from `source` to `self` as the `target`.
|
|
pub fn symbolic_ref<SN, SR>(
|
|
self,
|
|
source: Reference<SN, SR, Single>,
|
|
force: Force,
|
|
) -> SymbolicRef<Reference<SN, SR, Single>, Self>
|
|
where
|
|
R: Clone,
|
|
N: Clone,
|
|
{
|
|
SymbolicRef {
|
|
source,
|
|
target: self,
|
|
force,
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs/namespaces/<namespace>/refs/rad/id`
|
|
pub fn rad_id(namespace: impl Into<Option<N>>) -> Self {
|
|
Self {
|
|
remote: None,
|
|
category: RefsCategory::Rad,
|
|
name: reflike!("id"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs/namespaces/<namespace>/refs/rad/signed_refs`
|
|
/// * `refs/namespaces/<namespace>/refs/remote/<peer_id>/rad/
|
|
/// signed_refs`
|
|
pub fn rad_signed_refs(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Rad,
|
|
name: reflike!("signed_refs"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs/namespaces/<namespace>/refs/rad/self`
|
|
/// * `refs/namespaces/<namespace>/refs/remote/<peer_id>/rad/self`
|
|
pub fn rad_self(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Rad,
|
|
name: reflike!("self"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs/namespaces/<namespace>/refs/heads/<name>`
|
|
/// * `refs/namespaces/<namespace>/refs/remote/<peer_id>/heads/<name>`
|
|
pub fn head(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>, name: One) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Heads,
|
|
name,
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs/namespaces/<namespace>/refs/tags/<name>`
|
|
/// * `refs/namespaces/<namespace>/refs/remote/<peer_id>/tags/<name>`
|
|
pub fn tag(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>, name: One) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Tags,
|
|
name,
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<N, R> Display for Reference<N, R, One>
|
|
where
|
|
for<'a> &'a N: AsNamespace,
|
|
for<'a> &'a R: AsRemote,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(Into::<ext::RefLike>::into(self).as_str())
|
|
}
|
|
}
|
|
|
|
impl<N, R> From<Reference<N, R, One>> for ext::RefLike
|
|
where
|
|
for<'a> &'a N: AsNamespace,
|
|
for<'a> &'a R: AsRemote,
|
|
{
|
|
fn from(r: Reference<N, R, One>) -> Self {
|
|
Self::from(&r)
|
|
}
|
|
}
|
|
|
|
impl<'a, N, R> From<&'a Reference<N, R, One>> for ext::RefLike
|
|
where
|
|
&'a N: AsNamespace,
|
|
&'a R: AsRemote,
|
|
{
|
|
fn from(r: &'a Reference<N, R, One>) -> Self {
|
|
let mut refl = reflike!("refs");
|
|
|
|
if let Some(ref namespace) = r.namespace {
|
|
refl = refl
|
|
.join(reflike!("namespaces"))
|
|
.join(namespace)
|
|
.join(reflike!("refs"));
|
|
}
|
|
if let Some(ref remote) = r.remote {
|
|
refl = refl.join(reflike!("remotes")).join(remote);
|
|
}
|
|
|
|
refl.join(&r.category)
|
|
.join(ext::OneLevel::from(r.name.to_owned()))
|
|
}
|
|
}
|
|
|
|
impl<N, R> From<Reference<N, R, One>> for ext::RefspecPattern
|
|
where
|
|
for<'a> &'a N: AsNamespace,
|
|
for<'a> &'a R: AsRemote,
|
|
{
|
|
fn from(r: Reference<N, R, One>) -> Self {
|
|
Self::from(&r)
|
|
}
|
|
}
|
|
|
|
impl<'a, N, R> From<&'a Reference<N, R, One>> for ext::RefspecPattern
|
|
where
|
|
&'a N: AsNamespace,
|
|
&'a R: AsRemote,
|
|
{
|
|
fn from(r: &'a Reference<N, R, One>) -> Self {
|
|
Into::<ext::RefLike>::into(r).into()
|
|
}
|
|
}
|
|
|
|
// TODO(kim): what is this for?
|
|
#[allow(clippy::from_over_into)]
|
|
impl<'a, N, R> Into<ext::blob::Branch<'a>> for &'a Reference<N, R, Single>
|
|
where
|
|
Self: ToString,
|
|
{
|
|
fn into(self) -> ext::blob::Branch<'a> {
|
|
ext::blob::Branch::from(self.to_string())
|
|
}
|
|
}
|
|
|
|
// References with a `Many` cardinality
|
|
impl<N, R> Reference<N, R, Many> {
|
|
/// Get the iterator for these references.
|
|
pub fn references<'a>(
|
|
&self,
|
|
repo: &'a git2::Repository,
|
|
) -> Result<ext::References<'a>, git2::Error>
|
|
where
|
|
Self: ToString,
|
|
{
|
|
ext::References::from_globs(repo, [self.to_string()])
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs[/namespaces/<namespace>/refs]/rad/ids/*`
|
|
pub fn rad_ids_glob(namespace: impl Into<Option<N>>) -> Self {
|
|
Self {
|
|
remote: None,
|
|
category: RefsCategory::Rad,
|
|
name: refspec_pattern!("ids/*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs[/namespaces/<namespace>/refs][/remotes/<remote>]/heads/*`
|
|
pub fn heads(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Heads,
|
|
name: refspec_pattern!("*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/rad/*`
|
|
pub fn rads(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Rad,
|
|
name: refspec_pattern!("*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/tags/*`
|
|
pub fn tags(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Tags,
|
|
name: refspec_pattern!("*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to:
|
|
/// * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/notes/*`
|
|
pub fn notes(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Notes,
|
|
name: refspec_pattern!("*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
|
|
/// Build a reference that points to
|
|
/// * `refs[/namespaces/namespace]/refs[/remotes/<remote>]/cobs/*`
|
|
pub fn cob(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
|
|
Self {
|
|
remote: remote.into(),
|
|
category: RefsCategory::Cobs,
|
|
name: refspec_pattern!("*"),
|
|
namespace: namespace.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<N, R> Display for Reference<N, R, Many>
|
|
where
|
|
for<'a> &'a N: AsNamespace,
|
|
for<'a> &'a R: AsRemote,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(Into::<ext::RefspecPattern>::into(self).as_str())
|
|
}
|
|
}
|
|
|
|
impl<N, R> From<Reference<N, R, Many>> for ext::RefspecPattern
|
|
where
|
|
for<'a> &'a N: AsNamespace,
|
|
for<'a> &'a R: AsRemote,
|
|
{
|
|
fn from(r: Reference<N, R, Many>) -> Self {
|
|
Self::from(&r)
|
|
}
|
|
}
|
|
|
|
impl<'a, N, R> From<&'a Reference<N, R, Many>> for ext::RefspecPattern
|
|
where
|
|
&'a N: AsNamespace,
|
|
&'a R: AsRemote,
|
|
{
|
|
fn from(r: &'a Reference<N, R, Many>) -> Self {
|
|
let mut refl = reflike!("refs");
|
|
|
|
if let Some(ref namespace) = r.namespace {
|
|
refl = refl
|
|
.join(reflike!("namespaces"))
|
|
.join(namespace)
|
|
.join(reflike!("refs"));
|
|
}
|
|
if let Some(ref remote) = r.remote {
|
|
refl = refl.join(reflike!("remotes")).join(remote);
|
|
}
|
|
|
|
refl.join(&r.category)
|
|
.with_pattern_suffix(r.name.to_owned())
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// The data for creating a symbolic reference in a git repository.
|
|
pub struct SymbolicRef<S, T> {
|
|
/// The new symbolic reference.
|
|
pub source: S,
|
|
/// The reference that already exists and we want to create symbolic
|
|
/// reference of.
|
|
pub target: T,
|
|
/// Whether we should overwrite any pre-existing `source`.
|
|
pub force: Force,
|
|
}
|
|
|
|
impl<S, T> SymbolicRef<S, T> {
|
|
/// Create a symbolic reference of `target`, where the `source` is the newly
|
|
/// created reference.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// * If the `target` does not exist we won't create the symbolic
|
|
/// reference and we error early.
|
|
/// * If we could not create the new symbolic reference since the name
|
|
/// already exists. Note that this will not be the case if `Force::True`
|
|
/// is passed.
|
|
pub fn create<'a>(&self, repo: &'a git2::Repository) -> Result<git2::Reference<'a>, git2::Error>
|
|
where
|
|
for<'b> &'b S: Into<ext::RefLike>,
|
|
for<'b> &'b T: Into<ext::RefLike>,
|
|
{
|
|
let source = Into::<ext::RefLike>::into(&self.source);
|
|
let target = Into::<ext::RefLike>::into(&self.target);
|
|
|
|
let reflog_msg = &format!("creating symbolic ref {source} -> {target}");
|
|
tracing::debug!("{}", reflog_msg);
|
|
|
|
let _ = repo.refname_to_id(target.as_str())?;
|
|
repo.reference_ensure_log(source.as_str())?;
|
|
repo.reference_symbolic(
|
|
source.as_str(),
|
|
target.as_str(),
|
|
self.force.as_bool(),
|
|
reflog_msg,
|
|
)
|
|
}
|
|
}
|