rustygit/
types.rs

1use super::GitError;
2use regex::Regex;
3use std::str::FromStr;
4use std::{fmt, fmt::{Display, Formatter}, result::Result as stdResult};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Deserializer, de};
7
8pub type Result<A> = stdResult<A, GitError>;
9
10#[derive(Debug)]
11pub struct GitUrl {
12    pub(crate) value: String,
13}
14
15impl FromStr for GitUrl {
16    type Err = GitError;
17
18    fn from_str(value: &str) -> Result<Self> {
19        //Regex from https://github.com/jonschlinkert/is-git-url
20        let re =
21            Regex::new("(?:git|ssh|https?|git@[-\\w.]+):(//)?(.*?)(\\.git)(/?|\\#[-\\d\\w._]+?)$")
22                .unwrap();
23        if re.is_match(value) {
24            Ok(GitUrl {
25                value: String::from(value),
26            })
27        } else {
28            Err(GitError::InvalidUrl)
29        }
30    }
31}
32
33impl Display for GitUrl {
34    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
35        write!(f, "{}", self.value)
36    }
37}
38
39#[derive(Debug)]
40pub struct BranchName {
41    pub(crate) value: String
42}
43
44impl FromStr for BranchName {
45    type Err = GitError;
46    fn from_str(s: &str) -> Result<Self> { 
47        if is_valid_reference_name(s) {
48            Ok(BranchName {
49                value: String::from(s)
50            })
51        } else {
52            Err(GitError::InvalidRefName)
53        }
54    }
55    
56}
57
58impl Display for BranchName {
59    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
60        write!(f, "{}", self.value)
61    }
62}
63
64#[cfg(feature = "serde")]
65impl<'de> Deserialize<'de> for BranchName {
66    fn deserialize<D>(deserializer: D) -> stdResult<BranchName, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        let s = String::deserialize(deserializer)?;
71        BranchName::from_str(&s).map_err(de::Error::custom)
72    }
73}
74
75const INVALID_REFERENCE_CHARS: [char; 5] = [' ', '~', '^', ':', '\\'];
76const INVALID_REFERENCE_START: &str = "-";
77const INVALID_REFERENCE_END: &str = ".";
78
79fn is_valid_reference_name(name: &str) -> bool {
80    !name.starts_with(INVALID_REFERENCE_START)
81        && !name.ends_with(INVALID_REFERENCE_END)
82        && name.chars().all(|c| {
83            !c.is_ascii_control() && INVALID_REFERENCE_CHARS.iter().all(|invalid| &c != invalid)
84        })
85        && !name.contains("/.")
86        && !name.contains("@{")
87        && !name.contains("..")
88        && name != "@"
89}
90
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_valid_git_urls() {
98
99        let valid_urls = vec!(
100            "git://github.com/ember-cli/ember-cli.git#ff786f9f",
101            "git://github.com/ember-cli/ember-cli.git#gh-pages",
102            "git://github.com/ember-cli/ember-cli.git#master",
103            "git://github.com/ember-cli/ember-cli.git#Quick-Fix",
104            "git://github.com/ember-cli/ember-cli.git#quick_fix",
105            "git://github.com/ember-cli/ember-cli.git#v0.1.0",
106            "git://host.xz/path/to/repo.git/",
107            "git://host.xz/~user/path/to/repo.git/",
108            "git@192.168.101.127:user/project.git",
109            "git@github.com:user/project.git",
110            "git@github.com:user/some-project.git",
111            "git@github.com:user/some-project.git",
112            "git@github.com:user/some_project.git",
113            "git@github.com:user/some_project.git",
114            "http://192.168.101.127/user/project.git",
115            "http://github.com/user/project.git",
116            "http://host.xz/path/to/repo.git/",
117            "https://192.168.101.127/user/project.git",
118            "https://github.com/user/project.git",
119            "https://host.xz/path/to/repo.git/",
120            "https://username::;*%$:@github.com/username/repository.git",
121            "https://username:$fooABC@:@github.com/username/repository.git",
122            "https://username:password@github.com/username/repository.git",
123            "ssh://host.xz/path/to/repo.git/",
124            "ssh://host.xz/path/to/repo.git/",
125            "ssh://host.xz/~/path/to/repo.git",
126            "ssh://host.xz/~user/path/to/repo.git/",
127            "ssh://host.xz:port/path/to/repo.git/",
128            "ssh://user@host.xz/path/to/repo.git/",
129            "ssh://user@host.xz/path/to/repo.git/",
130            "ssh://user@host.xz/~/path/to/repo.git",
131            "ssh://user@host.xz/~user/path/to/repo.git/",
132            "ssh://user@host.xz:port/path/to/repo.git/",
133        );
134
135        for url in valid_urls.iter() {  
136            assert!(GitUrl::from_str(url).is_ok())
137        }
138    }
139
140
141    #[test]
142    fn test_invalid_git_urls() {
143        let invalid_urls = vec!(
144            "/path/to/repo.git/",
145            "file:///path/to/repo.git/",
146            "file://~/path/to/repo.git/",
147            "git@github.com:user/some_project.git/foo",
148            "git@github.com:user/some_project.gitfoo",
149            "host.xz:/path/to/repo.git/",
150            "host.xz:path/to/repo.git",
151            "host.xz:~user/path/to/repo.git/",
152            "path/to/repo.git/",
153            "rsync://host.xz/path/to/repo.git/",
154            "user@host.xz:/path/to/repo.git/",
155            "user@host.xz:path/to/repo.git",
156            "user@host.xz:~user/path/to/repo.git/",
157            "~/path/to/repo.git"
158        );
159
160        for url in invalid_urls.iter() {  
161            assert!(GitUrl::from_str(url).is_err())
162        }
163    }
164
165    #[test]
166    fn test_valid_reference_names() {
167        let valid_reference = "avalidreference";
168
169        assert!(is_valid_reference_name(valid_reference))
170    }
171
172    #[test]
173    fn test_invalid_reference_names() {
174        let invalid_references = vec!(
175            "double..dot",
176            "inavlid^character",
177            "invalid~character",
178            "invalid:character",
179            "invalid\\character",
180            "@",
181            "inavlid@{sequence"
182        );
183
184        for reference_name in invalid_references.iter() {
185            assert!(!is_valid_reference_name(reference_name))
186        }
187    }
188}