feat: basic packfile retrieval
This commit is contained in:
parent
ae3c1b23af
commit
a0cbb8dcda
1433
Cargo.lock
generated
1433
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,8 @@ clap = { version = "4.5.27", features = ["derive", "string"] }
|
|||||||
flate2 = "1.0.35"
|
flate2 = "1.0.35"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
nom = "8.0.0"
|
nom = "8.0.0"
|
||||||
|
reqwest = "0.12.12"
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
|
167
src/http.rs
Normal file
167
src/http.rs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use nom::AsBytes;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
pub async fn clone(repo: &str) -> Result<(), Error> {
|
||||||
|
let (size, refs) = get_refs(repo).await?;
|
||||||
|
|
||||||
|
println!("Refs:");
|
||||||
|
for (sha1, name) in refs.iter() {
|
||||||
|
println!("{} {}", sha1, name);
|
||||||
|
}
|
||||||
|
println!("Downloaded file size: {}", size);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_refs(input: &[u8]) -> Result<Vec<(String, String)>> {
|
||||||
|
let mut refs = Vec::new();
|
||||||
|
let mut index: usize = 0;
|
||||||
|
loop {
|
||||||
|
if index >= input.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// pick the next 4 bytes and convert to u32 from hex
|
||||||
|
let mut bytes = [0; 4];
|
||||||
|
bytes.copy_from_slice(&input[index..index + 4]);
|
||||||
|
|
||||||
|
let hex_str = std::str::from_utf8(&bytes)?;
|
||||||
|
let res = usize::from_str_radix(hex_str, 16)?;
|
||||||
|
|
||||||
|
if res == 0 {
|
||||||
|
index += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input[index + 4] == b'#' {
|
||||||
|
index += res;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sha1_bytes = [0; 40];
|
||||||
|
sha1_bytes.copy_from_slice(&input[index + 4..index + 44]);
|
||||||
|
let idx_0 = input[index + 45..index + res - 1]
|
||||||
|
.iter()
|
||||||
|
.position(|&x| x == 0);
|
||||||
|
|
||||||
|
let sha1 = std::str::from_utf8(&sha1_bytes)?;
|
||||||
|
let name = if let Some(idx_0) = idx_0 {
|
||||||
|
std::str::from_utf8(&input[index + 45..index + 45 + idx_0])?
|
||||||
|
} else {
|
||||||
|
std::str::from_utf8(&input[index + 45..index + res - 1])?
|
||||||
|
};
|
||||||
|
|
||||||
|
refs.push((name.to_string(), sha1.to_string()));
|
||||||
|
|
||||||
|
index += res;
|
||||||
|
}
|
||||||
|
Ok(refs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_refs(repo_url: &str) -> Result<(usize, Vec<(String, String)>), Error> {
|
||||||
|
let info_refs_url = format!("{}/info/refs?service=git-upload-pack", repo_url);
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
let response = client
|
||||||
|
.get(&info_refs_url)
|
||||||
|
.header("User-Agent", "git/2.30.0")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
response.error_for_status_ref()?;
|
||||||
|
|
||||||
|
let content = response.bytes().await?;
|
||||||
|
let refs = parse_refs(&content)?;
|
||||||
|
|
||||||
|
get_packfile(repo_url, refs).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packet_line(data: &str) -> Vec<u8> {
|
||||||
|
let length = format!("{:04x}", data.len() + 4);
|
||||||
|
let mut line = Vec::new();
|
||||||
|
line.extend_from_slice(length.as_bytes());
|
||||||
|
line.extend_from_slice(data.as_bytes());
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_packfile(
|
||||||
|
repo_url: &str,
|
||||||
|
refs: Vec<(String, String)>,
|
||||||
|
) -> Result<(usize, Vec<(String, String)>), Error> {
|
||||||
|
let upload_pack_url = format!("{}/git-upload-pack", repo_url);
|
||||||
|
|
||||||
|
let mut payload: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
|
payload.extend(packet_line("command=fetch").as_slice());
|
||||||
|
payload.extend(packet_line("agent=git/2.30.0").as_slice());
|
||||||
|
payload.extend(packet_line("object-format=sha1").as_slice());
|
||||||
|
payload.extend("0001".as_bytes());
|
||||||
|
payload.extend(packet_line("ofs-delta").as_slice());
|
||||||
|
payload.extend(packet_line("no-progress").as_slice());
|
||||||
|
|
||||||
|
for (_, sha1) in refs.iter() {
|
||||||
|
let want = format!("want {}\n", sha1);
|
||||||
|
payload.extend(packet_line(want.as_str()).as_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.extend("0000".as_bytes());
|
||||||
|
payload.extend(packet_line("done").as_slice());
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
let response = client
|
||||||
|
.post(&upload_pack_url)
|
||||||
|
.header("User-Agent", "git/2.30.0")
|
||||||
|
.header("Content-Type", "application/x-git-upload-pack-request")
|
||||||
|
.header("Accept-Encoding", "deflate")
|
||||||
|
.header("Accept", "application/x-git-upload-pack-result")
|
||||||
|
.header("Git-Protocol", "version=2")
|
||||||
|
.body(payload)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
response.error_for_status_ref()?;
|
||||||
|
|
||||||
|
let content = response.bytes().await?;
|
||||||
|
decode_git_response(content.as_bytes())?;
|
||||||
|
|
||||||
|
Ok((content.len(), refs))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_git_response(content: &[u8]) -> Result<(), Error> {
|
||||||
|
let mut cursor = 0;
|
||||||
|
let mut pack_data = Vec::new();
|
||||||
|
|
||||||
|
while cursor < content.len() {
|
||||||
|
let length_str = std::str::from_utf8(&content[cursor..cursor + 4])?;
|
||||||
|
cursor += 4;
|
||||||
|
|
||||||
|
let length = usize::from_str_radix(length_str, 16)?;
|
||||||
|
if length == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = &content[cursor..cursor + length - 4];
|
||||||
|
cursor += length - 4;
|
||||||
|
|
||||||
|
let side_band = payload[0];
|
||||||
|
let data = &payload[1..];
|
||||||
|
|
||||||
|
if side_band == 1 {
|
||||||
|
pack_data.extend(data);
|
||||||
|
} else if side_band == 2 {
|
||||||
|
println!("Progress: {}", std::str::from_utf8(data)?);
|
||||||
|
} else if side_band == 3 {
|
||||||
|
println!("Error: {}", std::str::from_utf8(data)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pack_data.is_empty() {
|
||||||
|
let mut packfile = std::fs::File::create("downloaded.pack")?;
|
||||||
|
packfile.write_all(&pack_data)?;
|
||||||
|
println!("Packfile saved as 'downloaded.pack'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
13
src/main.rs
13
src/main.rs
@ -8,6 +8,7 @@ use clap::Subcommand;
|
|||||||
|
|
||||||
mod commit;
|
mod commit;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod http;
|
||||||
mod index;
|
mod index;
|
||||||
mod kind;
|
mod kind;
|
||||||
mod log;
|
mod log;
|
||||||
@ -16,6 +17,7 @@ mod pack;
|
|||||||
mod repository;
|
mod repository;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
use crate::http::clone;
|
||||||
use crate::repository::Repository;
|
use crate::repository::Repository;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -83,9 +85,14 @@ enum Command {
|
|||||||
/// The object to hash
|
/// The object to hash
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
},
|
},
|
||||||
|
Clone {
|
||||||
|
/// The repository to clone
|
||||||
|
repo: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Error> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let mut repo = Repository::new()?;
|
let mut repo = Repository::new()?;
|
||||||
@ -147,6 +154,10 @@ fn main() -> Result<(), Error> {
|
|||||||
Ok(hash) => println!("{}", hex::encode(hash)),
|
Ok(hash) => println!("{}", hex::encode(hash)),
|
||||||
Err(e) => eprintln!("Failed to hash object: {}", e),
|
Err(e) => eprintln!("Failed to hash object: {}", e),
|
||||||
},
|
},
|
||||||
|
Command::Clone { repo } => match clone(&repo).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => eprintln!("Failed to clone: {}", e),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user