use gix_hash::ObjectId;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RefLine {
pub name: String,
pub oid: Option<ObjectId>,
pub peeled: Option<ObjectId>,
pub symref_target: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RefUpdate {
pub name: String,
pub expected: Option<ObjectId>,
pub new: Option<ObjectId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RefOutcome {
pub name: String,
pub result: Result<(), String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreparedPush {
pub unpack: Result<(), String>,
pub updates: Vec<RefUpdate>,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct PushOptions {
pub pairs: Vec<(String, String)>,
}
#[cfg(test)]
mod tests {
use super::*;
fn oid(b: u8) -> ObjectId {
ObjectId::from([b; 20])
}
#[test]
fn unborn_symref_line_has_no_oid() {
let line = RefLine {
name: "HEAD".to_string(),
oid: None,
peeled: None,
symref_target: Some("refs/heads/main".to_string()),
};
assert!(line.oid.is_none());
assert_eq!(line.symref_target.as_deref(), Some("refs/heads/main"));
}
#[test]
fn ref_line_carries_peeled_tag() {
let line = RefLine {
name: "refs/tags/v1".to_string(),
oid: Some(oid(1)),
peeled: Some(oid(2)),
symref_target: None,
};
assert_eq!(line.oid, Some(oid(1)));
assert_eq!(line.peeled, Some(oid(2)));
}
#[test]
fn create_update_has_no_expected() {
let u = RefUpdate {
name: "refs/heads/main".to_string(),
expected: None,
new: Some(oid(3)),
};
assert!(u.expected.is_none());
assert_eq!(u.new, Some(oid(3)));
}
#[test]
fn delete_update_has_no_new() {
let u = RefUpdate {
name: "refs/heads/main".to_string(),
expected: Some(oid(3)),
new: None,
};
assert!(u.new.is_none());
}
#[test]
fn ref_outcome_is_a_value_not_an_error() {
let ok = RefOutcome {
name: "refs/heads/main".to_string(),
result: Ok(()),
};
let stale = RefOutcome {
name: "refs/heads/other".to_string(),
result: Err("stale info".to_string()),
};
assert!(ok.result.is_ok());
assert_eq!(
stale.result.as_ref().err().map(String::as_str),
Some("stale info")
);
}
#[test]
fn pack_validation_failure_is_the_unpack_ng_leg() {
let prepared = PreparedPush {
unpack: Err("index-pack failed".to_string()),
updates: vec![RefUpdate {
name: "refs/heads/main".to_string(),
expected: None,
new: Some(oid(4)),
}],
};
assert!(prepared.unpack.is_err());
assert_eq!(prepared.updates.len(), 1);
}
#[test]
fn infrastructure_failure_is_err_on_prepare_result_only() {
let prepared = PreparedPush {
unpack: Ok(()),
updates: Vec::new(),
};
assert!(prepared.unpack.is_ok());
assert!(prepared.updates.is_empty());
}
#[test]
fn push_options_preserve_repeated_keys_verbatim() {
let opts = PushOptions {
pairs: vec![
("private".to_string(), "yes".to_string()),
("private".to_string(), "no".to_string()),
],
};
assert_eq!(opts.pairs.len(), 2);
assert_eq!(opts.pairs[0], ("private".to_string(), "yes".to_string()));
assert_eq!(opts.pairs[1], ("private".to_string(), "no".to_string()));
}
}