g1t/services/repos/src/refs.rs

145 lines5,422 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Pull requests from branches1//! Listing a repository's branches.
2//!
3//! The Artifacts binding reads commits, trees and blobs but does not list
4//! refs, so this asks the way a git client does: the ref advertisement of
5//! the smart HTTP protocol.
6
7use g1t_contracts::repos::{Branch, GitAccess};
8use worker::{Error, Fetch, Headers, Method, Request, RequestInit, Result};
9
10use crate::land::read_pkt_lines;
11
12const HEADS: &str = "refs/heads/";
13
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily14/// Every ref in a ref advertisement, with its commit, in the order
15/// advertised: `(refs/heads/main, <hash>)`.
16fn parse_refs(bytes: &[u8]) -> Vec<(String, String)> {
Pull requests from branches17 let (lines, _) = read_pkt_lines(bytes);
18 lines
19 .into_iter()
20 .filter_map(|line| {
21 // `<hash> <ref>`, and on the first ref a NUL then capabilities.
22 let line = line.split(|byte| *byte == 0).next()?;
23 let line = std::str::from_utf8(line).ok()?.trim_end();
24 let (hash, name) = line.split_once(' ')?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily25 (hash.len() >= 40 && name.starts_with("refs/")).then(|| (name.to_owned(), hash.to_owned()))
Pull requests from branches26 })
27 .collect()
28}
29
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily30/// The branches in a ref advertisement, in the order advertised.
31fn parse_advertisement(bytes: &[u8]) -> Vec<Branch> {
32 parse_refs(bytes)
33 .into_iter()
34 .filter_map(|(name, hash)| Some(Branch { name: name.strip_prefix(HEADS)?.to_owned(), hash }))
35 .collect()
36}
37
Composer from the workspace's own repositories, and go get from g1t.sh38/// Branches and tags from every ref, an annotated tag's commit taken from
39/// its peeled line (`refs/tags/v1^{}`) when the advertisement has one.
40pub fn heads_and_tags(all: Vec<(String, String)>) -> Vec<(String, String)> {
41 let mut out: Vec<(String, String)> = Vec::new();
42 for (name, hash) in all {
43 if let Some(tag) = name.strip_suffix("^{}") {
44 match out.iter_mut().find(|(seen, _)| seen == tag) {
45 Some(entry) => entry.1 = hash,
46 None => out.push((tag.to_owned(), hash)),
47 }
48 continue;
49 }
50 if name.starts_with(HEADS) || name.starts_with("refs/tags/") {
51 out.push((name, hash));
52 }
53 }
54 out
55}
56
Pull requests from branches57pub async fn branches(access: &GitAccess) -> Result<Vec<Branch>> {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily58 Ok(parse_advertisement(&advertisement(access).await?))
59}
60
61/// Every ref the repository has: branches, tags, and the heads of pull
62/// requests kept there (forks.rs).
63pub async fn all(access: &GitAccess) -> Result<Vec<(String, String)>> {
64 Ok(parse_refs(&advertisement(access).await?))
65}
66
67async fn advertisement(access: &GitAccess) -> Result<Vec<u8>> {
Pull requests from branches68 let headers = Headers::new();
69 headers.set("authorization", &format!("Bearer {}", access.token))?;
70 let mut init = RequestInit::new();
71 init.with_method(Method::Get).with_headers(headers);
72 let request = Request::new_with_init(
73 &format!("{}/info/refs?service=git-upload-pack", access.remote),
74 &init,
75 )?;
76 let mut response = Fetch::Request(request).send().await?;
77 let bytes = response.bytes().await?;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily78 crate::meters::record_remote("internal.git.info_refs", &access.remote, 0, bytes.len() as u64);
Pull requests from branches79 if response.status_code() != 200 {
80 return Err(Error::RustError(format!(
81 "listing refs returned {}",
82 response.status_code()
83 )));
84 }
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily85 Ok(bytes)
Pull requests from branches86}
87
88#[cfg(test)]
89mod tests {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily90 use super::{parse_advertisement, parse_refs};
Pull requests from branches91
92 fn pkt(payload: &str) -> Vec<u8> {
93 format!("{:04x}{payload}", payload.len() + 4).into_bytes()
94 }
95
96 #[test]
97 fn branches_are_read_from_an_advertisement() {
98 let main = "c71546fcd893ef8b0f57388b65e620d759705dda";
99 let shout = "4807077b296e6edbf410d55e72749d3e1170c291";
100 let advertisement = [
101 pkt("# service=git-upload-pack\n"),
102 b"0000".to_vec(),
103 pkt(&format!(
104 "{main} HEAD\0side-band-64k symref=HEAD:refs/heads/main\n"
105 )),
106 pkt(&format!("{main} refs/heads/main\n")),
107 pkt(&format!("{shout} refs/heads/shout\n")),
108 pkt(&format!("{shout} refs/tags/v1.0.0\n")),
109 b"0000".to_vec(),
110 ]
111 .concat();
112 let branches = parse_advertisement(&advertisement);
113 let names: Vec<&str> = branches.iter().map(|branch| branch.name.as_str()).collect();
114 assert_eq!(names, ["main", "shout"]);
115 assert_eq!(branches[1].hash, shout);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily116 let all = parse_refs(&advertisement);
117 assert_eq!(all.len(), 3);
118 assert_eq!(all[2], ("refs/tags/v1.0.0".to_owned(), shout.to_owned()));
Pull requests from branches119 }
120
121 #[test]
Composer from the workspace's own repositories, and go get from g1t.sh122 fn annotated_tags_are_peeled_and_other_refs_left_out() {
123 let all = vec![
124 ("refs/heads/main".to_owned(), "a".to_owned()),
125 ("refs/pull/1/head".to_owned(), "b".to_owned()),
126 ("refs/tags/v1".to_owned(), "tagobject".to_owned()),
127 ("refs/tags/v1^{}".to_owned(), "c".to_owned()),
128 ("refs/tags/v2".to_owned(), "d".to_owned()),
129 ];
130 assert_eq!(
131 super::heads_and_tags(all),
132 vec![
133 ("refs/heads/main".to_owned(), "a".to_owned()),
134 ("refs/tags/v1".to_owned(), "c".to_owned()),
135 ("refs/tags/v2".to_owned(), "d".to_owned()),
136 ]
137 );
138 }
139
140 #[test]
Pull requests from branches141 fn an_empty_repository_has_no_branches() {
142 let advertisement = [pkt("# service=git-upload-pack\n"), b"00000000".to_vec()].concat();
143 assert!(parse_advertisement(&advertisement).is_empty());
144 }
145}