gix_protocol/handshake/
mod.rs

1use bstr::BString;
2
3///
4pub mod refs;
5
6/// A git reference, commonly referred to as 'ref', as returned by a git server before sending a pack.
7#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum Ref {
10    /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit
11    Peeled {
12        /// The name at which the ref is located, like `refs/tags/1.0`.
13        full_ref_name: BString,
14        /// The hash of the tag the ref points to.
15        tag: gix_hash::ObjectId,
16        /// The hash of the object the `tag` points to.
17        object: gix_hash::ObjectId,
18    },
19    /// A ref pointing to a commit object
20    Direct {
21        /// The name at which the ref is located, like `refs/heads/main` or `refs/tags/v1.0` for lightweight tags.
22        full_ref_name: BString,
23        /// The hash of the object the ref points to.
24        object: gix_hash::ObjectId,
25    },
26    /// A symbolic ref pointing to `target` ref, which in turn, ultimately after possibly following `tag`, points to an `object`
27    Symbolic {
28        /// The name at which the symbolic ref is located, like `HEAD`.
29        full_ref_name: BString,
30        /// The path of the ref the symbolic ref points to, like `refs/heads/main`.
31        ///
32        /// See issue [#205] for details
33        ///
34        /// [#205]: https://github.com/GitoxideLabs/gitoxide/issues/205
35        target: BString,
36        /// The hash of the annotated tag the ref points to, if present.
37        ///
38        /// Note that this field is also `None` if `full_ref_name` is a lightweight tag.
39        tag: Option<gix_hash::ObjectId>,
40        /// The hash of the object the `target` ref ultimately points to.
41        object: gix_hash::ObjectId,
42    },
43    /// A ref is unborn on the remote and just points to the initial, unborn branch, as is the case in a newly initialized repository
44    /// or dangling symbolic refs.
45    Unborn {
46        /// The name at which the ref is located, typically `HEAD`.
47        full_ref_name: BString,
48        /// The path of the ref the symbolic ref points to, like `refs/heads/main`, even though the `target` does not yet exist.
49        target: BString,
50    },
51}
52
53#[cfg(feature = "handshake")]
54pub(crate) mod hero {
55    use crate::handshake::Ref;
56
57    /// The result of the [`handshake()`](crate::handshake()) function.
58    #[derive(Default, Debug, Clone)]
59    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60    pub struct Handshake {
61        /// The protocol version the server responded with. It might have downgraded the desired version.
62        pub server_protocol_version: gix_transport::Protocol,
63        /// The references reported as part of the `Protocol::V1` handshake, or `None` otherwise as V2 requires a separate request.
64        pub refs: Option<Vec<Ref>>,
65        /// Shallow updates as part of the `Protocol::V1`, to shallow a particular object.
66        /// Note that unshallowing isn't supported here.
67        pub v1_shallow_updates: Option<Vec<crate::fetch::response::ShallowUpdate>>,
68        /// The server capabilities.
69        pub capabilities: gix_transport::client::Capabilities,
70    }
71
72    #[cfg(feature = "fetch")]
73    mod fetch {
74        #[cfg(feature = "async-client")]
75        use crate::transport::client::async_io;
76        #[cfg(feature = "blocking-client")]
77        use crate::transport::client::blocking_io;
78        use crate::{fetch::RefMap, Handshake};
79        use gix_features::progress::Progress;
80        use std::borrow::Cow;
81
82        /// Intermediate state while potentially fetching a refmap after the handshake.
83        pub enum ObtainRefMap<'a> {
84            /// We already got a refmap to use from the V1 handshake, which always sends them.
85            Existing(RefMap),
86            /// We need to invoke another `ls-refs` command to retrieve the refmap as part of the V2 protocol.
87            LsRefsCommand(crate::LsRefsCommand<'a>, crate::fetch::refmap::init::Context),
88        }
89
90        impl ObtainRefMap<'_> {
91            /// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command.
92            #[cfg(feature = "async-client")]
93            #[allow(clippy::result_large_err)]
94            pub async fn fetch_async(
95                self,
96                mut progress: impl Progress,
97                transport: &mut impl async_io::Transport,
98                trace_packetlines: bool,
99            ) -> Result<RefMap, crate::fetch::refmap::init::Error> {
100                let (cmd, cx) = match self {
101                    ObtainRefMap::Existing(map) => return Ok(map),
102                    ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx),
103                };
104
105                let _span = gix_trace::coarse!("gix_protocol::handshake::ObtainRefMap::fetch_async()");
106                let capabilities = cmd.capabilities;
107                let remote_refs = cmd.invoke_async(transport, &mut progress, trace_packetlines).await?;
108                RefMap::from_refs(remote_refs, capabilities, cx)
109            }
110
111            /// Fetch the refmap, either by returning the existing one or invoking the `ls-refs` command.
112            #[cfg(feature = "blocking-client")]
113            #[allow(clippy::result_large_err)]
114            pub fn fetch_blocking(
115                self,
116                mut progress: impl Progress,
117                transport: &mut impl blocking_io::Transport,
118                trace_packetlines: bool,
119            ) -> Result<RefMap, crate::fetch::refmap::init::Error> {
120                let (cmd, cx) = match self {
121                    ObtainRefMap::Existing(map) => return Ok(map),
122                    ObtainRefMap::LsRefsCommand(cmd, cx) => (cmd, cx),
123                };
124
125                let _span = gix_trace::coarse!("gix_protocol::handshake::ObtainRefMap::fetch_blocking()");
126                let capabilities = cmd.capabilities;
127                let remote_refs = cmd.invoke_blocking(transport, &mut progress, trace_packetlines)?;
128                RefMap::from_refs(remote_refs, capabilities, cx)
129            }
130        }
131
132        impl Handshake {
133            /// Prepare fetching a [refmap](RefMap) if not present in the handshake.
134            #[allow(clippy::result_large_err)]
135            pub fn prepare_lsrefs_or_extract_refmap(
136                &mut self,
137                user_agent: (&'static str, Option<Cow<'static, str>>),
138                prefix_from_spec_as_filter_on_remote: bool,
139                refmap_context: crate::fetch::refmap::init::Context,
140            ) -> Result<ObtainRefMap<'_>, crate::fetch::refmap::init::Error> {
141                if let Some(refs) = self.refs.take() {
142                    return Ok(ObtainRefMap::Existing(RefMap::from_refs(
143                        refs,
144                        &self.capabilities,
145                        refmap_context,
146                    )?));
147                }
148
149                let all_refspecs = refmap_context.aggregate_refspecs();
150                let prefix_refspecs = prefix_from_spec_as_filter_on_remote.then_some(&all_refspecs[..]);
151                Ok(ObtainRefMap::LsRefsCommand(
152                    crate::LsRefsCommand::new(prefix_refspecs, &self.capabilities, user_agent),
153                    refmap_context,
154                ))
155            }
156        }
157    }
158}
159
160#[cfg(feature = "handshake")]
161mod error {
162    use bstr::BString;
163    use gix_transport::client;
164
165    use crate::{credentials, handshake::refs};
166
167    /// The error returned by [`handshake()`][crate::handshake()].
168    #[derive(Debug, thiserror::Error)]
169    #[allow(missing_docs)]
170    pub enum Error {
171        #[error("Failed to obtain credentials")]
172        Credentials(#[from] credentials::protocol::Error),
173        #[error("No credentials were returned at all as if the credential helper isn't functioning unknowingly")]
174        EmptyCredentials,
175        #[error("Credentials provided for \"{url}\" were not accepted by the remote")]
176        InvalidCredentials { url: BString, source: std::io::Error },
177        #[error(transparent)]
178        Transport(#[from] client::Error),
179        #[error("The transport didn't accept the advertised server version {actual_version:?} and closed the connection client side")]
180        TransportProtocolPolicyViolation { actual_version: gix_transport::Protocol },
181        #[error(transparent)]
182        ParseRefs(#[from] refs::parse::Error),
183    }
184
185    impl gix_transport::IsSpuriousError for Error {
186        fn is_spurious(&self) -> bool {
187            match self {
188                Error::Transport(err) => err.is_spurious(),
189                _ => false,
190            }
191        }
192    }
193}
194
195#[cfg(feature = "handshake")]
196pub use error::Error;
197
198#[cfg(any(feature = "blocking-client", feature = "async-client"))]
199#[cfg(feature = "handshake")]
200pub(crate) mod function;