145 lines
5.1 KiB
Rust
145 lines
5.1 KiB
Rust
use radicle::git;
|
|
use radicle::git::raw::Repository;
|
|
use radicle::prelude::Did;
|
|
|
|
use super::error;
|
|
|
|
pub(crate) struct Vote {
|
|
did: Did,
|
|
oid: git::Oid,
|
|
kind: git::canonical::CanonicalObjectType,
|
|
}
|
|
|
|
/// Validates a vote to update a canonical reference during push.
|
|
pub(crate) struct Canonical<'a, 'b> {
|
|
vote: Vote,
|
|
canonical: git::canonical::Canonical<'a, 'b>,
|
|
}
|
|
|
|
impl<'a, 'b> Canonical<'a, 'b> {
|
|
pub fn new(
|
|
me: Did,
|
|
head: git::Oid,
|
|
kind: git::canonical::CanonicalObjectType,
|
|
canonical: git::canonical::Canonical<'a, 'b>,
|
|
) -> Self {
|
|
Self {
|
|
vote: Vote {
|
|
did: me,
|
|
oid: head,
|
|
kind,
|
|
},
|
|
canonical,
|
|
}
|
|
}
|
|
|
|
/// Calculates the quorum of the [`git::canonical::Canonical`] provided.
|
|
///
|
|
/// In some cases, it ensures that the [`head`] is attempting to converge
|
|
/// with the set of commits of the other [`Did`]s.
|
|
///
|
|
/// If a quorum is found, then it is also ensured that the new [`head`] is a
|
|
/// descendant of the current canonical commit, otherwise the commits are
|
|
/// considered diverging.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Ensures that the commits of the other [`Did`]s are in the working
|
|
/// copy, and that checks that any two commits are related in the graph.
|
|
///
|
|
/// Ensures that the new head and the canonical commit do not diverge.
|
|
///
|
|
/// [`head`]: crate::push::canonical::Canonical::head
|
|
pub fn quorum(
|
|
mut self,
|
|
working: &Repository,
|
|
) -> Result<(git::Qualified<'a>, git::raw::ObjectType, git::Oid), error::Canonical> {
|
|
let converges = self
|
|
.canonical
|
|
.converges(working, (&self.vote.did, &self.vote.oid))?;
|
|
if converges || self.canonical.has_no_tips() || self.canonical.is_only(&self.vote.did) {
|
|
self.canonical
|
|
.modify_vote(self.vote.did, self.vote.oid, self.vote.kind);
|
|
}
|
|
|
|
match self.canonical.quorum(working) {
|
|
Ok((cref, quorum_type, quorum_head)) => {
|
|
// Canonical head is an ancestor of head.
|
|
let is_ff = self.vote.oid == quorum_head
|
|
|| working
|
|
.graph_descendant_of(*self.vote.oid, *quorum_head)
|
|
.map_err(|err| {
|
|
error::Canonical::graph_descendant(self.vote.oid, quorum_head, err)
|
|
})?;
|
|
|
|
if !is_ff && !converges {
|
|
Err(error::Canonical::heads_diverge(self.vote.oid, quorum_head))
|
|
} else {
|
|
Ok((cref, quorum_type, quorum_head))
|
|
}
|
|
}
|
|
Err(err) => Err(err.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) mod io {
|
|
use radicle::git;
|
|
use radicle::git::canonical::error::QuorumError;
|
|
|
|
use crate::push::error;
|
|
use crate::{hint, warn};
|
|
|
|
/// Handle recoverable errors, printing relevant information to the
|
|
/// terminal. Otherwise, convert the error into an unrecoverable error
|
|
/// [`error::CanonicalUnrecoverable`].
|
|
pub fn handle_error(
|
|
e: error::Canonical,
|
|
canonical: &git::Qualified,
|
|
hints: bool,
|
|
) -> Result<(), error::CanonicalUnrecoverable> {
|
|
match e {
|
|
error::Canonical::GraphDescendant(e) => Err(e.into()),
|
|
error::Canonical::Converges(e) => Err(e.into()),
|
|
error::Canonical::HeadsDiverge(e) => {
|
|
if hints {
|
|
hint(
|
|
format!("you are attempting to push a commit that would cause \
|
|
your upstream to diverge from the canonical reference {canonical}"),
|
|
);
|
|
hint(
|
|
"to integrate the remote changes, run `git pull --rebase` \
|
|
and try again",
|
|
);
|
|
}
|
|
Err(e.into())
|
|
}
|
|
error::Canonical::Quorum(e) => match e {
|
|
e @ QuorumError::DivergingCommits { .. } => {
|
|
warn(e.to_string());
|
|
warn("it is recommended to find a commit to agree upon");
|
|
Ok(())
|
|
}
|
|
e @ QuorumError::DivergingTags { .. } => {
|
|
warn(e.to_string());
|
|
warn("it is recommended to find a tag to agree upon");
|
|
Ok(())
|
|
}
|
|
e @ QuorumError::DifferentTypes { .. } => {
|
|
warn(e.to_string());
|
|
warn("it is recommended to find an object type (either commit or tag) to agree upon");
|
|
Ok(())
|
|
}
|
|
e @ QuorumError::NoCandidates { .. } => {
|
|
warn(e.to_string());
|
|
warn(
|
|
"it is recommended to find an object (either commit or tag) to agree upon",
|
|
);
|
|
Ok(())
|
|
}
|
|
QuorumError::Git(err) => Err(error::CanonicalUnrecoverable::Git { source: err }),
|
|
},
|
|
}
|
|
}
|
|
}
|