Skip to content

Commit 558043c

Browse files
committed
Merge branch 'issue-28' into master (fix #28)
2 parents f18715c + 8867e83 commit 558043c

3 files changed

Lines changed: 109 additions & 17 deletions

File tree

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ pub enum ErrorKind {
167167
dir: String,
168168
},
169169
BranchNameEmpty,
170+
InvalidUser {
171+
name: String,
172+
},
170173
}
171174

172175
impl fmt::Display for ErrorKind {
@@ -226,6 +229,7 @@ impl fmt::Display for ErrorKind {
226229
UserBrowseCommandFailed{cmd, url, msg} => write!(f, "Command '{}' failed to open URL {}. Please check $GIT_BRWS_BROWSE_COMMAND. stderr: {}", cmd, url, msg),
227230
SpecifiedDirNotExist{dir} => write!(f, "Specified directory '{}' with -d option does not exist", dir),
228231
BranchNameEmpty => write!(f, "Branch name cannot be empty"),
232+
InvalidUser{name} => write!(f, "Invalid user or organization name '{}'", name),
229233
}
230234
}
231235
}

src/service.rs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
6679
fn build_github_like_url(
6780
host: &str,
6881
user: &str,
@@ -224,6 +237,7 @@ fn build_gitlab_url(
224237
}
225238

226239
fn 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'
395412
pub 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,

src/test/service.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,3 +760,77 @@ fn tab_page_for_bitbucket() {
760760
assert_eq!(actual, expected, "{}", url);
761761
}
762762
}
763+
764+
#[test]
765+
fn subgroups_for_gitlab() {
766+
for repo in &[
767+
"https://gitlab.com/group/sub1/repo.git",
768+
"https://gitlab.com/group/sub1/repo",
769+
"https://gitlab.com/group/sub1/sub2/repo.git",
770+
"https://gitlab.com/group/sub1/sub2/repo",
771+
"https://gitlab.com/group/sub1/sub2/sub3/sub4/sub5/sub6/repo.git",
772+
"https://gitlab.somewhere.com/group/sub1/sub2/repo.git",
773+
"https://my-gitlab.example.com/group/sub1/sub2/repo.git",
774+
] {
775+
let mut env = empty_env();
776+
env.gitlab_url_host = Some("my-gitlab.example.com".to_string());
777+
let c = config(repo, None, Some(env));
778+
let expected = repo.trim_end_matches(".git");
779+
assert_eq!(build_page_url(&OPEN, &c).unwrap(), expected);
780+
}
781+
}
782+
783+
#[test]
784+
fn no_user_name_found_in_path() {
785+
for repo in &["https://gitlab.com/repo.git", "https://gitlab.com/repo"] {
786+
let c = config(repo, None, None);
787+
let err = build_page_url(&OPEN, &c).unwrap_err();
788+
let kind = err.kind();
789+
assert!(
790+
matches!(kind, ErrorKind::NoUserInPath { .. }),
791+
"{:?} {:?}",
792+
kind,
793+
repo
794+
);
795+
}
796+
}
797+
798+
#[test]
799+
fn no_repo_name_found_in_path() {
800+
for repo in &[
801+
"https://gitlab.com/user/",
802+
"https://gitlab.com/user/.git",
803+
"https://gitlab.com/",
804+
"https://gitlab.com/.git",
805+
"https://gitlab.com//",
806+
] {
807+
let c = config(repo, None, None);
808+
let err = build_page_url(&OPEN, &c).unwrap_err();
809+
let kind = err.kind();
810+
assert!(
811+
matches!(kind, ErrorKind::NoRepoInPath { .. }),
812+
"{:?} {:?}",
813+
kind,
814+
repo
815+
);
816+
}
817+
}
818+
819+
#[test]
820+
fn slash_in_github_user_name() {
821+
for repo in &[
822+
"https://github.com/foo/bar/repo.git",
823+
"https://github.somewhere.com/foo/bar/repo.git",
824+
"https://bitbucket.org/foo/bar/repo.git",
825+
] {
826+
let c = config(repo, None, None);
827+
let err = build_page_url(&OPEN, &c).unwrap_err();
828+
let kind = err.kind();
829+
assert!(
830+
matches!(kind, ErrorKind::InvalidUser { .. }),
831+
"{:?} {:?}",
832+
kind,
833+
repo
834+
);
835+
}
836+
}

0 commit comments

Comments
 (0)