Skip to content

Commit db749a6

Browse files
authored
Merge pull request #94 from jaysomani/feat/gitlab-adapter-pullrequest
feat(gitlab): add getPullRequest, getPullRequestFiles, getPullRequestFromBranch, createComment, getComment, updateComment and getUser
2 parents 49d7751 + 3076066 commit db749a6

6 files changed

Lines changed: 559 additions & 28 deletions

File tree

src/VCS/Adapter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,11 @@ abstract public function getComment(string $owner, string $repositoryName, strin
182182
*
183183
* @param string $owner The owner of the repository
184184
* @param string $repositoryName The name of the repository
185-
* @param int $commentId The ID of the comment to update
185+
* @param string $commentId The ID of the comment to update
186186
* @param string $comment The updated comment content
187187
* @return string The ID of the updated comment
188188
*/
189-
abstract public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string;
189+
abstract public function updateComment(string $owner, string $repositoryName, string $commentId, string $comment): string;
190190

191191
/**
192192
* Generates a clone command using app access token

src/VCS/Adapter/Git/GitHub.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,13 +587,13 @@ public function getComment(string $owner, string $repositoryName, string $commen
587587
*
588588
* @param string $owner The owner of the repository
589589
* @param string $repositoryName The name of the repository
590-
* @param int $commentId The ID of the comment to update
590+
* @param string $commentId The ID of the comment to update
591591
* @param string $comment The updated comment content
592592
* @return string The ID of the updated comment
593593
*
594594
* @throws Exception
595595
*/
596-
public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string
596+
public function updateComment(string $owner, string $repositoryName, string $commentId, string $comment): string
597597
{
598598
$url = '/repos/' . $owner . '/' . $repositoryName . '/issues/comments/' . $commentId;
599599

src/VCS/Adapter/Git/GitLab.php

Lines changed: 189 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,22 +465,85 @@ public function createWebhook(string $owner, string $repositoryName, string $url
465465

466466
public function createComment(string $owner, string $repositoryName, int $pullRequestNumber, string $comment): string
467467
{
468-
throw new Exception("Not implemented");
468+
$ownerPath = $this->getOwnerPath($owner);
469+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
470+
$url = "/projects/{$projectPath}/merge_requests/{$pullRequestNumber}/notes";
471+
472+
$response = $this->call(self::METHOD_POST, $url, ['PRIVATE-TOKEN' => $this->accessToken], ['body' => $comment]);
473+
474+
$responseHeaders = $response['headers'] ?? [];
475+
$statusCode = $responseHeaders['status-code'] ?? 0;
476+
if ($statusCode >= 400) {
477+
throw new Exception("Failed to create comment: HTTP {$statusCode}");
478+
}
479+
480+
$responseBody = $response['body'] ?? [];
481+
if (!array_key_exists('id', $responseBody)) {
482+
throw new Exception("Comment creation response is missing comment ID.");
483+
}
484+
485+
return $pullRequestNumber . ':' . ($responseBody['id'] ?? '');
469486
}
470487

471488
public function getComment(string $owner, string $repositoryName, string $commentId): string
472489
{
473-
throw new Exception("Not implemented");
490+
$ownerPath = $this->getOwnerPath($owner);
491+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
492+
493+
$parts = explode(':', $commentId, 2);
494+
if (count($parts) !== 2) {
495+
return '';
496+
}
497+
498+
[$mrIid, $noteId] = $parts;
499+
$url = "/projects/{$projectPath}/merge_requests/{$mrIid}/notes/{$noteId}";
500+
$response = $this->call(self::METHOD_GET, $url, ['PRIVATE-TOKEN' => $this->accessToken]);
501+
502+
return $response['body']['body'] ?? '';
474503
}
475504

476-
public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string
505+
public function updateComment(string $owner, string $repositoryName, string $commentId, string $comment): string
477506
{
478-
throw new Exception("Not implemented");
507+
$ownerPath = $this->getOwnerPath($owner);
508+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
509+
510+
$parts = explode(':', $commentId, 2);
511+
if (count($parts) !== 2) {
512+
throw new Exception("Invalid comment ID format: {$commentId}");
513+
}
514+
515+
[$mrIid, $noteId] = $parts;
516+
$url = "/projects/{$projectPath}/merge_requests/{$mrIid}/notes/{$noteId}";
517+
$response = $this->call(self::METHOD_PUT, $url, ['PRIVATE-TOKEN' => $this->accessToken], ['body' => $comment]);
518+
519+
$responseHeaders = $response['headers'] ?? [];
520+
if (($responseHeaders['status-code'] ?? 0) !== 200) {
521+
throw new Exception("Failed to update comment: HTTP " . ($responseHeaders['status-code'] ?? 0));
522+
}
523+
524+
return $commentId;
479525
}
480526

481527
public function getUser(string $username): array
482528
{
483-
throw new Exception("Not implemented");
529+
$url = "/users?username=" . rawurlencode($username);
530+
531+
$response = $this->call(self::METHOD_GET, $url, ['PRIVATE-TOKEN' => $this->accessToken]);
532+
533+
$responseHeaders = $response['headers'] ?? [];
534+
$statusCode = $responseHeaders['status-code'] ?? 0;
535+
if ($statusCode >= 400) {
536+
throw new Exception("Failed to get user: HTTP {$statusCode}");
537+
}
538+
539+
$body = $response['body'] ?? [];
540+
541+
// GitLab returns an array of users — return first match
542+
if (empty($body[0])) {
543+
throw new Exception("User not found: {$username}");
544+
}
545+
546+
return $body[0];
484547
}
485548

486549
public function getOwnerName(string $installationId, ?int $repositoryId = null): string
@@ -511,17 +574,123 @@ public function getOwnerName(string $installationId, ?int $repositoryId = null):
511574

512575
public function getPullRequest(string $owner, string $repositoryName, int $pullRequestNumber): array
513576
{
514-
throw new Exception("Not implemented");
577+
$ownerPath = $this->getOwnerPath($owner);
578+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
579+
$url = "/projects/{$projectPath}/merge_requests/{$pullRequestNumber}";
580+
581+
$response = $this->call(self::METHOD_GET, $url, ['PRIVATE-TOKEN' => $this->accessToken]);
582+
583+
$responseHeaders = $response['headers'] ?? [];
584+
$statusCode = $responseHeaders['status-code'] ?? 0;
585+
if ($statusCode >= 400) {
586+
throw new Exception("Failed to get merge request: HTTP {$statusCode}");
587+
}
588+
589+
$mr = $response['body'] ?? [];
590+
591+
// Normalize to match expected shape (consistent with Gitea/GitHub)
592+
return [
593+
'number' => $mr['iid'] ?? 0,
594+
'title' => $mr['title'] ?? '',
595+
'state' => $mr['state'] ?? '',
596+
'head' => [
597+
'ref' => $mr['source_branch'] ?? '',
598+
'sha' => $mr['sha'] ?? '',
599+
],
600+
'base' => [
601+
'ref' => $mr['target_branch'] ?? '',
602+
],
603+
];
515604
}
516605

517606
public function getPullRequestFiles(string $owner, string $repositoryName, int $pullRequestNumber): array
518607
{
519-
throw new Exception("Not implemented");
608+
$ownerPath = $this->getOwnerPath($owner);
609+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
610+
611+
// Poll until diff is ready (patch_id_sha not null)
612+
$maxAttempts = 10;
613+
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
614+
$mrResponse = $this->call(
615+
self::METHOD_GET,
616+
"/projects/{$projectPath}/merge_requests/{$pullRequestNumber}",
617+
['PRIVATE-TOKEN' => $this->accessToken]
618+
);
619+
$mrBody = $mrResponse['body'] ?? [];
620+
if (($mrBody['patch_id_sha'] ?? null) !== null) {
621+
break;
622+
}
623+
usleep(1000000); // 1 second
624+
}
625+
626+
// Fetch diffs with pagination
627+
$allFiles = [];
628+
$page = 1;
629+
$perPage = 100;
630+
631+
while (true) {
632+
$url = "/projects/{$projectPath}/merge_requests/{$pullRequestNumber}/diffs?page={$page}&per_page={$perPage}";
633+
$response = $this->call(self::METHOD_GET, $url, ['PRIVATE-TOKEN' => $this->accessToken]);
634+
635+
$responseHeaders = $response['headers'] ?? [];
636+
$statusCode = $responseHeaders['status-code'] ?? 0;
637+
if ($statusCode >= 400) {
638+
throw new Exception("Failed to get merge request files: HTTP {$statusCode}");
639+
}
640+
641+
$files = $response['body'] ?? [];
642+
if (!is_array($files) || empty($files)) {
643+
break;
644+
}
645+
646+
foreach ($files as $diff) {
647+
$allFiles[] = [
648+
'filename' => $diff['new_path'] ?? $diff['old_path'] ?? '',
649+
];
650+
}
651+
652+
if (count($files) < $perPage) {
653+
break;
654+
}
655+
$page++;
656+
}
657+
658+
return $allFiles;
520659
}
521660

522661
public function getPullRequestFromBranch(string $owner, string $repositoryName, string $branch): array
523662
{
524-
throw new Exception("Not implemented");
663+
$ownerPath = $this->getOwnerPath($owner);
664+
$projectPath = urlencode("{$ownerPath}/{$repositoryName}");
665+
$url = "/projects/{$projectPath}/merge_requests?state=opened&source_branch=" . urlencode($branch);
666+
667+
$response = $this->call(self::METHOD_GET, $url, ['PRIVATE-TOKEN' => $this->accessToken]);
668+
669+
$responseHeaders = $response['headers'] ?? [];
670+
$statusCode = $responseHeaders['status-code'] ?? 0;
671+
if ($statusCode >= 400) {
672+
throw new Exception("Failed to list merge requests: HTTP {$statusCode}");
673+
}
674+
675+
$body = $response['body'] ?? [];
676+
if (empty($body[0])) {
677+
return [];
678+
}
679+
680+
$mr = $body[0];
681+
682+
return [
683+
'number' => $mr['iid'] ?? 0,
684+
'title' => $mr['title'] ?? '',
685+
'state' => $mr['state'] ?? '',
686+
'head' => [
687+
'ref' => $mr['source_branch'] ?? '',
688+
'sha' => $mr['sha'] ?? '',
689+
],
690+
'base' => [
691+
'ref' => $mr['target_branch'] ?? '',
692+
],
693+
];
525694
}
526695

527696
public function listBranches(string $owner, string $repositoryName): array
@@ -704,14 +873,24 @@ public function generateCloneCommand(string $owner, string $repositoryName, stri
704873
public function getEvent(string $event, string $payload): array
705874
{
706875
$payloadArray = json_decode($payload, true);
707-
if ($payloadArray === false || $payloadArray === null) {
876+
if ($payloadArray === null || !is_array($payloadArray)) {
708877
return [];
709878
}
710879

711880
switch ($event) {
712881
case 'Push Hook':
713882
$commits = $payloadArray['commits'] ?? [];
714-
$latestCommit = !empty($commits) ? $commits[0] : [];
883+
$checkoutSha = $payloadArray['checkout_sha'] ?? '';
884+
$latestCommit = [];
885+
foreach ($commits as $c) {
886+
if (($c['id'] ?? '') === $checkoutSha) {
887+
$latestCommit = $c;
888+
break;
889+
}
890+
}
891+
if (empty($latestCommit) && !empty($commits)) {
892+
$latestCommit = $commits[0];
893+
}
715894
$ref = $payloadArray['ref'] ?? '';
716895
// ref format: refs/heads/main
717896
$branch = str_replace('refs/heads/', '', $ref);

src/VCS/Adapter/Git/Gitea.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ public function getComment(string $owner, string $repositoryName, string $commen
569569
return $responseBody['body'] ?? '';
570570
}
571571

572-
public function updateComment(string $owner, string $repositoryName, int $commentId, string $comment): string
572+
public function updateComment(string $owner, string $repositoryName, string $commentId, string $comment): string
573573
{
574574
$url = "/repos/{$owner}/{$repositoryName}/issues/comments/{$commentId}";
575575

0 commit comments

Comments
 (0)