[F] Fix gitlab project listing
This commit is contained in:
+77
-2
@@ -371,7 +371,19 @@ impl<'a> ProviderClient<'a> {
|
||||
self.site.api_base(),
|
||||
endpoint.namespace
|
||||
);
|
||||
self.paged_remote_repos::<GitlabProject>(&url)
|
||||
let mut projects = self.paged_get::<GitlabProject>(&url)?;
|
||||
let owned_url = format!(
|
||||
"{}/projects?owned=true&simple=true&per_page=100",
|
||||
self.site.api_base()
|
||||
);
|
||||
for project in self.paged_get::<GitlabProject>(&owned_url)? {
|
||||
if project.is_in_namespace(&endpoint.namespace)
|
||||
&& !projects.iter().any(|existing| existing.same_path(&project))
|
||||
{
|
||||
projects.push(project);
|
||||
}
|
||||
}
|
||||
Ok(projects.into_iter().map(Into::into).collect())
|
||||
}
|
||||
NamespaceKind::Org | NamespaceKind::Group => {
|
||||
let encoded = urlencoding(&endpoint.namespace);
|
||||
@@ -411,8 +423,20 @@ impl<'a> ProviderClient<'a> {
|
||||
}
|
||||
|
||||
let url = format!("{}/projects", self.site.api_base());
|
||||
self.post_json::<GitlabProject>(&url, &serde_json::Value::Object(body))
|
||||
match self.post_json::<GitlabProject>(&url, &serde_json::Value::Object(body)) {
|
||||
Ok(project) => Ok(project.into()),
|
||||
Err(error) if is_already_taken_error(&error) => {
|
||||
let existing_url = self.gitlab_project_url(endpoint, name);
|
||||
self.get_json::<GitlabProject>(&existing_url)
|
||||
.map(Into::into)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"GitLab reported that {name} already exists, but failed to fetch {existing_url}: {error:#}"
|
||||
)
|
||||
})
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn gitlab_delete_repo(&self, endpoint: &EndpointConfig, repo_name: &str) -> Result<()> {
|
||||
@@ -982,6 +1006,16 @@ fn is_conflict_error(error: &anyhow::Error) -> bool {
|
||||
error.to_string().contains("409 Conflict")
|
||||
}
|
||||
|
||||
fn is_already_taken_error(error: &anyhow::Error) -> bool {
|
||||
let text = error
|
||||
.chain()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
.to_ascii_lowercase();
|
||||
text.contains("has already been taken")
|
||||
}
|
||||
|
||||
fn webhook_urls_match(left: &str, right: &str) -> bool {
|
||||
if left == right {
|
||||
return true;
|
||||
@@ -1066,11 +1100,52 @@ impl From<GithubRepo> for RemoteRepo {
|
||||
struct GitlabProject {
|
||||
name: String,
|
||||
path: Option<String>,
|
||||
path_with_namespace: Option<String>,
|
||||
namespace: Option<GitlabNamespace>,
|
||||
http_url_to_repo: String,
|
||||
visibility: String,
|
||||
description: Option<String>,
|
||||
}
|
||||
|
||||
impl GitlabProject {
|
||||
fn project_path(&self) -> &str {
|
||||
self.path.as_deref().unwrap_or(&self.name)
|
||||
}
|
||||
|
||||
fn is_in_namespace(&self, namespace: &str) -> bool {
|
||||
self.namespace
|
||||
.as_ref()
|
||||
.and_then(GitlabNamespace::full_path)
|
||||
.is_some_and(|path| path.eq_ignore_ascii_case(namespace))
|
||||
|| self
|
||||
.path_with_namespace
|
||||
.as_deref()
|
||||
.and_then(|path| path.rsplit_once('/').map(|(namespace, _)| namespace))
|
||||
.is_some_and(|path| path.eq_ignore_ascii_case(namespace))
|
||||
}
|
||||
|
||||
fn same_path(&self, other: &Self) -> bool {
|
||||
match (&self.path_with_namespace, &other.path_with_namespace) {
|
||||
(Some(left), Some(right)) => left.eq_ignore_ascii_case(right),
|
||||
_ => self
|
||||
.project_path()
|
||||
.eq_ignore_ascii_case(other.project_path()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GitlabNamespace {
|
||||
path: Option<String>,
|
||||
full_path: Option<String>,
|
||||
}
|
||||
|
||||
impl GitlabNamespace {
|
||||
fn full_path(&self) -> Option<&str> {
|
||||
self.full_path.as_deref().or(self.path.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GitlabProject> for RemoteRepo {
|
||||
fn from(value: GitlabProject) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -184,6 +184,104 @@ fn detect_namespace_kind_uses_authenticated_gitea_api() {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_gitlab_user_repos_merges_authenticated_owned_projects() {
|
||||
let owned_projects = r#"[
|
||||
{"name":"repo","path":"repo","path_with_namespace":"alice/repo","http_url_to_repo":"https://gitlab.example.test/alice/repo.git","visibility":"private","description":null,"namespace":{"path":"alice","full_path":"alice"}},
|
||||
{"name":"other","path":"other","path_with_namespace":"bob/other","http_url_to_repo":"https://gitlab.example.test/bob/other.git","visibility":"public","description":null,"namespace":{"path":"bob","full_path":"bob"}}
|
||||
]"#;
|
||||
let (api_url, handle) = request_server(
|
||||
vec![("200 OK", "[]"), ("200 OK", owned_projects)],
|
||||
|index, request| match index {
|
||||
0 => assert!(
|
||||
request
|
||||
.starts_with("GET /users/alice/projects?simple=true&per_page=100&owned=true "),
|
||||
"request was {request}"
|
||||
),
|
||||
1 => assert!(
|
||||
request.starts_with("GET /projects?owned=true&simple=true&per_page=100 "),
|
||||
"request was {request}"
|
||||
),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
);
|
||||
let site = SiteConfig {
|
||||
api_url: Some(api_url),
|
||||
..site(ProviderKind::Gitlab, None)
|
||||
};
|
||||
|
||||
let repos = ProviderClient::new(&site)
|
||||
.unwrap()
|
||||
.list_repos(&EndpointConfig {
|
||||
site: "gitlab".to_string(),
|
||||
kind: NamespaceKind::User,
|
||||
namespace: "alice".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(repos.len(), 1);
|
||||
assert_eq!(repos[0].name, "repo");
|
||||
assert!(repos[0].private);
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_gitlab_repo_returns_existing_repo_when_path_is_taken() {
|
||||
let existing = r#"{"name":"repo","path":"repo","path_with_namespace":"alice/repo","http_url_to_repo":"https://gitlab.example.test/alice/repo.git","visibility":"public","description":"existing","namespace":{"path":"alice","full_path":"alice"}}"#;
|
||||
let (api_url, handle) = request_server(
|
||||
vec![
|
||||
(
|
||||
"400 Bad Request",
|
||||
r#"{"message":{"name":["has already been taken"],"path":["has already been taken"]}}"#,
|
||||
),
|
||||
("200 OK", existing),
|
||||
],
|
||||
|index, request| match index {
|
||||
0 => {
|
||||
assert!(
|
||||
request.starts_with("POST /projects "),
|
||||
"request was {request}"
|
||||
);
|
||||
assert!(
|
||||
request.contains(r#""name":"repo""#),
|
||||
"request was {request}"
|
||||
);
|
||||
assert!(
|
||||
request.contains(r#""path":"repo""#),
|
||||
"request was {request}"
|
||||
);
|
||||
}
|
||||
1 => assert!(
|
||||
request.starts_with("GET /projects/alice%2Frepo "),
|
||||
"request was {request}"
|
||||
),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
);
|
||||
let site = SiteConfig {
|
||||
api_url: Some(api_url),
|
||||
..site(ProviderKind::Gitlab, None)
|
||||
};
|
||||
|
||||
let repo = ProviderClient::new(&site)
|
||||
.unwrap()
|
||||
.create_repo(
|
||||
&EndpointConfig {
|
||||
site: "gitlab".to_string(),
|
||||
kind: NamespaceKind::User,
|
||||
namespace: "alice".to_string(),
|
||||
},
|
||||
"repo",
|
||||
&Visibility::Public,
|
||||
Some("description"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(repo.name, "repo");
|
||||
assert_eq!(repo.clone_url, "https://gitlab.example.test/alice/repo.git");
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_webhook_posts_github_hook_when_missing() {
|
||||
let (api_url, handle) = request_server(
|
||||
|
||||
Reference in New Issue
Block a user