use serde_json::Value;
use crate::service_review_key::ReviewKey;
use std::path::{Path, PathBuf};
pub const PROTOCOL_VERSION: u32 = 7;
pub const RELEASE: &str = env!("CARGO_PKG_VERSION");
pub const HEARTBEAT_EVERY: std::time::Duration = std::time::Duration::from_secs(1);
pub const HEARTBEAT_SILENCE: std::time::Duration = std::time::Duration::from_secs(5);
pub const P99_CHANGED_PATH_CHARACTERS: usize = 118;
pub const MAX_LINE_BYTES: usize = 1 << 20;
pub const MAX_CHANGES: usize = 4096;
pub const MAX_ID_BYTES: usize = 128;
pub const ALLOWED_MODES: [u32; 3] = [0o100_644, 0o100_755, 0o120_000];
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Change {
pub path: String,
pub mode: u32,
pub blob: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Cleanup {
pub git_dir: PathBuf,
pub scratch: PathBuf,
pub reftable: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommitRequest {
pub id: String,
pub repository: PathBuf,
pub common_dir: PathBuf,
pub git_dir: PathBuf,
pub reference: String,
pub base: String,
pub changes: Vec<Change>,
pub message: String,
pub author: Option<String>,
pub committer: String,
pub amend: bool,
pub tree: Option<String>,
pub commit: Option<String>,
pub cleanup: Option<Cleanup>,
pub index: PathBuf,
pub captured: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LandedCapture {
pub commit: String,
pub captured: u64,
}
pub fn is_zero_object(name: &str) -> bool {
return is_object_name(name) && name.trim_start_matches('0').is_empty();
}
impl CommitRequest {
pub fn lands_alone(&self) -> bool {
return self.amend || self.commit.is_some();
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FailureCode {
RepositoryFull,
RepositoryReadOnly,
IndexInUse,
Rejected,
BranchKeepsChanging,
BranchMoved,
StepTooSlow,
Unavailable,
}
impl FailureCode {
pub fn word(self) -> &'static str {
return match self {
Self::RepositoryFull => "repository-full",
Self::RepositoryReadOnly => "repository-read-only",
Self::IndexInUse => "index-in-use",
Self::Rejected => "rejected",
Self::BranchKeepsChanging => "branch-keeps-changing",
Self::BranchMoved => "branch-moved",
Self::StepTooSlow => "step-too-slow",
Self::Unavailable => "unavailable",
};
}
pub const ALL: [Self; 8] = [
Self::RepositoryFull,
Self::RepositoryReadOnly,
Self::IndexInUse,
Self::Rejected,
Self::BranchKeepsChanging,
Self::BranchMoved,
Self::StepTooSlow,
Self::Unavailable,
];
pub fn from_word(word: &str) -> Option<Self> {
for code in Self::ALL {
if code.word() == word {
return Some(code);
}
}
return None;
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ClientMessage {
Commit(Box<CommitRequest>),
Retire,
Withdraw {
id: String,
},
Scratch {
git_dir: PathBuf,
path: PathBuf,
},
Work {
git_dir: PathBuf,
review_key: Option<ReviewKey>,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ServiceMessage {
Welcome { version: u32, release: Option<String> },
Position { number: usize, of: usize },
Landed { id: String, commit: String, already: bool },
RetryPrepare { id: String, base: String, landed: Vec<LandedCapture> },
Failed { id: String, code: FailureCode },
Waiting { text: String },
InsideWrite { text: String },
Withdrawn { id: String },
Holder { who: String },
Heartbeat,
}
pub(crate) fn field<'a>(value: &'a Value, name: &str) -> Result<&'a str, String> {
return match value.get(name).and_then(Value::as_str) {
Some(text) => Ok(text),
None => Err(format!("field {name} is missing or not text")),
};
}
pub fn is_object_name(text: &str) -> bool {
return (text.len() == 40 || text.len() == 64) && text.as_bytes().iter().all(u8::is_ascii_hexdigit);
}
fn is_plain_part(part: &str) -> bool {
return !part.is_empty() && part != "." && part != ".." && part != ".git";
}
pub(crate) fn is_identity_byte(byte: &u8) -> bool {
return byte.is_ascii_alphanumeric() || *byte == b'-';
}
fn is_plain_component(part: std::path::Component<'_>) -> bool {
return !matches!(part, std::path::Component::ParentDir | std::path::Component::CurDir);
}
pub fn is_plain_path(path: &str) -> bool {
return !path.is_empty()
&& !path.starts_with('/')
&& !path.contains(['\0', '\n'])
&& path.split('/').all(is_plain_part);
}
pub const SCRATCH_PREFIX: &str = "monochromatic-git-policy-cli-service-";
pub fn is_scratch_path(path: &Path, git_dir: &Path) -> bool {
let Some(name) = path.file_name().and_then(std::ffi::OsStr::to_str) else { return false };
let identity: &str = name.strip_prefix(SCRATCH_PREFIX).unwrap_or("");
return git_dir.is_absolute()
&& path.parent() == Some(git_dir)
&& !identity.is_empty()
&& identity.as_bytes().iter().all(is_identity_byte)
&& git_dir.components().all(is_plain_component)
&& !path.to_string_lossy().contains('\0');
}