@@ -628,6 +628,129 @@ async def get_fork_parent(self, project_id: str) -> Optional[str]:
628
628
f"Failed to get fork parent for { project_id } : { e } " , self .platform_name
629
629
)
630
630
631
+ async def get_merge_request_diff (
632
+ self , project_id : str , mr_id : str , ** options
633
+ ) -> Dict [str , Any ]:
634
+ """Get diff/changes for a GitLab merge request."""
635
+ if not self .client :
636
+ await self .authenticate ()
637
+
638
+ try :
639
+ project = self .client .projects .get (project_id )
640
+ mr = project .mergerequests .get (mr_id )
641
+
642
+ # Get diff format option (default: json)
643
+ diff_format = options .get ("format" , "json" )
644
+ include_diff = options .get ("include_diff" , True )
645
+
646
+ # Get changes using GitLab changes API
647
+ changes = mr .changes ()
648
+
649
+ # Initialize response structure
650
+ response = {
651
+ "mr_id" : str (mr_id ),
652
+ "total_changes" : {
653
+ "additions" : 0 ,
654
+ "deletions" : 0 ,
655
+ "files_changed" : len (changes .get ("changes" , [])),
656
+ },
657
+ "files" : [],
658
+ "diff_format" : diff_format ,
659
+ "truncated" : False ,
660
+ }
661
+
662
+ # Process file changes
663
+ for change in changes .get ("changes" , []):
664
+ file_info = {
665
+ "path" : change .get ("new_path" ) or change .get ("old_path" , "" ),
666
+ "status" : self ._get_file_status (change ),
667
+ "additions" : 0 , # GitLab doesn't provide line counts in changes
668
+ "deletions" : 0 ,
669
+ "binary" : change .get ("diff" , "" ).startswith ("Binary files" ),
670
+ }
671
+
672
+ # Include diff content if requested
673
+ if include_diff and not file_info ["binary" ]:
674
+ file_info ["diff" ] = change .get ("diff" , "" )
675
+
676
+ response ["files" ].append (file_info )
677
+
678
+ # Try to get overall stats from MR
679
+ if hasattr (mr , "changes_count" ):
680
+ response ["total_changes" ]["files_changed" ] = mr .changes_count
681
+
682
+ return response
683
+
684
+ except GitlabError as e :
685
+ if e .response_code == 404 :
686
+ raise ResourceNotFoundError ("merge_request" , mr_id , self .platform_name )
687
+ raise PlatformError (
688
+ f"Failed to get merge request diff { mr_id } : { e } " , self .platform_name
689
+ )
690
+
691
+ async def get_merge_request_commits (
692
+ self , project_id : str , mr_id : str , ** filters
693
+ ) -> Dict [str , Any ]:
694
+ """Get commits for a GitLab merge request."""
695
+ if not self .client :
696
+ await self .authenticate ()
697
+
698
+ try :
699
+ project = self .client .projects .get (project_id )
700
+ mr = project .mergerequests .get (mr_id )
701
+
702
+ # Get commits from the merge request
703
+ commits = mr .commits ()
704
+
705
+ response : Dict [str , Any ] = {
706
+ "mr_id" : str (mr_id ),
707
+ "total_commits" : len (commits ),
708
+ "commits" : [],
709
+ }
710
+
711
+ # Process each commit
712
+ for commit in commits :
713
+ commit_info = {
714
+ "sha" : commit .id ,
715
+ "message" : commit .message ,
716
+ "author" : commit .author_name ,
717
+ "authored_date" : commit .authored_date ,
718
+ "committer" : commit .committer_name ,
719
+ "committed_date" : commit .committed_date ,
720
+ "url" : commit .web_url if hasattr (commit , "web_url" ) else "" ,
721
+ }
722
+
723
+ # Add stats if available
724
+ if hasattr (commit , "stats" ):
725
+ commit_info ["additions" ] = getattr (commit .stats , "additions" , 0 )
726
+ commit_info ["deletions" ] = getattr (commit .stats , "deletions" , 0 )
727
+
728
+ response ["commits" ].append (commit_info )
729
+
730
+ return response
731
+
732
+ except GitlabError as e :
733
+ if e .response_code == 404 :
734
+ raise ResourceNotFoundError ("merge_request" , mr_id , self .platform_name )
735
+ raise PlatformError (
736
+ f"Failed to get merge request commits { mr_id } : { e } " , self .platform_name
737
+ )
738
+
739
+ def _get_file_status (self , change : Dict ) -> str :
740
+ """Determine file status from GitLab change object."""
741
+ new_file = change .get ("new_file" , False )
742
+ deleted_file = change .get ("deleted_file" , False )
743
+ renamed_file = change .get ("renamed_file" , False )
744
+
745
+ if new_file :
746
+ return "added"
747
+ elif deleted_file :
748
+ return "deleted"
749
+ elif renamed_file :
750
+ return "renamed"
751
+ else :
752
+ return "modified"
753
+
631
754
def parse_branch_reference (self , branch_ref : str ) -> Dict [str , Any ]:
632
755
"""Parse GitLab branch reference into components.
633
756
0 commit comments