Skip to main content

git_remote_htree/git/
refs.rs

1//! Git references (branches, tags, HEAD)
2//!
3//! Refs are named pointers to commits.
4
5use super::object::ObjectId;
6use super::{Error, Result};
7
8/// A git reference
9#[derive(Debug, Clone)]
10pub enum Ref {
11    /// Direct reference to an object
12    Direct(ObjectId),
13    /// Symbolic reference to another ref (e.g., HEAD -> refs/heads/main)
14    Symbolic(String),
15}
16
17/// Validate a ref name according to git rules
18pub fn validate_ref_name(name: &str) -> Result<()> {
19    if name.is_empty() {
20        return Err(Error::InvalidRefName("empty ref name".into()));
21    }
22
23    if name.starts_with('/') || name.ends_with('/') {
24        return Err(Error::InvalidRefName("cannot start or end with /".into()));
25    }
26
27    if name.contains("//") {
28        return Err(Error::InvalidRefName("cannot contain //".into()));
29    }
30
31    if name.contains("..") {
32        return Err(Error::InvalidRefName("cannot contain ..".into()));
33    }
34
35    for c in name.chars() {
36        if c.is_control()
37            || c == ' '
38            || c == '~'
39            || c == '^'
40            || c == ':'
41            || c == '?'
42            || c == '*'
43            || c == '['
44        {
45            return Err(Error::InvalidRefName(format!("invalid character: {:?}", c)));
46        }
47    }
48
49    if name.ends_with(".lock") {
50        return Err(Error::InvalidRefName("cannot end with .lock".into()));
51    }
52
53    if name.contains("@{") {
54        return Err(Error::InvalidRefName("cannot contain @{".into()));
55    }
56
57    if name == "@" {
58        return Err(Error::InvalidRefName("cannot be @".into()));
59    }
60
61    if name.ends_with('.') {
62        return Err(Error::InvalidRefName("cannot end with .".into()));
63    }
64
65    Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_valid_ref_names() {
74        assert!(validate_ref_name("refs/heads/main").is_ok());
75        assert!(validate_ref_name("refs/heads/feature/test").is_ok());
76        assert!(validate_ref_name("refs/tags/v1.0.0").is_ok());
77        assert!(validate_ref_name("HEAD").is_ok());
78    }
79
80    #[test]
81    fn test_invalid_ref_names() {
82        assert!(validate_ref_name("").is_err());
83        assert!(validate_ref_name("/refs/heads/main").is_err());
84        assert!(validate_ref_name("refs/heads/main/").is_err());
85        assert!(validate_ref_name("refs//heads").is_err());
86        assert!(validate_ref_name("refs/heads/..").is_err());
87        assert!(validate_ref_name("refs/heads/test.lock").is_err());
88        assert!(validate_ref_name("refs/heads/te st").is_err());
89    }
90}