1+ import logging
12import os
23from functools import cached_property
34from typing import Self , override
67from git import Remote
78from git import Repo as GitCLI
89from git .remote import PushInfoList
10+ from github import Github
11+ from github .PullRequest import PullRequest
912
13+ from codegen .git .clients .git_repo_client import GitRepoClient
1014from codegen .git .repo_operator .repo_operator import RepoOperator
1115from codegen .git .schemas .enums import FetchResult
16+ from codegen .git .schemas .github import GithubType
1217from codegen .git .schemas .repo_config import BaseRepoConfig
1318from codegen .git .utils .clone_url import url_to_github
1419from codegen .git .utils .file_utils import create_files
1520
21+ logger = logging .getLogger (__name__ )
22+
1623
1724class OperatorIsLocal (Exception ):
1825 """Error raised while trying to do a remote operation on a local operator"""
@@ -29,20 +36,54 @@ class LocalRepoOperator(RepoOperator):
2936 _repo_name : str
3037 _git_cli : GitCLI
3138 repo_config : BaseRepoConfig
39+ _github_api_key : str | None
40+ _remote_git_repo : GitRepoClient | None = None
3241
3342 def __init__ (
3443 self ,
3544 repo_path : str , # full path to the repo
45+ github_api_key : str | None = None ,
3646 repo_config : BaseRepoConfig | None = None ,
3747 bot_commit : bool = False ,
3848 ) -> None :
3949 self ._repo_path = repo_path
4050 self ._repo_name = os .path .basename (repo_path )
51+ self ._github_api_key = github_api_key
52+ self .github_type = GithubType .Github
53+ self ._remote_git_repo = None
4154 os .makedirs (self .repo_path , exist_ok = True )
4255 GitCLI .init (self .repo_path )
4356 repo_config = repo_config or BaseRepoConfig ()
4457 super ().__init__ (repo_config , self .repo_path , bot_commit )
4558
59+ ####################################################################################################################
60+ # PROPERTIES
61+ ####################################################################################################################
62+
63+ @property
64+ def remote_git_repo (self ) -> GitRepoClient :
65+ if self ._remote_git_repo is None :
66+ if not self ._github_api_key :
67+ return None
68+
69+ if not (base_url := self .base_url ):
70+ msg = "Could not determine GitHub URL from remotes"
71+ raise ValueError (msg )
72+
73+ # Extract owner and repo from the base URL
74+ # Format: https://github.com/owner/repo
75+ parts = base_url .split ("/" )
76+ if len (parts ) < 2 :
77+ msg = f"Invalid GitHub URL format: { base_url } "
78+ raise ValueError (msg )
79+
80+ owner = parts [- 4 ]
81+ repo = parts [- 3 ]
82+
83+ github = Github (self ._github_api_key )
84+ self ._remote_git_repo = github .get_repo (f"{ owner } /{ repo } " )
85+ return self ._remote_git_repo
86+
4687 ####################################################################################################################
4788 # CLASS METHODS
4889 ####################################################################################################################
@@ -70,9 +111,16 @@ def create_from_files(cls, repo_path: str, files: dict[str, str], bot_commit: bo
70111 return op
71112
72113 @classmethod
73- def create_from_commit (cls , repo_path : str , commit : str , url : str ) -> Self :
74- """Do a shallow checkout of a particular commit to get a repository from a given remote URL."""
75- op = cls (repo_config = BaseRepoConfig (), repo_path = repo_path , bot_commit = False )
114+ def create_from_commit (cls , repo_path : str , commit : str , url : str , github_api_key : str | None = None ) -> Self :
115+ """Do a shallow checkout of a particular commit to get a repository from a given remote URL.
116+
117+ Args:
118+ repo_path (str): Path where the repo should be cloned
119+ commit (str): The commit hash to checkout
120+ url (str): Git URL of the repository
121+ github_api_key (str | None): Optional GitHub API key for operations that need GitHub access
122+ """
123+ op = cls (repo_path = repo_path , bot_commit = False , github_api_key = github_api_key )
76124 op .discard_changes ()
77125 if op .get_active_branch_or_commit () != commit :
78126 op .create_remote ("origin" , url )
@@ -81,12 +129,13 @@ def create_from_commit(cls, repo_path: str, commit: str, url: str) -> Self:
81129 return op
82130
83131 @classmethod
84- def create_from_repo (cls , repo_path : str , url : str ) -> Self :
132+ def create_from_repo (cls , repo_path : str , url : str , github_api_key : str | None = None ) -> Self :
85133 """Create a fresh clone of a repository or use existing one if up to date.
86134
87135 Args:
88136 repo_path (str): Path where the repo should be cloned
89137 url (str): Git URL of the repository
138+ github_api_key (str | None): Optional GitHub API key for operations that need GitHub access
90139 """
91140 # Check if repo already exists
92141 if os .path .exists (repo_path ):
@@ -102,7 +151,7 @@ def create_from_repo(cls, repo_path: str, url: str) -> Self:
102151 remote_head = git_cli .remotes .origin .refs [git_cli .active_branch .name ].commit
103152 # If up to date, use existing repo
104153 if local_head .hexsha == remote_head .hexsha :
105- return cls (repo_config = BaseRepoConfig (), repo_path = repo_path , bot_commit = False )
154+ return cls (repo_path = repo_path , bot_commit = False , github_api_key = github_api_key )
106155 except Exception :
107156 # If any git operations fail, fallback to fresh clone
108157 pass
@@ -113,13 +162,13 @@ def create_from_repo(cls, repo_path: str, url: str) -> Self:
113162
114163 shutil .rmtree (repo_path )
115164
116- # Do a fresh clone with depth=1 to get latest commit
165+ # Clone the repository
117166 GitCLI .clone_from (url = url , to_path = repo_path , depth = 1 )
118167
119168 # Initialize with the cloned repo
120169 git_cli = GitCLI (repo_path )
121170
122- return cls (repo_config = BaseRepoConfig (), repo_path = repo_path , bot_commit = False )
171+ return cls (repo_path = repo_path , bot_commit = False , github_api_key = github_api_key )
123172
124173 ####################################################################################################################
125174 # PROPERTIES
@@ -153,3 +202,26 @@ def pull_repo(self) -> None:
153202
154203 def fetch_remote (self , remote_name : str = "origin" , refspec : str | None = None , force : bool = True ) -> FetchResult :
155204 raise OperatorIsLocal ()
205+
206+ def get_pull_request (self , pr_number : int ) -> PullRequest | None :
207+ """Get a GitHub Pull Request object for the given PR number.
208+
209+ Args:
210+ pr_number (int): The PR number to fetch
211+
212+ Returns:
213+ PullRequest | None: The PyGitHub PullRequest object if found, None otherwise
214+
215+ Note:
216+ This requires a GitHub API key to be set when creating the LocalRepoOperator
217+ """
218+ try :
219+ # Create GitHub client and get the PR
220+ repo = self .remote_git_repo
221+ if repo is None :
222+ logger .warning ("GitHub API key is required to fetch pull requests" )
223+ return None
224+ return repo .get_pull (pr_number )
225+ except Exception as e :
226+ logger .warning (f"Failed to get PR { pr_number } : { e !s} " )
227+ return None
0 commit comments