git clone https://code.lmjtfy.fun/lmjtfy.git: the code, for
anyone, without a GitHub account and without publishing it on GitHub.
Git's smart HTTP is two requests: GET <repo>/info/refs?service= git-upload-pack lists the refs, and POST <repo>/git-upload-pack sends
the pack. Both are forwarded to the private repository on GitHub with a
fine-grained token that can read these two repositories and do nothing
else, so a push through here is refused by GitHub, not only by the routes.
Nothing is kept and nothing is spent: a clone reaches neither Jev nor the
LLM, and the budgets do not count it.
The code host is code.lmjtfy.fun; the paths on lmjtfy.fun lead there
(host::gate). Which repositories are served is Repo::ALL, and the code
host's front page (index) lists exactly that.
jevcrates is served beside lmjtfy because lmjtfy's .gitmodules names it
by a relative URL (../jevcrates.git), which git resolves against
wherever the clone came from: here, the Worker; from GitHub, GitHub.
The secret's name: a GitHub fine-grained token with Contents: read on
deizel/lmjtfy and deizel/jevcrates, and no other permission.
40pub const TOKEN: &str = "LMJTFY_GITHUB_TOKEN";
The one git service served. git-receive-pack is a push.
43const UPLOAD_PACK: &str = "git-upload-pack";
The largest request body forwarded. An upload-pack request is the refs a client wants and has, a few kilobytes even for a long history.
47const MAX_REQUEST_BYTES: usize = 1 << 20;
The headers a git client sends that change GitHub's answer, forwarded as
they came. git-protocol selects protocol v2, which modern git asks for;
GitHub answers smart HTTP only to a git/ user agent.
52const FORWARDED: [&str; 5] = ["accept", "content-type", "content-encoding", "git-protocol", "user-agent"];
The latest commit on lmjtfy's main, from GitHub's API. The token's
Contents: read covers it. per_page=1 makes the last page's number the
count of commits.
How long an isolate keeps the latest commit before asking again. Chats fetch a pasted link's page and picture together; this makes that one call, not two.
61const LATEST_MS: f64 = 60_000.0;
63thread_local! {
Each repository's latest commit and when it was read, in this isolate.
68thread_local! {
Each repository's metadata from GitHub and when it was read.
Declares Repo and Repo::ALL from one list, so a repository cannot be
served without being listed, or listed without being served: the routes,
the redirect from the old addresses (host::gate) and the code host's
landing page all read ALL, and ALL is the variants written here.
The repositories that can be cloned from here. Anything else is not found.
To serve another: add its
name to the list under repositories! below, then answer the
compiler's match errors (served, github,
submodules). Nothing else changes: it is cloned at
code.lmjtfy.fun/<served>, listed on that host's front page and
redirected to from lmjtfy.fun. Its token needs Contents: read on
the repository too (README, "Credentials").
94 impl Repo {
Every repository served, in the order the code pages list them: this site, the client, then the other projects that use it.
102repositories! {
lmjtfy and the three other projects that ask Jev, jevcrates, the client those four share, jevstrudel, which asks Jev from the browser, and whiskers, which asks it to guard a child's chat.
116impl Repo {
The name its downloads are under (/whiskers/latest/...): served
without .git.
The path segment it is cloned by: lmjtfy.git.
124 pub fn served(self) -> &'static str { 125 match self { 126 Repo::Lmjtfy => "lmjtfy.git", 127 Repo::Jevcrates => "jevcrates.git", 128 Repo::Postjevsql => "postjevsql.git", 129 Repo::Jevsnes => "jevsnes.git", 130 Repo::Jevhooks => "jevhooks.git", 131 Repo::Jevstrudel => "jevstrudel.git", 132 Repo::Whiskers => "whiskers.git", 133 } 134 }
owner/name on GitHub.
141 pub fn github(self) -> &'static str { 142 match self { 143 Repo::Lmjtfy => "deizel/lmjtfy", 144 Repo::Jevcrates => "deizel/jevcrates", 145 Repo::Postjevsql => "deizel/postjevsql", 146 Repo::Jevsnes => "deizel/jevsnes", 147 Repo::Jevhooks => "deizel/jevhooks", 148 Repo::Jevstrudel => "deizel/jevstrudel", 149 Repo::Whiskers => "deizel/whiskers", 150 } 151 }
Its submodules: where each is mounted, and which repository it is.
The test below holds this to .gitmodules, where the checkout has it.
155 pub fn submodules(self) -> &'static [(&'static str, Repo)] { 156 match self { 157 Repo::Lmjtfy | Repo::Postjevsql | Repo::Jevhooks | Repo::Whiskers => &[("third-party/jevcrates", Repo::Jevcrates)], 158 Repo::Jevsnes => &[("third-party/rust/jevcrates", Repo::Jevcrates)], 159 Repo::Jevcrates | Repo::Jevstrudel => &[], 160 } 161 }
The branch the pages read: GitHub's default for it.
The command the page offers, for this site's origin.
What the code pages need to know about it.
GET / at the code host: every repository served, from Repo::ALL, with
the command that clones it.
189#[worker::send] 190pub async fn index(State(app): State<App>, headers: HeaderMap) -> Response<Body> { 191 let origin = super::origin(&headers); 192 let metas = metas(&app.env).await; 193 let listings: Vec<_> = Repo::ALL 194 .iter() 195 .zip(metas) 196 .map(|(repo, meta)| crate::view::repos::Listing { project: repo.project(), command: repo.command(&origin), meta }) 197 .collect(); 198 Response::builder() 199 .header(header::CONTENT_TYPE, "text/html; charset=utf-8") 200 .header(header::CACHE_CONTROL, "no-cache") 201 .body(Body::from(crate::view::repos::page(&origin, &listings).into_string())) 202 .expect("static headers are valid") 203}
What GitHub says about each repository, in the order of Repo::ALL,
asked for together.
What GitHub says repo is (description, homepage, topics), kept a
minute per isolate like latest. Only a good answer is kept: if GitHub
cannot be read, the last good one however old, else None, and the
next page asks again.
215pub async fn meta(env: &worker::Env, repo: Repo) -> Option<Meta> { 216 let now = js_sys::Date::now(); 217 let kept = META.with(|kept| kept.borrow().iter().find(|(seen, _, _)| *seen == repo).map(|(_, at, meta)| (*at, meta.clone()))); 218 if let Some((at, meta)) = &kept 219 && now - at < LATEST_MS 220 { 221 return Some(meta.clone()); 222 } 223 let url = format!("https://api.github.com/repos/{}", repo.github()); 224 let read = match github(env, &url).await { 225 Some((200, body, _)) => Meta::parse(&body), 226 _ => None, 227 }; 228 match read { 229 Some(meta) => { 230 META.with(|kept| { 231 let mut kept = kept.borrow_mut(); 232 kept.retain(|(seen, _, _)| *seen != repo); 233 kept.push((repo, now, meta.clone())); 234 }); 235 Some(meta) 236 } 237 None => kept.map(|(_, meta)| meta), 238 } 239}
The command the home page offers: lmjtfy's.
?view=agents on the front page shows the root CLAUDE.md.
GET /lmjtfy.git (or any served repository) from a browser or a chat's
link preview: its front page. Git never asks for this path, so the clone
address is also the page.
261#[worker::send] 262pub async fn landing( 263 State(app): State<App>, 264 Path(segment): Path<String>, 265 Query(asked): Query<Viewed>, 266 headers: HeaderMap, 267) -> Response<Body> { 268 let Some(repo) = Repo::named(&segment) else { return refused(StatusCode::NOT_FOUND, "No such page.") }; 269 let latest = latest(&app.env, repo).await; 270 let view = crate::view::code::View::asked(asked.view.as_deref()); 271 let docs = crate::browse::root(&app.env, repo, view).await; 272 let whole = crate::browse::explorer(&app.env, repo).await; 273 let origin = super::origin(&headers); 274 let metas = metas(&app.env).await; 275 let frame = crate::browse::frame(&origin, super::host_of(&headers), super::git_redirect(&app.env), repo, "", &whole, latest.as_ref(), metas); 276 let page = crate::view::code::page(&frame, crate::view::code::Shown::Dir { docs }); 277 Response::builder() 278 .header(header::CONTENT_TYPE, "text/html; charset=utf-8") 279 .header(header::CACHE_CONTROL, "no-cache") 280 .body(Body::from(page.into_string())) 281 .expect("static headers are valid") 282}
The latest commit on main, read at most once a minute per isolate. If
GitHub cannot be read, the last one read, however old; None only if
there has never been one.
287pub async fn latest(env: &worker::Env, repo: Repo) -> Option<Commit> { 288 let now = js_sys::Date::now(); 289 let kept = LATEST.with(|latest| latest.borrow().iter().find(|(seen, _, _)| *seen == repo).map(|(_, at, commit)| (*at, commit.clone()))); 290 if let Some((at, commit)) = &kept 291 && now - at < LATEST_MS 292 { 293 return Some(commit.clone()); 294 } 295 match read_latest(env, repo).await { 296 Some(commit) => { 297 LATEST.with(|latest| { 298 let mut latest = latest.borrow_mut(); 299 latest.retain(|(seen, _, _)| *seen != repo); 300 latest.push((repo, now, commit.clone())); 301 }); 302 Some(commit) 303 } 304 None => kept.map(|(_, commit)| commit), 305 } 306}
308async fn read_latest(env: &worker::Env, repo: Repo) -> Option<Commit> { 309 let url = format!("https://api.github.com/repos/{}/commits?sha={}&per_page=1", repo.github(), repo.branch()); 310 match github(env, &url).await? { 311 (200, body, link) => listed(&body, link.as_deref()), 312 _ => None, 313 } 314}
A GET of GitHub's API with the read-only token: its status, body and
link header. None if there is no token or GitHub did not answer.
318pub async fn github(env: &worker::Env, url: &str) -> Option<(u16, String, Option<String>)> { 319 let token = env.secret(TOKEN).ok()?.to_string(); 320 let headers = worker::Headers::new(); 321 headers.set("authorization", &format!("Bearer {token}")).ok()?; 322 headers.set("accept", "application/vnd.github+json").ok()?; 323 // GitHub refuses API requests without one. 324 headers.set("user-agent", "lmjtfy").ok()?; 325 let mut init = worker::RequestInit::new(); 326 init.with_headers(headers); 327 let request = worker::Request::new_with_init(url, &init).ok()?; 328 let mut response = worker::Fetch::Request(request).send().await.ok()?; 329 let link = response.headers().get("link").ok().flatten(); 330 Some((response.status_code(), response.text().await.ok()?, link)) 331}
The commit in GitHub's one-item list, and the count from its link
header.
352fn listed(body: &str, link: Option<&str>) -> Option<Commit> { 353 let mut listed: Vec<Listed> = serde_json::from_str(body).ok()?; 354 let first = (!listed.is_empty()).then(|| listed.swap_remove(0))?; 355 Some(Commit { 356 subject: first.commit.message.lines().next().unwrap_or_default().to_owned(), 357 date: first.commit.committer.map(|signed| signed.date.chars().take(10).collect()).unwrap_or_default(), 358 count: link.and_then(last_page).or(Some(1)), 359 sha: first.sha, 360 }) 361}
The page number of rel="last" in a link header. With no link there
is one page, which the caller counts.
GET /<repo>/info/refs?service=git-upload-pack. Without the service
parameter this is git's old dumb protocol, which is not served.
373#[worker::send] 374pub async fn refs(State(app): State<App>, Path(segment): Path<String>, Query(asked): Query<Service>, headers: HeaderMap) -> Response<Body> { 375 let Some(repo) = Repo::named(&segment) else { return refused(StatusCode::NOT_FOUND, "No such repository.") }; 376 if asked.service != UPLOAD_PACK { 377 return refused(StatusCode::FORBIDDEN, "This copy can be cloned and fetched, and nothing else."); 378 } 379 let url = format!("{}/info/refs?service={UPLOAD_PACK}", repo.upstream()); 380 forward(&app, Method::GET, url, &headers, None).await 381}
POST /<repo>/git-upload-pack: the pack.
384#[worker::send] 385pub async fn upload_pack( 386 State(app): State<App>, 387 Path(segment): Path<String>, 388 axum::extract::Extension(event): axum::extract::Extension<crate::events::Event>, 389 headers: HeaderMap, 390 body: Body, 391) -> Response<Body> { 392 let Some(repo) = Repo::named(&segment) else { return refused(StatusCode::NOT_FOUND, "No such repository.") }; 393 let Ok(body) = to_bytes(body, MAX_REQUEST_BYTES).await else { 394 return refused(StatusCode::PAYLOAD_TOO_LARGE, "That request is too large."); 395 }; 396 let fetch = fetched(&body, headers.get(header::CONTENT_ENCODING).and_then(|value| value.to_str().ok())); 397 let url = format!("{}/{UPLOAD_PACK}", repo.upstream()); 398 let response = forward(&app, Method::POST, url, &headers, Some(body.to_vec())).await; 399 let Some(fetch) = fetch.filter(|_| response.status() == StatusCode::OK) else { return response }; 400 // A round of negotiation that ends a fetch is the one GitHub answers 401 // with the pack. The start of the reply is read to see, and sent on 402 // with the rest. 403 let (parts, body) = response.into_parts(); 404 let mut rest = body.into_data_stream(); 405 let mut start = Vec::new(); 406 while start.len() < PEEK_BYTES && !packs(&start) { 407 match rest.next().await { 408 Some(Ok(chunk)) => start.extend_from_slice(&chunk), 409 _ => break, 410 } 411 } 412 if packs(&start) { 413 let which = match fetch { 414 Fetch::Clone => "clone", 415 Fetch::Pull => "pull", 416 }; 417 let mut event = event.named("fetch").with(which); 418 event.status = 200.0; 419 crate::events::record(&app.env, event).await; 420 crate::archive::fetched(&app.env, repo.served(), fetch).await; 421 } 422 let first = futures_util::stream::once(async move { Ok::<_, axum::Error>(bytes::Bytes::from(start)) }); 423 Response::from_parts(parts, Body::from_stream(first.chain(rest))) 424}
How much of a reply is read before giving up on finding a pack: past the acknowledgments a long negotiation can send.
428const PEEK_BYTES: usize = 64 * 1024;
Whether a reply has begun sending a pack: protocol v2's packfile
section, or a v0 pack's PACK signature.
What a fetch is, from its upload-pack request: a clone if it names no
commit the client has, a pull if it does. None for anything that is
not a fetch, such as protocol v2's ls-refs, which every clone and pull
(and a pull with nothing new) starts with. Every round of a negotiation
looks like this; the reply says which round sent the pack (packs).
441pub fn fetched(body: &[u8], encoding: Option<&str>) -> Option<Fetch> { 442 let body = match encoding { 443 Some(encoding) if encoding.eq_ignore_ascii_case("gzip") => { 444 let mut unzipped = Vec::new(); 445 flate2::read::GzDecoder::new(body).take(MAX_REQUEST_BYTES as u64 * 8).read_to_end(&mut unzipped).ok()?; 446 unzipped 447 } 448 Some(_) => return None, 449 None => body.to_vec(), 450 }; 451 let lines = pkt_lines(&body)?; 452 let command = lines.iter().find_map(|line| line.strip_prefix(b"command=".as_slice())); 453 if command.is_some_and(|command| command != b"fetch") { 454 return None; 455 } 456 let wants = lines.iter().any(|line| line.starts_with(b"want ")); 457 let haves = lines.iter().any(|line| line.starts_with(b"have ")); 458 match (wants, haves) { 459 (true, false) => Some(Fetch::Clone), 460 (true, true) => Some(Fetch::Pull), 461 (false, _) => None, 462 } 463}
Git's pkt-lines: four hex digits of length, then the payload, without its
newline. Flush, delimiter and response-end packets (0000, 0001,
0002) carry nothing. None if the body is not pkt-lines.
468fn pkt_lines(body: &[u8]) -> Option<Vec<&[u8]>> { 469 let mut lines = Vec::new(); 470 let mut at = 0; 471 while at < body.len() { 472 let length = usize::from_str_radix(std::str::from_utf8(body.get(at..at + 4)?).ok()?, 16).ok()?; 473 if length < 4 { 474 at += 4; 475 continue; 476 } 477 let line = body.get(at + 4..at + length)?; 478 lines.push(line.strip_suffix(b"\n").unwrap_or(line)); 479 at += length; 480 } 481 Some(lines) 482}
484async fn forward(app: &App, method: Method, url: String, headers: &HeaderMap, body: Option<Vec<u8>>) -> Response<Body> { 485 let Ok(token) = app.env.secret(TOKEN).map(|secret| secret.to_string()) else { 486 return refused(StatusCode::SERVICE_UNAVAILABLE, "Cloning is not set up here yet."); 487 }; 488 let mut request = http::Request::builder().method(method).uri(url); 489 for (name, value) in forwarded(headers) { 490 request = request.header(name, value); 491 } 492 request = request.header(header::AUTHORIZATION, authorization(&token)); 493 let request = match request.body(Body::from(body.unwrap_or_default())) { 494 Ok(request) => request, 495 Err(_) => return refused(StatusCode::BAD_REQUEST, "That request could not be read."), 496 }; 497 let sent = match worker::Request::try_from(request) { 498 Ok(request) => worker::Fetch::Request(request).send().await, 499 Err(error) => Err(error), 500 }; 501 match sent { 502 // Streamed back as GitHub sends it: a pack can be megabytes. 503 Ok(response) => response.into(), 504 Err(_) => refused(StatusCode::BAD_GATEWAY, "GitHub did not answer."), 505 } 506}
The client's headers that are passed on. Its own authorization, if it
sent one, is not: the token is the only credential GitHub sees.
GitHub takes a token over git's HTTP as the password of Basic auth.
519pub fn refused(status: StatusCode, why: &str) -> Response<Body> { 520 Response::builder() 521 .status(status) 522 .header(header::CONTENT_TYPE, "text/plain; charset=utf-8") 523 .body(Body::from(format!("{why}\n"))) 524 .expect("a static response") 525} 526 527#[cfg(test)] 528mod tests { 529 use super::*; 530 531 #[test] 532 fn only_the_listed_repositories_are_served() { 533 assert_eq!(Repo::named("lmjtfy.git"), Some(Repo::Lmjtfy)); 534 assert_eq!(Repo::named("jevcrates.git"), Some(Repo::Jevcrates)); 535 assert_eq!(Repo::named("postjevsql.git"), Some(Repo::Postjevsql)); 536 assert_eq!(Repo::named("jevhooks.git"), Some(Repo::Jevhooks)); 537 assert_eq!(Repo::named("jevsnes.git"), Some(Repo::Jevsnes)); 538 assert_eq!(Repo::named("lmjtfy"), None); 539 assert_eq!(Repo::named("nixos-config.git"), None); 540 assert_eq!(Repo::named("..%2Fnixos-config.git"), None); 541 } 542 543 #[test] 544 fn what_is_served_is_what_is_listed_once_each() { 545 // `Repo::ALL` is the variants written in `repositories!`, so these 546 // are the checks a hand-kept list would need: no name twice, every one 547 // a `.git` that `named` finds, and a GitHub repository of its own. 548 let served: std::collections::HashSet<_> = Repo::ALL.iter().map(|repo| repo.served()).collect(); 549 let github: std::collections::HashSet<_> = Repo::ALL.iter().map(|repo| repo.github()).collect(); 550 assert_eq!(served.len(), Repo::ALL.len()); 551 assert_eq!(github.len(), Repo::ALL.len()); 552 for repo in Repo::ALL { 553 assert!(repo.served().ends_with(".git") && !repo.served().contains('/'), "{repo:?}"); 554 assert_eq!(Repo::named(repo.served()), Some(repo)); 555 assert!(repo.github().starts_with("deizel/"), "{repo:?}"); 556 } 557 } 558 559 #[test] 560 fn the_submodule_resolves_to_a_served_repository() { 561 // `.gitmodules` names jevcrates relative to lmjtfy, so the path it 562 // resolves to here must be one that is served. 563 let gitmodules = include_str!("../../../.gitmodules"); 564 assert!(gitmodules.contains("url = ../jevcrates.git"), "{gitmodules}"); 565 assert_eq!(Repo::named("jevcrates.git"), Some(Repo::Jevcrates)); 566 for (mount, repo) in Repo::Lmjtfy.submodules() { 567 assert!(gitmodules.contains(&format!("path = {mount}")), "{mount}"); 568 assert!(gitmodules.contains(&format!("url = ../{}", repo.served())), "{mount}"); 569 } 570 } 571 572 #[test] 573 fn a_repository_with_submodules_is_cloned_with_them() { 574 assert_eq!(Repo::Lmjtfy.command("https://x"), "git clone --recurse-submodules https://x/lmjtfy.git"); 575 assert_eq!(Repo::Jevcrates.command("https://x"), "git clone https://x/jevcrates.git"); 576 // Every submodule is a served repository, so a clone from here can 577 // fetch it from here. 578 for repo in Repo::ALL { 579 for (_, inner) in repo.submodules() { 580 assert!(Repo::ALL.contains(inner), "{repo:?}"); 581 } 582 } 583 } 584 585 #[test] 586 fn the_clients_own_credentials_are_not_forwarded() { 587 let mut headers = HeaderMap::new(); 588 headers.insert("authorization", "Basic c29tZW9uZQ==".parse().unwrap()); 589 headers.insert("cookie", "a=b".parse().unwrap()); 590 headers.insert("git-protocol", "version=2".parse().unwrap()); 591 headers.insert("user-agent", "git/2.51.0".parse().unwrap()); 592 let names: Vec<_> = forwarded(&headers).map(|(name, _)| name).collect(); 593 assert_eq!(names, ["git-protocol", "user-agent"]); 594 } 595 596 #[test] 597 fn the_token_is_the_basic_password() { 598 let decoded = STANDARD.decode(authorization("t0k").trim_start_matches("Basic ")).unwrap(); 599 assert_eq!(decoded, b"x-access-token:t0k"); 600 } 601 602 #[test] 603 fn the_latest_commit_is_read_from_githubs_list() { 604 let body = r#"[{"sha":"4d93353abc","commit":{"message":"clone: the token\n\nmore","committer":{"date":"2026-10-02T23:59:31Z"}}}]"#; 605 let link = r#"<https://api.github.com/repositories/1/commits?sha=main&per_page=1&page=2>; rel="next", <https://api.github.com/repositories/1/commits?sha=main&per_page=1&page=140>; rel="last""#; 606 let commit = listed(body, Some(link)).unwrap(); 607 assert_eq!(commit.short(), "4d93353"); 608 assert_eq!(commit.subject, "clone: the token"); 609 assert_eq!(commit.date, "2026-10-02"); 610 assert_eq!(commit.count, Some(140)); 611 assert_eq!(listed(body, None).unwrap().count, Some(1)); 612 assert_eq!(listed("[]", None), None); 613 } 614 615 fn pkt(lines: &[&str]) -> Vec<u8> { 616 let mut out = Vec::new(); 617 for line in lines { 618 match *line { 619 "0000" | "0001" => out.extend_from_slice(line.as_bytes()), 620 line => out.extend_from_slice(format!("{:04x}{line}\n", line.len() + 5).as_bytes()), 621 } 622 } 623 out 624 } 625 626 #[test] 627 fn a_clone_and_a_pull_are_told_apart() { 628 let want = "want 4d93353131e2d6b5a1b1a3a3c5f2f6a7b8c9d0e1"; 629 let have = "have 886ece71e2d6b5a1b1a3a3c5f2f6a7b8c9d0e1f2"; 630 // Protocol v2, as git 2.x sends it. 631 let clone = pkt(&["command=fetch", "agent=git/2.51.0", "0001", "thin-pack", want, "done", "0000"]); 632 assert_eq!(fetched(&clone, None), Some(Fetch::Clone)); 633 let pull = pkt(&["command=fetch", "0001", want, have, "done", "0000"]); 634 assert_eq!(fetched(&pull, None), Some(Fetch::Pull)); 635 // Without `done`: the server may still send the pack, and the reply 636 // says whether it did. 637 let round = pkt(&["command=fetch", "0001", want, have, "0000"]); 638 assert_eq!(fetched(&round, None), Some(Fetch::Pull)); 639 let refs = pkt(&["command=ls-refs", "0001", "peel", "symrefs", "0000"]); 640 assert_eq!(fetched(&refs, None), None); 641 // Protocol v0, with capabilities on the first want. 642 let old = pkt(&[&format!("{want} multi_ack side-band-64k"), "0000", "done"]); 643 assert_eq!(fetched(&old, None), Some(Fetch::Clone)); 644 assert_eq!(fetched(b"not pkt lines", None), None); 645 } 646 647 #[test] 648 fn only_a_reply_that_sends_the_pack_counts() { 649 assert!(packs(&pkt(&["acknowledgments", "ACK 886ece7", "ready", "0001", "packfile", "0000"]))); 650 assert!(packs(b"0008NAK\n0031\x01PACK\x00\x00\x00\x02")); 651 assert!(!packs(&pkt(&["acknowledgments", "NAK", "0000"]))); 652 assert!(!packs(&pkt(&["886ece7 HEAD symref-target:refs/heads/main", "0000"]))); 653 } 654 655 #[test] 656 fn a_gzipped_request_is_read() { 657 use std::io::Write; 658 let pull = pkt(&["command=fetch", "0001", "want a", "have b", "done", "0000"]); 659 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); 660 gzip.write_all(&pull).unwrap(); 661 assert_eq!(fetched(&gzip.finish().unwrap(), Some("gzip")), Some(Fetch::Pull)); 662 assert_eq!(fetched(&pull, Some("br")), None); 663 } 664 665 #[test] 666 fn the_command_clones_the_submodule_too() { 667 assert_eq!(command("https://code.lmjtfy.fun"), "git clone --recurse-submodules https://code.lmjtfy.fun/lmjtfy.git"); 668 } 669}