use crate::refs::RefName;
use crate::{ObjectId, PackCompression, PackWriteLimits, ReadLimits};
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub enum ForcePolicy {
#[default]
FastForwardOnly,
Allow,
}
#[derive(Debug, Clone)]
pub struct PushCommand {
pub name: RefName,
pub expected: Option<ObjectId>,
pub new: ObjectId,
pub force: ForcePolicy,
}
impl PushCommand {
pub fn deletes(&self) -> bool {
self.new.is_null()
}
}
#[derive(Debug, Clone, Copy)]
pub struct PushLimits {
pub max_advertisement_bytes: usize,
pub max_refs: usize,
pub max_commands: usize,
pub max_command_bytes: usize,
pub max_status_bytes: usize,
pub max_edges: usize,
pub max_ancestry_steps: usize,
pub read: ReadLimits,
pub pack: PackWriteLimits,
pub compression: PackCompression,
}
impl Default for PushLimits {
fn default() -> Self {
Self {
max_advertisement_bytes: 4 * 1024 * 1024,
max_refs: 100_000,
max_commands: 100_000,
max_command_bytes: 16 * 1024 * 1024,
max_status_bytes: 4 * 1024 * 1024,
max_edges: 4_000_000,
max_ancestry_steps: 4_000_000,
read: ReadLimits::default(),
pack: PackWriteLimits::default(),
compression: PackCompression::Delta(crate::DeltaOptions::DEFAULT),
}
}
}
impl PushLimits {
pub const fn trusted() -> Self {
const UNBOUNDED: usize = usize::MAX / 4;
Self {
max_advertisement_bytes: UNBOUNDED,
max_refs: UNBOUNDED,
max_commands: UNBOUNDED,
max_command_bytes: UNBOUNDED,
max_status_bytes: UNBOUNDED,
max_edges: UNBOUNDED,
max_ancestry_steps: UNBOUNDED,
read: ReadLimits::trusted(),
pack: PackWriteLimits::trusted(),
compression: PackCompression::Delta(crate::DeltaOptions::DEFAULT),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Status {
Ok,
Rejected(Vec<u8>),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum RejectionOrigin {
ExpectedValue,
Receiver,
}
#[derive(Debug, Clone)]
pub struct RefStatus {
pub command: PushCommand,
pub status: Option<Status>,
pub rejection_origin: Option<RejectionOrigin>,
pub attempted: bool,
pub effects: Option<crate::refs::RefEditOutcome>,
pub rewrites: Vec<RefRewrite>,
pub rewrite_complete: bool,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct RefRewrite {
pub name: Option<RefName>,
pub old: Option<ObjectId>,
pub new: Option<ObjectId>,
pub forced: bool,
}
#[derive(Debug, Clone)]
pub struct PushReport {
pub unpack: Option<Status>,
pub refs: Vec<RefStatus>,
pub progress: Vec<Vec<u8>>,
pub warnings: Vec<Vec<u8>>,
}
impl PushReport {
pub(super) fn pending(commands: &[PushCommand]) -> Self {
Self {
unpack: None,
progress: vec![],
warnings: vec![],
refs: commands
.iter()
.cloned()
.map(|command| RefStatus {
command,
status: None,
rejection_origin: None,
attempted: false,
effects: None,
rewrites: vec![],
rewrite_complete: false,
})
.collect(),
}
}
pub(super) fn mark_attempted(&mut self, request: &[u8], written: usize) {
let mut offset = 0usize;
for reference in &mut self.refs {
if written > offset {
reference.attempted = true;
}
let header = &request[offset..offset + 4];
let length =
usize::from_str_radix(std::str::from_utf8(header).expect("encoded packet"), 16)
.expect("encoded packet length");
offset += length;
}
}
pub fn all_succeeded(&self) -> bool {
(self.refs.is_empty() || self.unpack == Some(Status::Ok))
&& self
.refs
.iter()
.all(|r| r.status == Some(Status::Ok) && r.rewrite_complete)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PushError {
#[error("push not sent")]
NotSent(#[source] PushFailure),
#[error("push outcome uncertain")]
Uncertain {
#[source]
cause: PushFailure,
report: Box<PushReport>,
},
}
#[derive(Debug, thiserror::Error)]
pub enum PushFailure {
#[error("local destination")]
Destination(#[source] Box<crate::OpenError>),
#[error("local object installation")]
Install(#[source] Box<crate::fetch::FetchError>),
#[error("local reference publication")]
Reference(#[source] Box<crate::refs::ReferenceError>),
#[error("invalid push reflog identity")]
Identity,
#[error(transparent)]
ObjectFormat(#[from] crate::ObjectFormatError),
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
#[error(transparent)]
Ssh(crate::transport::ssh::SshError),
#[cfg(feature = "http")]
#[error(transparent)]
Http(crate::transport::http::HttpError),
#[error("push I/O")]
Io(#[source] std::io::Error),
#[error("invalid receive-pack response: {0}")]
Protocol(&'static str),
#[error("unsupported push feature: {0}")]
Unsupported(&'static str),
#[error("receive-pack error: {0:?}")]
Remote(Vec<u8>),
#[error("push limit exceeded: {0}")]
Limit(&'static str),
#[error("push cancelled")]
Cancelled,
#[error("transport deadline expired")]
Deadline,
#[error("receiver history root is not advertised: {0}")]
KnowledgeChanged(ObjectId),
#[error("invalid push command: {0}")]
Command(&'static str),
#[error("replacement requires explicit force for {0:?}")]
WouldForce(RefName),
#[error("missing reachable object {0}")]
Missing(ObjectId),
#[error("wrong reachable object kind for {0}")]
Kind(ObjectId),
#[error("push object {id}")]
Read {
id: ObjectId,
#[source]
source: crate::ObjectReadError,
},
#[error("push pack")]
Pack(#[from] crate::PackWriteError),
#[error("push commit {id}")]
Commit {
id: ObjectId,
#[source]
source: crate::CommitError,
},
#[error("push tree {id}")]
Tree {
id: ObjectId,
#[source]
source: crate::TreeError,
},
#[error("push tag {id}")]
Tag {
id: ObjectId,
#[source]
source: crate::TagError,
},
#[error("local receive-pack exited unsuccessfully: {0}")]
Process(std::process::ExitStatus),
}
impl PushFailure {
pub(super) fn destination(error: crate::OpenError) -> Self {
Self::Destination(Box::new(error))
}
pub(super) fn install(error: crate::fetch::FetchError) -> Self {
Self::Install(Box::new(error))
}
pub(super) fn reference(error: crate::refs::ReferenceError) -> Self {
Self::Reference(Box::new(error))
}
}
impl From<crate::packet::Error> for PushFailure {
fn from(error: crate::packet::Error) -> Self {
match error {
crate::packet::Error::Io(e) => Self::from(e),
crate::packet::Error::Protocol(e) => Self::Protocol(e),
crate::packet::Error::Limit(e) => Self::Limit(e),
crate::packet::Error::Cancelled => Self::Cancelled,
crate::packet::Error::Remote(e) => Self::Remote(e),
}
}
}
impl From<std::io::Error> for PushFailure {
fn from(error: std::io::Error) -> Self {
match crate::transport::interruption(&error) {
Some(crate::transport::Interruption::Cancelled) => Self::Cancelled,
Some(crate::transport::Interruption::Deadline) => Self::Deadline,
None => Self::Io(error),
}
}
}
#[cfg(feature = "http")]
impl From<crate::transport::http::HttpError> for PushFailure {
fn from(error: crate::transport::http::HttpError) -> Self {
match error {
crate::transport::http::HttpError::Cancelled => Self::Cancelled,
crate::transport::http::HttpError::Deadline => Self::Deadline,
error => Self::Http(error),
}
}
}
#[cfg(all(feature = "ssh", any(target_os = "macos", target_os = "linux")))]
impl From<crate::transport::ssh::SshError> for PushFailure {
fn from(error: crate::transport::ssh::SshError) -> Self {
match error {
crate::transport::ssh::SshError::Cancelled => Self::Cancelled,
crate::transport::ssh::SshError::Deadline => Self::Deadline,
error => Self::Ssh(error),
}
}
}
impl From<crate::edges::Error> for PushFailure {
fn from(error: crate::edges::Error) -> Self {
match error {
crate::edges::Error::Commit { id, source } => Self::Commit { id, source },
crate::edges::Error::Tree { id, source } => Self::Tree { id, source },
crate::edges::Error::Tag { id, source } => Self::Tag { id, source },
}
}
}