git_internal/protocol/
core.rs

1//! Core Git protocol implementation
2//!
3//! This module provides the main `GitProtocol` struct and `RepositoryAccess` trait
4//! that form the core interface of the git-internal library.
5use std::collections::HashMap;
6use std::str::FromStr;
7
8use async_trait::async_trait;
9use bytes::Bytes;
10use futures::stream::StreamExt;
11
12use crate::hash::SHA1;
13use crate::internal::object::ObjectTrait;
14
15use crate::protocol::smart::SmartProtocol;
16use crate::protocol::types::{ProtocolError, ProtocolStream, ServiceType};
17
18/// Repository access trait for storage operations
19///
20/// This trait only handles storage-level operations, not Git protocol details.
21/// The git-internal library handles all Git protocol formatting and parsing.
22#[async_trait]
23pub trait RepositoryAccess: Send + Sync + Clone {
24    /// Get repository references as raw (name, hash) pairs
25    async fn get_repository_refs(&self) -> Result<Vec<(String, String)>, ProtocolError>;
26
27    /// Check if an object exists in the repository
28    async fn has_object(&self, object_hash: &str) -> Result<bool, ProtocolError>;
29
30    /// Get raw object data by hash
31    async fn get_object(&self, object_hash: &str) -> Result<Vec<u8>, ProtocolError>;
32
33    /// Store pack data in the repository
34    async fn store_pack_data(&self, pack_data: &[u8]) -> Result<(), ProtocolError>;
35
36    /// Update a single reference
37    async fn update_reference(
38        &self,
39        ref_name: &str,
40        old_hash: Option<&str>,
41        new_hash: &str,
42    ) -> Result<(), ProtocolError>;
43
44    /// Get objects needed for pack generation
45    async fn get_objects_for_pack(
46        &self,
47        wants: &[String],
48        haves: &[String],
49    ) -> Result<Vec<String>, ProtocolError>;
50
51    /// Check if repository has a default branch
52    async fn has_default_branch(&self) -> Result<bool, ProtocolError>;
53
54    /// Post-receive hook after successful push
55    async fn post_receive_hook(&self) -> Result<(), ProtocolError>;
56
57    /// Get blob data by hash
58    ///
59    /// Default implementation parses the object data using the internal object module.
60    /// Override this method if you need custom blob handling logic.
61    async fn get_blob(
62        &self,
63        object_hash: &str,
64    ) -> Result<crate::internal::object::blob::Blob, ProtocolError> {
65        let data = self.get_object(object_hash).await?;
66        let hash = SHA1::from_str(object_hash)
67            .map_err(|e| ProtocolError::repository_error(format!("Invalid hash format: {}", e)))?;
68
69        crate::internal::object::blob::Blob::from_bytes(&data, hash)
70            .map_err(|e| ProtocolError::repository_error(format!("Failed to parse blob: {}", e)))
71    }
72
73    /// Get commit data by hash
74    ///
75    /// Default implementation parses the object data using the internal object module.
76    /// Override this method if you need custom commit handling logic.
77    async fn get_commit(
78        &self,
79        commit_hash: &str,
80    ) -> Result<crate::internal::object::commit::Commit, ProtocolError> {
81        let data = self.get_object(commit_hash).await?;
82        let hash = SHA1::from_str(commit_hash)
83            .map_err(|e| ProtocolError::repository_error(format!("Invalid hash format: {}", e)))?;
84
85        crate::internal::object::commit::Commit::from_bytes(&data, hash)
86            .map_err(|e| ProtocolError::repository_error(format!("Failed to parse commit: {}", e)))
87    }
88
89    /// Get tree data by hash
90    ///
91    /// Default implementation parses the object data using the internal object module.
92    /// Override this method if you need custom tree handling logic.
93    async fn get_tree(
94        &self,
95        tree_hash: &str,
96    ) -> Result<crate::internal::object::tree::Tree, ProtocolError> {
97        let data = self.get_object(tree_hash).await?;
98        let hash = SHA1::from_str(tree_hash)
99            .map_err(|e| ProtocolError::repository_error(format!("Invalid hash format: {}", e)))?;
100
101        crate::internal::object::tree::Tree::from_bytes(&data, hash)
102            .map_err(|e| ProtocolError::repository_error(format!("Failed to parse tree: {}", e)))
103    }
104
105    /// Check if a commit exists
106    ///
107    /// Default implementation checks object existence and validates it's a commit.
108    /// Override this method if you have more efficient commit existence checking.
109    async fn commit_exists(&self, commit_hash: &str) -> Result<bool, ProtocolError> {
110        match self.has_object(commit_hash).await {
111            Ok(exists) => {
112                if !exists {
113                    return Ok(false);
114                }
115
116                // Verify it's actually a commit by trying to parse it
117                match self.get_commit(commit_hash).await {
118                    Ok(_) => Ok(true),
119                    Err(_) => Ok(false), // Object exists but is not a valid commit
120                }
121            }
122            Err(e) => Err(e),
123        }
124    }
125
126    /// Handle pack objects after unpacking
127    ///
128    /// Default implementation stores each object individually using store_pack_data.
129    /// Override this method if you need batch processing or custom storage logic.
130    async fn handle_pack_objects(
131        &self,
132        commits: Vec<crate::internal::object::commit::Commit>,
133        trees: Vec<crate::internal::object::tree::Tree>,
134        blobs: Vec<crate::internal::object::blob::Blob>,
135    ) -> Result<(), ProtocolError> {
136        // Store blobs
137        for blob in blobs {
138            let data = blob.to_data().map_err(|e| {
139                ProtocolError::repository_error(format!("Failed to serialize blob: {}", e))
140            })?;
141            self.store_pack_data(&data).await.map_err(|e| {
142                ProtocolError::repository_error(format!("Failed to store blob {}: {}", blob.id, e))
143            })?;
144        }
145
146        // Store trees
147        for tree in trees {
148            let data = tree.to_data().map_err(|e| {
149                ProtocolError::repository_error(format!("Failed to serialize tree: {}", e))
150            })?;
151            self.store_pack_data(&data).await.map_err(|e| {
152                ProtocolError::repository_error(format!("Failed to store tree {}: {}", tree.id, e))
153            })?;
154        }
155
156        // Store commits
157        for commit in commits {
158            let data = commit.to_data().map_err(|e| {
159                ProtocolError::repository_error(format!("Failed to serialize commit: {}", e))
160            })?;
161            self.store_pack_data(&data).await.map_err(|e| {
162                ProtocolError::repository_error(format!(
163                    "Failed to store commit {}: {}",
164                    commit.id, e
165                ))
166            })?;
167        }
168
169        Ok(())
170    }
171}
172
173/// Authentication service trait
174#[async_trait]
175pub trait AuthenticationService: Send + Sync {
176    /// Authenticate HTTP request
177    async fn authenticate_http(
178        &self,
179        headers: &std::collections::HashMap<String, String>,
180    ) -> Result<(), ProtocolError>;
181
182    /// Authenticate SSH public key
183    async fn authenticate_ssh(
184        &self,
185        username: &str,
186        public_key: &[u8],
187    ) -> Result<(), ProtocolError>;
188}
189
190/// Transport-agnostic Git smart protocol handler
191/// Main Git protocol handler
192///
193/// This struct provides the core Git protocol implementation that works
194/// across HTTP, SSH, and other transports. It uses SmartProtocol internally
195/// to handle all Git protocol details.
196pub struct GitProtocol<R: RepositoryAccess, A: AuthenticationService> {
197    smart_protocol: SmartProtocol<R, A>,
198}
199
200impl<R: RepositoryAccess, A: AuthenticationService> GitProtocol<R, A> {
201    /// Create a new GitProtocol instance
202    pub fn new(repo_access: R, auth_service: A) -> Self {
203        Self {
204            smart_protocol: SmartProtocol::new(
205                super::types::TransportProtocol::Http,
206                repo_access,
207                auth_service,
208            ),
209        }
210    }
211
212    /// Authenticate HTTP request before serving Git operations
213    pub async fn authenticate_http(
214        &self,
215        headers: &HashMap<String, String>,
216    ) -> Result<(), ProtocolError> {
217        self.smart_protocol.authenticate_http(headers).await
218    }
219
220    /// Authenticate SSH session before serving Git operations
221    pub async fn authenticate_ssh(
222        &self,
223        username: &str,
224        public_key: &[u8],
225    ) -> Result<(), ProtocolError> {
226        self.smart_protocol
227            .authenticate_ssh(username, public_key)
228            .await
229    }
230
231    /// Set transport protocol (Http, Ssh, etc.)
232    pub fn set_transport(&mut self, protocol: super::types::TransportProtocol) {
233        self.smart_protocol.set_transport_protocol(protocol);
234    }
235
236    /// Handle git info-refs request
237    pub async fn info_refs(&self, service: &str) -> Result<Vec<u8>, ProtocolError> {
238        let service_type = match service {
239            "git-upload-pack" => ServiceType::UploadPack,
240            "git-receive-pack" => ServiceType::ReceivePack,
241            _ => return Err(ProtocolError::invalid_service(service)),
242        };
243
244        let bytes = self.smart_protocol.git_info_refs(service_type).await?;
245        Ok(bytes.to_vec())
246    }
247
248    /// Handle git-upload-pack request (for clone/fetch)
249    pub async fn upload_pack(
250        &mut self,
251        request_data: &[u8],
252    ) -> Result<ProtocolStream, ProtocolError> {
253        let request_bytes = bytes::Bytes::from(request_data.to_vec());
254        let (stream, _) = self.smart_protocol.git_upload_pack(request_bytes).await?;
255        Ok(Box::pin(stream.map(|data| Ok(Bytes::from(data)))))
256    }
257
258    /// Handle git-receive-pack request (for push)
259    pub async fn receive_pack(
260        &mut self,
261        request_stream: ProtocolStream,
262    ) -> Result<ProtocolStream, ProtocolError> {
263        let result_bytes = self
264            .smart_protocol
265            .git_receive_pack_stream(request_stream)
266            .await?;
267        // Return the report status as a single-chunk stream
268        Ok(Box::pin(futures::stream::once(async { Ok(result_bytes) })))
269    }
270}