@@ -63,6 +63,19 @@ fn fetch_homepage(
6363 async_runtime:: blocking ( client. repo_homepage ( user, repo) )
6464}
6565
66+ fn check_slash_in_user ( user : & str ) -> Result < ( ) > {
67+ if user. contains ( '/' ) {
68+ // Enter here because slug_from_path() allows '/' in user name to support GitLab's
69+ // subgroups feature (#28). But GitHub, GitHubEnterprise and Bitbucket does not allow a user
70+ // name to include '/'.
71+ Err ( Error :: new ( ErrorKind :: InvalidUser {
72+ name : user. to_string ( ) ,
73+ } ) )
74+ } else {
75+ Ok ( ( ) )
76+ }
77+ }
78+
6679fn build_github_like_url (
6780 host : & str ,
6881 user : & str ,
@@ -224,6 +237,7 @@ fn build_gitlab_url(
224237}
225238
226239fn build_bitbucket_url ( user : & str , repo : & str , cfg : & Config , page : & Page ) -> Result < String > {
240+ check_slash_in_user ( user) ?;
227241 match page {
228242 Page :: Open { website : true , .. } => {
229243 // Build bitbucket cloud URL:
@@ -391,27 +405,25 @@ pub fn azure_devops_slug_from_path<'a>(path: &'a str) -> Result<(&'a str, &'a st
391405 Ok ( ( team, repo) )
392406}
393407
394- // Note: Parse '/user/repo.git' or '/user/repo' or 'user/repo' into 'user' and 'repo'
408+ // Note: Parse '/user/repo.git' or '/user/repo' or 'user/repo' into 'user' and 'repo'.
409+ // Note: GitLab has subgroups feature. The last '/' needs to be searched to get correct repository
410+ // name (#28): https://docs.gitlab.com/ee/user/group/subgroups/
411+ // e.g. 'sub1/sub2/sub3/repo' into 'sub1/sub2/sub3' and 'repo'
395412pub fn slug_from_path < ' a > ( path : & ' a str ) -> Result < ( & ' a str , & ' a str ) > {
396- let mut split = path. split ( '/' ) . skip_while ( |s| s. is_empty ( ) ) ;
397- let user = split. next ( ) . ok_or_else ( || {
398- Error :: new ( ErrorKind :: NoUserInPath {
413+ // Byte offset at the last '/' in path
414+ match path. rfind ( '/' ) . map ( |offset| {
415+ let user = path[ 0 ..offset] . trim_start_matches ( '/' ) ;
416+ let repo = path[ offset + 1 ..] . trim_end_matches ( ".git" ) ;
417+ ( user, repo)
418+ } ) {
419+ None | Some ( ( _, "" ) ) => Err ( Error :: new ( ErrorKind :: NoRepoInPath {
399420 path : path. to_string ( ) ,
400- } )
401- } ) ?;
402-
403- let mut repo = split. next ( ) . ok_or_else ( || {
404- Error :: new ( ErrorKind :: NoRepoInPath {
421+ } ) ) ,
422+ Some ( ( "" , _) ) => Err ( Error :: new ( ErrorKind :: NoUserInPath {
405423 path : path. to_string ( ) ,
406- } )
407- } ) ?;
408-
409- if repo. ends_with ( ".git" ) {
410- // Slice '.git' from 'repo.git'
411- repo = & repo[ 0 ..repo. len ( ) - 4 ] ;
424+ } ) ) ,
425+ Some ( found) => Ok ( found) ,
412426 }
413-
414- Ok ( ( user, repo) )
415427}
416428
417429// Known URL formats
@@ -443,6 +455,7 @@ pub fn build_page_url(page: &Page, cfg: &Config) -> Result<String> {
443455
444456 match host {
445457 "github.com" => {
458+ check_slash_in_user ( user) ?;
446459 build_github_like_url ( host, user, repo_name, Some ( "api.github.com" ) , cfg, page)
447460 }
448461 "gitlab.com" => build_gitlab_url ( host, user, repo_name, cfg, page) ,
@@ -478,6 +491,7 @@ pub fn build_page_url(page: &Page, cfg: &Config) -> Result<String> {
478491 if is_gitlab {
479492 build_gitlab_url ( & host, user, repo_name, cfg, page)
480493 } else {
494+ check_slash_in_user ( user) ?;
481495 build_github_like_url (
482496 & host,
483497 user,
0 commit comments