gitoxide-core 0.14.0

The library implementating all capabilities of the gitoxide CLI
Documentation
pub mod refs {
    use git_repository::{
        protocol,
        protocol::{
            fetch::{Action, Arguments, Ref, Response},
            transport,
        },
    };

    use crate::OutputFormat;

    pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=2;

    use std::io;

    #[derive(Default)]
    struct LsRemotes {
        refs: Vec<Ref>,
    }

    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<Action> {
            self.refs = refs.into();
            Ok(Action::Cancel)
        }

        fn negotiate(
            &mut self,
            _refs: &[Ref],
            _arguments: &mut Arguments,
            _previous_response: Option<&Response>,
        ) -> io::Result<Action> {
            unreachable!("not to be called due to Action::Close in `prepare_fetch`")
        }
    }

    #[cfg(feature = "async-client")]
    mod async_io {
        use std::io;

        use async_trait::async_trait;
        use futures_io::AsyncBufRead;
        use git_repository::{
            protocol,
            protocol::fetch::{Ref, Response},
            Progress,
        };

        use super::{Context, LsRemotes};
        use crate::{net, remote::refs::print, OutputFormat};

        #[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<net::Protocol>,
            url: &str,
            progress: impl Progress,
            ctx: Context<impl io::Write + Send + 'static>,
        ) -> 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(super::JsonRef::from).collect::<Vec<_>>(),
                            )?,
                        }
                        Ok(())
                    })
                },
            )
            .await
        }
    }
    #[cfg(feature = "async-client")]
    pub use self::async_io::list;

    #[cfg(feature = "blocking-client")]
    mod blocking_io {
        use std::io;

        use git_repository::{
            protocol,
            protocol::fetch::{Ref, Response},
            Progress,
        };

        #[cfg(feature = "serde1")]
        use super::JsonRef;
        use super::{print, Context, LsRemotes};
        use crate::{net, OutputFormat};

        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<net::Protocol>,
            url: &str,
            progress: impl Progress,
            ctx: Context<impl io::Write>,
        ) -> 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::<Vec<_>>(),
                )?,
            };
            Ok(())
        }
    }
    #[cfg(feature = "blocking-client")]
    pub use blocking_io::list;

    pub struct Context<W: io::Write> {
        pub thread_limit: Option<usize>,
        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<Ref> 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_hex(), path),
                Ref::Peeled { path, object, tag } => {
                    writeln!(&mut out, "{} {} tag:{}", object.to_hex(), path, tag)
                }
                Ref::Symbolic { path, target, object } => {
                    writeln!(&mut out, "{} {} symref-target:{}", object.to_hex(), path, target)
                }
            }?;
        }
        Ok(())
    }
}