Skip to content

Commit 02d766e

Browse files
authored
Merge branch 'utopia-php:main' into feat/gitea-repository-operations
2 parents 3560352 + 9201e5b commit 02d766e

5 files changed

Lines changed: 437 additions & 24 deletions

File tree

src/VCS/Adapter/Git.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function getType(): string
4545
* @param string $message Commit message
4646
* @return array<mixed> Response from API
4747
*/
48-
abstract public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file'): array;
48+
abstract public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file', string $branch = ''): array;
4949

5050
/**
5151
* Create a branch in a repository
@@ -57,4 +57,17 @@ abstract public function createFile(string $owner, string $repositoryName, strin
5757
* @return array<mixed> Response from API
5858
*/
5959
abstract public function createBranch(string $owner, string $repositoryName, string $newBranchName, string $oldBranchName): array;
60+
61+
/**
62+
* Create a pull request
63+
*
64+
* @param string $owner Owner of the repository
65+
* @param string $repositoryName Name of the repository
66+
* @param string $title PR title
67+
* @param string $head Source branch
68+
* @param string $base Target branch
69+
* @param string $body PR description (optional)
70+
* @return array<mixed> Created PR details
71+
*/
72+
abstract public function createPullRequest(string $owner, string $repositoryName, string $title, string $head, string $base, string $body = ''): array;
6073
}

src/VCS/Adapter/Git/GitHub.php

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,61 @@ public function createRepository(string $owner, string $repositoryName, bool $pr
9393

9494
return $response['body'] ?? [];
9595
}
96+
/**
97+
* Create a pull request
98+
*
99+
* @param string $owner Owner of the repository
100+
* @param string $repositoryName Name of the repository
101+
* @param string $title PR title
102+
* @param string $head Source branch
103+
* @param string $base Target branch
104+
* @param string $body PR description (optional)
105+
* @return array<mixed> Created PR details
106+
*/
107+
public function createPullRequest(string $owner, string $repositoryName, string $title, string $head, string $base, string $body = ''): array
108+
{
109+
throw new Exception('Not implemented');
110+
}
96111

97112
/**
98113
* Create a file in a repository
99114
*
100-
* @param string $owner Owner of the repository
101-
* @param string $repositoryName Name of the repository
102-
* @param string $filepath Path where file should be created
103-
* @param string $content Content of the file
104-
* @param string $message Commit message
115+
* @param string $owner Owner of the repository
116+
* @param string $repositoryName Name of the repository
117+
* @param string $filepath Path where file should be created
118+
* @param string $content Content of the file
119+
* @param string $message Commit message
120+
* @param string $branch Branch to create file on (optional)
105121
* @return array<mixed> Response from API
106122
*/
107-
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file'): array
123+
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file', string $branch = ''): array
108124
{
109-
throw new Exception("Not implemented");
125+
$url = "/repos/{$owner}/{$repositoryName}/contents/{$filepath}";
126+
127+
$payload = [
128+
'message' => $message,
129+
'content' => base64_encode($content),
130+
];
131+
132+
// GitHub supports branch parameter
133+
if (! empty($branch)) {
134+
$payload['branch'] = $branch;
135+
}
136+
137+
$response = $this->call(
138+
self::METHOD_PUT,
139+
$url,
140+
['Authorization' => "Bearer $this->accessToken"],
141+
$payload
142+
);
143+
144+
$responseHeaders = $response['headers'] ?? [];
145+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
146+
if ($responseHeadersStatusCode >= 400) {
147+
throw new Exception("Failed to create file {$filepath}: HTTP {$responseHeadersStatusCode}");
148+
}
149+
150+
return $response['body'] ?? [];
110151
}
111152

112153
/**

src/VCS/Adapter/Git/Gitea.php

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,18 +264,25 @@ public function getRepositoryTree(string $owner, string $repositoryName, string
264264
* @param string $message Commit message
265265
* @return array<mixed> Response from API
266266
*/
267-
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file'): array
267+
public function createFile(string $owner, string $repositoryName, string $filepath, string $content, string $message = 'Add file', string $branch = ''): array
268268
{
269269
$url = "/repos/{$owner}/{$repositoryName}/contents/{$filepath}";
270270

271+
$payload = [
272+
'content' => base64_encode($content),
273+
'message' => $message
274+
];
275+
276+
// Add branch if specified
277+
if (!empty($branch)) {
278+
$payload['branch'] = $branch;
279+
}
280+
271281
$response = $this->call(
272282
self::METHOD_POST,
273283
$url,
274284
['Authorization' => "token $this->accessToken"],
275-
[
276-
'content' => base64_encode($content),
277-
'message' => $message
278-
]
285+
$payload
279286
);
280287

281288
$responseHeaders = $response['headers'] ?? [];
@@ -423,19 +430,100 @@ public function deleteRepository(string $owner, string $repositoryName): bool
423430
return true;
424431
}
425432

433+
/**
434+
* Create a pull request
435+
*
436+
* @param string $owner Owner of the repository
437+
* @param string $repositoryName Name of the repository
438+
* @param string $title PR title
439+
* @param string $head Source branch
440+
* @param string $base Target branch
441+
* @param string $body PR description (optional)
442+
* @return array<mixed> Created PR details
443+
*/
444+
public function createPullRequest(string $owner, string $repositoryName, string $title, string $head, string $base, string $body = ''): array
445+
{
446+
$url = "/repos/{$owner}/{$repositoryName}/pulls";
447+
448+
$payload = [
449+
'title' => $title,
450+
'head' => $head,
451+
'base' => $base,
452+
];
453+
454+
if (!empty($body)) {
455+
$payload['body'] = $body;
456+
}
457+
458+
$response = $this->call(
459+
self::METHOD_POST,
460+
$url,
461+
['Authorization' => "token $this->accessToken"],
462+
$payload
463+
);
464+
465+
$responseHeaders = $response['headers'] ?? [];
466+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
467+
if ($responseHeadersStatusCode >= 400) {
468+
throw new Exception("Failed to create pull request: HTTP {$responseHeadersStatusCode}");
469+
}
470+
471+
$responseBody = $response['body'] ?? [];
472+
473+
return $responseBody;
474+
}
475+
426476
public function createComment(string $owner, string $repositoryName, int $pullRequestNumber, string $comment): string
427477
{
428-
throw new Exception("Not implemented yet");
478+
$url = "/repos/{$owner}/{$repositoryName}/issues/{$pullRequestNumber}/comments";
479+
480+
$response = $this->call(self::METHOD_POST, $url, ['Authorization' => "token $this->accessToken"], ['body' => $comment]);
481+
482+
$responseHeaders = $response['headers'] ?? [];
483+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
484+
if ($responseHeadersStatusCode >= 400) {
485+
throw new Exception("Failed to create comment: HTTP {$responseHeadersStatusCode}");
486+
}
487+
488+
$responseBody = $response['body'] ?? [];
489+
490+
if (!array_key_exists('id', $responseBody)) {
491+
throw new Exception("Comment creation response is missing comment ID.");
492+
}
493+
494+
return (string) ($responseBody['id'] ?? '');
429495
}
430496

431497
public function getComment(string $owner, string $repositoryName, string $commentId): string
432498
{
433-
throw new Exception("Not implemented yet");
499+
$url = "/repos/{$owner}/{$repositoryName}/issues/comments/{$commentId}";
500+
501+
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);
502+
503+
$responseBody = $response['body'] ?? [];
504+
505+
return $responseBody['body'] ?? '';
434506
}
435507

436508
public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string
437509
{
438-
throw new Exception("Not implemented yet");
510+
$url = "/repos/{$owner}/{$repositoryName}/issues/comments/{$commentId}";
511+
512+
$response = $this->call(self::METHOD_PATCH, $url, ['Authorization' => "token $this->accessToken"], ['body' => $comment]);
513+
514+
$responseHeaders = $response['headers'] ?? [];
515+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
516+
if ($responseHeadersStatusCode >= 400) {
517+
throw new Exception("Failed to update comment: HTTP {$responseHeadersStatusCode}");
518+
}
519+
520+
$responseBody = $response['body'] ?? [];
521+
522+
if (!array_key_exists('id', $responseBody)) {
523+
throw new Exception("Comment update response is missing comment ID.");
524+
}
525+
526+
return (string) ($responseBody['id'] ?? '');
439527
}
440528

441529
public function getUser(string $username): array
@@ -450,12 +538,35 @@ public function getOwnerName(string $installationId): string
450538

451539
public function getPullRequest(string $owner, string $repositoryName, int $pullRequestNumber): array
452540
{
453-
throw new Exception("Not implemented yet");
541+
$url = "/repos/{$owner}/{$repositoryName}/pulls/{$pullRequestNumber}";
542+
543+
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);
544+
545+
$responseHeaders = $response['headers'] ?? [];
546+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
547+
if ($responseHeadersStatusCode >= 400) {
548+
throw new Exception("Failed to get pull request: HTTP {$responseHeadersStatusCode}");
549+
}
550+
551+
return $response['body'] ?? [];
454552
}
455553

456554
public function getPullRequestFromBranch(string $owner, string $repositoryName, string $branch): array
457555
{
458-
throw new Exception("Not implemented yet");
556+
557+
$url = "/repos/{$owner}/{$repositoryName}/pulls?state=open&head=" . urlencode($branch);
558+
559+
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);
560+
561+
$responseHeaders = $response['headers'] ?? [];
562+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
563+
if ($responseHeadersStatusCode >= 400) {
564+
throw new Exception("Failed to list pull requests: HTTP {$responseHeadersStatusCode}");
565+
}
566+
567+
$responseBody = $response['body'] ?? [];
568+
569+
return $responseBody[0] ?? [];
459570
}
460571

461572
/**

tests/VCS/Adapter/GitHubTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ public function testGetPullRequest(): void
344344

345345
public function testGenerateCloneCommand(): void
346346
{
347+
\exec('rm -rf /tmp/clone-branch');
347348
$gitCloneCommand = $this->vcsAdapter->generateCloneCommand('test-kh', 'test2', 'test', GitHub::CLONE_TYPE_BRANCH, '/tmp/clone-branch', '*');
348349
$this->assertNotEmpty($gitCloneCommand);
349350
$this->assertStringContainsString('sparse-checkout', $gitCloneCommand);
@@ -358,6 +359,7 @@ public function testGenerateCloneCommand(): void
358359

359360
public function testGenerateCloneCommandWithCommitHash(): void
360361
{
362+
\exec('rm -rf /tmp/clone-commit');
361363
$gitCloneCommand = $this->vcsAdapter->generateCloneCommand('test-kh', 'test2', '4fb10447faea8a55c5cad7b5ebdfdbedca349fe4', GitHub::CLONE_TYPE_COMMIT, '/tmp/clone-commit', '*');
362364
$this->assertNotEmpty($gitCloneCommand);
363365
$this->assertStringContainsString('sparse-checkout', $gitCloneCommand);
@@ -372,6 +374,7 @@ public function testGenerateCloneCommandWithCommitHash(): void
372374

373375
public function testGenerateCloneCommandWithTag(): void
374376
{
377+
\exec('rm -rf /tmp/clone-tag /tmp/clone-tag2 /tmp/clone-tag3');
375378
$gitCloneCommand = $this->vcsAdapter->generateCloneCommand('test-kh', 'test2', '0.1.0', GitHub::CLONE_TYPE_TAG, '/tmp/clone-tag', '*');
376379
$this->assertNotEmpty($gitCloneCommand);
377380
$this->assertStringContainsString('sparse-checkout', $gitCloneCommand);
@@ -439,8 +442,8 @@ public function testGetCommit(): void
439442
public function testGetLatestCommit(): void
440443
{
441444
$commitDetails = $this->vcsAdapter->getLatestCommit('test-kh', 'test1', 'test');
442-
$this->assertSame('Khushboo Verma', $commitDetails['commitAuthor']);
443-
$this->assertSame('https://avatars.githubusercontent.com/u/43381712?v=4', $commitDetails['commitAuthorAvatar']);
444-
$this->assertSame('https://github.com/vermakhushboo', $commitDetails['commitAuthorUrl']);
445+
$this->assertSame('appwritedemoapp[bot]', $commitDetails['commitAuthor']);
446+
$this->assertSame('https://avatars.githubusercontent.com/in/287220?v=4', $commitDetails['commitAuthorAvatar']);
447+
$this->assertSame('https://github.com/apps/appwritedemoapp', $commitDetails['commitAuthorUrl']);
445448
}
446449
}

0 commit comments

Comments
 (0)