]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use gix_protocol::transport::client::Transport; |
2 | ||
3 | use crate::{ | |
4 | bstr::BString, | |
5 | remote, | |
6 | remote::{ | |
7 | fetch::{DryRun, RefMap}, | |
8 | ref_map, Connection, | |
9 | }, | |
10 | Progress, | |
11 | }; | |
12 | ||
13 | mod error; | |
14 | pub use error::Error; | |
15 | ||
16 | use crate::remote::fetch::WritePackedRefs; | |
17 | ||
18 | /// The way reflog messages should be composed whenever a ref is written with recent objects from a remote. | |
19 | pub enum RefLogMessage { | |
20 | /// Prefix the log with `action` and generate the typical suffix as `git` would. | |
21 | Prefixed { | |
22 | /// The action to use, like `fetch` or `pull`. | |
23 | action: String, | |
24 | }, | |
25 | /// Control the entire message, using `message` verbatim. | |
26 | Override { | |
27 | /// The complete reflog message. | |
28 | message: BString, | |
29 | }, | |
30 | } | |
31 | ||
32 | impl RefLogMessage { | |
33 | pub(crate) fn compose(&self, context: &str) -> BString { | |
34 | match self { | |
35 | RefLogMessage::Prefixed { action } => format!("{action}: {context}").into(), | |
36 | RefLogMessage::Override { message } => message.to_owned(), | |
37 | } | |
38 | } | |
39 | } | |
40 | ||
41 | /// The status of the repository after the fetch operation | |
42 | #[derive(Debug, Clone)] | |
43 | pub enum Status { | |
44 | /// Nothing changed as the remote didn't have anything new compared to our tracking branches, thus no pack was received | |
45 | /// and no new object was added. | |
fe692bf9 FG |
46 | /// |
47 | /// As we could determine that nothing changed without remote interaction, there was no negotiation at all. | |
0a29b90c FG |
48 | NoPackReceived { |
49 | /// However, depending on the refspecs, references might have been updated nonetheless to point to objects as | |
50 | /// reported by the remote. | |
51 | update_refs: refs::update::Outcome, | |
52 | }, | |
53 | /// There was at least one tip with a new object which we received. | |
54 | Change { | |
fe692bf9 FG |
55 | /// The number of rounds it took to minimize the pack to contain only the objects we don't have. |
56 | negotiation_rounds: usize, | |
0a29b90c FG |
57 | /// Information collected while writing the pack and its index. |
58 | write_pack_bundle: gix_pack::bundle::write::Outcome, | |
59 | /// Information collected while updating references. | |
60 | update_refs: refs::update::Outcome, | |
61 | }, | |
62 | /// A dry run was performed which leaves the local repository without any change | |
63 | /// nor will a pack have been received. | |
64 | DryRun { | |
fe692bf9 FG |
65 | /// The number of rounds it took to minimize the *would-be-sent*-pack to contain only the objects we don't have. |
66 | negotiation_rounds: usize, | |
0a29b90c FG |
67 | /// Information about what updates to refs would have been done. |
68 | update_refs: refs::update::Outcome, | |
69 | }, | |
70 | } | |
71 | ||
72 | /// The outcome of receiving a pack via [`Prepare::receive()`]. | |
73 | #[derive(Debug, Clone)] | |
74 | pub struct Outcome { | |
75 | /// The result of the initial mapping of references, the prerequisite for any fetch. | |
76 | pub ref_map: RefMap, | |
77 | /// The status of the operation to indicate what happened. | |
78 | pub status: Status, | |
79 | } | |
80 | ||
81 | /// The progress ids used in during various steps of the fetch operation. | |
82 | /// | |
83 | /// Note that tagged progress isn't very widely available yet, but support can be improved as needed. | |
84 | /// | |
85 | /// Use this information to selectively extract the progress of interest in case the parent application has custom visualization. | |
86 | #[derive(Debug, Copy, Clone)] | |
87 | pub enum ProgressId { | |
88 | /// The progress name is defined by the remote and the progress messages it sets, along with their progress values and limits. | |
89 | RemoteProgress, | |
90 | } | |
91 | ||
92 | impl From<ProgressId> for gix_features::progress::Id { | |
93 | fn from(v: ProgressId) -> Self { | |
94 | match v { | |
95 | ProgressId::RemoteProgress => *b"FERP", | |
96 | } | |
97 | } | |
98 | } | |
99 | ||
fe692bf9 | 100 | pub(crate) mod negotiate; |
0a29b90c FG |
101 | |
102 | /// | |
103 | pub mod prepare { | |
104 | /// The error returned by [`prepare_fetch()`][super::Connection::prepare_fetch()]. | |
105 | #[derive(Debug, thiserror::Error)] | |
106 | #[allow(missing_docs)] | |
107 | pub enum Error { | |
108 | #[error("Cannot perform a meaningful fetch operation without any configured ref-specs")] | |
109 | MissingRefSpecs, | |
110 | #[error(transparent)] | |
111 | RefMap(#[from] crate::remote::ref_map::Error), | |
112 | } | |
113 | ||
114 | impl gix_protocol::transport::IsSpuriousError for Error { | |
115 | fn is_spurious(&self) -> bool { | |
116 | match self { | |
117 | Error::RefMap(err) => err.is_spurious(), | |
118 | _ => false, | |
119 | } | |
120 | } | |
121 | } | |
122 | } | |
123 | ||
49aad941 | 124 | impl<'remote, 'repo, T> Connection<'remote, 'repo, T> |
0a29b90c FG |
125 | where |
126 | T: Transport, | |
0a29b90c FG |
127 | { |
128 | /// Perform a handshake with the remote and obtain a ref-map with `options`, and from there one | |
129 | /// Note that at this point, the `transport` should already be configured using the [`transport_mut()`][Self::transport_mut()] | |
130 | /// method, as it will be consumed here. | |
131 | /// | |
132 | /// From there additional properties of the fetch can be adjusted to override the defaults that are configured via gix-config. | |
133 | /// | |
134 | /// # Async Experimental | |
135 | /// | |
136 | /// Note that this implementation is currently limited correctly in blocking mode only as it relies on Drop semantics to close the connection | |
137 | /// should the fetch not be performed. Furthermore, there the code doing the fetch is inherently blocking and it's not offloaded to a thread, | |
138 | /// making this call block the executor. | |
139 | /// It's best to unblock it by placing it into its own thread or offload it should usage in an async context be truly required. | |
140 | #[allow(clippy::result_large_err)] | |
141 | #[gix_protocol::maybe_async::maybe_async] | |
142 | pub async fn prepare_fetch( | |
143 | mut self, | |
49aad941 | 144 | progress: impl Progress, |
0a29b90c | 145 | options: ref_map::Options, |
49aad941 | 146 | ) -> Result<Prepare<'remote, 'repo, T>, prepare::Error> { |
0a29b90c FG |
147 | if self.remote.refspecs(remote::Direction::Fetch).is_empty() { |
148 | return Err(prepare::Error::MissingRefSpecs); | |
149 | } | |
49aad941 | 150 | let ref_map = self.ref_map_inner(progress, options).await?; |
0a29b90c FG |
151 | Ok(Prepare { |
152 | con: Some(self), | |
153 | ref_map, | |
154 | dry_run: DryRun::No, | |
155 | reflog_message: None, | |
156 | write_packed_refs: WritePackedRefs::Never, | |
49aad941 | 157 | shallow: Default::default(), |
0a29b90c FG |
158 | }) |
159 | } | |
160 | } | |
161 | ||
49aad941 | 162 | impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> |
0a29b90c FG |
163 | where |
164 | T: Transport, | |
165 | { | |
fe692bf9 | 166 | /// Return the `ref_map` (that includes the server handshake) which was part of listing refs prior to fetching a pack. |
0a29b90c FG |
167 | pub fn ref_map(&self) -> &RefMap { |
168 | &self.ref_map | |
169 | } | |
170 | } | |
171 | ||
172 | mod config; | |
173 | mod receive_pack; | |
174 | /// | |
175 | #[path = "update_refs/mod.rs"] | |
176 | pub mod refs; | |
177 | ||
178 | /// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation. | |
49aad941 | 179 | pub struct Prepare<'remote, 'repo, T> |
0a29b90c FG |
180 | where |
181 | T: Transport, | |
182 | { | |
49aad941 | 183 | con: Option<Connection<'remote, 'repo, T>>, |
0a29b90c FG |
184 | ref_map: RefMap, |
185 | dry_run: DryRun, | |
186 | reflog_message: Option<RefLogMessage>, | |
187 | write_packed_refs: WritePackedRefs, | |
49aad941 | 188 | shallow: remote::fetch::Shallow, |
0a29b90c FG |
189 | } |
190 | ||
191 | /// Builder | |
49aad941 | 192 | impl<'remote, 'repo, T> Prepare<'remote, 'repo, T> |
0a29b90c FG |
193 | where |
194 | T: Transport, | |
195 | { | |
196 | /// If dry run is enabled, no change to the repository will be made. | |
197 | /// | |
198 | /// This works by not actually fetching the pack after negotiating it, nor will refs be updated. | |
199 | pub fn with_dry_run(mut self, enabled: bool) -> Self { | |
200 | self.dry_run = if enabled { DryRun::Yes } else { DryRun::No }; | |
201 | self | |
202 | } | |
203 | ||
204 | /// If enabled, don't write ref updates to loose refs, but put them exclusively to packed-refs. | |
205 | /// | |
206 | /// This improves performance and allows case-sensitive filesystems to deal with ref names that would otherwise | |
207 | /// collide. | |
208 | pub fn with_write_packed_refs_only(mut self, enabled: bool) -> Self { | |
209 | self.write_packed_refs = if enabled { | |
210 | WritePackedRefs::Only | |
211 | } else { | |
212 | WritePackedRefs::Never | |
213 | }; | |
214 | self | |
215 | } | |
216 | ||
217 | /// Set the reflog message to use when updating refs after fetching a pack. | |
218 | pub fn with_reflog_message(mut self, reflog_message: RefLogMessage) -> Self { | |
219 | self.reflog_message = reflog_message.into(); | |
220 | self | |
221 | } | |
49aad941 FG |
222 | |
223 | /// Define what to do when the current repository is a shallow clone. | |
224 | /// | |
225 | /// *Has no effect if the current repository is not as shallow clone.* | |
226 | pub fn with_shallow(mut self, shallow: remote::fetch::Shallow) -> Self { | |
227 | self.shallow = shallow; | |
228 | self | |
229 | } | |
0a29b90c FG |
230 | } |
231 | ||
49aad941 | 232 | impl<'remote, 'repo, T> Drop for Prepare<'remote, 'repo, T> |
0a29b90c FG |
233 | where |
234 | T: Transport, | |
235 | { | |
236 | fn drop(&mut self) { | |
237 | if let Some(mut con) = self.con.take() { | |
238 | #[cfg(feature = "async-network-client")] | |
239 | { | |
240 | // TODO: this should be an async drop once the feature is available. | |
241 | // Right now we block the executor by forcing this communication, but that only | |
242 | // happens if the user didn't actually try to receive a pack, which consumes the | |
243 | // connection in an async context. | |
244 | gix_protocol::futures_lite::future::block_on(gix_protocol::indicate_end_of_interaction( | |
245 | &mut con.transport, | |
246 | )) | |
247 | .ok(); | |
248 | } | |
249 | #[cfg(not(feature = "async-network-client"))] | |
250 | { | |
251 | gix_protocol::indicate_end_of_interaction(&mut con.transport).ok(); | |
252 | } | |
253 | } | |
254 | } | |
255 | } |