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, Handshake};
16
17pub use gix_protocol::{transport::bstr::ByteSlice, RemoteProgress};
18pub use handle::Handle;
19pub use policy::{Allowed, BlockList, Scope};
20use radicle::storage::git::Repository;
21pub use state::{FetchLimit, FetchResult};
22pub use transport::Transport;
23
24use radicle::crypto::PublicKey;
25use radicle::storage::refs::RefsAt;
26use radicle::storage::ReadRepository as _;
27use state::FetchState;
28use thiserror::Error;
29
30#[derive(Debug, Error)]
31pub enum Error {
32 #[error(transparent)]
33 Handshake(Box<HandshakeError>),
34 #[error("failed to load `rad/id`")]
35 Identity {
36 #[source]
37 err: Box<dyn std::error::Error + Send + Sync + 'static>,
38 },
39 #[error(transparent)]
40 Protocol(#[from] state::error::Protocol),
41 #[error("missing `rad/id`")]
42 MissingRadId,
43 #[error("attempted to replicate from self")]
44 ReplicateSelf,
45}
46
47impl From<HandshakeError> for Error {
48 fn from(err: HandshakeError) -> Self {
49 Self::Handshake(Box::new(err))
50 }
51}
52
53#[derive(Debug, Error)]
54pub enum HandshakeError {
55 #[error("failed to perform fetch handshake: {0}")]
56 Gix(handshake::Error),
57 #[error("an I/O error occurred during the fetch handshake ({0})")]
58 Io(io::Error),
59}
60
61pub fn pull<R, S>(
67 handle: &mut Handle<R, S>,
68 limit: FetchLimit,
69 remote: PublicKey,
70 refs_at: Option<Vec<RefsAt>>,
71) -> Result<FetchResult, Error>
72where
73 R: AsRef<Repository>,
74 S: transport::ConnectionStream,
75{
76 let start = Instant::now();
77 let local = *handle.local();
78 if local == remote {
79 return Err(Error::ReplicateSelf);
80 }
81 let handshake = perform_handshake(handle)?;
82 let state = FetchState::default();
83
84 handle.blocked.extend([local]);
86 let result = state
87 .run(handle, &handshake, limit, remote, refs_at)
88 .map_err(Error::Protocol);
89
90 log::debug!(
91 "Finished pull of {} ({}ms)",
92 handle.repository().id(),
93 start.elapsed().as_millis()
94 );
95 result
96}
97
98pub fn clone<R, S>(
103 handle: &mut Handle<R, S>,
104 limit: FetchLimit,
105 remote: PublicKey,
106) -> Result<FetchResult, Error>
107where
108 R: AsRef<Repository>,
109 S: transport::ConnectionStream,
110{
111 let start = Instant::now();
112 if *handle.local() == remote {
113 return Err(Error::ReplicateSelf);
114 }
115 let handshake = perform_handshake(handle)?;
116 let state = FetchState::default();
117 let result = state
118 .run(handle, &handshake, limit, remote, None)
119 .map_err(Error::Protocol);
120 let elapsed = start.elapsed().as_millis();
121 let rid = handle.repository().id();
122
123 match &result {
124 Ok(_) => {
125 log::debug!("Finished clone of {rid} from {remote} ({elapsed}ms)",);
126 }
127 Err(e) => {
128 log::debug!("Clone of {rid} from {remote} failed with '{e}' ({elapsed}ms)",);
129 }
130 }
131 result
132}
133
134fn perform_handshake<R, S>(handle: &mut Handle<R, S>) -> Result<Handshake, Error>
135where
136 S: transport::ConnectionStream,
137{
138 handle
139 .transport
140 .handshake()
141 .map_err(handle_handshake_err)
142 .map_err(Error::from)
143}
144
145fn handle_handshake_err(err: handshake::Error) -> HandshakeError {
146 match err {
147 handshake::Error::Transport(error) => match error {
148 gix_transport::client::Error::Io(error) => HandshakeError::Io(error),
149 err => HandshakeError::Gix(handshake::Error::Transport(err)),
150 },
151 err => HandshakeError::Gix(err),
152 }
153}