[F] Fix gitlab project listing
This commit is contained in:
+78
-3
@@ -371,7 +371,19 @@ impl<'a> ProviderClient<'a> {
|
|||||||
self.site.api_base(),
|
self.site.api_base(),
|
||||||
endpoint.namespace
|
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 => {
|
NamespaceKind::Org | NamespaceKind::Group => {
|
||||||
let encoded = urlencoding(&endpoint.namespace);
|
let encoded = urlencoding(&endpoint.namespace);
|
||||||
@@ -411,8 +423,20 @@ impl<'a> ProviderClient<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let url = format!("{}/projects", self.site.api_base());
|
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)) {
|
||||||
.map(Into::into)
|
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<()> {
|
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")
|
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 {
|
fn webhook_urls_match(left: &str, right: &str) -> bool {
|
||||||
if left == right {
|
if left == right {
|
||||||
return true;
|
return true;
|
||||||
@@ -1066,11 +1100,52 @@ impl From<GithubRepo> for RemoteRepo {
|
|||||||
struct GitlabProject {
|
struct GitlabProject {
|
||||||
name: String,
|
name: String,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
|
path_with_namespace: Option<String>,
|
||||||
|
namespace: Option<GitlabNamespace>,
|
||||||
http_url_to_repo: String,
|
http_url_to_repo: String,
|
||||||
visibility: String,
|
visibility: String,
|
||||||
description: Option<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 {
|
impl From<GitlabProject> for RemoteRepo {
|
||||||
fn from(value: GitlabProject) -> Self {
|
fn from(value: GitlabProject) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -184,6 +184,104 @@ fn detect_namespace_kind_uses_authenticated_gitea_api() {
|
|||||||
handle.join().unwrap();
|
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]
|
#[test]
|
||||||
fn install_webhook_posts_github_hook_when_missing() {
|
fn install_webhook_posts_github_hook_when_missing() {
|
||||||
let (api_url, handle) = request_server(
|
let (api_url, handle) = request_server(
|
||||||
|
|||||||
Reference in New Issue
Block a user