Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9495a72
Report a malformed GitLab webhook payload instead of returning nothing
HarshMN2345 Jul 29, 2026
7b3d4ef
Assert per-provider facts instead of accepting either shape
HarshMN2345 Jul 29, 2026
a68b687
Assert pushed_at, clone failure and webhook scopes as they actually b…
HarshMN2345 Jul 30, 2026
7d672cd
Only tolerate a missing repository when cleaning up
HarshMN2345 Jul 30, 2026
f202ea6
Attempt every repository before reporting a cleanup failure
HarshMN2345 Jul 30, 2026
fa050f2
Wait for the commit before writing a status or a tag to it
HarshMN2345 Jul 30, 2026
90fef7e
Keep cleanup from replacing the reason a test failed
HarshMN2345 Jul 30, 2026
f46c5ee
Give Git a default presigned url that reports it unsupported
HarshMN2345 Jul 30, 2026
e70a83f
Share the rest of the adapter tests through Base
HarshMN2345 Jul 30, 2026
09ee0fb
Correct two GitHub facts the shared tests got wrong
HarshMN2345 Jul 30, 2026
5a31f62
Declare what a provider cannot do instead of overriding tests to skip
HarshMN2345 Jul 30, 2026
efcc95a
Share the check run contract and what a provider says about an author
HarshMN2345 Jul 30, 2026
c2c60ae
Resolve path sentinels the same way on every provider
HarshMN2345 Jul 30, 2026
69d8791
Report a missing GitHub pull request instead of returning the error body
HarshMN2345 Jul 30, 2026
282168b
Share the last four tests GitHub was keeping to itself
HarshMN2345 Jul 30, 2026
01ee240
Share the webhook payload tests through per-provider builders
HarshMN2345 Jul 30, 2026
6520bfa
Report a repository cleanup could not delete
HarshMN2345 Jul 30, 2026
33e50da
Retry a repository deletion before failing on it
HarshMN2345 Jul 30, 2026
a6fe107
Clean up what the moved tests left behind
HarshMN2345 Jul 30, 2026
1db4b02
Default every capability to supported in Base
HarshMN2345 Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/VCS/Adapter/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Utopia\VCS\Adapter;

use Exception;
use Utopia\VCS\Adapter;
use Utopia\Cache\Cache;

Expand Down Expand Up @@ -96,6 +97,106 @@ abstract public function createWebhook(string $owner, string $repositoryName, st
*/
abstract public function createTag(string $owner, string $repositoryName, string $tagName, string $target, string $message = ''): array;

/**
* Get a short-lived URL to download the repository archive.
*
* Not every provider offers one, so the default reports it as unsupported
* rather than forcing an implementation.
*
* @param string $owner Owner of the repository
* @param string $repositoryName Name of the repository
* @param string $ref Branch, tag or commit to archive
* @param string $format Either 'tarball' or 'zipball'
*/
public function getRepositoryPresignedUrl(string $owner, string $repositoryName, string $ref = '', string $format = 'tarball'): string
{
throw new Exception('getRepositoryPresignedUrl() is not supported by ' . $this->getName());
}

/**
* Create a check run for a commit.
*
* Only some providers model checks separately from commit statuses, so the
* default reports it as unsupported.
*
* @param array<mixed> $annotations
* @param array<mixed> $images
* @param array<mixed> $actions
* @return array<mixed>
*/
public function createCheckRun(
string $owner,
string $repositoryName,
string $headSha,
string $name,
string $status = 'queued',
string $conclusion = '',
string $title = '',
string $summary = '',
string $text = '',
array $annotations = [],
array $images = [],
array $actions = [],
string $detailsUrl = '',
string $externalId = '',
string $startedAt = '',
string $completedAt = '',
): array {
throw new Exception('createCheckRun() is not supported by ' . $this->getName());
}

/**
* Get a check run by id.
*
* @return array<mixed>
*/
public function getCheckRun(string $owner, string $repositoryName, int $checkRunId): array
{
throw new Exception('getCheckRun() is not supported by ' . $this->getName());
}

/**
* Update a check run.
*
* @param array<mixed> $annotations
* @param array<mixed> $images
* @param array<mixed> $actions
* @return array<mixed>
*/
public function updateCheckRun(
string $owner,
string $repositoryName,
int $checkRunId,
string $name = '',
string $status = '',
string $conclusion = '',
string $title = '',
string $summary = '',
string $text = '',
array $annotations = [],
array $images = [],
array $actions = [],
string $detailsUrl = '',
string $externalId = '',
string $startedAt = '',
string $completedAt = '',
): array {
throw new Exception('updateCheckRun() is not supported by ' . $this->getName());
}

/**
* List namespaces the credentials can create repositories in.
*
* Only some providers model namespaces separately, so the default reports
* it as unsupported.
*
* @return array{items: array<array<string, mixed>>, total: int}
*/
public function listNamespaces(int $page, int $per_page, string $search = ''): array
{
throw new Exception('listNamespaces() is not supported by ' . $this->getName());
}

/**
* Get commit statuses
*
Expand All @@ -109,6 +210,21 @@ abstract public function createTag(string $owner, string $repositoryName, string
*/
abstract public function getCommitStatuses(string $owner, string $repositoryName, string $commitHash): array;

/**
* Resolve the path sentinels a caller may pass - '', '.', './', 'src//' -
* to the plain path every provider's API expects. Providers differ on
* whether they do this themselves, so adapters normalize before calling.
*/
protected function normalizeRepositoryPath(string $path): string
{
$segments = \array_filter(
\explode('/', $path),
fn (string $segment): bool => $segment !== '' && $segment !== '.'
);

return \implode('/', $segments);
}

/**
* Filter ref names by a shell glob pattern (e.g. 'v1.*', 'v?.0.0').
* An empty pattern returns every name unchanged.
Expand Down
9 changes: 8 additions & 1 deletion src/VCS/Adapter/Git/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ public function listRepositoryLanguages(string $owner, string $repositoryName):
*/
public function getRepositoryContent(string $owner, string $repositoryName, string $path, string $ref = ''): array
{
$url = "/repos/$owner/$repositoryName/contents/" . $path;
$url = "/repos/$owner/$repositoryName/contents/" . $this->normalizeRepositoryPath($path);
if (!empty($ref)) {
$url .= "?ref=$ref";
}
Expand Down Expand Up @@ -526,6 +526,7 @@ public function getRepositoryContent(string $owner, string $repositoryName, stri
*/
public function listRepositoryContents(string $owner, string $repositoryName, string $path = '', string $ref = ''): array
{
$path = $this->normalizeRepositoryPath($path);
$url = "/repos/$owner/$repositoryName/contents";
if (!empty($path)) {
$url .= "/$path";
Expand Down Expand Up @@ -736,6 +737,12 @@ public function getPullRequest(string $owner, string $repositoryName, int $pullR

$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]);

$responseHeaders = $response['headers'] ?? [];
$statusCode = $responseHeaders['status-code'] ?? 0;
if ($statusCode >= 400) {
throw new Exception("Failed to get pull request: HTTP {$statusCode}", $statusCode);
}

return $response['body'] ?? [];
}

Expand Down
16 changes: 1 addition & 15 deletions src/VCS/Adapter/Git/GitLab.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,20 +122,6 @@ private function getOwnerPath(string $owner): string
return $owner;
}

/**
* GitLab passes path as a literal query/URL value, so unlike GitHub it
* never resolves './' or '.' to the repository root on its own.
*/
private function normalizeRepositoryPath(string $path): string
{
$segments = array_filter(
explode('/', $path),
fn (string $segment): bool => $segment !== '' && $segment !== '.'
);

return implode('/', $segments);
}

/**
* Extract namespace ID from "id:path" format
*/
Expand Down Expand Up @@ -1060,7 +1046,7 @@ public function getEvent(string $event, string $payload): array
{
$payloadArray = json_decode($payload, true);
if ($payloadArray === null || !is_array($payloadArray)) {
return [];
throw new Exception("Invalid payload.");
}

switch ($event) {
Expand Down
2 changes: 2 additions & 0 deletions src/VCS/Adapter/Git/Gitea.php
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ public function listRepositoryLanguages(string $owner, string $repositoryName):

public function getRepositoryContent(string $owner, string $repositoryName, string $path, string $ref = ''): array
{
$path = $this->normalizeRepositoryPath($path);
$url = "/repos/{$owner}/{$repositoryName}/contents/{$path}";
if (!empty($ref)) {
$url .= "?ref=" . urlencode($ref);
Expand Down Expand Up @@ -456,6 +457,7 @@ public function getRepositoryContent(string $owner, string $repositoryName, stri

public function listRepositoryContents(string $owner, string $repositoryName, string $path = '', string $ref = ''): array
{
$path = $this->normalizeRepositoryPath($path);
$url = "/repos/{$owner}/{$repositoryName}/contents";
if (!empty($path)) {
$url .= "/{$path}";
Expand Down
1 change: 0 additions & 1 deletion tests/VCS/Adapter/ForgejoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class ForgejoTest extends GiteaTest
protected static string $eventHeader = 'x-forgejo-event';
protected static string $signatureHeader = 'x-forgejo-signature';


protected function setupAdapter(): void
{
if (empty(static::$accessToken)) {
Expand Down
Loading
Loading