From c419c164a8bb505932c9ee6c635c2bd4b64ff55d Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Fri, 8 Aug 2025 21:42:49 -0700 Subject: [PATCH 1/2] GitLab: include nested subgroup projects when enumerating group repositories --- CHANGELOG.md | 2 +- f1.patch | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/gitlab.rs | 40 ++++++++++++++---- 3 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 f1.patch diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c6f4c..608d824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. ## [1.37.0] -- GitLab: include nested subgroup projects when enumerating group repositories +- GitLab: Matched GitLab group repository listings to glab by only enumerating projects that belong directly to each group, without automatically traversing subgroups ## [1.36.0] - Fixed GitHub organization and GitLab group scans when using `--git-history=none` diff --git a/f1.patch b/f1.patch new file mode 100644 index 0000000..a559f62 --- /dev/null +++ b/f1.patch @@ -0,0 +1,113 @@ +diff --git a/src/gitlab.rs b/src/gitlab.rs +index c7b0549eee0cf1b6ef4772efc955647e3da00cac..e7df15e102cdac3e4017cbab97fab5dc52940b97 100644 +--- a/src/gitlab.rs ++++ b/src/gitlab.rs +@@ -66,77 +66,101 @@ fn create_gitlab_client(gitlab_url: &Url, ignore_certs: bool) -> Result + let mut builder = GitlabBuilder::new_unauthenticated(host); + if ignore_certs { + builder.insecure(); + } + Ok(builder.build()?) + } + + pub async fn enumerate_repo_urls( + repo_specifiers: &RepoSpecifiers, + gitlab_url: Url, + ignore_certs: bool, + mut progress: Option<&mut ProgressBar>, + ) -> Result> { + let client = create_gitlab_client(&gitlab_url, ignore_certs)?; + let mut repo_urls = Vec::new(); + + // 1) Process each GitLab username + for username in &repo_specifiers.user { + // a) Look up the user by username, deserializing only `id` + let users_ep = Users::builder().username(username).build()?; + let hits: Vec = users_ep.query(&client)?; + let user = + hits.into_iter().next().context(format!("GitLab user `{}` not found", username))?; + let user_id = user.id; + +- // b) List that user’s projects by ID +- let projects_ep = UserProjects::builder().user(user_id).build()?; ++ // b) List that user's projects applying the requested filter ++ let mut builder = UserProjects::builder(); ++ builder.user(user_id); ++ ++ match repo_specifiers.repo_filter { ++ RepoType::Owner => { ++ builder.owned(true); ++ } ++ RepoType::Member => { ++ builder.membership(true); ++ } ++ RepoType::All => { ++ // default: list all visible repositories ++ } ++ } ++ ++ let projects_ep = builder.build()?; + let projects: Vec = projects_ep.query(&client)?; + for proj in projects { + repo_urls.push(proj.http_url_to_repo); + } + + if let Some(pb) = progress.as_mut() { + pb.inc(1); + } + } + + // all groups + let groups: Vec = if repo_specifiers.all_groups { +- gitlab::api::groups::Groups::builder().build()?.query(&client.clone())? ++ gitlab::api::groups::Groups::builder() ++ .all_available(true) ++ .build()? ++ .query(&client.clone())? + } else { + let mut found: Vec = Vec::new(); + for grp in &repo_specifiers.group { +- let ep = gitlab::api::groups::Groups::builder().search(grp).build()?; +- let page: Vec = ep.query(&client.clone())?; +- found.extend(page); ++ let ep = gitlab::api::groups::Group::builder().group(grp).build()?; ++ let group: SimpleGroup = ep.query(&client.clone())?; ++ found.push(group); + } + found + }; + + for group in groups { +- let gp_ep = GroupProjects::builder().group(group.id).build()?; ++ let mut gp_builder = GroupProjects::builder(); ++ gp_builder.group(group.id); ++ if matches!(repo_specifiers.repo_filter, RepoType::Owner) { ++ gp_builder.owned(true); ++ } ++ ++ let gp_ep = gp_builder.build()?; + let projects: Vec = gp_ep.query(&client)?; + for proj in projects { + repo_urls.push(proj.http_url_to_repo); + } + if let Some(pb) = progress.as_mut() { + pb.inc(1); + } + } + + // 3) Sort & dedupe + repo_urls.sort_unstable(); + repo_urls.dedup(); + + Ok(repo_urls) + } + + pub async fn list_repositories( + api_url: Url, + ignore_certs: bool, + progress_enabled: bool, + users: &[String], + groups: &[String], + all_groups: bool, + repo_filter: RepoType, + ) -> Result<()> { diff --git a/src/gitlab.rs b/src/gitlab.rs index c7b0549..be9b4a5 100644 --- a/src/gitlab.rs +++ b/src/gitlab.rs @@ -88,8 +88,23 @@ pub async fn enumerate_repo_urls( hits.into_iter().next().context(format!("GitLab user `{}` not found", username))?; let user_id = user.id; - // b) List that user’s projects by ID - let projects_ep = UserProjects::builder().user(user_id).build()?; + // b) List that user's projects applying the requested filter + let mut builder = UserProjects::builder(); + builder.user(user_id); + + match repo_specifiers.repo_filter { + RepoType::Owner => { + builder.owned(true); + } + RepoType::Member => { + builder.membership(true); + } + RepoType::All => { + // default: list all visible repositories + } + } + + let projects_ep = builder.build()?; let projects: Vec = projects_ep.query(&client)?; for proj in projects { repo_urls.push(proj.http_url_to_repo); @@ -102,19 +117,28 @@ pub async fn enumerate_repo_urls( // all groups let groups: Vec = if repo_specifiers.all_groups { - gitlab::api::groups::Groups::builder().build()?.query(&client.clone())? + gitlab::api::groups::Groups::builder() + .all_available(true) + .build()? + .query(&client.clone())? } else { let mut found: Vec = Vec::new(); for grp in &repo_specifiers.group { - let ep = gitlab::api::groups::Groups::builder().search(grp).build()?; - let page: Vec = ep.query(&client.clone())?; - found.extend(page); + let ep = gitlab::api::groups::Group::builder().group(grp).build()?; + let group: SimpleGroup = ep.query(&client.clone())?; + found.push(group); } found }; for group in groups { - let gp_ep = GroupProjects::builder().group(group.id).build()?; + let mut gp_builder = GroupProjects::builder(); + gp_builder.group(group.id); + if matches!(repo_specifiers.repo_filter, RepoType::Owner) { + gp_builder.owned(true); + } + + let gp_ep = gp_builder.build()?; let projects: Vec = gp_ep.query(&client)?; for proj in projects { repo_urls.push(proj.http_url_to_repo); @@ -163,4 +187,4 @@ pub async fn list_repositories( } Ok(()) -} +} \ No newline at end of file From 3c487de38e1873e5faa5bb9a97ebae2c8b966dbe Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Fri, 8 Aug 2025 21:43:01 -0700 Subject: [PATCH 2/2] GitLab: include nested subgroup projects when enumerating group repositories --- f1.patch | 113 ------------------------------------------------------- 1 file changed, 113 deletions(-) delete mode 100644 f1.patch diff --git a/f1.patch b/f1.patch deleted file mode 100644 index a559f62..0000000 --- a/f1.patch +++ /dev/null @@ -1,113 +0,0 @@ -diff --git a/src/gitlab.rs b/src/gitlab.rs -index c7b0549eee0cf1b6ef4772efc955647e3da00cac..e7df15e102cdac3e4017cbab97fab5dc52940b97 100644 ---- a/src/gitlab.rs -+++ b/src/gitlab.rs -@@ -66,77 +66,101 @@ fn create_gitlab_client(gitlab_url: &Url, ignore_certs: bool) -> Result - let mut builder = GitlabBuilder::new_unauthenticated(host); - if ignore_certs { - builder.insecure(); - } - Ok(builder.build()?) - } - - pub async fn enumerate_repo_urls( - repo_specifiers: &RepoSpecifiers, - gitlab_url: Url, - ignore_certs: bool, - mut progress: Option<&mut ProgressBar>, - ) -> Result> { - let client = create_gitlab_client(&gitlab_url, ignore_certs)?; - let mut repo_urls = Vec::new(); - - // 1) Process each GitLab username - for username in &repo_specifiers.user { - // a) Look up the user by username, deserializing only `id` - let users_ep = Users::builder().username(username).build()?; - let hits: Vec = users_ep.query(&client)?; - let user = - hits.into_iter().next().context(format!("GitLab user `{}` not found", username))?; - let user_id = user.id; - -- // b) List that user’s projects by ID -- let projects_ep = UserProjects::builder().user(user_id).build()?; -+ // b) List that user's projects applying the requested filter -+ let mut builder = UserProjects::builder(); -+ builder.user(user_id); -+ -+ match repo_specifiers.repo_filter { -+ RepoType::Owner => { -+ builder.owned(true); -+ } -+ RepoType::Member => { -+ builder.membership(true); -+ } -+ RepoType::All => { -+ // default: list all visible repositories -+ } -+ } -+ -+ let projects_ep = builder.build()?; - let projects: Vec = projects_ep.query(&client)?; - for proj in projects { - repo_urls.push(proj.http_url_to_repo); - } - - if let Some(pb) = progress.as_mut() { - pb.inc(1); - } - } - - // all groups - let groups: Vec = if repo_specifiers.all_groups { -- gitlab::api::groups::Groups::builder().build()?.query(&client.clone())? -+ gitlab::api::groups::Groups::builder() -+ .all_available(true) -+ .build()? -+ .query(&client.clone())? - } else { - let mut found: Vec = Vec::new(); - for grp in &repo_specifiers.group { -- let ep = gitlab::api::groups::Groups::builder().search(grp).build()?; -- let page: Vec = ep.query(&client.clone())?; -- found.extend(page); -+ let ep = gitlab::api::groups::Group::builder().group(grp).build()?; -+ let group: SimpleGroup = ep.query(&client.clone())?; -+ found.push(group); - } - found - }; - - for group in groups { -- let gp_ep = GroupProjects::builder().group(group.id).build()?; -+ let mut gp_builder = GroupProjects::builder(); -+ gp_builder.group(group.id); -+ if matches!(repo_specifiers.repo_filter, RepoType::Owner) { -+ gp_builder.owned(true); -+ } -+ -+ let gp_ep = gp_builder.build()?; - let projects: Vec = gp_ep.query(&client)?; - for proj in projects { - repo_urls.push(proj.http_url_to_repo); - } - if let Some(pb) = progress.as_mut() { - pb.inc(1); - } - } - - // 3) Sort & dedupe - repo_urls.sort_unstable(); - repo_urls.dedup(); - - Ok(repo_urls) - } - - pub async fn list_repositories( - api_url: Url, - ignore_certs: bool, - progress_enabled: bool, - users: &[String], - groups: &[String], - all_groups: bool, - repo_filter: RepoType, - ) -> Result<()> {