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