gitdataai/libs/git/ssh/ref_update.rs
ZhenYi 0703816482 feat(ssh): enhance ref update handling and add push queue
- Add post-receive refs tracking with mutex-protected storage
- Improve branch protection error messages with actionable guidance
- Add push queue slot waiting mechanism for concurrent push control
- Support for checking push queue availability before push operations
2026-05-15 11:48:40 +08:00

114 lines
3.6 KiB
Rust

#[derive(Clone, Debug)]
pub struct RefUpdate {
pub name: String,
pub old_oid: String,
pub new_oid: String,
}
impl RefUpdate {
/// Parse git receive-pack reference update commands from pkt-line data.
/// Payload format: "<old-oid> <new-oid> <ref-name>\0capabilities\n".
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");
}
}