Skip to main content

git_zero_client/
reference.rs

1//! Named checkout selection and ordinary-HTTP symbolic-reference resolution.
2use crate::Transport;
3use git_zero_core::{
4    Error, ObjectId, Result,
5    http::Request,
6    io::{SliceWriter, Write},
7    repository::validate_ref_name,
8};
9
10/// Normalize HEAD, a full ref, or an unqualified branch into caller storage.
11pub fn reference_name<'a>(name: &str, storage: &'a mut [u8]) -> Result<&'a str> {
12    let mut output = SliceWriter::new(storage);
13    if name != "HEAD" && !name.starts_with("refs/") {
14        output.write_all(b"refs/heads/")?;
15    }
16    output.write_all(name.as_bytes())?;
17    let bytes = output.finish();
18    let name = core::str::from_utf8(bytes).map_err(|_| Error::InvalidReference)?;
19    if name != "HEAD" {
20        validate_ref_name(name)?;
21    }
22    Ok(name)
23}
24
25fn encode_path<'a>(name: &str, storage: &'a mut [u8]) -> Result<&'a str> {
26    let mut output = SliceWriter::new(storage);
27    for byte in name.bytes() {
28        if byte.is_ascii_alphanumeric() || b"/._-".contains(&byte) {
29            output.write_all(&[byte])?;
30        } else {
31            const HEX: &[u8] = b"0123456789ABCDEF";
32            output.write_all(&[b'%', HEX[(byte >> 4) as usize], HEX[(byte & 15) as usize]])?;
33        }
34    }
35    core::str::from_utf8(output.finish()).map_err(|_| Error::InvalidReference)
36}
37
38/// Resolve a normalized ref using bounded GETs, without discovery. `name` is
39/// HEAD or a validated fully qualified ref. Only symbolic refs are followed;
40/// object traversal and tag peeling belong to the shared checkout layer.
41pub fn resolve_reference(
42    transport: &mut impl Transport,
43    name: &str,
44    response: &mut [u8],
45    path: &mut [u8],
46    max_reads: u32,
47) -> Result<ObjectId> {
48    if name != "HEAD" {
49        validate_ref_name(name)?;
50    }
51    let mut request_path = encode_path(name, path)?;
52    for _ in 0..max_reads {
53        let (status, length) = transport.request(
54            Request {
55                content_type: None,
56                git_protocol: None,
57                method: "GET",
58                path: request_path,
59                query: None,
60                body: &[],
61            },
62            response,
63        )?;
64        if status == 404 {
65            return Err(Error::ObjectNotFound);
66        }
67        if status != 200 {
68            return Err(Error::Backend);
69        }
70        let line = response.get(..length).ok_or(Error::BufferTooSmall)?;
71        let line = line.strip_suffix(b"\n").unwrap_or(line);
72        if let Some(target) = line.strip_prefix(b"ref: ") {
73            let target = core::str::from_utf8(target).map_err(|_| Error::InvalidReference)?;
74            validate_ref_name(target)?;
75            request_path = encode_path(target, path)?;
76        } else {
77            return ObjectId::from_hex(line);
78        }
79    }
80    Err(Error::LimitExceeded)
81}