pub const TRAILER_PUBLISH_CHANGE: &str = "Cflx-Publish-Change";
pub const TRAILER_PUBLISH_REMOTE: &str = "Cflx-Publish-Remote";
pub const TRAILER_PUBLISH_BRANCH: &str = "Cflx-Publish-Branch";
pub const PUBLICATION_MARKER_SUBJECT_PREFIX: &str = "Publish required:";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicationTrailers {
pub change_id: String,
pub remote: String,
pub branch: String,
}
pub fn format_publication_marker_message(change_id: &str, remote: &str, branch: &str) -> String {
format!(
"{} {}\n\n{}: {}\n{}: {}\n{}: {}\n",
PUBLICATION_MARKER_SUBJECT_PREFIX,
change_id,
TRAILER_PUBLISH_CHANGE,
change_id,
TRAILER_PUBLISH_REMOTE,
remote,
TRAILER_PUBLISH_BRANCH,
branch
)
}
pub fn parse_publication_trailers(commit_message: &str) -> Option<PublicationTrailers> {
let mut change_id: Option<String> = None;
let mut remote: Option<String> = None;
let mut branch: Option<String> = None;
for line in commit_message.lines() {
let line = line.trim();
let assign = |slot: &mut Option<String>, value: &str| -> bool {
if slot.is_some() {
return false;
}
*slot = Some(value.trim().to_string());
true
};
if let Some(value) = line.strip_prefix(&format!("{}:", TRAILER_PUBLISH_CHANGE)) {
if !assign(&mut change_id, value) {
return None;
}
} else if let Some(value) = line.strip_prefix(&format!("{}:", TRAILER_PUBLISH_REMOTE)) {
if !assign(&mut remote, value) {
return None;
}
} else if let Some(value) = line.strip_prefix(&format!("{}:", TRAILER_PUBLISH_BRANCH)) {
if !assign(&mut branch, value) {
return None;
}
}
}
let (change_id, remote, branch) = (change_id?, remote?, branch?);
if change_id.is_empty() || remote.is_empty() || branch.is_empty() {
return None;
}
Some(PublicationTrailers {
change_id,
remote,
branch,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicationEvidence {
pub commit: String,
pub trailers: PublicationTrailers,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn per_change_upstream_formats_and_parses_publication_trailers() {
let message = format_publication_marker_message("alpha", "origin", "main");
assert!(message.starts_with("Publish required: alpha"));
assert_eq!(
parse_publication_trailers(&message).unwrap(),
PublicationTrailers {
change_id: "alpha".into(),
remote: "origin".into(),
branch: "main".into(),
}
);
}
#[test]
fn per_change_upstream_rejects_incomplete_publication_trailers() {
assert!(parse_publication_trailers("Publish required: alpha").is_none());
assert!(parse_publication_trailers(&format!(
"subject\n\n{}: alpha\n{}: origin\n",
TRAILER_PUBLISH_CHANGE, TRAILER_PUBLISH_REMOTE
))
.is_none());
assert!(parse_publication_trailers(&format!(
"subject\n\n{}: \n{}: origin\n{}: main\n",
TRAILER_PUBLISH_CHANGE, TRAILER_PUBLISH_REMOTE, TRAILER_PUBLISH_BRANCH
))
.is_none());
}
#[test]
fn per_change_upstream_rejects_duplicated_publication_trailers() {
let duplicated = format!(
"{}{}: beta\n",
format_publication_marker_message("alpha", "origin", "main"),
TRAILER_PUBLISH_CHANGE
);
assert!(parse_publication_trailers(&duplicated).is_none());
}
#[test]
fn per_change_upstream_ignores_ordinary_merge_history() {
assert!(parse_publication_trailers("Merge change: alpha").is_none());
assert!(parse_publication_trailers(
&crate::upstream::trailers::format_upstream_merge_message(
"origin",
"main",
"1111111111111111111111111111111111111111"
)
)
.is_none());
}
}