1pub mod git;
2pub mod handle;
3pub mod policy;
4pub mod transport;
5
6pub(crate) mod sigrefs;
7
8mod refs;
9mod stage;
10mod state;
11
12use std::time::Instant;
13
14use gix_protocol::handshake;
15
16pub use gix_protocol::{transport::bstr::ByteSlice, RemoteProgress};
17pub use handle::Handle;
18pub use policy::{Allowed, BlockList, Scope};
19use radicle::storage::git::Repository;
20pub use state::{FetchLimit, FetchResult};
21pub use transport::Transport;
22
23use radicle::crypto::PublicKey;
24use radicle::storage::refs::RefsAt;
25use radicle::storage::ReadRepository as _;
26use state::FetchState;
27use thiserror::Error;
28
29#[derive(Debug, Error)]
30pub enum Error {
31 #[error("failed to perform fetch handshake: {0}")]
32 Handshake(#[from] Box<handshake::Error>),
33 #[error("failed to load `rad/id`")]
34 Identity {
35 #[source]
36 err: Box<dyn std::error::Error + Send + Sync + 'static>,
37 },
38 #[error(transparent)]
39 Protocol(#[from] state::error::Protocol),
40 #[error("missing `rad/id`")]
41 MissingRadId,
42 #[error("attempted to replicate from self")]
43 ReplicateSelf,
44}
45
46pub fn pull<R, S>(
52 handle: &mut Handle<R, S>,
53 limit: FetchLimit,
54 remote: PublicKey,
55 refs_at: Option<Vec<RefsAt>>,
56) -> Result<FetchResult, Error>
57where
58 R: AsRef<Repository>,
59 S: transport::ConnectionStream,
60{
61 let start = Instant::now();
62 let local = *handle.local();
63 if local == remote {
64 return Err(Error::ReplicateSelf);
65 }
66 let handshake = perform_handshake(handle)?;
67 let state = FetchState::default();
68
69 handle.blocked.extend([local]);
71 let result = state
72 .run(handle, &handshake, limit, remote, refs_at)
73 .map_err(Error::Protocol);
74
75 log::debug!(
76 target: "fetch",
77 "Finished pull of {} ({}ms)",
78 handle.repository().id(),
79 start.elapsed().as_millis()
80 );
81 result
82}
83
84pub fn clone<R, S>(
89 handle: &mut Handle<R, S>,
90 limit: FetchLimit,
91 remote: PublicKey,
92) -> Result<FetchResult, Error>
93where
94 R: AsRef<Repository>,
95 S: transport::ConnectionStream,
96{
97 let start = Instant::now();
98 if *handle.local() == remote {
99 return Err(Error::ReplicateSelf);
100 }
101 let handshake = perform_handshake(handle)?;
102 let state = FetchState::default();
103 let result = state
104 .run(handle, &handshake, limit, remote, None)
105 .map_err(Error::Protocol);
106 let elapsed = start.elapsed().as_millis();
107 let rid = handle.repository().id();
108
109 match &result {
110 Ok(_) => {
111 log::debug!(
112 target: "fetch",
113 "Finished clone of {rid} from {remote} ({elapsed}ms)",
114 );
115 }
116 Err(e) => {
117 log::debug!(
118 target: "fetch",
119 "Clone of {rid} from {remote} failed with '{e}' ({elapsed}ms)",
120 );
121 }
122 }
123 result
124}
125
126fn perform_handshake<R, S>(handle: &mut Handle<R, S>) -> Result<handshake::Outcome, Error>
127where
128 S: transport::ConnectionStream,
129{
130 let result = handle.transport.handshake();
131
132 if let Err(err) = &result {
133 log::warn!(target: "fetch", "Failed to perform handshake: {err}");
134 }
135
136 Ok(result?)
137}