gix_protocol/fetch/types.rs
1use std::path::PathBuf;
2
3use crate::fetch::response::{Acknowledgement, ShallowUpdate, WantedRef};
4
5/// Options for use in [`fetch()`](`crate::fetch()`)
6#[derive(Debug, Clone)]
7pub struct Options<'a> {
8 /// The path to the file containing the shallow commit boundary.
9 ///
10 /// When needed, it will be locked in preparation for being modified.
11 pub shallow_file: PathBuf,
12 /// How to deal with shallow repositories. It does affect how negotiations are performed.
13 pub shallow: &'a Shallow,
14 /// Describe how to handle tags when fetching.
15 pub tags: Tags,
16 /// If `true`, if we fetch from a remote that only offers shallow clones, the operation will fail with an error
17 /// instead of writing the shallow boundary to the shallow file.
18 pub reject_shallow_remote: bool,
19}
20
21/// For use in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] and [`fetch`](crate::fetch()).
22#[cfg(feature = "handshake")]
23pub struct Context<'a, T> {
24 /// The outcome of the handshake performed with the remote.
25 ///
26 /// Note that it's mutable as depending on the protocol, it may contain refs that have been sent unconditionally.
27 pub handshake: &'a mut crate::Handshake,
28 /// The transport to use when making an `ls-refs` or `fetch` call.
29 ///
30 /// This is always done if the underlying protocol is V2, which is implied by the absence of refs in the `handshake` outcome.
31 pub transport: &'a mut T,
32 /// How to self-identify during the `ls-refs` call in [`crate::Handshake::prepare_lsrefs_or_extract_refmap()`] or the `fetch` call in [`fetch()`](crate::fetch()).
33 ///
34 /// This could be read from the `gitoxide.userAgent` configuration variable.
35 pub user_agent: (&'static str, Option<std::borrow::Cow<'static, str>>),
36 /// If `true`, output all packetlines using the `gix-trace` machinery.
37 pub trace_packetlines: bool,
38}
39
40#[cfg(feature = "fetch")]
41mod with_fetch {
42 use bstr::ByteSlice;
43
44 use crate::fetch::{self, negotiate, refmap};
45
46 /// For use in [`fetch`](crate::fetch()).
47 pub struct NegotiateContext<'a, 'b, 'c, Objects, Alternates, AlternatesOut, AlternatesErr, Find>
48 where
49 Objects: gix_object::Find + gix_object::FindHeader + gix_object::Exists,
50 Alternates: FnOnce() -> Result<AlternatesOut, AlternatesErr>,
51 AlternatesErr: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
52 AlternatesOut: Iterator<Item = (gix_ref::file::Store, Find)>,
53 Find: gix_object::Find,
54 {
55 /// Access to the object database.
56 /// *Note* that the `exists()` calls must not trigger a refresh of the ODB packs as plenty of them might fail, i.e. find on object.
57 pub objects: &'a Objects,
58 /// Access to the git references database.
59 pub refs: &'a gix_ref::file::Store,
60 /// A function that returns an iterator over `(refs, objects)` for each alternate repository, to assure all known objects are added also according to their tips.
61 pub alternates: Alternates,
62 /// The implementation that performs the negotiation later, i.e. prepare wants and haves.
63 pub negotiator: &'a mut dyn gix_negotiate::Negotiator,
64 /// The commit-graph for use by the `negotiator` - we populate it with tips to initialize the graph traversal.
65 pub graph: &'a mut gix_negotiate::Graph<'b, 'c>,
66 }
67
68 /// A trait to encapsulate steps to negotiate the contents of the pack.
69 ///
70 /// Typical implementations use the utilities found in the [`negotiate`] module.
71 pub trait Negotiate {
72 /// Typically invokes [`negotiate::mark_complete_and_common_ref()`].
73 fn mark_complete_and_common_ref(&mut self) -> Result<negotiate::Action, negotiate::Error>;
74 /// Typically invokes [`negotiate::add_wants()`].
75 /// Returns `true` if wants were added, or `false` if the negotiation should be aborted.
76 #[must_use]
77 fn add_wants(&mut self, arguments: &mut fetch::Arguments, remote_ref_target_known: &[bool]) -> bool;
78 /// Typically invokes [`negotiate::one_round()`].
79 fn one_round(
80 &mut self,
81 state: &mut negotiate::one_round::State,
82 arguments: &mut fetch::Arguments,
83 previous_response: Option<&fetch::Response>,
84 ) -> Result<(negotiate::Round, bool), negotiate::Error>;
85 }
86
87 /// The outcome of [`fetch()`](crate::fetch()).
88 #[derive(Debug, Clone)]
89 pub struct Outcome {
90 /// The most recent server response.
91 ///
92 /// Useful to obtain information about new shallow boundaries.
93 pub last_response: fetch::Response,
94 /// Information about the negotiation to receive the new pack.
95 pub negotiate: NegotiateOutcome,
96 }
97
98 /// The negotiation-specific outcome of [`fetch()`](crate::fetch()).
99 #[derive(Debug, Clone)]
100 pub struct NegotiateOutcome {
101 /// The outcome of the negotiation stage of the fetch operation.
102 ///
103 /// If it is…
104 ///
105 /// * [`negotiate::Action::MustNegotiate`] there will always be a `pack`.
106 /// * [`negotiate::Action::SkipToRefUpdate`] there is no `pack` but references can be updated right away.
107 ///
108 /// Note that this is never [negotiate::Action::NoChange`] as this would mean there is no negotiation information at all
109 /// so this structure wouldn't be present.
110 pub action: negotiate::Action,
111 /// Additional information for each round of negotiation.
112 pub rounds: Vec<negotiate::Round>,
113 }
114
115 /// Information about the relationship between our refspecs, and remote references with their local counterparts.
116 ///
117 /// It's the first stage that offers connection to the server, and is typically required to perform one or more fetch operations.
118 #[derive(Default, Debug, Clone)]
119 pub struct RefMap {
120 /// A mapping between a remote reference and a local tracking branch.
121 pub mappings: Vec<refmap::Mapping>,
122 /// The explicit refspecs that were supposed to be used for fetching.
123 ///
124 /// Typically, they are configured by the remote and are referred to by
125 /// [`refmap::SpecIndex::ExplicitInRemote`] in [`refmap::Mapping`].
126 pub refspecs: Vec<gix_refspec::RefSpec>,
127 /// Refspecs which have been added implicitly due to settings of the `remote`, usually pre-initialized from
128 /// [`extra_refspecs` in RefMap options](refmap::init::Context).
129 /// They are referred to by [`refmap::SpecIndex::Implicit`] in [`refmap::Mapping`].
130 ///
131 /// They are never persisted nor are they typically presented to the user.
132 pub extra_refspecs: Vec<gix_refspec::RefSpec>,
133 /// Information about the fixes applied to the `mapping` due to validation and sanitization.
134 pub fixes: Vec<gix_refspec::match_group::validate::Fix>,
135 /// All refs advertised by the remote.
136 pub remote_refs: Vec<crate::handshake::Ref>,
137 /// The kind of hash used for all data sent by the server, if understood by this client implementation.
138 ///
139 /// It was extracted from the `handshake` as advertised by the server.
140 pub object_hash: gix_hash::Kind,
141 }
142
143 impl RefMap {
144 /// Return `true` if the explicit fetch refspecs represented by this mapping failed to match the remote in a way
145 /// that should typically be reported as "no mapping".
146 ///
147 /// Use this before negotiation or reference updates when callers need to reject a fetch early instead of proceeding
148 /// with an empty or purely implicit mapping set.
149 ///
150 /// This is the case if the server advertised refs but none matched at all, or if only implicit mappings were produced
151 /// while at least one explicit refspec required an actual ref match, as is the case for exact ref names like `HEAD`
152 /// or `refs/heads/main`.
153 pub fn is_missing_required_mapping(&self) -> bool {
154 let has_explicit_mapping = self
155 .mappings
156 .iter()
157 .any(|mapping| matches!(mapping.spec_index, crate::fetch::refmap::SpecIndex::ExplicitInRemote(_)));
158
159 (self.mappings.is_empty() && !self.remote_refs.is_empty())
160 || (!has_explicit_mapping && explicit_fetch_refspecs_require_a_match(&self.refspecs))
161 }
162 }
163
164 fn explicit_fetch_refspecs_require_a_match(refspecs: &[gix_refspec::RefSpec]) -> bool {
165 refspecs.iter().any(|spec| match spec.to_ref().instruction() {
166 gix_refspec::Instruction::Fetch(
167 gix_refspec::instruction::Fetch::Only { src } | gix_refspec::instruction::Fetch::AndUpdate { src, .. },
168 ) => src.find_byteset(b"*?[]\\").is_none() && gix_hash::ObjectId::from_hex(src).is_err(),
169 gix_refspec::Instruction::Fetch(gix_refspec::instruction::Fetch::Exclude { .. })
170 | gix_refspec::Instruction::Push(_) => false,
171 })
172 }
173}
174#[cfg(feature = "fetch")]
175pub use with_fetch::*;
176
177/// Describe how shallow clones are handled when fetching, with variants defining how the *shallow boundary* is handled.
178///
179/// The *shallow boundary* is a set of commits whose parents are not present in the repository.
180#[derive(Default, Debug, Clone, PartialEq, Eq)]
181pub enum Shallow {
182 /// Fetch all changes from the remote without affecting the shallow boundary at all.
183 ///
184 /// This also means that repositories that aren't shallow will remain like that.
185 #[default]
186 NoChange,
187 /// Receive update to `depth` commits in the history of the refs to fetch (from the viewpoint of the remote),
188 /// with the value of `1` meaning to receive only the commit a ref is pointing to.
189 ///
190 /// This may update the shallow boundary to increase or decrease the amount of available history.
191 DepthAtRemote(std::num::NonZeroU32),
192 /// Increase the number of commits and thus expand the shallow boundary by `depth` commits as seen from our local
193 /// shallow boundary, with a value of `0` having no effect.
194 Deepen(u32),
195 /// Set the shallow boundary at the `cutoff` time, meaning that there will be no commits beyond that time.
196 Since {
197 /// The date beyond which there will be no history.
198 cutoff: gix_date::Time,
199 },
200 /// Receive all history excluding all commits reachable from `remote_refs`. These can be long or short
201 /// ref names or tag names.
202 Exclude {
203 /// The ref names to exclude, short or long. Note that ambiguous short names will cause the remote to abort
204 /// without an error message being transferred (because the protocol does not support it)
205 remote_refs: Vec<gix_ref::PartialName>,
206 /// If some, this field has the same meaning as [`Shallow::Since`] which can be used in combination
207 /// with excluded references.
208 since_cutoff: Option<gix_date::Time>,
209 },
210}
211
212impl Shallow {
213 /// Produce a variant that causes the repository to loose its shallow boundary, effectively by extending it
214 /// beyond all limits.
215 pub fn undo() -> Self {
216 Shallow::DepthAtRemote((i32::MAX as u32).try_into().expect("valid at compile time"))
217 }
218}
219
220/// Describe how to handle tags when fetching
221#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
222pub enum Tags {
223 /// Fetch all tags from the remote, even if these are not reachable from objects referred to by our refspecs.
224 All,
225 /// Fetch only the tags that point to the objects being sent.
226 /// That way, annotated tags that point to an object we receive are automatically transmitted and their refs are created.
227 /// The same goes for lightweight tags.
228 #[default]
229 Included,
230 /// Do not fetch any tags.
231 None,
232}
233
234impl Tags {
235 /// Obtain a refspec that determines whether or not to fetch all tags, depending on this variant.
236 ///
237 /// The returned refspec is the default refspec for tags, but won't overwrite local tags ever.
238 #[cfg(feature = "fetch")]
239 pub fn to_refspec(&self) -> Option<gix_refspec::RefSpecRef<'static>> {
240 match self {
241 Tags::All | Tags::Included => Some(
242 gix_refspec::parse("refs/tags/*:refs/tags/*".into(), gix_refspec::parse::Operation::Fetch)
243 .expect("valid"),
244 ),
245 Tags::None => None,
246 }
247 }
248}
249
250/// A representation of a complete fetch response
251#[derive(Debug, Clone)]
252pub struct Response {
253 pub(crate) acks: Vec<Acknowledgement>,
254 pub(crate) shallows: Vec<ShallowUpdate>,
255 pub(crate) wanted_refs: Vec<WantedRef>,
256 pub(crate) has_pack: bool,
257}
258
259/// The progress ids used in during various steps of the fetch operation.
260///
261/// Note that tagged progress isn't very widely available yet, but support can be improved as needed.
262///
263/// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
264#[derive(Debug, Copy, Clone)]
265pub enum ProgressId {
266 /// The progress name is defined by the remote and the progress messages it sets, along with their progress values and limits.
267 RemoteProgress,
268}
269
270impl From<ProgressId> for gix_features::progress::Id {
271 fn from(v: ProgressId) -> Self {
272 match v {
273 ProgressId::RemoteProgress => *b"FERP",
274 }
275 }
276}