diff --git a/src/git/auth.rs b/src/git/auth.rs index b39e6836..220800ac 100644 --- a/src/git/auth.rs +++ b/src/git/auth.rs @@ -13,16 +13,34 @@ pub(super) fn extract_url_password(url: &str) -> Option<(String, String)> { Some((user.to_string(), password.to_string())) } +pub(super) fn host_of(url: &str) -> Option<&str> { + let rest = match url.split_once("://") { + Some((_, rest)) => rest, + None => url, + }; + let authority = rest.split(['/', '\\', '?', '#']).next()?; + let host = match authority.rsplit_once('@') { + Some((_, host)) => host, + None => authority, + }; + let host = host.split(':').next()?; + (!host.is_empty()).then_some(host) +} + +fn is_gitlab(url: &str) -> bool { + host_of(url).is_some_and(|host| host.contains("gitlab")) +} + pub(super) fn token_for_url(url: &str) -> Option<(String, String)> { if let Ok(token) = std::env::var("FERRFLOW_TOKEN") { - let user = if url.contains("gitlab") { + let user = if is_gitlab(url) { "oauth2" } else { "x-access-token" }; return Some((user.to_string(), token)); } - if url.contains("gitlab") { + if is_gitlab(url) { if let Ok(token) = std::env::var("GITLAB_TOKEN") { return Some(("oauth2".to_string(), token)); } diff --git a/src/git/tests.rs b/src/git/tests.rs index 5093a3c9..29555e28 100644 --- a/src/git/tests.rs +++ b/src/git/tests.rs @@ -1018,6 +1018,89 @@ fn command_env(cmd: &std::process::Command, key: &str) -> Option { }) } +#[test] +fn a_repository_name_does_not_pick_the_forge() { + let _guard = EnvGuard::new() + .unset("FERRFLOW_TOKEN") + .unset("GITLAB_TOKEN") + .set("GITHUB_TOKEN", "gh_secret"); + + for url in [ + "https://github.com/acme/gitlab-migration-tool.git", + "https://github.com/gitlab-ce-mirrors/whatever.git", + "git@github.com:acme/gitlab-runner-config.git", + "https://x-access-token:tok@github.com/acme/gitlab-migration-tool.git", + ] { + assert_eq!( + token_for_url(url), + Some(("x-access-token".to_string(), "gh_secret".to_string())), + "{url} should use the GitHub token" + ); + } +} + +#[test] +fn a_gitlab_host_still_picks_gitlab() { + let _guard = EnvGuard::new() + .unset("FERRFLOW_TOKEN") + .unset("GITHUB_TOKEN") + .set("GITLAB_TOKEN", "gl_secret"); + + for url in [ + "https://gitlab.com/acme/repo.git", + "https://gitlab.acme.com/team/repo.git", + "https://gitlab-ci-token:tok@gitlab.com/acme/repo.git", + "https://oauth2:tok@gitlab.acme.com/team/repo.git", + "git@gitlab.com:acme/repo.git", + "ssh://git@gitlab.acme.com:2222/team/repo.git", + ] { + assert_eq!( + token_for_url(url), + Some(("oauth2".to_string(), "gl_secret".to_string())), + "{url} should use the GitLab token" + ); + } +} + +#[test] +fn host_of_handles_the_remote_shapes_git_accepts() { + use super::auth::host_of; + assert_eq!( + host_of("https://github.com/acme/repo.git"), + Some("github.com") + ); + assert_eq!( + host_of("https://user@github.com/acme/repo"), + Some("github.com") + ); + assert_eq!( + host_of("https://github.com:8443/acme/repo"), + Some("github.com") + ); + assert_eq!( + host_of("https://gitlab-ci-token:tok@gitlab.com/acme/repo.git"), + Some("gitlab.com") + ); + assert_eq!( + host_of("https://x-access-token:tok@github.com/acme/repo.git"), + Some("github.com") + ); + assert_eq!( + host_of("https://oauth2:tok@gitlab.acme.com:8443/team/repo.git"), + Some("gitlab.acme.com") + ); + assert_eq!( + host_of(r"https://github.com\@gitlab.com/acme/repo.git"), + Some("github.com") + ); + assert_eq!(host_of("git@gitlab.com:acme/repo.git"), Some("gitlab.com")); + assert_eq!( + host_of("ssh://git@gitlab.acme.com:2222/team/repo.git"), + Some("gitlab.acme.com") + ); + assert_eq!(host_of(""), None); +} + #[test] fn configure_git_command_passes_the_credential_through_the_environment() { let _guard = EnvGuard::new().set("FERRFLOW_TOKEN", "ff_secret");