Merge pull request #12243 from appwrite/feat/repository-branch-search-pagination

feat: Add search and pagination for repository branches
This commit is contained in:
Harsh Mahajan
2026-05-08 16:14:06 +05:30
committed by GitHub
5 changed files with 174 additions and 2 deletions
@@ -1 +1 @@
Get a list of all branches from a GitHub repository in your installation. This endpoint returns the names of all branches in the repository and their total count. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
Get a list of branches from a GitHub repository in your installation. This endpoint supports filtering by a search term and pagination using query strings such as `Query.limit()`, `Query.offset()`, `Query.cursorAfter()`, and `Query.cursorBefore()`. It returns branch names along with the total number of matches. The GitHub installation must be properly configured and have access to the requested repository for this endpoint to work.
@@ -7,9 +7,12 @@ use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Branches;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -49,6 +52,8 @@ class XList extends Action
))
->param('installationId', '', new Text(256), 'Installation Id')
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('queries', [], new Branches(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit, offset, cursorAfter, and cursorBefore', true)
->inject('gitHub')
->inject('response')
->inject('dbForPlatform')
@@ -58,10 +63,18 @@ class XList extends Action
public function action(
string $installationId,
string $providerRepositoryId,
string $search,
array $queries,
GitHub $github,
Response $response,
Database $dbForPlatform
) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
@@ -85,11 +98,48 @@ class XList extends Action
$branches = $github->listBranches($owner, $repositoryName);
if (!empty($search)) {
$branches = \array_values(\array_filter($branches, fn (string $branch) => \stripos($branch, $search) !== false));
}
$total = \count($branches);
[
'limit' => $limit,
'offset' => $offset,
] = Query::groupByType($queries);
$cursorQuery = \current(Query::getCursorQueries($queries, false));
$limit ??= APP_LIMIT_LIST_DEFAULT;
$offset ??= 0;
if ($cursorQuery instanceof Query) {
$cursor = $cursorQuery->getValue();
$cursorDirection = $cursorQuery->getMethod() === Query::TYPE_CURSOR_AFTER
? Database::CURSOR_AFTER
: Database::CURSOR_BEFORE;
$cursorIndex = \array_search($cursor, $branches, true);
if ($cursorIndex === false) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Branch '{$cursor}' for the 'cursor' value not found.");
}
$offset += $cursorDirection === Database::CURSOR_AFTER ? $cursorIndex + 1 : 0;
if ($cursorDirection === Database::CURSOR_BEFORE) {
$start = \max(0, $cursorIndex - $limit);
$branches = \array_slice($branches, $start, $cursorIndex - $start);
} else {
$branches = \array_slice($branches, $offset, $limit);
}
} else {
$branches = \array_slice($branches, $offset, $limit);
}
$response->dynamic(new Document([
'branches' => \array_map(function ($branch) {
return new Document(['name' => $branch]);
}, $branches),
'total' => \count($branches),
'total' => $total,
]), Response::MODEL_BRANCH_LIST);
}
}
@@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
use Appwrite\Utopia\Database\Validator\Query\BranchCursor;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
class Branches extends Queries
{
public function __construct()
{
parent::__construct([
new Limit(),
new Offset(),
new BranchCursor(),
]);
}
}
@@ -0,0 +1,39 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Base;
use Utopia\Validator\Text;
class BranchCursor extends Base
{
public function isValid($value): bool
{
if (!$value instanceof Query) {
return false;
}
$method = $value->getMethod();
if (!\in_array($method, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE], true)) {
$this->message = 'Invalid query method: ' . $method;
return false;
}
$cursor = $value->getValue();
$validator = new Text(256);
if (!$validator->isValid($cursor)) {
$this->message = 'Invalid cursor: ' . $validator->getDescription();
return false;
}
return true;
}
public function getMethodType(): string
{
return self::METHOD_TYPE_CURSOR;
}
}
@@ -513,6 +513,59 @@ class VCSConsoleClientTest extends Scope
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
$this->assertEquals($repositoryBranches['body']['branches'][1]['name'], 'test');
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'tes',
]);
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
$this->assertEquals($repositoryBranches['body']['total'], 1);
$this->assertCount(1, $repositoryBranches['body']['branches']);
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
Query::offset(1)->toString(),
],
]);
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
$this->assertEquals($repositoryBranches['body']['total'], 2);
$this->assertCount(1, $repositoryBranches['body']['branches']);
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
Query::cursorAfter(new \Utopia\Database\Document(['$id' => 'main']))->toString(),
],
]);
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
$this->assertEquals($repositoryBranches['body']['total'], 2);
$this->assertCount(1, $repositoryBranches['body']['branches']);
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'test');
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(1)->toString(),
Query::cursorBefore(new \Utopia\Database\Document(['$id' => 'test']))->toString(),
],
]);
$this->assertEquals(200, $repositoryBranches['headers']['status-code']);
$this->assertEquals($repositoryBranches['body']['total'], 2);
$this->assertCount(1, $repositoryBranches['body']['branches']);
$this->assertEquals($repositoryBranches['body']['branches'][0]['name'], 'main');
/**
* Test for FAILURE
*/
@@ -522,6 +575,16 @@ class VCSConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals(404, $repositoryBranches['headers']['status-code']);
$repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::cursorAfter(new \Utopia\Database\Document(['$id' => 'missing-branch']))->toString(),
],
]);
$this->assertEquals(400, $repositoryBranches['headers']['status-code']);
}
public function testCreateFunctionUsingVCS(): void