]> git.proxmox.com Git - rustc.git/blame - vendor/gix/src/remote/connection/fetch/mod.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / vendor / gix / src / remote / connection / fetch / mod.rs
CommitLineData
0a29b90c
FG
1use gix_protocol::transport::client::Transport;
2
3use crate::{
4 bstr::BString,
5 remote,
6 remote::{
7 fetch::{DryRun, RefMap},
8 ref_map, Connection,
9 },
10 Progress,
11};
12
13mod error;
14pub use error::Error;
15
16use crate::remote::fetch::WritePackedRefs;
17
18/// The way reflog messages should be composed whenever a ref is written with recent objects from a remote.
19pub 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
32impl 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)]
43pub 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)]
74pub 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)]
87pub 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
92impl 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 100pub(crate) mod negotiate;
0a29b90c
FG
101
102///
103pub 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 124impl<'remote, 'repo, T> Connection<'remote, 'repo, T>
0a29b90c
FG
125where
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 162impl<'remote, 'repo, T> Prepare<'remote, 'repo, T>
0a29b90c
FG
163where
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
172mod config;
173mod receive_pack;
174///
175#[path = "update_refs/mod.rs"]
176pub mod refs;
177
178/// A structure to hold the result of the handshake with the remote and configure the upcoming fetch operation.
49aad941 179pub struct Prepare<'remote, 'repo, T>
0a29b90c
FG
180where
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 192impl<'remote, 'repo, T> Prepare<'remote, 'repo, T>
0a29b90c
FG
193where
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 232impl<'remote, 'repo, T> Drop for Prepare<'remote, 'repo, T>
0a29b90c
FG
233where
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}