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::io;
13use std::time::Instant;
14
15use gix_protocol::handshake;
16
17pub use gix_protocol::{transport::bstr::ByteSlice, RemoteProgress};
18pub use handle::Handle;
19pub use policy::{Allowed, BlockList, Scope};
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")]
32 Handshake {
33 #[source]
34 err: io::Error,
35 },
36 #[error("failed to load `rad/id`")]
37 Identity {
38 #[source]
39 err: Box<dyn std::error::Error + Send + Sync + 'static>,
40 },
41 #[error(transparent)]
42 Protocol(#[from] state::error::Protocol),
43 #[error("missing `rad/id`")]
44 MissingRadId,
45 #[error("attempted to replicate from self")]
46 ReplicateSelf,
47}
48
49pub fn pull<S>(
55 handle: &mut Handle<S>,
56 limit: FetchLimit,
57 remote: PublicKey,
58 refs_at: Option<Vec<RefsAt>>,
59) -> Result<FetchResult, Error>
60where
61 S: transport::ConnectionStream,
62{
63 let start = Instant::now();
64 let local = *handle.local();
65 if local == remote {
66 return Err(Error::ReplicateSelf);
67 }
68 let handshake = perform_handshake(handle)?;
69 let state = FetchState::default();
70
71 handle.blocked.extend([local]);
73 let result = state
74 .run(handle, &handshake, limit, remote, refs_at)
75 .map_err(Error::Protocol);
76
77 log::debug!(
78 target: "fetch",
79 "Finished pull of {} ({}ms)",
80 handle.repo.id(),
81 start.elapsed().as_millis()
82 );
83 result
84}
85
86pub fn clone<S>(
91 handle: &mut Handle<S>,
92 limit: FetchLimit,
93 remote: PublicKey,
94) -> Result<FetchResult, Error>
95where
96 S: transport::ConnectionStream,
97{
98 let start = Instant::now();
99 if *handle.local() == remote {
100 return Err(Error::ReplicateSelf);
101 }
102 let handshake = perform_handshake(handle)?;
103 let state = FetchState::default();
104 let result = state
105 .run(handle, &handshake, limit, remote, None)
106 .map_err(Error::Protocol);
107 let elapsed = start.elapsed().as_millis();
108 let rid = handle.repo.id();
109
110 match &result {
111 Ok(_) => {
112 log::debug!(
113 target: "fetch",
114 "Finished clone of {rid} from {remote} ({elapsed}ms)",
115 );
116 }
117 Err(e) => {
118 log::debug!(
119 target: "fetch",
120 "Clone of {rid} from {remote} failed with '{e}' ({elapsed}ms)",
121 );
122 }
123 }
124 result
125}
126
127fn perform_handshake<S>(handle: &mut Handle<S>) -> Result<handshake::Outcome, Error>
128where
129 S: transport::ConnectionStream,
130{
131 handle.transport.handshake().map_err(|err| {
132 log::warn!(target: "fetch", "Failed to perform handshake: {err}");
133 Error::Handshake { err }
134 })
135}