Page MenuHomeSoftware Heritage
Paste P1267

Command-Line Input
ActivePublic

Authored by olasd on Jan 26 2022, 9:46 PM.
use anyhow::Result;
use tempfile::TempDir;
use std::{
env, io,
io::BufRead,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use git_repository::{
hash::ObjectId,
objs::bstr::{BString, ByteSlice},
odb::pack,
protocol::{
credentials, fetch,
fetch::{Action, Arguments, Delegate, DelegateBlocking, LsRefsAction, Ref, Response},
transport,
transport::client::Capabilities,
FetchConnection,
},
Progress,
};
use quick_error::quick_error;
quick_error! {
#[derive(Debug)]
enum Error {
DecodeObject(err: git_repository::objs::decode::Error) {
display("Could not decode object")
source(err)
from()
}
}
}
pub struct Context<W> {
pub thread_limit: Option<usize>,
pub should_interrupt: Arc<AtomicBool>,
pub out: W,
pub object_hash: git_repository::hash::Kind,
}
struct FetchDelegate<W> {
ctx: Context<W>,
directory: Option<PathBuf>,
ref_filter: Option<&'static [&'static str]>,
wanted_refs: Vec<BString>,
detected_version: Option<transport::Protocol>,
fetched_packfile: Option<PathBuf>,
}
static FILTER: &[&str] = &[
"HEAD",
"refs/tags",
"refs/heads",
"refs/pull",
"refs/merge-requests",
];
fn remote_supports_ref_in_want(server: &Capabilities) -> bool {
server
.capability("fetch")
.and_then(|cap| cap.supports("ref-in-want"))
.unwrap_or(false)
}
impl<W> DelegateBlocking for FetchDelegate<W> {
fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
Vec::new()
}
fn prepare_ls_refs(
&mut self,
server: &Capabilities,
arguments: &mut Vec<BString>,
_features: &mut Vec<(&str, Option<&str>)>,
) -> io::Result<LsRefsAction> {
if server.contains("ls-refs") {
arguments.extend(FILTER.iter().map(|r| format!("ref-prefix {}", r).into()));
}
Ok(if self.wanted_refs.is_empty() {
LsRefsAction::Continue
} else {
LsRefsAction::Skip
})
}
fn prepare_fetch(
&mut self,
version: transport::Protocol,
server: &Capabilities,
_features: &mut Vec<(&str, Option<&str>)>,
_refs: &[Ref],
) -> io::Result<Action> {
if !self.wanted_refs.is_empty() && !remote_supports_ref_in_want(server) {
return Err(io::Error::new(
io::ErrorKind::Other,
"Want to get specific refs, but remote doesn't support this capability",
));
}
if version == transport::Protocol::V1 {
self.ref_filter = Some(FILTER);
}
self.detected_version = Some(version);
Ok(Action::Continue)
}
fn negotiate(
&mut self,
refs: &[Ref],
arguments: &mut Arguments,
_previous_response: Option<&Response>,
) -> io::Result<Action> {
if self.detected_version == Some(transport::Protocol::V2) {
if arguments.can_use_filter() {
arguments.filter("tree:0")
}
}
if self.wanted_refs.is_empty() {
for r in refs {
let (path, id) = r.unpack();
match self.ref_filter {
Some(ref_prefixes) => {
if ref_prefixes
.iter()
.any(|prefix| path.starts_with_str(prefix))
{
arguments.want(id);
}
}
None => arguments.want(id),
}
}
} else {
for r in &self.wanted_refs {
arguments.want_ref(r.as_ref())
}
}
Ok(Action::Cancel)
}
}
impl<W: std::io::Write> Delegate for FetchDelegate<W> {
fn receive_pack(
&mut self,
input: impl BufRead,
progress: impl Progress,
refs: &[Ref],
_previous_response: &Response,
) -> io::Result<()> {
let options = pack::bundle::write::Options {
thread_limit: self.ctx.thread_limit,
index_kind: pack::index::Version::V2,
iteration_mode: pack::data::input::Mode::Verify,
object_hash: self.ctx.object_hash,
};
let outcome = pack::Bundle::write_to_directory(
input,
self.directory.take(),
progress,
&self.ctx.should_interrupt,
None,
options,
)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
self.fetched_packfile = outcome.data_path;
Ok(())
}
}
fn print_hash_and_path(
out: &mut impl io::Write,
name: &str,
id: ObjectId,
path: Option<PathBuf>,
) -> io::Result<()> {
match path {
Some(path) => writeln!(out, "{}: {} ({})", name, id, path.display()),
None => writeln!(out, "{}: {}", name, id),
}
}
fn print(
out: &mut impl io::Write,
res: pack::bundle::write::Outcome,
refs: &[Ref],
) -> io::Result<()> {
print_hash_and_path(out, "index", res.index.index_hash, res.index_path)?;
print_hash_and_path(out, "pack", res.index.data_hash, res.data_path)?;
writeln!(out)?;
for r in refs {
match r {
Ref::Direct { path, object } => writeln!(out, "{} {}", object.to_hex(), path),
Ref::Peeled { path, object, tag } => {
writeln!(out, "{} {} tag:{}", object.to_hex(), path, tag)
}
Ref::Symbolic {
path,
target,
object,
} => {
writeln!(out, "{} {} symref-target:{}", object.to_hex(), path, target)
}
}?;
}
Ok(())
}
fn main() -> Result<()> {
for argument in env::args().skip(1) {
let url = argument.as_bytes();
let client = transport::connect(url, transport::Protocol::V2)?;
println!(
"Connected with transport {:?}",
client.supported_protocol_versions()
);
let tmp_dir = TempDir::new()?;
let mut delegate = FetchDelegate {
ctx: Context {
should_interrupt: Arc::new(AtomicBool::new(false)),
out: std::io::stderr(),
thread_limit: None,
object_hash: git_repository::hash::Kind::Sha1,
},
directory: Some(tmp_dir.path().into()),
ref_filter: None,
wanted_refs: vec![],
detected_version: None,
fetched_packfile: None,
};
fetch(
client,
&mut delegate,
credentials::helper,
git_repository::progress::Discard,
FetchConnection::AllowReuse,
)?;
let pack_path = delegate.fetched_packfile.expect("fetched a packfile");
let bundle = git_pack::Bundle::at(pack_path, delegate.ctx.object_hash)?;
let _result: Result<_, git_pack::index::traverse::Error<Error>> = bundle.index.traverse(
&bundle.pack,
git_repository::progress::Discard,
&delegate.ctx.should_interrupt,
move || {
move |object_kind, buf, index_entry, progress| {
let obj = git_repository::objs::Data::new(object_kind, buf)
.decode()
.map_err(|e| Error::DecodeObject(e))?;
println!("{:#?}", obj);
Ok(())
}
},
git_pack::index::traverse::Options {
traversal: git_pack::index::traverse::Algorithm::Lookup,
thread_limit: delegate.ctx.thread_limit,
check: pack::index::traverse::SafetyCheck::All,
make_pack_lookup_cache: git_pack::cache::lru::StaticLinkedList::<64>::default,
},
);
}
Ok(())
}
// let capabilities = res.capabilities.clone();
// match res.actual_protocol {
// Protocol::V2 => {
// drop(res);
// let base_client_options = [
// ("agent", Some("swh.loader.git.rs/0.0.0devel")),
// ("object-format", Some("sha1")),
// ];
// let ls_refs = client.invoke(
// "ls-refs",
// base_client_options.iter().cloned(),
// Some(
// [
// "peel",
// "symrefs",
// "ref-prefix HEAD",
// "ref-prefix refs/heads",
// "ref-prefix refs/tags",
// ]
// .iter()
// .map(|s| s.as_bytes().as_bstr().to_owned()),
// ),
// )?;
// let listed_refs: HashMap<String, (String, Option<String>)> = ls_refs
// .lines()
// .flat_map(|l| {
// l.map(|s| {
// s.trim()
// .split_once(' ')
// .map(|(head, tail)| match tail.split_once(' ') {
// None => (tail.to_string(), (head.to_string(), None)),
// Some((refname, opts)) => (
// refname.to_string(),
// (head.to_string(), Some(opts.to_string())),
// ),
// })
// .unwrap()
// })
// })
// .collect();
// println!("{:#?}\n\n", listed_refs);
// let mut fetch_arguments = vec![];
// if capabilities
// .capability("fetch")
// .as_ref()
// .map(|cap| cap.supports("filter").unwrap_or(false))
// .unwrap_or(false)
// {
// fetch_arguments.push("filter tree:0".to_string());
// }
// fetch_arguments.extend(
// listed_refs
// .iter()
// .map(|(_refname, (head, opts))| match opts {
// None => Some(head.as_str()),
// Some(ref opts) => opts.strip_prefix("peeled:"),
// })
// .flatten()
// .unique()
// .map(|wanted| format!("want {}", wanted)),
// );
// fetch_arguments.push("done".to_string());
// eprintln!("{:#?}", fetch_arguments);
// let mut fetcher = client.invoke(
// "fetch",
// base_client_options.iter().cloned(),
// Some(
// fetch_arguments
// .iter()
// .map(|s| s.as_bytes().as_bstr().to_owned()),
// ),
// )?;
// let mut buf = String::new();
// fetcher.read_line(&mut buf)?;
// if buf != "packfile\n" {
// panic!("Unexpected read: {}", buf)
// }
// fetcher.set_progress_handler(Some(Box::new(|_is_err, data| {
// eprintln!("{}", std::str::from_utf8(data).expect("valid utf8"))
// })));
// let tmp_dir = TempDir::new()?;
// let bundle = git_pack::Bundle::write_to_directory(
// fetcher,
// Some(tmp_dir.path()),
// git_features::progress::Discard,
// &AtomicBool::new(false),
// None,
// git_pack::bundle::write::Options::default(),
// )
// .map(|r| r.to_bundle())?;
// }
// Protocol::V1 => {}
// };
// }