@@ -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