@@ -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 );
0 commit comments