pub mod refs { use crate::OutputFormat; use git_repository::{ protocol, protocol::{ fetch::{Action, Arguments, Ref, Response}, transport, }, }; pub const PROGRESS_RANGE: std::ops::RangeInclusive = 1..=2; use std::io; #[derive(Default)] struct LsRemotes { refs: Vec, } impl protocol::fetch::DelegateBlocking for LsRemotes { fn prepare_fetch( &mut self, _version: transport::Protocol, _server: &transport::client::Capabilities, _features: &mut Vec<(&str, Option<&str>)>, refs: &[Ref], ) -> io::Result { self.refs = refs.into(); Ok(Action::Cancel) } fn negotiate( &mut self, _refs: &[Ref], _arguments: &mut Arguments, _previous_response: Option<&Response>, ) -> io::Result { unreachable!("not to be called due to Action::Close in `prepare_fetch`") } } #[cfg(feature = "async-client")] mod async_io { use super::{Context, LsRemotes}; use crate::{net, remote::refs::print, OutputFormat}; use async_trait::async_trait; use futures_io::AsyncBufRead; use git_repository::{ protocol, protocol::fetch::{Ref, Response}, Progress, }; use std::io; #[async_trait(?Send)] impl protocol::fetch::Delegate for LsRemotes { async fn receive_pack( &mut self, input: impl AsyncBufRead + Unpin + 'async_trait, progress: impl Progress, refs: &[Ref], previous_response: &Response, ) -> io::Result<()> { unreachable!("not called for ls-refs") } } pub async fn list( protocol: Option, url: &str, progress: impl Progress, ctx: Context, ) -> anyhow::Result<()> { let url = url.to_owned(); let transport = net::connect(url.as_bytes(), protocol.unwrap_or_default().into()).await?; blocking::unblock( // `blocking` really needs a way to unblock futures, which is what it does internally anyway. // Both fetch() needs unblocking as it executes blocking code within the future, and the other // block does blocking IO because it's primarily a blocking codebase. move || { futures_lite::future::block_on(async move { let mut delegate = LsRemotes::default(); protocol::fetch( transport, &mut delegate, protocol::credentials::helper, progress, protocol::FetchConnection::TerminateOnSuccessfulCompletion, ) .await?; match ctx.format { OutputFormat::Human => drop(print(ctx.out, &delegate.refs)), #[cfg(feature = "serde1")] OutputFormat::Json => serde_json::to_writer_pretty( ctx.out, &delegate.refs.into_iter().map(JsonRef::from).collect::>(), )?, } Ok(()) }) }, ) .await } } #[cfg(feature = "async-client")] pub use self::async_io::list; #[cfg(feature = "blocking-client")] mod blocking_io { #[cfg(feature = "serde1")] use super::JsonRef; use super::{print, Context, LsRemotes}; use crate::{net, OutputFormat}; use git_repository::{ protocol, protocol::fetch::{Ref, Response}, Progress, }; use std::io; impl protocol::fetch::Delegate for LsRemotes { fn receive_pack( &mut self, _input: impl io::BufRead, _progress: impl Progress, _refs: &[Ref], _previous_response: &Response, ) -> io::Result<()> { unreachable!("not called for ls-refs") } } pub fn list( protocol: Option, url: &str, progress: impl Progress, ctx: Context, ) -> anyhow::Result<()> { let transport = net::connect(url.as_bytes(), protocol.unwrap_or_default().into())?; let mut delegate = LsRemotes::default(); protocol::fetch( transport, &mut delegate, protocol::credentials::helper, progress, protocol::FetchConnection::TerminateOnSuccessfulCompletion, )?; match ctx.format { OutputFormat::Human => drop(print(ctx.out, &delegate.refs)), #[cfg(feature = "serde1")] OutputFormat::Json => serde_json::to_writer_pretty( ctx.out, &delegate.refs.into_iter().map(JsonRef::from).collect::>(), )?, }; Ok(()) } } #[cfg(feature = "blocking-client")] pub use blocking_io::list; pub struct Context { pub thread_limit: Option, pub format: OutputFormat, pub out: W, } #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum JsonRef { Peeled { path: String, tag: String, object: String, }, Direct { path: String, object: String, }, Symbolic { path: String, target: String, object: String, }, } impl From for JsonRef { fn from(value: Ref) -> Self { match value { Ref::Direct { path, object } => JsonRef::Direct { path: path.to_string(), object: object.to_string(), }, Ref::Symbolic { path, target, object } => JsonRef::Symbolic { path: path.to_string(), target: target.to_string(), object: object.to_string(), }, Ref::Peeled { path, tag, object } => JsonRef::Peeled { path: path.to_string(), tag: tag.to_string(), object: object.to_string(), }, } } } pub(crate) fn print(mut out: impl io::Write, refs: &[Ref]) -> io::Result<()> { for r in refs { match r { Ref::Direct { path, object } => writeln!(&mut out, "{} {}", object.to_sha1_hex_string(), path), Ref::Peeled { path, object, tag } => { writeln!(&mut out, "{} {} tag:{}", object.to_sha1_hex_string(), path, tag) } Ref::Symbolic { path, target, object } => writeln!( &mut out, "{} {} symref-target:{}", object.to_sha1_hex_string(), path, target ), }?; } Ok(()) } }