upstream_ontologist/
vcs_command.rs1use crate::vcs;
2use crate::GuesserSettings;
3use log::warn;
4
5fn parse_command_bytes(command: &[u8]) -> Option<Vec<String>> {
6 if command.ends_with(b"\\") {
7 warn!(
8 "Ignoring command with line break: {}",
9 String::from_utf8_lossy(command)
10 );
11 return None;
12 }
13 let command_str = match String::from_utf8(command.to_vec()) {
14 Ok(s) => s,
15 Err(_) => {
16 warn!(
17 "Ignoring command with non-UTF-8: {}",
18 String::from_utf8_lossy(command)
19 );
20 return None;
21 }
22 };
23 let args: Vec<_> = shlex::split(command_str.as_str())?
24 .into_iter()
25 .filter(|arg| !arg.trim().is_empty())
26 .collect();
27
28 if args.is_empty() {
29 None
30 } else {
31 Some(args)
32 }
33}
34
35pub fn url_from_vcs_command(command: &[u8]) -> Option<String> {
39 if let Some(url) = url_from_git_clone_command(command) {
40 return Some(url);
41 }
42 if let Some(url) = url_from_fossil_clone_command(command) {
43 return Some(url);
44 }
45 #[cfg(feature = "breezy")]
46 if let Some(url) = url_from_cvs_co_command(command) {
47 return Some(url);
48 }
49 if let Some(url) = url_from_svn_co_command(command) {
50 return Some(url);
51 }
52 None
53}
54
55pub fn url_from_git_clone_command(command: &[u8]) -> Option<String> {
58 let mut args = parse_command_bytes(command)?;
59 if args.remove(0) != "git" || args.remove(0) != "clone" {
60 return None;
61 }
62 let mut i = 0;
63 while i < args.len() {
64 if !args[i].starts_with('-') {
65 i += 1;
66 continue;
67 }
68 if args[i].contains('=') {
69 args.remove(i);
70 continue;
71 }
72 if args[i] == "-b" || args[i] == "--depth" || args[i] == "--branch" {
74 args.remove(i);
75 args.remove(i);
76 continue;
77 }
78 args.remove(i);
79 }
80 let url = args
81 .get(2)
82 .cloned()
83 .unwrap_or_else(|| args.first().cloned().unwrap_or_default());
84 if vcs::plausible_url(&url) {
85 Some(url)
86 } else {
87 None
88 }
89}
90
91#[test]
92fn test_url_from_git_clone_command() {
93 assert_eq!(
94 url_from_git_clone_command(b"git clone https://github.com/foo/bar foo"),
95 Some("https://github.com/foo/bar".to_string())
96 );
97
98 assert_eq!(
99 Some("https://github.com/jelmer/blah".to_string()),
100 url_from_git_clone_command(b"git clone https://github.com/jelmer/blah"),
101 );
102
103 assert_eq!(
104 Some("https://github.com/jelmer/blah".to_string()),
105 url_from_git_clone_command(b"git clone https://github.com/jelmer/blah target"),
106 );
107
108 assert_eq!(
109 Some("https://github.com/jelmer/blah".to_string()),
110 url_from_git_clone_command(b"git clone -b foo https://github.com/jelmer/blah target"),
111 );
112
113 assert_eq!(None, url_from_git_clone_command(b"git ls-tree"));
114}
115
116pub fn url_from_fossil_clone_command(command: &[u8]) -> Option<String> {
119 let mut args = parse_command_bytes(command)?;
120 if args.remove(0) != "fossil" || args.remove(0) != "clone" {
121 return None;
122 }
123 let mut i = 0;
124 while i < args.len() {
125 if !args[i].starts_with('-') {
126 i += 1;
127 continue;
128 }
129 if args[i].contains('=') {
130 args.remove(i);
131 continue;
132 }
133 args.remove(i);
134 }
135 let url = args
136 .get(2)
137 .cloned()
138 .unwrap_or_else(|| args.first().cloned().unwrap_or_default());
139 if vcs::plausible_url(&url) {
140 Some(url)
141 } else {
142 None
143 }
144}
145
146#[test]
147fn test_url_from_fossil_clone_command() {
148 assert_eq!(
149 Some("https://example.com/repo/blah".to_string()),
150 url_from_fossil_clone_command(b"fossil clone https://example.com/repo/blah blah.fossil"),
151 );
152}
153
154#[cfg(feature = "breezy")]
157pub fn url_from_cvs_co_command(command: &[u8]) -> Option<String> {
158 let mut args = parse_command_bytes(command)?;
159 let i = 0;
160 let mut cvsroot = None;
161 let mut module = None;
162 let mut command_seen = false;
163 if args.remove(0) != "cvs" {
164 return None;
165 }
166 while i < args.len() {
167 if args[i] == "-d" {
168 args.remove(i);
169 cvsroot = Some(args.remove(i));
170 continue;
171 }
172 if args[i].starts_with("-d") {
173 cvsroot = Some(args.remove(i)[2..].to_string());
174 continue;
175 }
176 if command_seen && !args[i].starts_with('-') {
177 module = Some(args[i].clone());
178 } else if args[i] == "co" || args[i] == "checkout" {
179 command_seen = true;
180 }
181 args.remove(i);
182 }
183 if let Some(cvsroot) = cvsroot {
184 if breezyshim::try_init().is_err() {
185 return None;
186 }
187 let url = breezyshim::location::cvs_to_url(&cvsroot);
188 if let Some(module) = module {
189 return Some(url.join(module.as_str()).unwrap().to_string());
190 }
191 return Some(url.to_string());
192 }
193 None
194}
195
196pub fn url_from_svn_co_command(command: &[u8]) -> Option<String> {
199 let args = parse_command_bytes(command)?;
200 if args[0] != "svn" || args[1] != "co" {
201 return None;
202 }
203 let url_schemes = ["svn+ssh", "http", "https", "svn"];
204 args.into_iter().find(|arg| {
205 url_schemes
206 .iter()
207 .any(|scheme| arg.starts_with(&format!("{}://", scheme)))
208 })
209}
210
211pub fn guess_from_get_orig_source(
214 path: &std::path::Path,
215 _settings: &GuesserSettings,
216) -> Result<Vec<crate::UpstreamDatumWithMetadata>, crate::ProviderError> {
217 let text = std::fs::read(path)?;
218 let mut result = Vec::new();
219
220 for line in text.split(|b| *b == b'\n') {
221 if let Some(url) = url_from_vcs_command(line) {
222 let certainty = if url.contains('$') {
223 crate::Certainty::Possible
224 } else {
225 crate::Certainty::Likely
226 };
227
228 result.push(crate::UpstreamDatumWithMetadata {
229 datum: crate::UpstreamDatum::Repository(url),
230 certainty: Some(certainty),
231 origin: Some(path.into()),
232 });
233 }
234 }
235
236 Ok(result)
237}