Skip to content

Commit bc31d2a

Browse files
committed
Merge pull request #88 from brson/fix
Basic URL canonicalization. Fixes #84
2 parents 0bcf18d + 39157ba commit bc31d2a

File tree

1 file changed

+65
-2
lines changed

1 file changed

+65
-2
lines changed

src/cargo/sources/git/source.rs

+65-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ fn ident(location: &Location) -> String {
6767
let last = path.components().last().unwrap();
6868
str::from_utf8(last).unwrap().to_str()
6969
}
70-
Remote(ref url) => url.path.as_slice().split('/').last().unwrap().to_str()
70+
Remote(ref url) => {
71+
let path = canonicalize_url(url.path.as_slice());
72+
path.as_slice().split('/').last().unwrap().to_str()
73+
}
7174
};
7275

7376
let ident = if ident.as_slice() == "" {
@@ -76,7 +79,47 @@ fn ident(location: &Location) -> String {
7679
ident
7780
};
7881

79-
format!("{}-{}", ident, to_hex(hasher.hash(&location.to_str())))
82+
let location = canonicalize_url(location.to_str().as_slice());
83+
84+
format!("{}-{}", ident, to_hex(hasher.hash(&location.as_slice())))
85+
}
86+
87+
fn strip_trailing_slash<'a>(path: &'a str) -> &'a str {
88+
// Remove the trailing '/' so that 'split' doesn't give us
89+
// an empty string, making '../foo/' and '../foo' both
90+
// result in the name 'foo' (#84)
91+
if path.as_bytes().last() != Some(&('/' as u8)) {
92+
path.clone()
93+
} else {
94+
path.slice(0, path.len() - 1)
95+
}
96+
}
97+
98+
// Some hacks and heuristics for making equivalent URLs hash the same
99+
fn canonicalize_url(url: &str) -> String {
100+
let url = strip_trailing_slash(url);
101+
102+
// HACKHACK: For github URL's specifically just lowercase
103+
// everything. GitHub traits both the same, but they hash
104+
// differently, and we're gonna be hashing them. This wants a more
105+
// general solution, and also we're almost certainly not using the
106+
// same case conversion rules that GitHub does. (#84)
107+
108+
let lower_url = url.chars().map(|c|c.to_lowercase()).collect::<String>();
109+
let url = if lower_url.as_slice().contains("github.com") {
110+
lower_url
111+
} else {
112+
url.to_string()
113+
};
114+
115+
// Repos generally can be accessed with or w/o '.git'
116+
let url = if !url.as_slice().ends_with(".git") {
117+
url
118+
} else {
119+
url.as_slice().slice(0, url.len() - 4).to_string()
120+
};
121+
122+
return url;
80123
}
81124

82125
impl<'a, 'b> Show for GitSource<'a, 'b> {
@@ -150,6 +193,26 @@ mod test {
150193
assert_eq!(ident.as_slice(), "_empty-fc065c9b6b16fc00");
151194
}
152195

196+
#[test]
197+
fn test_canonicalize_idents_by_stripping_trailing_url_slash() {
198+
let ident1 = ident(&Remote(url("https://github.com/PistonDevelopers/piston/")));
199+
let ident2 = ident(&Remote(url("https://github.com/PistonDevelopers/piston")));
200+
assert_eq!(ident1, ident2);
201+
}
202+
203+
#[test]
204+
fn test_canonicalize_idents_by_lowercasing_github_urls() {
205+
let ident1 = ident(&Remote(url("https://github.com/PistonDevelopers/piston")));
206+
let ident2 = ident(&Remote(url("https://github.com/pistondevelopers/piston")));
207+
assert_eq!(ident1, ident2);
208+
}
209+
210+
#[test]
211+
fn test_canonicalize_idents_by_stripping_dot_git() {
212+
let ident1 = ident(&Remote(url("https://github.com/PistonDevelopers/piston")));
213+
let ident2 = ident(&Remote(url("https://github.com/PistonDevelopers/piston.git")));
214+
assert_eq!(ident1, ident2);
215+
}
153216

154217
fn url(s: &str) -> Url {
155218
url::from_str(s).unwrap()

0 commit comments

Comments
 (0)