1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use {
crate::{semver_range::SemverRange, specifier::Specifier},
std::rc::Rc,
};
#[derive(Debug, PartialEq)]
pub struct Git {
/// The exact version specifier as it was provided.
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli"
/// - "git+ssh://git@github.com/npm/cli#1.2.3"
/// - "git+ssh://git@github.com/npm/cli#HEAD"
/// - "git@github.com:npm/cli.git"
/// - "git@github.com:npm/cli.git#1.2.3"
/// - "git@github.com:npm/cli.git#HEAD"
/// - "github:uNetworking/uWebSockets.js"
/// - "github:uNetworking/uWebSockets.js#1.2.3"
/// - "github:uNetworking/uWebSockets.js#HEAD"
/// - "git://github.com/user/foo#semver:^1.2.3"
pub raw: String,
/// Used when checking if specifiers satisfy each other
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli" → None
/// - "git+ssh://git@github.com/npm/cli#1.2.3" → Some("1.2.3")
/// - "git+ssh://git@github.com/npm/cli#HEAD" → None
/// - "git@github.com:npm/cli.git#1.2.3" → Some("1.2.3")
/// - "github:uNetworking/uWebSockets.js#1.2.3" → Some("1.2.3")
/// - "git://github.com/user/foo#semver:^1.2.3" → Some("^1.2.3")
pub node_range: Option<Rc<node_semver::Range>>,
/// Used for ordering and comparison, semver range characters are NOT
/// included
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli" → None
/// - "git+ssh://git@github.com/npm/cli#1.2.3" → Some("1.2.3")
/// - "git+ssh://git@github.com/npm/cli#HEAD" → None
/// - "git@github.com:npm/cli.git#1.2.3" → Some("1.2.3")
/// - "github:uNetworking/uWebSockets.js#1.2.3" → Some("1.2.3")
/// - "git://github.com/user/foo#semver:^1.2.3" → Some("1.2.3")
pub node_version: Option<Rc<node_semver::Version>>,
/// The name of the dependency being aliased.
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli"
/// - "git@github.com:npm/cli.git"
/// - "github:uNetworking/uWebSockets.js"
pub origin: String,
/// The tagged version if set.
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli" → None
/// - "git+ssh://git@github.com/npm/cli#1.2.3" → Some("1.2.3")
/// - "git+ssh://git@github.com/npm/cli#HEAD" → None
/// - "git@github.com:npm/cli.git" → None
/// - "git@github.com:npm/cli.git#1.2.3" → Some("1.2.3")
/// - "git@github.com:npm/cli.git#HEAD" → None
/// - "github:uNetworking/uWebSockets.js" → None
/// - "github:uNetworking/uWebSockets.js#1.2.3" → Some("1.2.3")
/// - "github:uNetworking/uWebSockets.js#HEAD" → None
pub semver_number: Option<String>,
/// The semver range characters used in this specifier, if set
///
/// Examples:
/// - "git+ssh://git@github.com/npm/cli" → None
/// - "git+ssh://git@github.com/npm/cli#1.2.3" → Some(SemverRange::Exact)
/// - "git://github.com/user/foo#semver:^1.2.3" → Some(SemverRange::Minor)
pub semver_range: Option<SemverRange>,
}
impl Git {
pub fn create(raw: &str) -> Specifier {
raw
.find('#')
.map(|hash_pos| {
let origin = &raw[..hash_pos];
let git_tag = &raw[hash_pos + 1..];
(origin, git_tag)
})
.map(|(origin, git_tag)| {
if origin.is_empty() {
Specifier::Unsupported(raw.to_string())
} else {
// Strip "semver:" prefix if present
let tag = if git_tag.starts_with("semver:") {
git_tag.strip_prefix("semver:").unwrap_or(git_tag)
} else {
git_tag
};
if tag.is_empty() {
// Empty tag
return Specifier::Git(Self {
raw: raw.to_string(),
node_range: None,
node_version: None,
origin: origin.to_string(),
semver_number: None,
semver_range: None,
});
}
// Try to parse as node_range (includes range characters)
let node_range = Specifier::new_node_range(tag);
if node_range.is_none() {
// Not a valid semver range (e.g. HEAD, branch name)
return Specifier::Git(Self {
raw: raw.to_string(),
node_range: None,
node_version: None,
origin: origin.to_string(),
semver_number: None,
semver_range: None,
});
}
// Parse semver_range to strip range chars for bare version
let semver_range = SemverRange::parse(tag);
let range_str = semver_range.unwrap();
let bare_version = tag.strip_prefix(&range_str).unwrap_or(tag);
// Parse the bare version - note that node_range can parse shorthand like "^1"
// but node_version requires complete versions like "1.0.0"
let node_version = Specifier::new_node_version(bare_version);
// Valid semver tag - store semver_number and semver_range even for shorthand
// versions where node_version parsing fails
Specifier::Git(Self {
raw: raw.to_string(),
node_range: Some(node_range.unwrap()),
node_version,
origin: origin.to_string(),
semver_number: Some(bare_version.to_string()),
semver_range: Some(semver_range),
})
}
})
// There is no hash, just the origin
.unwrap_or_else(|| {
Specifier::Git(Self {
raw: raw.to_string(),
node_range: None,
node_version: None,
origin: raw.to_string(),
semver_number: None,
semver_range: None,
})
})
}
}