1
+ import logging
1
2
import os
2
3
from functools import cached_property
3
4
from typing import Self , override
6
7
from git import Remote
7
8
from git import Repo as GitCLI
8
9
from git .remote import PushInfoList
10
+ from github import Github
11
+ from github .PullRequest import PullRequest
9
12
13
+ from codegen .git .clients .git_repo_client import GitRepoClient
10
14
from codegen .git .repo_operator .repo_operator import RepoOperator
11
15
from codegen .git .schemas .enums import FetchResult
16
+ from codegen .git .schemas .github import GithubType
12
17
from codegen .git .schemas .repo_config import BaseRepoConfig
13
18
from codegen .git .utils .clone_url import url_to_github
14
19
from codegen .git .utils .file_utils import create_files
15
20
21
+ logger = logging .getLogger (__name__ )
22
+
16
23
17
24
class OperatorIsLocal (Exception ):
18
25
"""Error raised while trying to do a remote operation on a local operator"""
@@ -29,20 +36,54 @@ class LocalRepoOperator(RepoOperator):
29
36
_repo_name : str
30
37
_git_cli : GitCLI
31
38
repo_config : BaseRepoConfig
39
+ _github_api_key : str | None
40
+ _remote_git_repo : GitRepoClient | None = None
32
41
33
42
def __init__ (
34
43
self ,
35
44
repo_path : str , # full path to the repo
45
+ github_api_key : str | None = None ,
36
46
repo_config : BaseRepoConfig | None = None ,
37
47
bot_commit : bool = False ,
38
48
) -> None :
39
49
self ._repo_path = repo_path
40
50
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
41
54
os .makedirs (self .repo_path , exist_ok = True )
42
55
GitCLI .init (self .repo_path )
43
56
repo_config = repo_config or BaseRepoConfig ()
44
57
super ().__init__ (repo_config , self .repo_path , bot_commit )
45
58
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
+
46
87
####################################################################################################################
47
88
# CLASS METHODS
48
89
####################################################################################################################
@@ -70,9 +111,16 @@ def create_from_files(cls, repo_path: str, files: dict[str, str], bot_commit: bo
70
111
return op
71
112
72
113
@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 )
76
124
op .discard_changes ()
77
125
if op .get_active_branch_or_commit () != commit :
78
126
op .create_remote ("origin" , url )
@@ -81,12 +129,13 @@ def create_from_commit(cls, repo_path: str, commit: str, url: str) -> Self:
81
129
return op
82
130
83
131
@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 :
85
133
"""Create a fresh clone of a repository or use existing one if up to date.
86
134
87
135
Args:
88
136
repo_path (str): Path where the repo should be cloned
89
137
url (str): Git URL of the repository
138
+ github_api_key (str | None): Optional GitHub API key for operations that need GitHub access
90
139
"""
91
140
# Check if repo already exists
92
141
if os .path .exists (repo_path ):
@@ -102,7 +151,7 @@ def create_from_repo(cls, repo_path: str, url: str) -> Self:
102
151
remote_head = git_cli .remotes .origin .refs [git_cli .active_branch .name ].commit
103
152
# If up to date, use existing repo
104
153
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 )
106
155
except Exception :
107
156
# If any git operations fail, fallback to fresh clone
108
157
pass
@@ -113,13 +162,13 @@ def create_from_repo(cls, repo_path: str, url: str) -> Self:
113
162
114
163
shutil .rmtree (repo_path )
115
164
116
- # Do a fresh clone with depth=1 to get latest commit
165
+ # Clone the repository
117
166
GitCLI .clone_from (url = url , to_path = repo_path , depth = 1 )
118
167
119
168
# Initialize with the cloned repo
120
169
git_cli = GitCLI (repo_path )
121
170
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 )
123
172
124
173
####################################################################################################################
125
174
# PROPERTIES
@@ -153,3 +202,26 @@ def pull_repo(self) -> None:
153
202
154
203
def fetch_remote (self , remote_name : str = "origin" , refspec : str | None = None , force : bool = True ) -> FetchResult :
155
204
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