@@ -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
82125impl < ' 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