112 lines
3.5 KiB
Rust
112 lines
3.5 KiB
Rust
#[derive(Clone, Debug)]
|
|
pub struct RefUpdate {
|
|
pub name: String,
|
|
pub old_oid: String,
|
|
pub new_oid: String,
|
|
}
|
|
|
|
impl RefUpdate {
|
|
pub fn parse_ref_updates(data: &[u8]) -> Result<Vec<Self>, String> {
|
|
let mut refs = Vec::new();
|
|
|
|
for payload in parse_pkt_line_payloads(data)? {
|
|
let line = String::from_utf8_lossy(payload);
|
|
let line = line.trim_end_matches(['\r', '\n']);
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
let mut parts = line.splitn(3, ' ');
|
|
let old_oid = parts.next().unwrap_or_default();
|
|
let new_oid = parts.next().unwrap_or_default();
|
|
let raw_name = parts.next().unwrap_or_default();
|
|
let name = raw_name
|
|
.split_once('\0')
|
|
.map(|(name, _)| name)
|
|
.unwrap_or(raw_name)
|
|
.trim();
|
|
|
|
if old_oid.len() != 40 || new_oid.len() != 40 || name.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
refs.push(RefUpdate {
|
|
old_oid: old_oid.to_string(),
|
|
new_oid: new_oid.to_string(),
|
|
name: name.to_string(),
|
|
});
|
|
}
|
|
|
|
Ok(refs)
|
|
}
|
|
}
|
|
|
|
fn parse_pkt_line_payloads(data: &[u8]) -> Result<Vec<&[u8]>, String> {
|
|
let mut payloads = Vec::new();
|
|
let mut offset = 0;
|
|
|
|
while offset + 4 <= data.len() {
|
|
let header = std::str::from_utf8(&data[offset..offset + 4])
|
|
.map_err(|_| "invalid pkt-line header encoding".to_string())?;
|
|
let len = usize::from_str_radix(header, 16)
|
|
.map_err(|_| format!("invalid pkt-line length: {header}"))?;
|
|
offset += 4;
|
|
|
|
match len {
|
|
0 => break,
|
|
1..=3 => return Err(format!("invalid pkt-line length: {len}")),
|
|
_ => {
|
|
let payload_len = len - 4;
|
|
if offset + payload_len > data.len() {
|
|
return Err("truncated pkt-line payload".to_string());
|
|
}
|
|
payloads.push(&data[offset..offset + payload_len]);
|
|
offset += payload_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(payloads)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::RefUpdate;
|
|
|
|
fn pkt(payload: &str) -> Vec<u8> {
|
|
let len = payload.len() + 4;
|
|
let mut out = format!("{len:04x}").into_bytes();
|
|
out.extend_from_slice(payload.as_bytes());
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn parses_receive_pack_ref_with_capabilities() {
|
|
let mut data = pkt(
|
|
"0000000000000000000000000000000000000000 1111111111111111111111111111111111111111 refs/heads/feature\0 report-status\n",
|
|
);
|
|
data.extend_from_slice(b"0000");
|
|
|
|
let refs = RefUpdate::parse_ref_updates(&data).unwrap();
|
|
|
|
assert_eq!(refs.len(), 1);
|
|
assert_eq!(refs[0].old_oid, "0000000000000000000000000000000000000000");
|
|
assert_eq!(refs[0].new_oid, "1111111111111111111111111111111111111111");
|
|
assert_eq!(refs[0].name, "refs/heads/feature");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_receive_pack_ref_without_pack_payload() {
|
|
let mut data = pkt(
|
|
"2222222222222222222222222222222222222222 0000000000000000000000000000000000000000 refs/heads/old\n",
|
|
);
|
|
data.extend_from_slice(b"0000");
|
|
|
|
let refs = RefUpdate::parse_ref_updates(&data).unwrap();
|
|
|
|
assert_eq!(refs.len(), 1);
|
|
assert_eq!(refs[0].name, "refs/heads/old");
|
|
assert_eq!(refs[0].new_oid, "0000000000000000000000000000000000000000");
|
|
}
|
|
}
|