diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index 1af30388..10b23658 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -742,32 +742,58 @@ public function getPullRequestFromBranch(string $owner, string $repositoryName, } /** - * Lists branches for a given repository + * Lists branches for a given repository, optionally filtered by a search string. * - * @param string $owner Owner name of the repository - * @param string $repositoryName Name of the GitHub repository - * @param int $perPage Number of branches to fetch per page - * @param int $page Page number to start fetching from - * @return array List of branch names as array + * @param string $owner + * @param string $repositoryName + * @param int $perPage Clamped to [1, 100] + * @param int $page Page number (1-based) + * @param string $search Substring filter; empty returns all branches + * @return array List of branch names */ - public function listBranches(string $owner, string $repositoryName, int $perPage = 100, int $page = 1): array + public function listBranches(string $owner, string $repositoryName, int $perPage = 100, int $page = 1, string $search = ''): array { - $url = "/repos/$owner/$repositoryName/branches"; $perPage = min(max($perPage, 1), 100); - $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"], [ - 'page' => $page, - 'per_page' => $perPage, + $gql = <<<'GRAPHQL' +query ListBranches($owner: String!, $name: String!, $first: Int!, $query: String) { + repository(owner: $owner, name: $name) { + refs(refPrefix: "refs/heads/", first: $first, orderBy: {field: ALPHABETICAL, direction: ASC}, query: $query) { + edges { + node { + name + } + } + } + } +} +GRAPHQL; + + $response = $this->call(self::METHOD_POST, '/graphql', ['Authorization' => "Bearer $this->accessToken"], [ + 'query' => $gql, + 'variables' => [ + 'owner' => $owner, + 'name' => $repositoryName, + 'first' => $perPage, + 'query' => $search !== '' ? $search : null, + ], ]); $statusCode = $response['headers']['status-code'] ?? 0; $responseBody = $response['body'] ?? []; - if ($statusCode < 200 || $statusCode >= 300 || !is_array($responseBody)) { + if ($statusCode < 200 || $statusCode >= 300 || !is_array($responseBody) || array_key_exists('errors', $responseBody)) { return []; } - return array_values(array_map(fn ($branch) => $branch['name'] ?? '', $responseBody)); + $repository = $responseBody['data']['repository'] ?? null; + $refs = is_array($repository) ? ($repository['refs'] ?? null) : null; + + if (!is_array($refs)) { + return []; + } + + return array_map(fn ($edge) => $edge['node']['name'] ?? '', $refs['edges'] ?? []); } /** @@ -831,15 +857,13 @@ public function getLatestCommit(string $owner, string $repositoryName, string $b $responseBody = $response['body'] ?? []; $responseBodyCommit = $responseBody['commit'] ?? []; $responseBodyCommitAuthor = $responseBodyCommit['author'] ?? []; - $responseBodyAuthor = $responseBody['author'] ?? []; + $responseBodyAuthor = is_array($responseBody['author'] ?? null) ? $responseBody['author'] : []; if ( !array_key_exists('name', $responseBodyCommitAuthor) || !array_key_exists('message', $responseBodyCommit) || !array_key_exists('sha', $responseBody) || - !array_key_exists('html_url', $responseBody) || - !array_key_exists('avatar_url', $responseBodyAuthor) || - !array_key_exists('html_url', $responseBodyAuthor) + !array_key_exists('html_url', $responseBody) ) { throw new Exception("Latest commit response is missing required information."); } diff --git a/tests/VCS/Adapter/GitHubTest.php b/tests/VCS/Adapter/GitHubTest.php index f1c4b2fc..91266416 100644 --- a/tests/VCS/Adapter/GitHubTest.php +++ b/tests/VCS/Adapter/GitHubTest.php @@ -540,14 +540,15 @@ public function testListBranchesPagination(): void /** @var GitHub $adapter */ $adapter = $this->vcsAdapter; - $page1 = $adapter->listBranches(static::$owner, $repositoryName, 1, 1); - $this->assertSame(['branch-a'], $page1); - - $page2 = $adapter->listBranches(static::$owner, $repositoryName, 1, 2); - $this->assertSame(['branch-b'], $page2); - $all = $adapter->listBranches(static::$owner, $repositoryName, 100, 1); $this->assertEqualsCanonicalizing([static::$defaultBranch, 'branch-a', 'branch-b'], $all); + + $searchResults = $adapter->listBranches(static::$owner, $repositoryName, 100, 1, 'branch'); + $this->assertEqualsCanonicalizing(['branch-a', 'branch-b'], $searchResults); + + // GitHub refs(query:) does substring matching, so 'ranch' matches 'branch-a' and 'branch-b' + $substringSearch = $adapter->listBranches(static::$owner, $repositoryName, 100, 1, 'ranch'); + $this->assertEqualsCanonicalizing(['branch-a', 'branch-b'], $substringSearch); } finally { $this->vcsAdapter->deleteRepository(static::$owner, $repositoryName); }