Skip to content

Commit b0d73f9

Browse files
authored
Merge pull request #71 from jaysomani/feat/gitea-git-operations
feat: Add Gitea git operations and user endpoints
2 parents fa3fa76 + 6c75665 commit b0d73f9

File tree

4 files changed

+445
-7
lines changed

4 files changed

+445
-7
lines changed

src/VCS/Adapter/Git.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,26 @@ abstract public function createPullRequest(string $owner, string $repositoryName
8383
*/
8484
abstract public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int;
8585

86+
87+
/**
88+
* Create a tag in a repository
89+
*
90+
* @param string $owner Owner of the repository
91+
* @param string $repositoryName Name of the repository
92+
* @param string $tagName Name of the tag (e.g., 'v1.0.0')
93+
* @param string $target Target commit SHA or branch name
94+
* @param string $message Tag message (optional)
95+
* @return array<mixed> Created tag details
96+
*/
97+
abstract public function createTag(string $owner, string $repositoryName, string $tagName, string $target, string $message = ''): array;
98+
99+
/**
100+
* Get commit statuses
101+
*
102+
* @param string $owner Owner of the repository
103+
* @param string $repositoryName Name of the repository
104+
* @param string $commitHash SHA of the commit
105+
* @return array<mixed> List of commit statuses
106+
*/
107+
abstract public function getCommitStatuses(string $owner, string $repositoryName, string $commitHash): array;
86108
}

src/VCS/Adapter/Git/GitHub.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,4 +1004,14 @@ public function validateWebhookEvent(string $payload, string $signature, string
10041004
{
10051005
return $signature === ('sha256=' . hash_hmac('sha256', $payload, $signatureKey));
10061006
}
1007+
1008+
public function createTag(string $owner, string $repositoryName, string $tagName, string $target, string $message = ''): array
1009+
{
1010+
throw new Exception('createTag() is not implemented for GitHub');
1011+
}
1012+
1013+
public function getCommitStatuses(string $owner, string $repositoryName, string $commitHash): array
1014+
{
1015+
throw new Exception('getCommitStatuses() is not implemented for GitHub');
1016+
}
10071017
}

src/VCS/Adapter/Git/Gitea.php

Lines changed: 178 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,19 @@ public function searchRepositories(string $owner, int $page, int $per_page, stri
212212
];
213213
}
214214

215+
/**
216+
* Get installation repository
217+
*
218+
* Note: Gitea doesn't have GitHub App installations.
219+
* This method is not applicable and throws an exception.
220+
*
221+
* @param string $repositoryName Name of the repository
222+
* @return array<mixed>
223+
* @throws Exception Always throws as installations don't exist in Gitea
224+
*/
215225
public function getInstallationRepository(string $repositoryName): array
216226
{
217-
throw new Exception("Not implemented yet");
227+
throw new Exception("getInstallationRepository is not applicable for Gitea - use getRepository() with owner and repo name instead");
218228
}
219229

220230
public function getRepository(string $owner, string $repositoryName): array
@@ -574,9 +584,25 @@ public function updateComment(string $owner, string $repositoryName, int $commen
574584
return (string) ($responseBody['id'] ?? '');
575585
}
576586

587+
/**
588+
* Get user information
589+
*
590+
* @param string $username Username to look up
591+
* @return array<mixed> User information
592+
*/
577593
public function getUser(string $username): array
578594
{
579-
throw new Exception("Not implemented yet");
595+
$url = "/users/" . rawurlencode($username);
596+
597+
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);
598+
599+
$responseHeaders = $response['headers'] ?? [];
600+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
601+
if ($responseHeadersStatusCode >= 400) {
602+
throw new Exception("Failed to get user: HTTP {$responseHeadersStatusCode}");
603+
}
604+
605+
return $response['body'] ?? [];
580606
}
581607

582608
public function getOwnerName(string $installationId, ?int $repositoryId = null): string
@@ -777,14 +803,100 @@ public function getLatestCommit(string $owner, string $repositoryName, string $b
777803
];
778804
}
779805

806+
/**
807+
* Update commit status
808+
*
809+
* @param string $repositoryName Name of the repository
810+
* @param string $commitHash SHA of the commit
811+
* @param string $owner Owner of the repository
812+
* @param string $state Status: success, error, failure, pending, warning
813+
* @param string $description Status description
814+
* @param string $target_url Target URL for status
815+
* @param string $context Status context/identifier
816+
* @return void
817+
*/
780818
public function updateCommitStatus(string $repositoryName, string $commitHash, string $owner, string $state, string $description = '', string $target_url = '', string $context = ''): void
781819
{
782-
throw new Exception("Not implemented yet");
820+
$url = "/repos/{$owner}/{$repositoryName}/statuses/{$commitHash}";
821+
822+
$body = [
823+
'state' => $state,
824+
];
825+
826+
if (!empty($description)) {
827+
$body['description'] = $description;
828+
}
829+
830+
if (!empty($target_url)) {
831+
$body['target_url'] = $target_url;
832+
}
833+
834+
if (!empty($context)) {
835+
$body['context'] = $context;
836+
}
837+
838+
$response = $this->call(self::METHOD_POST, $url, ['Authorization' => "token $this->accessToken"], $body);
839+
840+
$responseHeaders = $response['headers'] ?? [];
841+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
842+
if ($responseHeadersStatusCode >= 400) {
843+
throw new Exception("Failed to update commit status: HTTP {$responseHeadersStatusCode}");
844+
}
783845
}
784846

847+
/**
848+
* Generate git clone command
849+
*
850+
* @param string $owner Owner of the repository
851+
* @param string $repositoryName Name of the repository
852+
* @param string $version Branch name, commit hash, or tag
853+
* @param string $versionType Type: branch, commit, or tag
854+
* @param string $directory Directory to clone into
855+
* @param string $rootDirectory Root directory for sparse checkout
856+
* @return string Shell command to execute
857+
*/
785858
public function generateCloneCommand(string $owner, string $repositoryName, string $version, string $versionType, string $directory, string $rootDirectory): string
786859
{
787-
throw new Exception("Not implemented yet");
860+
$cloneUrl = "{$this->giteaUrl}/{$owner}/{$repositoryName}";
861+
if (!empty($this->accessToken)) {
862+
$cloneUrl = str_replace('://', "://{$owner}:{$this->accessToken}@", $this->giteaUrl) . "/{$owner}/{$repositoryName}";
863+
}
864+
865+
// SECURITY FIX: Escape clone URL
866+
$cloneUrl = escapeshellarg($cloneUrl);
867+
$directory = escapeshellarg($directory);
868+
$rootDirectory = escapeshellarg($rootDirectory);
869+
870+
$commands = [
871+
"mkdir -p {$directory}",
872+
"cd {$directory}",
873+
"git config --global init.defaultBranch main",
874+
"git init",
875+
"git remote add origin {$cloneUrl}",
876+
"git config core.sparseCheckout true",
877+
"echo {$rootDirectory} >> .git/info/sparse-checkout",
878+
"git config --add remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'",
879+
"git config remote.origin.tagopt --no-tags",
880+
];
881+
882+
switch ($versionType) {
883+
case self::CLONE_TYPE_BRANCH:
884+
$branchName = escapeshellarg($version);
885+
$commands[] = "if git ls-remote --exit-code --heads origin {$branchName}; then git pull --depth=1 origin {$branchName} && git checkout {$branchName}; else git checkout -b {$branchName}; fi";
886+
break;
887+
case self::CLONE_TYPE_COMMIT:
888+
$commitHash = escapeshellarg($version);
889+
$commands[] = "git fetch --depth=1 origin {$commitHash} && git checkout {$commitHash}";
890+
break;
891+
case self::CLONE_TYPE_TAG:
892+
$tagName = escapeshellarg($version);
893+
$commands[] = "git fetch --depth=1 origin refs/tags/{$version} && git checkout FETCH_HEAD";
894+
break;
895+
default:
896+
throw new Exception("Unsupported clone type: {$versionType}");
897+
}
898+
899+
return implode(' && ', $commands);
788900
}
789901

790902
/**
@@ -925,4 +1037,66 @@ public function validateWebhookEvent(string $payload, string $signature, string
9251037
{
9261038
return hash_equals($signature, hash_hmac('sha256', $payload, $signatureKey));
9271039
}
1040+
1041+
/**
1042+
* Create a tag in a repository
1043+
*
1044+
* @param string $owner Owner of the repository
1045+
* @param string $repositoryName Name of the repository
1046+
* @param string $tagName Name of the tag (e.g., 'v1.0.0')
1047+
* @param string $target Target commit SHA or branch name
1048+
* @param string $message Tag message (optional)
1049+
* @return array<mixed> Response from API
1050+
*/
1051+
public function createTag(string $owner, string $repositoryName, string $tagName, string $target, string $message = ''): array
1052+
{
1053+
$url = "/repos/{$owner}/{$repositoryName}/tags";
1054+
1055+
$payload = [
1056+
'tag_name' => $tagName,
1057+
'target' => $target,
1058+
];
1059+
1060+
if (!empty($message)) {
1061+
$payload['message'] = $message;
1062+
}
1063+
1064+
$response = $this->call(
1065+
self::METHOD_POST,
1066+
$url,
1067+
['Authorization' => "token $this->accessToken"],
1068+
$payload
1069+
);
1070+
1071+
$responseHeaders = $response['headers'] ?? [];
1072+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
1073+
if ($responseHeadersStatusCode >= 400) {
1074+
throw new Exception("Failed to create tag {$tagName}: HTTP {$responseHeadersStatusCode}");
1075+
}
1076+
1077+
return $response['body'] ?? [];
1078+
}
1079+
1080+
/**
1081+
* Get commit statuses
1082+
*
1083+
* @param string $owner Owner of the repository
1084+
* @param string $repositoryName Name of the repository
1085+
* @param string $commitHash SHA of the commit
1086+
* @return array<mixed> List of commit statuses
1087+
*/
1088+
public function getCommitStatuses(string $owner, string $repositoryName, string $commitHash): array
1089+
{
1090+
$url = "/repos/{$owner}/{$repositoryName}/commits/{$commitHash}/statuses";
1091+
1092+
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "token $this->accessToken"]);
1093+
1094+
$responseHeaders = $response['headers'] ?? [];
1095+
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
1096+
if ($responseHeadersStatusCode >= 400) {
1097+
throw new Exception("Failed to get commit statuses: HTTP {$responseHeadersStatusCode}");
1098+
}
1099+
1100+
return $response['body'] ?? [];
1101+
}
9281102
}

0 commit comments

Comments
 (0)