git_internal/protocol/
types.rs

1use bytes::Bytes;
2use futures::stream::Stream;
3use std::fmt;
4use std::pin::Pin;
5use std::str::FromStr;
6
7/// Type alias for protocol data streams to reduce nesting
8pub type ProtocolStream = Pin<Box<dyn Stream<Item = Result<Bytes, ProtocolError>> + Send>>;
9
10/// Protocol error types
11#[derive(Debug, thiserror::Error)]
12pub enum ProtocolError {
13    #[error("Invalid service: {0}")]
14    InvalidService(String),
15
16    #[error("Repository not found: {0}")]
17    RepositoryNotFound(String),
18
19    #[error("Object not found: {0}")]
20    ObjectNotFound(String),
21
22    #[error("Invalid request: {0}")]
23    InvalidRequest(String),
24
25    #[error("Unauthorized: {0}")]
26    Unauthorized(String),
27
28    #[error("IO error: {0}")]
29    Io(#[from] std::io::Error),
30
31    #[error("Pack error: {0}")]
32    Pack(String),
33
34    #[error("Internal error: {0}")]
35    Internal(String),
36}
37
38impl ProtocolError {
39    pub fn invalid_service(service: &str) -> Self {
40        ProtocolError::InvalidService(service.to_string())
41    }
42
43    pub fn repository_error(msg: String) -> Self {
44        ProtocolError::Internal(msg)
45    }
46
47    pub fn invalid_request(msg: &str) -> Self {
48        ProtocolError::InvalidRequest(msg.to_string())
49    }
50
51    pub fn unauthorized(msg: &str) -> Self {
52        ProtocolError::Unauthorized(msg.to_string())
53    }
54}
55
56/// Git transport protocol types
57#[derive(Debug, PartialEq, Clone, Copy, Default)]
58pub enum TransportProtocol {
59    Local,
60    #[default]
61    Http,
62    Ssh,
63    Git,
64}
65
66/// Git service types for smart protocol
67#[derive(Debug, PartialEq, Clone, Copy)]
68pub enum ServiceType {
69    UploadPack,
70    ReceivePack,
71}
72
73impl fmt::Display for ServiceType {
74    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75        match self {
76            ServiceType::UploadPack => write!(f, "git-upload-pack"),
77            ServiceType::ReceivePack => write!(f, "git-receive-pack"),
78        }
79    }
80}
81
82impl FromStr for ServiceType {
83    type Err = ProtocolError;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        match s {
87            "git-upload-pack" => Ok(ServiceType::UploadPack),
88            "git-receive-pack" => Ok(ServiceType::ReceivePack),
89            _ => Err(ProtocolError::InvalidService(s.to_string())),
90        }
91    }
92}
93
94/// Git protocol capabilities
95///
96/// ## Implementation Status Overview
97///
98/// ### Implemented capabilities:
99/// - **Data transmission**: SideBand, SideBand64k - Multiplexed data streams via side-band formatter
100/// - **Status reporting**: ReportStatus, ReportStatusv2 - Push status feedback via protocol handlers
101/// - **Pack optimization**: OfsDelta, ThinPack, NoThin - Delta compression and efficient transmission
102/// - **Protocol control**: MultiAckDetailed, NoDone - ACK mechanism optimization for upload-pack
103/// - **Push control**: Atomic, DeleteRefs, Quiet - Atomic operations and reference management
104/// - **Tag handling**: IncludeTag - Automatic tag inclusion for upload-pack
105/// - **Client identification**: Agent - Client/server identification in capability negotiation
106///
107/// ### Not yet implemented capabilities:
108/// - **Basic protocol**: MultiAck - Basic multi-ack support (only detailed version implemented)
109/// - **Shallow cloning**: Shallow, DeepenSince, DeepenNot, DeepenRelative - Depth control for shallow clones
110/// - **Progress control**: NoProgress - Progress output suppression
111/// - **Special fetch**: AllowTipSha1InWant, AllowReachableSha1InWant - SHA1 validation in want processing
112/// - **Security**: PushCert - Push certificate verification mechanism
113/// - **Extensions**: PushOptions, Filter, Symref - Extended parameter handling
114/// - **Session management**: SessionId, ObjectFormat - Session and format negotiation
115#[derive(Debug, Clone, PartialEq)]
116pub enum Capability {
117    /// Multi-ack capability for upload-pack protocol
118    MultiAck,
119    /// Multi-ack-detailed capability for more granular acknowledgment
120    MultiAckDetailed,
121    /// No-done capability to optimize upload-pack protocol
122    NoDone,
123    /// Side-band capability for multiplexing data streams
124    SideBand,
125    /// Side-band-64k capability for larger side-band packets
126    SideBand64k,
127    /// Report-status capability for push status reporting
128    ReportStatus,
129    /// Report-status-v2 capability for enhanced push status reporting
130    ReportStatusv2,
131    /// OFS-delta capability for offset-based delta compression
132    OfsDelta,
133    /// Deepen-since capability for shallow clone with time-based depth
134    DeepenSince,
135    /// Deepen-not capability for shallow clone exclusions
136    DeepenNot,
137    /// Deepen-relative capability for relative depth specification
138    DeepenRelative,
139    /// Thin-pack capability for efficient pack transmission
140    ThinPack,
141    /// Shallow capability for shallow clone support
142    Shallow,
143    /// Include-tag capability for automatic tag inclusion
144    IncludeTag,
145    /// Delete-refs capability for reference deletion
146    DeleteRefs,
147    /// Quiet capability to suppress output
148    Quiet,
149    /// Atomic capability for atomic push operations
150    Atomic,
151    /// No-thin capability to disable thin pack
152    NoThin,
153    /// No-progress capability to disable progress reporting
154    NoProgress,
155    /// Allow-tip-sha1-in-want capability for fetching specific commits
156    AllowTipSha1InWant,
157    /// Allow-reachable-sha1-in-want capability for fetching reachable commits
158    AllowReachableSha1InWant,
159    /// Push-cert capability for signed push certificates
160    PushCert(String),
161    /// Push-options capability for additional push metadata
162    PushOptions,
163    /// Object-format capability for specifying hash algorithm
164    ObjectFormat(String),
165    /// Session-id capability for session tracking
166    SessionId(String),
167    /// Filter capability for partial clone support
168    Filter(String),
169    /// Symref capability for symbolic reference information
170    Symref(String),
171    /// Agent capability for client/server identification
172    Agent(String),
173    /// Unknown capability for forward compatibility
174    Unknown(String),
175}
176
177impl FromStr for Capability {
178    type Err = ();
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        // Parameterized capabilities
182        if let Some(rest) = s.strip_prefix("agent=") {
183            return Ok(Capability::Agent(rest.to_string()));
184        }
185        if let Some(rest) = s.strip_prefix("session-id=") {
186            return Ok(Capability::SessionId(rest.to_string()));
187        }
188        if let Some(rest) = s.strip_prefix("push-cert=") {
189            return Ok(Capability::PushCert(rest.to_string()));
190        }
191        if let Some(rest) = s.strip_prefix("object-format=") {
192            return Ok(Capability::ObjectFormat(rest.to_string()));
193        }
194        if let Some(rest) = s.strip_prefix("filter=") {
195            return Ok(Capability::Filter(rest.to_string()));
196        }
197        if let Some(rest) = s.strip_prefix("symref=") {
198            return Ok(Capability::Symref(rest.to_string()));
199        }
200
201        match s {
202            "multi_ack" => Ok(Capability::MultiAck),
203            "multi_ack_detailed" => Ok(Capability::MultiAckDetailed),
204            "no-done" => Ok(Capability::NoDone),
205            "side-band" => Ok(Capability::SideBand),
206            "side-band-64k" => Ok(Capability::SideBand64k),
207            "report-status" => Ok(Capability::ReportStatus),
208            "report-status-v2" => Ok(Capability::ReportStatusv2),
209            "ofs-delta" => Ok(Capability::OfsDelta),
210            "deepen-since" => Ok(Capability::DeepenSince),
211            "deepen-not" => Ok(Capability::DeepenNot),
212            "deepen-relative" => Ok(Capability::DeepenRelative),
213            "thin-pack" => Ok(Capability::ThinPack),
214            "shallow" => Ok(Capability::Shallow),
215            "include-tag" => Ok(Capability::IncludeTag),
216            "delete-refs" => Ok(Capability::DeleteRefs),
217            "quiet" => Ok(Capability::Quiet),
218            "atomic" => Ok(Capability::Atomic),
219            "no-thin" => Ok(Capability::NoThin),
220            "no-progress" => Ok(Capability::NoProgress),
221            "allow-tip-sha1-in-want" => Ok(Capability::AllowTipSha1InWant),
222            "allow-reachable-sha1-in-want" => Ok(Capability::AllowReachableSha1InWant),
223            "push-options" => Ok(Capability::PushOptions),
224            _ => Ok(Capability::Unknown(s.to_string())),
225        }
226    }
227}
228
229impl std::fmt::Display for Capability {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        match self {
232            Capability::MultiAck => write!(f, "multi_ack"),
233            Capability::MultiAckDetailed => write!(f, "multi_ack_detailed"),
234            Capability::NoDone => write!(f, "no-done"),
235            Capability::SideBand => write!(f, "side-band"),
236            Capability::SideBand64k => write!(f, "side-band-64k"),
237            Capability::ReportStatus => write!(f, "report-status"),
238            Capability::ReportStatusv2 => write!(f, "report-status-v2"),
239            Capability::OfsDelta => write!(f, "ofs-delta"),
240            Capability::DeepenSince => write!(f, "deepen-since"),
241            Capability::DeepenNot => write!(f, "deepen-not"),
242            Capability::DeepenRelative => write!(f, "deepen-relative"),
243            Capability::ThinPack => write!(f, "thin-pack"),
244            Capability::Shallow => write!(f, "shallow"),
245            Capability::IncludeTag => write!(f, "include-tag"),
246            Capability::DeleteRefs => write!(f, "delete-refs"),
247            Capability::Quiet => write!(f, "quiet"),
248            Capability::Atomic => write!(f, "atomic"),
249            Capability::NoThin => write!(f, "no-thin"),
250            Capability::NoProgress => write!(f, "no-progress"),
251            Capability::AllowTipSha1InWant => write!(f, "allow-tip-sha1-in-want"),
252            Capability::AllowReachableSha1InWant => write!(f, "allow-reachable-sha1-in-want"),
253            Capability::PushCert(value) => write!(f, "push-cert={}", value),
254            Capability::PushOptions => write!(f, "push-options"),
255            Capability::ObjectFormat(format) => write!(f, "object-format={}", format),
256            Capability::SessionId(id) => write!(f, "session-id={}", id),
257            Capability::Filter(filter) => write!(f, "filter={}", filter),
258            Capability::Symref(symref) => write!(f, "symref={}", symref),
259            Capability::Agent(agent) => write!(f, "agent={}", agent),
260            Capability::Unknown(s) => write!(f, "{}", s),
261        }
262    }
263}
264
265/// Side-band types for multiplexed data streams
266pub enum SideBand {
267    /// Sideband 1 contains packfile data
268    PackfileData,
269    /// Sideband 2 contains progress information
270    ProgressInfo,
271    /// Sideband 3 contains error information
272    Error,
273}
274
275impl SideBand {
276    pub fn value(&self) -> u8 {
277        match self {
278            Self::PackfileData => b'\x01',
279            Self::ProgressInfo => b'\x02',
280            Self::Error => b'\x03',
281        }
282    }
283}
284
285/// Reference types in Git
286#[derive(Debug, PartialEq, Clone, Copy)]
287pub enum RefTypeEnum {
288    Branch,
289    Tag,
290}
291
292/// Git reference information
293#[derive(Clone, Debug)]
294pub struct GitRef {
295    pub name: String,
296    pub hash: String,
297}
298
299/// Reference command for push operations
300#[derive(Debug, Clone)]
301pub struct RefCommand {
302    pub old_hash: String,
303    pub new_hash: String,
304    pub ref_name: String,
305    pub ref_type: RefTypeEnum,
306    pub default_branch: bool,
307    pub status: CommandStatus,
308    pub error_message: Option<String>,
309}
310
311#[derive(Debug, Clone)]
312pub enum CommandStatus {
313    Pending,
314    Success,
315    Failed,
316}
317
318impl RefCommand {
319    pub fn new(old_hash: String, new_hash: String, ref_name: String) -> Self {
320        // Determine ref type based on ref name
321        let ref_type = if ref_name.starts_with("refs/tags/") {
322            RefTypeEnum::Tag
323        } else {
324            RefTypeEnum::Branch
325        };
326
327        Self {
328            old_hash,
329            new_hash,
330            ref_name,
331            ref_type,
332            default_branch: false,
333            status: CommandStatus::Pending,
334            error_message: None,
335        }
336    }
337
338    pub fn failed(&mut self, error: String) {
339        self.status = CommandStatus::Failed;
340        self.error_message = Some(error);
341    }
342
343    pub fn success(&mut self) {
344        self.status = CommandStatus::Success;
345        self.error_message = None;
346    }
347
348    pub fn get_status(&self) -> String {
349        match &self.status {
350            CommandStatus::Success => format!("ok {}", self.ref_name),
351            CommandStatus::Failed => {
352                let error = self.error_message.as_deref().unwrap_or("unknown error");
353                format!("ng {} {}", self.ref_name, error)
354            }
355            CommandStatus::Pending => format!("ok {}", self.ref_name), // Default to ok for pending
356        }
357    }
358}
359
360#[derive(Debug, PartialEq, Clone)]
361pub enum CommandType {
362    Create,
363    Update,
364    Delete,
365}
366
367/// Protocol constants
368pub const LF: char = '\n';
369pub const SP: char = ' ';
370pub const NUL: char = '\0';
371pub const PKT_LINE_END_MARKER: &[u8; 4] = b"0000";
372
373// Git protocol capability lists
374pub const RECEIVE_CAP_LIST: &str =
375    "report-status report-status-v2 delete-refs quiet atomic no-thin ";
376pub const COMMON_CAP_LIST: &str = "side-band-64k ofs-delta agent=git-internal/0.1.0";
377pub const UPLOAD_CAP_LIST: &str = "multi_ack_detailed no-done include-tag ";