Skip to content

Conversation

@hmacr
Copy link
Contributor

@hmacr hmacr commented Jan 26, 2026

Related SER-642

Ensures only the repositories accessible to the installation is returned

Summary by CodeRabbit

  • Chores
    • Improved repository listing to fetch all pages from the provider, locally aggregate and filter results, and deliver consistent counts.
    • Now handles missing or partial remote responses gracefully and slices aggregated results to match requested paging, yielding steadier listings and fewer errors for consumers.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Walkthrough

The adapter replaces the GitHub search endpoint /search/repositories with /installation/repositories, removes query and sort parameters, and implements a paginated fetch loop using per_page=100 and a currentPage counter. It accumulates body.repositories across pages, applies local name filtering against the search string, and throws if a response body lacks repositories. The loop breaks when a page returns fewer than 100 repositories. After paging, the accumulated repositories are sliced to produce the requested page and total is set to the accumulated count.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: switching the API endpoint from /search/repositories to /installation/repositories for repository searching.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/VCS/Adapter/Git/GitHub.php`:
- Line 120: The array_filter call assigning $repositories uses case-sensitive
str_contains and the redundant null-coalescing ?? []; update the filter to use
case-insensitive matching via stripos (i.e., replace str_contains with a check
that stripos($repo['name'], $search) !== false) and remove the ?? [] so it
directly iterates $response['body']['repositories'] (which is already validated
earlier); keep the $repositories variable and array_filter call but swap the
matcher and drop the redundant fallback.
- Around line 109-125: The current implementation calls
$this->call(self::METHOD_GET, $url, ...) to fetch a single page and then filters
$response['body']['repositories'] locally, which breaks pagination and makes
'total' inaccurate; modify the logic in GitHub.php so that when $search is
non-empty you page through all results from $this->call(self::METHOD_GET, $url,
['Authorization' => "Bearer $this->accessToken"], ...) (looping with 'page'
param until no more items), accumulate all repositories, apply the local filter
(str_contains on $repo['name']), then return the filtered slice and accurate
total; keep single-page behavior when $search is empty to preserve performance.
🧹 Nitpick comments (1)
src/VCS/Adapter/Git/GitHub.php (1)

107-107: Remove the unused $owner parameter or update the endpoint to use owner-based filtering.

The $owner parameter is declared in the method signature and documented in the abstract definition, but it's never used in the implementation since the method uses the /installation/repositories endpoint, which lists all repositories accessible to the GitHub App installation regardless of owner. Either remove the parameter to match the current endpoint behavior, or change the endpoint to use owner-specific filtering (e.g., /users/{owner}/repos or /orgs/{owner}/repos).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/VCS/Adapter/Git/GitHub.php`:
- Around line 119-121: The code currently breaks out when
(!isset($response['body']['repositories'])) which silences API errors and can
return incomplete data; replace the break with throwing an exception (e.g.,
RuntimeException or a VCS-specific exception) from the GitHub adapter
(file/class GitHub.php) at the check for $response['body']['repositories'],
including contextual details such as the full $response payload (or status code
and error message) and the current request/page info so callers can surface and
handle the API error instead of silently continuing.
♻️ Duplicate comments (1)
src/VCS/Adapter/Git/GitHub.php (1)

123-131: Stop condition uses filtered count; can truncate search results.

If a page has <100 matches but more repositories exist, the loop ends early and misses later matches. Use the unfiltered page size to decide when to stop.

✅ Suggested fix
-            // Filter repositories to only include those that match the search query.
-            $filteredRepositories = array_filter($response['body']['repositories'], fn ($repo) => empty($search) || stripos($repo['name'], $search) !== false);
+            $pageRepositories = $response['body']['repositories'];
+            // Filter repositories to only include those that match the search query.
+            $filteredRepositories = array_filter($pageRepositories, fn ($repo) => empty($search) || stripos($repo['name'], $search) !== false);

             // Merge with result so far.
             $repositories = array_merge($repositories, $filteredRepositories);

-            // If less than 100 repositories are returned, we have fetched all repositories.
-            if (\count($filteredRepositories) < 100) {
+            // If less than 100 repositories are returned, we have fetched all repositories.
+            if (\count($pageRepositories) < 100) {
                 break;
             }
🧹 Nitpick comments (1)
src/VCS/Adapter/Git/GitHub.php (1)

109-143: Avoid full scan when $search is empty (perf regression).

When no filter is needed, a single paged request is enough and avoids N+1 API calls.

♻️ Suggested refactor
     public function searchRepositories(string $owner, int $page, int $per_page, string $search = ''): array
     {
+        if (empty($search)) {
+            $response = $this->call(self::METHOD_GET, '/installation/repositories', ['Authorization' => "Bearer $this->accessToken"], [
+                'page' => $page,
+                'per_page' => $per_page,
+            ]);
+            if (!isset($response['body']['repositories'])) {
+                $status = $response['headers']['status-code'] ?? 'unknown';
+                throw new Exception("Repositories list missing in response (status {$status}).");
+            }
+            return [
+                'items' => $response['body']['repositories'],
+                'total' => $response['body']['total_count'] ?? \count($response['body']['repositories']),
+            ];
+        }
+
         $repositories = [];
         $currentPage = 1;
         while (true) {
             $url = '/installation/repositories';
             $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [
                 'page' => $currentPage,
                 'per_page' => 100, // Maximum allowed by GitHub API
             ]);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/VCS/Adapter/Git/GitHub.php`:
- Around line 129-132: The loop break currently checks
count($filteredRepositories) (the post-filtered list) which causes premature
termination when many results are filtered out; change the condition to inspect
the raw page response count instead (the variable holding the unfiltered page
results—e.g. the array populated from the GitHub API response before filtering)
so use count(...) on that raw response variable rather than
$filteredRepositories inside the loop to determine when fewer than 100 items
were returned and to decide to break.
- Around line 107-108: The searchRepositories method currently takes an $owner
parameter that is never used; either implement local owner filtering, document
its deprecation, or remove it—choose one. To implement filtering, update
searchRepositories to, after fetching repositories from the
/installation/repositories response, filter the results by comparing
$repo['owner']['login'] to the $owner value (and handle empty $owner to mean no
filtering), preserving pagination semantics; alternatively add a docstring or
comment to searchRepositories noting that $owner is ignored by the API; or
remove the $owner parameter from the searchRepositories signature and update all
callers to avoid passing owner. Ensure the chosen approach updates the function
signature, callers, and any tests consistently.
🧹 Nitpick comments (1)
src/VCS/Adapter/Git/GitHub.php (1)

109-143: Consider optimizing when no search term is provided.

When $search is empty, the code fetches all pages only to slice the result at the end. For installations with hundreds of repositories, this is inefficient.

When no search filtering is needed, you could use the API's native pagination directly:

Suggested optimization
 public function searchRepositories(string $owner, int $page, int $per_page, string $search = ''): array
 {
+    $url = '/installation/repositories';
+
+    // When no search term, use API pagination directly
+    if (empty($search)) {
+        $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [
+            'page' => $page,
+            'per_page' => $per_page,
+        ]);
+
+        if (!isset($response['body']['repositories'])) {
+            throw new Exception("Repositories list missing in the response.");
+        }
+
+        return [
+            'items' => $response['body']['repositories'],
+            'total' => $response['body']['total_count'] ?? count($response['body']['repositories']),
+        ];
+    }
+
+    // When searching, fetch all and filter locally
     $repositories = [];
-
     $currentPage = 1;
     while (true) {
-        $url = '/installation/repositories';
         $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [
             // ... rest of the loop

Comment on lines +129 to +132
// If less than 100 repositories are returned, we have fetched all repositories.
if (\count($filteredRepositories) < 100) {
break;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Loop termination condition checks filtered count instead of raw response count.

The break condition uses count($filteredRepositories) which is the count after filtering by search term. If a page returns 100 repositories but only a few match the search, the loop exits prematurely and skips remaining pages.

For example: Page 1 returns 100 repos, 5 match search → loop breaks → pages 2-N never fetched.

Proposed fix
             // Filter repositories to only include those that match the search query.
             $filteredRepositories = array_filter($response['body']['repositories'], fn ($repo) => empty($search) || stripos($repo['name'], $search) !== false);

             // Merge with result so far.
             $repositories = array_merge($repositories, $filteredRepositories);

             // If less than 100 repositories are returned, we have fetched all repositories.
-            if (\count($filteredRepositories) < 100) {
+            if (\count($response['body']['repositories']) < 100) {
                 break;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If less than 100 repositories are returned, we have fetched all repositories.
if (\count($filteredRepositories) < 100) {
break;
}
// Filter repositories to only include those that match the search query.
$filteredRepositories = array_filter($response['body']['repositories'], fn ($repo) => empty($search) || stripos($repo['name'], $search) !== false);
// Merge with result so far.
$repositories = array_merge($repositories, $filteredRepositories);
// If less than 100 repositories are returned, we have fetched all repositories.
if (\count($response['body']['repositories']) < 100) {
break;
}
🤖 Prompt for AI Agents
In `@src/VCS/Adapter/Git/GitHub.php` around lines 129 - 132, The loop break
currently checks count($filteredRepositories) (the post-filtered list) which
causes premature termination when many results are filtered out; change the
condition to inspect the raw page response count instead (the variable holding
the unfiltered page results—e.g. the array populated from the GitHub API
response before filtering) so use count(...) on that raw response variable
rather than $filteredRepositories inside the loop to determine when fewer than
100 items were returned and to decide to break.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants