@@ -3,13 +3,13 @@ use log::debug;
33use regex:: Regex ;
44use std:: fmt;
55use std:: str:: FromStr ;
6- use strum_macros:: { EnumString , EnumVariantNames } ;
6+ use strum_macros:: { Display , EnumString , EnumVariantNames } ;
77use url:: Url ;
88
99/// Supported uri schemes for parsing
10- #[ derive( Debug , PartialEq , EnumString , EnumVariantNames , Clone ) ]
10+ #[ derive( Debug , PartialEq , EnumString , EnumVariantNames , Clone , Display , Copy ) ]
1111#[ strum( serialize_all = "kebab_case" ) ]
12- pub enum Protocol {
12+ pub enum Scheme {
1313 /// Represents No url scheme
1414 Unspecified ,
1515 /// Represents `file://` url scheme
@@ -27,14 +27,12 @@ pub enum Protocol {
2727 GitSsh ,
2828}
2929
30- /// GitUrl represents an input url `href` that is a url used by git
30+ /// GitUrl represents an input url that is a url used by git
3131/// Internally during parsing the url is sanitized and uses the `url` crate to perform
3232/// the majority of the parsing effort, and with some extra handling to expose
3333/// metadata used my many git hosting services
3434#[ derive( Debug , PartialEq , Clone ) ]
3535pub struct GitUrl {
36- /// The input url
37- pub href : String ,
3836 /// The fully qualified domain name (FQDN) or IP of the repo
3937 pub host : Option < String > ,
4038 /// The name of the repo
@@ -45,8 +43,8 @@ pub struct GitUrl {
4543 pub organization : Option < String > ,
4644 /// The full name of the repo, formatted as "owner/name"
4745 pub fullname : String ,
48- /// The git url protocol
49- pub protocol : Protocol ,
46+ /// The git url scheme
47+ pub scheme : Scheme ,
5048 /// The authentication user
5149 pub user : Option < String > ,
5250 /// The oauth token (could appear in the https urls)
@@ -57,40 +55,89 @@ pub struct GitUrl {
5755 pub path : String ,
5856 /// Indicate if url uses the .git suffix
5957 pub git_suffix : bool ,
58+ /// Indicate if url explicitly uses its scheme
59+ pub scheme_prefix : bool ,
6060}
6161
62+ /// Build the printable GitUrl from its components
6263impl fmt:: Display for GitUrl {
6364 fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
64- write ! ( f, "{}" , self . href)
65+ let scheme_prefix = match self . scheme_prefix {
66+ true => format ! ( "{}://" , self . scheme) ,
67+ false => format ! ( "" ) ,
68+ } ;
69+
70+ let auth_info = match self . scheme {
71+ Scheme :: Ssh | Scheme :: Git | Scheme :: GitSsh => {
72+ if let Some ( user) = & self . user {
73+ format ! ( "{}@" , user)
74+ } else {
75+ format ! ( "" )
76+ }
77+ }
78+ Scheme :: Http | Scheme :: Https => match ( & self . user , & self . token ) {
79+ ( Some ( user) , Some ( token) ) => format ! ( "{}:{}@" , user, token) ,
80+ ( Some ( user) , None ) => format ! ( "{}@" , user) ,
81+ ( None , Some ( token) ) => format ! ( "{}@" , token) ,
82+ ( None , None ) => format ! ( "" ) ,
83+ } ,
84+ _ => format ! ( "" ) ,
85+ } ;
86+
87+ let host = match & self . host {
88+ Some ( host) => format ! ( "{}" , host) ,
89+ None => format ! ( "" ) ,
90+ } ;
91+
92+ let port = match & self . port {
93+ Some ( p) => format ! ( ":{}" , p) ,
94+ None => format ! ( "" ) ,
95+ } ;
96+
97+ let path = match & self . scheme {
98+ Scheme :: Ssh => {
99+ if self . port . is_some ( ) {
100+ format ! ( "/{}" , & self . path)
101+ } else {
102+ format ! ( ":{}" , & self . path)
103+ }
104+ }
105+ _ => format ! ( "{}" , & self . path) ,
106+ } ;
107+
108+ let git_url_str = format ! ( "{}{}{}{}{}" , scheme_prefix, auth_info, host, port, path) ;
109+
110+ write ! ( f, "{}" , git_url_str)
65111 }
66112}
67113
68114impl Default for GitUrl {
69115 fn default ( ) -> Self {
70116 GitUrl {
71- href : "" . to_string ( ) ,
72117 host : None ,
73118 name : "" . to_string ( ) ,
74119 owner : None ,
75120 organization : None ,
76121 fullname : "" . to_string ( ) ,
77- protocol : Protocol :: Unspecified ,
122+ scheme : Scheme :: Unspecified ,
78123 user : None ,
79124 token : None ,
80125 port : None ,
81126 path : "" . to_string ( ) ,
82- git_suffix : true ,
127+ git_suffix : false ,
128+ scheme_prefix : false ,
83129 }
84130 }
85131}
86132
87133impl GitUrl {
88- /// Returns a new `GitUrl` with provided `url` set as `href`
89- pub fn new ( url : & str ) -> GitUrl {
90- GitUrl {
91- href : url. to_string ( ) ,
92- ..Default :: default ( )
93- }
134+ /// Returns `GitUrl` after removing `user` and `token` values
135+ /// Intended use-case is for non-destructive printing GitUrl excluding any embedded auth info
136+ pub fn trim_auth ( & self ) -> GitUrl {
137+ let mut new_giturl = self . clone ( ) ;
138+ new_giturl. user = None ;
139+ new_giturl. token = None ;
140+ new_giturl
94141 }
95142
96143 /// Returns a `Result<GitUrl>` after normalizing and parsing `url` for metadata
@@ -99,12 +146,12 @@ impl GitUrl {
99146 let normalized = normalize_url ( url) . expect ( "Url normalization failed" ) ;
100147
101148 // Some pre-processing for paths
102- let protocol = Protocol :: from_str ( normalized. scheme ( ) )
103- . expect ( & format ! ( "Protocol unsupported: {:?}" , normalized. scheme( ) ) ) ;
149+ let scheme = Scheme :: from_str ( normalized. scheme ( ) )
150+ . expect ( & format ! ( "Scheme unsupported: {:?}" , normalized. scheme( ) ) ) ;
104151
105152 // Normalized ssh urls can always have their first '/' removed
106- let urlpath = match & protocol {
107- Protocol :: Ssh => {
153+ let urlpath = match & scheme {
154+ Scheme :: Ssh => {
108155 // At the moment, we're relying on url::Url's parse() behavior to not duplicate
109156 // the leading '/' when we normalize
110157 normalized. path ( ) [ 1 ..] . to_string ( )
@@ -130,9 +177,9 @@ impl GitUrl {
130177
131178 let name = splitpath[ 0 ] . trim_end_matches ( ".git" ) . to_string ( ) ;
132179
133- let ( owner, organization, fullname) = match & protocol {
180+ let ( owner, organization, fullname) = match & scheme {
134181 // We're not going to assume anything about metadata from a filepath
135- Protocol :: File => ( None :: < String > , None :: < String > , name. clone ( ) ) ,
182+ Scheme :: File => ( None :: < String > , None :: < String > , name. clone ( ) ) ,
136183 _ => {
137184 let mut fullname: Vec < & str > = Vec :: new ( ) ;
138185
@@ -145,11 +192,11 @@ impl GitUrl {
145192 true => {
146193 debug ! ( "Found a git provider with an org" ) ;
147194
148- // The path differs between git:// and https:// protocols
195+ // The path differs between git:// and https:// schemes
149196
150- match & protocol {
197+ match & scheme {
151198 // Example: "[email protected] :v3/CompanyName/ProjectName/RepoName", 152- Protocol :: Ssh => {
199+ Scheme :: Ssh => {
153200 // Organization
154201 fullname. push ( splitpath[ 2 ] . clone ( ) ) ;
155202 // Project/Owner name
@@ -164,7 +211,7 @@ impl GitUrl {
164211 )
165212 }
166213 // Example: "https://[email protected] /CompanyName/ProjectName/_git/RepoName", 167- Protocol :: Https => {
214+ Scheme :: Https => {
168215 // Organization
169216 fullname. push ( splitpath[ 3 ] . clone ( ) ) ;
170217 // Project/Owner name
@@ -178,7 +225,7 @@ impl GitUrl {
178225 fullname. join ( "/" ) . to_string ( ) ,
179226 )
180227 }
181- _ => panic ! ( "Protocol not supported for host" ) ,
228+ _ => panic ! ( "Scheme not supported for host" ) ,
182229 }
183230 }
184231 false => {
@@ -198,7 +245,6 @@ impl GitUrl {
198245 } ;
199246
200247 Ok ( GitUrl {
201- href : url. to_string ( ) ,
202248 host : match normalized. host_str ( ) {
203249 Some ( h) => Some ( h. to_string ( ) ) ,
204250 None => None ,
@@ -207,7 +253,7 @@ impl GitUrl {
207253 owner : owner,
208254 organization : organization,
209255 fullname : fullname,
210- protocol : Protocol :: from_str ( normalized. scheme ( ) ) . expect ( "Protocol unsupported" ) ,
256+ scheme : Scheme :: from_str ( normalized. scheme ( ) ) . expect ( "Scheme unsupported" ) ,
211257 user : match normalized. username ( ) . to_string ( ) . len ( ) {
212258 0 => None ,
213259 _ => Some ( normalized. username ( ) . to_string ( ) ) ,
@@ -219,6 +265,7 @@ impl GitUrl {
219265 port : normalized. port ( ) ,
220266 path : urlpath,
221267 git_suffix : * git_suffix_check,
268+ scheme_prefix : url. contains ( "://" ) ,
222269 ..Default :: default ( )
223270 } )
224271 }
@@ -269,35 +316,38 @@ fn normalize_file_path(filepath: &str) -> Result<Url> {
269316pub fn normalize_url ( url : & str ) -> Result < Url > {
270317 debug ! ( "Processing: {:?}" , & url) ;
271318
272- let url_parse = Url :: parse ( & url) ;
319+ // We're going to remove any trailing slash before running through Url::parse
320+ let trim_url = url. trim_end_matches ( "/" ) ;
321+
322+ let url_parse = Url :: parse ( & trim_url) ;
273323
274324 Ok ( match url_parse {
275325 Ok ( u) => {
276- match Protocol :: from_str ( u. scheme ( ) ) {
326+ match Scheme :: from_str ( u. scheme ( ) ) {
277327 Ok ( _p) => u,
278328 Err ( _e) => {
279329 // Catch case when an ssh url is given w/o a user
280330 debug ! ( "Scheme parse fail. Assuming a userless ssh url" ) ;
281- normalize_ssh_url ( url ) ?
331+ normalize_ssh_url ( trim_url ) ?
282332 }
283333 }
284334 }
285335 Err ( _e) => {
286336 // e will most likely be url::ParseError::RelativeUrlWithoutBase
287- // If we're here, we're only looking for Protocol ::Ssh or Protocol ::File
337+ // If we're here, we're only looking for Scheme ::Ssh or Scheme ::File
288338
289- // Assuming we have found Protocol ::Ssh if we can find an "@" before ":"
290- // Otherwise we have Protocol ::File
339+ // Assuming we have found Scheme ::Ssh if we can find an "@" before ":"
340+ // Otherwise we have Scheme ::File
291341 let re = Regex :: new ( r"^\S+(@)\S+(:).*$" ) ?;
292342
293- match re. is_match ( & url ) {
343+ match re. is_match ( & trim_url ) {
294344 true => {
295- debug ! ( "Protocol ::SSH match for normalization" ) ;
296- normalize_ssh_url ( url ) ?
345+ debug ! ( "Scheme ::SSH match for normalization" ) ;
346+ normalize_ssh_url ( trim_url ) ?
297347 }
298348 false => {
299- debug ! ( "Protocol ::File match for normalization" ) ;
300- normalize_file_path ( & format ! ( "{}" , url ) ) ?
349+ debug ! ( "Scheme ::File match for normalization" ) ;
350+ normalize_file_path ( & format ! ( "{}" , trim_url ) ) ?
301351 }
302352 }
303353 }
0 commit comments