2727import textwrap
2828from pathlib import Path
2929from urllib .parse import urljoin
30+ from urllib .parse import urlparse
3031
3132from django .conf import settings
3233
4344logger = logging .getLogger (__name__ )
4445
4546
47+ def url_exists (url , timeout = 5 ):
48+ try :
49+ response = requests .head (url , timeout = timeout )
50+ response .raise_for_status ()
51+ except requests .exceptions .RequestException as request_exception :
52+ logger .debug (f"Error while checking { url } : { request_exception } " )
53+ return False
54+
55+ return response .status_code == requests .codes .ok
56+
57+
4658def is_configured ():
4759 """Return True if the required FederatedCode settings have been set."""
4860 if all (
@@ -57,19 +69,17 @@ def is_configured():
5769 return False
5870
5971
72+ def create_federatedcode_working_dir ():
73+ """Create temporary working dir for cloning federatedcode repositories."""
74+ return Path (tempfile .mkdtemp ())
75+
76+
6077def is_available ():
6178 """Return True if the configured Git account is available."""
6279 if not is_configured ():
6380 return False
6481
65- try :
66- response = requests .head (settings .FEDERATEDCODE_GIT_ACCOUNT_URL , timeout = 5 )
67- response .raise_for_status ()
68- except requests .exceptions .RequestException as request_exception :
69- logger .debug (f"FederatedCode is_available() error: { request_exception } " )
70- return False
71-
72- return response .status_code == requests .codes .ok
82+ return url_exists (settings .FEDERATEDCODE_GIT_ACCOUNT_URL )
7383
7484
7585def get_package_repository (project_purl , logger = None ):
@@ -85,7 +95,7 @@ def get_package_repository(project_purl, logger=None):
8595 )
8696 package_git_repo_url = urljoin (git_account_url , f"{ package_repo_name } .git" )
8797
88- return package_git_repo_url , package_scan_path
98+ return package_repo_name , package_git_repo_url , package_scan_path
8999
90100
91101def check_federatedcode_eligibility (project ):
@@ -146,27 +156,93 @@ def check_federatedcode_configured_and_available(logger=None):
146156 logger ("Federatedcode repositories are configured and available." )
147157
148158
149- def clone_repository (repo_url , logger = None ):
150- """Clone repository to local_path ."""
151- local_dir = tempfile . mkdtemp ( )
159+ def clone_repository (repo_url , clone_path , logger , shallow_clone = True ):
160+ """Clone repository to clone_path ."""
161+ logger ( f"Cloning repository { repo_url } " )
152162
153163 authenticated_repo_url = repo_url .replace (
154164 "https://" ,
155165 f"https://{ settings .FEDERATEDCODE_GIT_SERVICE_TOKEN } @" ,
156166 )
157- repo = Repo .clone_from (url = authenticated_repo_url , to_path = local_dir , depth = 1 )
158-
167+ clone_args = {
168+ "url" : authenticated_repo_url ,
169+ "to_path" : clone_path ,
170+ }
171+ if shallow_clone :
172+ clone_args ["depth" ] = 1
173+
174+ repo = Repo .clone_from (** clone_args )
159175 repo .config_writer (config_level = "repository" ).set_value (
160176 "user" , "name" , settings .FEDERATEDCODE_GIT_SERVICE_NAME
161177 ).release ()
162-
163178 repo .config_writer (config_level = "repository" ).set_value (
164179 "user" , "email" , settings .FEDERATEDCODE_GIT_SERVICE_EMAIL
165180 ).release ()
166181
167182 return repo
168183
169184
185+ def get_github_org (url ):
186+ """Return Org username from GitHub account URL."""
187+ github_account_url = urlparse (url )
188+ path_after_domain = github_account_url .path .lstrip ("/" )
189+ org_name = path_after_domain .split ("/" )[0 ]
190+ return org_name
191+
192+
193+ def create_repository (repo_name , clone_path , logger , shallow_clone = True ):
194+ account_url = settings .FEDERATEDCODE_GIT_ACCOUNT_URL
195+ repo_url = urljoin (account_url , repo_name )
196+ headers = {
197+ "Authorization" : f"token { settings .FEDERATEDCODE_GIT_SERVICE_TOKEN } " ,
198+ "Accept" : "application/vnd.github+json" ,
199+ }
200+
201+ data = {
202+ "name" : repo_name ,
203+ "private" : False ,
204+ "auto_init" : True ,
205+ "CC-BY-4.0" : "cc-by-4.0" ,
206+ }
207+ org_name = get_github_org (account_url )
208+ create_repo_api = f"https://api.github.com/orgs/{ org_name } /repos"
209+ response = requests .post (
210+ create_repo_api ,
211+ headers = headers ,
212+ json = data ,
213+ timeout = 5 ,
214+ )
215+ response .raise_for_status ()
216+ return clone_repository (
217+ repo_url = repo_url ,
218+ clone_path = clone_path ,
219+ shallow_clone = shallow_clone ,
220+ logger = logger ,
221+ )
222+
223+
224+ def get_or_create_repository (repo_name , working_path , logger , shallow_clone = True ):
225+ repo_url = urljoin (settings .FEDERATEDCODE_GIT_ACCOUNT_URL , repo_name )
226+ clone_path = working_path / repo_name
227+
228+ if clone_path .exists ():
229+ return False , Repo (clone_path )
230+ if url_exists (repo_url ):
231+ return False , clone_repository (
232+ repo_url = repo_url ,
233+ clone_path = clone_path ,
234+ logger = logger ,
235+ shallow_clone = shallow_clone ,
236+ )
237+
238+ return True , create_repository (
239+ repo_name = repo_name ,
240+ clone_path = clone_path ,
241+ logger = logger ,
242+ shallow_clone = shallow_clone ,
243+ )
244+
245+
170246def add_scan_result (project , repo , package_scan_file , logger = None ):
171247 """Add package scan result to the local Git repository."""
172248 relative_scan_file_path = Path (* package_scan_file .parts [1 :])
0 commit comments