##// END OF EJS Templates
fix(server-info): removed dulwich from update-server-info logic, and added force-flag feature
super-admin -
r1197:bad64234 default
parent child Browse files
Show More
@@ -1,1517 +1,1518 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2023 RhodeCode GmbH
2 # Copyright (C) 2014-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import collections
18 import collections
19 import logging
19 import logging
20 import os
20 import os
21 import re
21 import re
22 import stat
22 import stat
23 import traceback
23 import traceback
24 import urllib.request
24 import urllib.request
25 import urllib.parse
25 import urllib.parse
26 import urllib.error
26 import urllib.error
27 from functools import wraps
27 from functools import wraps
28
28
29 import more_itertools
29 import more_itertools
30 import pygit2
30 import pygit2
31 from pygit2 import Repository as LibGit2Repo
31 from pygit2 import Repository as LibGit2Repo
32 from pygit2 import index as LibGit2Index
32 from pygit2 import index as LibGit2Index
33 from dulwich import index, objects
33 from dulwich import index, objects
34 from dulwich.client import HttpGitClient, LocalGitClient, FetchPackResult
34 from dulwich.client import HttpGitClient, LocalGitClient, FetchPackResult
35 from dulwich.errors import (
35 from dulwich.errors import (
36 NotGitRepository, ChecksumMismatch, WrongObjectException,
36 NotGitRepository, ChecksumMismatch, WrongObjectException,
37 MissingCommitError, ObjectMissing, HangupException,
37 MissingCommitError, ObjectMissing, HangupException,
38 UnexpectedCommandError)
38 UnexpectedCommandError)
39 from dulwich.repo import Repo as DulwichRepo
39 from dulwich.repo import Repo as DulwichRepo
40 from dulwich.server import update_server_info
41
40
42 import rhodecode
41 import rhodecode
43 from vcsserver import exceptions, settings, subprocessio
42 from vcsserver import exceptions, settings, subprocessio
44 from vcsserver.str_utils import safe_str, safe_int, safe_bytes, ascii_bytes, convert_to_str
43 from vcsserver.str_utils import safe_str, safe_int, safe_bytes, ascii_bytes, convert_to_str
45 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, store_archive_in_cache, BytesEnvelope, BinaryEnvelope
44 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, store_archive_in_cache, BytesEnvelope, BinaryEnvelope
46 from vcsserver.hgcompat import (
45 from vcsserver.hgcompat import (
47 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
46 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
48 from vcsserver.git_lfs.lib import LFSOidStore
47 from vcsserver.git_lfs.lib import LFSOidStore
49 from vcsserver.vcs_base import RemoteBase
48 from vcsserver.vcs_base import RemoteBase
50
49
51 DIR_STAT = stat.S_IFDIR
50 DIR_STAT = stat.S_IFDIR
52 FILE_MODE = stat.S_IFMT
51 FILE_MODE = stat.S_IFMT
53 GIT_LINK = objects.S_IFGITLINK
52 GIT_LINK = objects.S_IFGITLINK
54 PEELED_REF_MARKER = b'^{}'
53 PEELED_REF_MARKER = b'^{}'
55 HEAD_MARKER = b'HEAD'
54 HEAD_MARKER = b'HEAD'
56
55
57 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
58
57
59
58
60 def reraise_safe_exceptions(func):
59 def reraise_safe_exceptions(func):
61 """Converts Dulwich exceptions to something neutral."""
60 """Converts Dulwich exceptions to something neutral."""
62
61
63 @wraps(func)
62 @wraps(func)
64 def wrapper(*args, **kwargs):
63 def wrapper(*args, **kwargs):
65 try:
64 try:
66 return func(*args, **kwargs)
65 return func(*args, **kwargs)
67 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
66 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
68 exc = exceptions.LookupException(org_exc=e)
67 exc = exceptions.LookupException(org_exc=e)
69 raise exc(safe_str(e))
68 raise exc(safe_str(e))
70 except (HangupException, UnexpectedCommandError) as e:
69 except (HangupException, UnexpectedCommandError) as e:
71 exc = exceptions.VcsException(org_exc=e)
70 exc = exceptions.VcsException(org_exc=e)
72 raise exc(safe_str(e))
71 raise exc(safe_str(e))
73 except Exception:
72 except Exception:
74 # NOTE(marcink): because of how dulwich handles some exceptions
73 # NOTE(marcink): because of how dulwich handles some exceptions
75 # (KeyError on empty repos), we cannot track this and catch all
74 # (KeyError on empty repos), we cannot track this and catch all
76 # exceptions, it's an exceptions from other handlers
75 # exceptions, it's an exceptions from other handlers
77 #if not hasattr(e, '_vcs_kind'):
76 #if not hasattr(e, '_vcs_kind'):
78 #log.exception("Unhandled exception in git remote call")
77 #log.exception("Unhandled exception in git remote call")
79 #raise_from_original(exceptions.UnhandledException)
78 #raise_from_original(exceptions.UnhandledException)
80 raise
79 raise
81 return wrapper
80 return wrapper
82
81
83
82
84 class Repo(DulwichRepo):
83 class Repo(DulwichRepo):
85 """
84 """
86 A wrapper for dulwich Repo class.
85 A wrapper for dulwich Repo class.
87
86
88 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
87 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
89 "Too many open files" error. We need to close all opened file descriptors
88 "Too many open files" error. We need to close all opened file descriptors
90 once the repo object is destroyed.
89 once the repo object is destroyed.
91 """
90 """
92 def __del__(self):
91 def __del__(self):
93 if hasattr(self, 'object_store'):
92 if hasattr(self, 'object_store'):
94 self.close()
93 self.close()
95
94
96
95
97 class Repository(LibGit2Repo):
96 class Repository(LibGit2Repo):
98
97
99 def __enter__(self):
98 def __enter__(self):
100 return self
99 return self
101
100
102 def __exit__(self, exc_type, exc_val, exc_tb):
101 def __exit__(self, exc_type, exc_val, exc_tb):
103 self.free()
102 self.free()
104
103
105
104
106 class GitFactory(RepoFactory):
105 class GitFactory(RepoFactory):
107 repo_type = 'git'
106 repo_type = 'git'
108
107
109 def _create_repo(self, wire, create, use_libgit2=False):
108 def _create_repo(self, wire, create, use_libgit2=False):
110 if use_libgit2:
109 if use_libgit2:
111 repo = Repository(safe_bytes(wire['path']))
110 repo = Repository(safe_bytes(wire['path']))
112 else:
111 else:
113 # dulwich mode
112 # dulwich mode
114 repo_path = safe_str(wire['path'], to_encoding=settings.WIRE_ENCODING)
113 repo_path = safe_str(wire['path'], to_encoding=settings.WIRE_ENCODING)
115 repo = Repo(repo_path)
114 repo = Repo(repo_path)
116
115
117 log.debug('repository created: got GIT object: %s', repo)
116 log.debug('repository created: got GIT object: %s', repo)
118 return repo
117 return repo
119
118
120 def repo(self, wire, create=False, use_libgit2=False):
119 def repo(self, wire, create=False, use_libgit2=False):
121 """
120 """
122 Get a repository instance for the given path.
121 Get a repository instance for the given path.
123 """
122 """
124 return self._create_repo(wire, create, use_libgit2)
123 return self._create_repo(wire, create, use_libgit2)
125
124
126 def repo_libgit2(self, wire):
125 def repo_libgit2(self, wire):
127 return self.repo(wire, use_libgit2=True)
126 return self.repo(wire, use_libgit2=True)
128
127
129
128
130 def create_signature_from_string(author_str, **kwargs):
129 def create_signature_from_string(author_str, **kwargs):
131 """
130 """
132 Creates a pygit2.Signature object from a string of the format 'Name <email>'.
131 Creates a pygit2.Signature object from a string of the format 'Name <email>'.
133
132
134 :param author_str: String of the format 'Name <email>'
133 :param author_str: String of the format 'Name <email>'
135 :return: pygit2.Signature object
134 :return: pygit2.Signature object
136 """
135 """
137 match = re.match(r'^(.+) <(.+)>$', author_str)
136 match = re.match(r'^(.+) <(.+)>$', author_str)
138 if match is None:
137 if match is None:
139 raise ValueError(f"Invalid format: {author_str}")
138 raise ValueError(f"Invalid format: {author_str}")
140
139
141 name, email = match.groups()
140 name, email = match.groups()
142 return pygit2.Signature(name, email, **kwargs)
141 return pygit2.Signature(name, email, **kwargs)
143
142
144
143
145 def get_obfuscated_url(url_obj):
144 def get_obfuscated_url(url_obj):
146 url_obj.passwd = b'*****' if url_obj.passwd else url_obj.passwd
145 url_obj.passwd = b'*****' if url_obj.passwd else url_obj.passwd
147 url_obj.query = obfuscate_qs(url_obj.query)
146 url_obj.query = obfuscate_qs(url_obj.query)
148 obfuscated_uri = str(url_obj)
147 obfuscated_uri = str(url_obj)
149 return obfuscated_uri
148 return obfuscated_uri
150
149
151
150
152 class GitRemote(RemoteBase):
151 class GitRemote(RemoteBase):
153
152
154 def __init__(self, factory):
153 def __init__(self, factory):
155 self._factory = factory
154 self._factory = factory
156 self._bulk_methods = {
155 self._bulk_methods = {
157 "date": self.date,
156 "date": self.date,
158 "author": self.author,
157 "author": self.author,
159 "branch": self.branch,
158 "branch": self.branch,
160 "message": self.message,
159 "message": self.message,
161 "parents": self.parents,
160 "parents": self.parents,
162 "_commit": self.revision,
161 "_commit": self.revision,
163 }
162 }
164 self._bulk_file_methods = {
163 self._bulk_file_methods = {
165 "size": self.get_node_size,
164 "size": self.get_node_size,
166 "data": self.get_node_data,
165 "data": self.get_node_data,
167 "flags": self.get_node_flags,
166 "flags": self.get_node_flags,
168 "is_binary": self.get_node_is_binary,
167 "is_binary": self.get_node_is_binary,
169 "md5": self.md5_hash
168 "md5": self.md5_hash
170 }
169 }
171
170
172 def _wire_to_config(self, wire):
171 def _wire_to_config(self, wire):
173 if 'config' in wire:
172 if 'config' in wire:
174 return {x[0] + '_' + x[1]: x[2] for x in wire['config']}
173 return {x[0] + '_' + x[1]: x[2] for x in wire['config']}
175 return {}
174 return {}
176
175
177 def _remote_conf(self, config):
176 def _remote_conf(self, config):
178 params = [
177 params = [
179 '-c', 'core.askpass=""',
178 '-c', 'core.askpass=""',
180 ]
179 ]
181 config_attrs = {
180 config_attrs = {
182 'vcs_ssl_dir': 'http.sslCAinfo={}',
181 'vcs_ssl_dir': 'http.sslCAinfo={}',
183 'vcs_git_lfs_store_location': 'lfs.storage={}'
182 'vcs_git_lfs_store_location': 'lfs.storage={}'
184 }
183 }
185 for key, param in config_attrs.items():
184 for key, param in config_attrs.items():
186 if value := config.get(key):
185 if value := config.get(key):
187 params.extend(['-c', param.format(value)])
186 params.extend(['-c', param.format(value)])
188 return params
187 return params
189
188
190 @reraise_safe_exceptions
189 @reraise_safe_exceptions
191 def discover_git_version(self):
190 def discover_git_version(self):
192 stdout, _ = self.run_git_command(
191 stdout, _ = self.run_git_command(
193 {}, ['--version'], _bare=True, _safe=True)
192 {}, ['--version'], _bare=True, _safe=True)
194 prefix = b'git version'
193 prefix = b'git version'
195 if stdout.startswith(prefix):
194 if stdout.startswith(prefix):
196 stdout = stdout[len(prefix):]
195 stdout = stdout[len(prefix):]
197 return safe_str(stdout.strip())
196 return safe_str(stdout.strip())
198
197
199 @reraise_safe_exceptions
198 @reraise_safe_exceptions
200 def is_empty(self, wire):
199 def is_empty(self, wire):
201 repo_init = self._factory.repo_libgit2(wire)
200 repo_init = self._factory.repo_libgit2(wire)
202 with repo_init as repo:
201 with repo_init as repo:
203
204 try:
202 try:
205 has_head = repo.head.name
203 has_head = repo.head.name
206 if has_head:
204 if has_head:
207 return False
205 return False
208
206
209 # NOTE(marcink): check again using more expensive method
207 # NOTE(marcink): check again using more expensive method
210 return repo.is_empty
208 return repo.is_empty
211 except Exception:
209 except Exception:
212 pass
210 pass
213
211
214 return True
212 return True
215
213
216 @reraise_safe_exceptions
214 @reraise_safe_exceptions
217 def assert_correct_path(self, wire):
215 def assert_correct_path(self, wire):
218 cache_on, context_uid, repo_id = self._cache_on(wire)
216 cache_on, context_uid, repo_id = self._cache_on(wire)
219 region = self._region(wire)
217 region = self._region(wire)
220
218
221 @region.conditional_cache_on_arguments(condition=cache_on)
219 @region.conditional_cache_on_arguments(condition=cache_on)
222 def _assert_correct_path(_context_uid, _repo_id, fast_check):
220 def _assert_correct_path(_context_uid, _repo_id, fast_check):
223 if fast_check:
221 if fast_check:
224 path = safe_str(wire['path'])
222 path = safe_str(wire['path'])
225 if pygit2.discover_repository(path):
223 if pygit2.discover_repository(path):
226 return True
224 return True
227 return False
225 return False
228 else:
226 else:
229 try:
227 try:
230 repo_init = self._factory.repo_libgit2(wire)
228 repo_init = self._factory.repo_libgit2(wire)
231 with repo_init:
229 with repo_init:
232 pass
230 pass
233 except pygit2.GitError:
231 except pygit2.GitError:
234 path = wire.get('path')
232 path = wire.get('path')
235 tb = traceback.format_exc()
233 tb = traceback.format_exc()
236 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
234 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
237 return False
235 return False
238 return True
236 return True
239
237
240 return _assert_correct_path(context_uid, repo_id, True)
238 return _assert_correct_path(context_uid, repo_id, True)
241
239
242 @reraise_safe_exceptions
240 @reraise_safe_exceptions
243 def bare(self, wire):
241 def bare(self, wire):
244 repo_init = self._factory.repo_libgit2(wire)
242 repo_init = self._factory.repo_libgit2(wire)
245 with repo_init as repo:
243 with repo_init as repo:
246 return repo.is_bare
244 return repo.is_bare
247
245
248 @reraise_safe_exceptions
246 @reraise_safe_exceptions
249 def get_node_data(self, wire, commit_id, path):
247 def get_node_data(self, wire, commit_id, path):
250 repo_init = self._factory.repo_libgit2(wire)
248 repo_init = self._factory.repo_libgit2(wire)
251 with repo_init as repo:
249 with repo_init as repo:
252 commit = repo[commit_id]
250 commit = repo[commit_id]
253 blob_obj = commit.tree[path]
251 blob_obj = commit.tree[path]
254
252
255 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
253 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
256 raise exceptions.LookupException()(
254 raise exceptions.LookupException()(
257 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
255 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
258
256
259 return BytesEnvelope(blob_obj.data)
257 return BytesEnvelope(blob_obj.data)
260
258
261 @reraise_safe_exceptions
259 @reraise_safe_exceptions
262 def get_node_size(self, wire, commit_id, path):
260 def get_node_size(self, wire, commit_id, path):
263 repo_init = self._factory.repo_libgit2(wire)
261 repo_init = self._factory.repo_libgit2(wire)
264 with repo_init as repo:
262 with repo_init as repo:
265 commit = repo[commit_id]
263 commit = repo[commit_id]
266 blob_obj = commit.tree[path]
264 blob_obj = commit.tree[path]
267
265
268 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
266 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
269 raise exceptions.LookupException()(
267 raise exceptions.LookupException()(
270 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
268 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
271
269
272 return blob_obj.size
270 return blob_obj.size
273
271
274 @reraise_safe_exceptions
272 @reraise_safe_exceptions
275 def get_node_flags(self, wire, commit_id, path):
273 def get_node_flags(self, wire, commit_id, path):
276 repo_init = self._factory.repo_libgit2(wire)
274 repo_init = self._factory.repo_libgit2(wire)
277 with repo_init as repo:
275 with repo_init as repo:
278 commit = repo[commit_id]
276 commit = repo[commit_id]
279 blob_obj = commit.tree[path]
277 blob_obj = commit.tree[path]
280
278
281 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
279 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
282 raise exceptions.LookupException()(
280 raise exceptions.LookupException()(
283 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
281 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
284
282
285 return blob_obj.filemode
283 return blob_obj.filemode
286
284
287 @reraise_safe_exceptions
285 @reraise_safe_exceptions
288 def get_node_is_binary(self, wire, commit_id, path):
286 def get_node_is_binary(self, wire, commit_id, path):
289 repo_init = self._factory.repo_libgit2(wire)
287 repo_init = self._factory.repo_libgit2(wire)
290 with repo_init as repo:
288 with repo_init as repo:
291 commit = repo[commit_id]
289 commit = repo[commit_id]
292 blob_obj = commit.tree[path]
290 blob_obj = commit.tree[path]
293
291
294 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
292 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
295 raise exceptions.LookupException()(
293 raise exceptions.LookupException()(
296 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
294 f'Tree for commit_id:{commit_id} is not a blob: {blob_obj.type_str}')
297
295
298 return blob_obj.is_binary
296 return blob_obj.is_binary
299
297
300 @reraise_safe_exceptions
298 @reraise_safe_exceptions
301 def blob_as_pretty_string(self, wire, sha):
299 def blob_as_pretty_string(self, wire, sha):
302 repo_init = self._factory.repo_libgit2(wire)
300 repo_init = self._factory.repo_libgit2(wire)
303 with repo_init as repo:
301 with repo_init as repo:
304 blob_obj = repo[sha]
302 blob_obj = repo[sha]
305 return BytesEnvelope(blob_obj.data)
303 return BytesEnvelope(blob_obj.data)
306
304
307 @reraise_safe_exceptions
305 @reraise_safe_exceptions
308 def blob_raw_length(self, wire, sha):
306 def blob_raw_length(self, wire, sha):
309 cache_on, context_uid, repo_id = self._cache_on(wire)
307 cache_on, context_uid, repo_id = self._cache_on(wire)
310 region = self._region(wire)
308 region = self._region(wire)
311
309
312 @region.conditional_cache_on_arguments(condition=cache_on)
310 @region.conditional_cache_on_arguments(condition=cache_on)
313 def _blob_raw_length(_repo_id, _sha):
311 def _blob_raw_length(_repo_id, _sha):
314
312
315 repo_init = self._factory.repo_libgit2(wire)
313 repo_init = self._factory.repo_libgit2(wire)
316 with repo_init as repo:
314 with repo_init as repo:
317 blob = repo[sha]
315 blob = repo[sha]
318 return blob.size
316 return blob.size
319
317
320 return _blob_raw_length(repo_id, sha)
318 return _blob_raw_length(repo_id, sha)
321
319
322 def _parse_lfs_pointer(self, raw_content):
320 def _parse_lfs_pointer(self, raw_content):
323 spec_string = b'version https://git-lfs.github.com/spec'
321 spec_string = b'version https://git-lfs.github.com/spec'
324 if raw_content and raw_content.startswith(spec_string):
322 if raw_content and raw_content.startswith(spec_string):
325
323
326 pattern = re.compile(rb"""
324 pattern = re.compile(rb"""
327 (?:\n)?
325 (?:\n)?
328 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
326 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
329 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
327 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
330 ^size[ ](?P<oid_size>[0-9]+)\n
328 ^size[ ](?P<oid_size>[0-9]+)\n
331 (?:\n)?
329 (?:\n)?
332 """, re.VERBOSE | re.MULTILINE)
330 """, re.VERBOSE | re.MULTILINE)
333 match = pattern.match(raw_content)
331 match = pattern.match(raw_content)
334 if match:
332 if match:
335 return match.groupdict()
333 return match.groupdict()
336
334
337 return {}
335 return {}
338
336
339 @reraise_safe_exceptions
337 @reraise_safe_exceptions
340 def is_large_file(self, wire, commit_id):
338 def is_large_file(self, wire, commit_id):
341 cache_on, context_uid, repo_id = self._cache_on(wire)
339 cache_on, context_uid, repo_id = self._cache_on(wire)
342 region = self._region(wire)
340 region = self._region(wire)
343
341
344 @region.conditional_cache_on_arguments(condition=cache_on)
342 @region.conditional_cache_on_arguments(condition=cache_on)
345 def _is_large_file(_repo_id, _sha):
343 def _is_large_file(_repo_id, _sha):
346 repo_init = self._factory.repo_libgit2(wire)
344 repo_init = self._factory.repo_libgit2(wire)
347 with repo_init as repo:
345 with repo_init as repo:
348 blob = repo[commit_id]
346 blob = repo[commit_id]
349 if blob.is_binary:
347 if blob.is_binary:
350 return {}
348 return {}
351
349
352 return self._parse_lfs_pointer(blob.data)
350 return self._parse_lfs_pointer(blob.data)
353
351
354 return _is_large_file(repo_id, commit_id)
352 return _is_large_file(repo_id, commit_id)
355
353
356 @reraise_safe_exceptions
354 @reraise_safe_exceptions
357 def is_binary(self, wire, tree_id):
355 def is_binary(self, wire, tree_id):
358 cache_on, context_uid, repo_id = self._cache_on(wire)
356 cache_on, context_uid, repo_id = self._cache_on(wire)
359 region = self._region(wire)
357 region = self._region(wire)
360
358
361 @region.conditional_cache_on_arguments(condition=cache_on)
359 @region.conditional_cache_on_arguments(condition=cache_on)
362 def _is_binary(_repo_id, _tree_id):
360 def _is_binary(_repo_id, _tree_id):
363 repo_init = self._factory.repo_libgit2(wire)
361 repo_init = self._factory.repo_libgit2(wire)
364 with repo_init as repo:
362 with repo_init as repo:
365 blob_obj = repo[tree_id]
363 blob_obj = repo[tree_id]
366 return blob_obj.is_binary
364 return blob_obj.is_binary
367
365
368 return _is_binary(repo_id, tree_id)
366 return _is_binary(repo_id, tree_id)
369
367
370 @reraise_safe_exceptions
368 @reraise_safe_exceptions
371 def md5_hash(self, wire, commit_id, path):
369 def md5_hash(self, wire, commit_id, path):
372 cache_on, context_uid, repo_id = self._cache_on(wire)
370 cache_on, context_uid, repo_id = self._cache_on(wire)
373 region = self._region(wire)
371 region = self._region(wire)
374
372
375 @region.conditional_cache_on_arguments(condition=cache_on)
373 @region.conditional_cache_on_arguments(condition=cache_on)
376 def _md5_hash(_repo_id, _commit_id, _path):
374 def _md5_hash(_repo_id, _commit_id, _path):
377 repo_init = self._factory.repo_libgit2(wire)
375 repo_init = self._factory.repo_libgit2(wire)
378 with repo_init as repo:
376 with repo_init as repo:
379 commit = repo[_commit_id]
377 commit = repo[_commit_id]
380 blob_obj = commit.tree[_path]
378 blob_obj = commit.tree[_path]
381
379
382 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
380 if blob_obj.type != pygit2.GIT_OBJ_BLOB:
383 raise exceptions.LookupException()(
381 raise exceptions.LookupException()(
384 f'Tree for commit_id:{_commit_id} is not a blob: {blob_obj.type_str}')
382 f'Tree for commit_id:{_commit_id} is not a blob: {blob_obj.type_str}')
385
383
386 return ''
384 return ''
387
385
388 return _md5_hash(repo_id, commit_id, path)
386 return _md5_hash(repo_id, commit_id, path)
389
387
390 @reraise_safe_exceptions
388 @reraise_safe_exceptions
391 def in_largefiles_store(self, wire, oid):
389 def in_largefiles_store(self, wire, oid):
392 conf = self._wire_to_config(wire)
390 conf = self._wire_to_config(wire)
393 repo_init = self._factory.repo_libgit2(wire)
391 repo_init = self._factory.repo_libgit2(wire)
394 with repo_init as repo:
392 with repo_init as repo:
395 repo_name = repo.path
393 repo_name = repo.path
396
394
397 store_location = conf.get('vcs_git_lfs_store_location')
395 store_location = conf.get('vcs_git_lfs_store_location')
398 if store_location:
396 if store_location:
399
397
400 store = LFSOidStore(
398 store = LFSOidStore(
401 oid=oid, repo=repo_name, store_location=store_location)
399 oid=oid, repo=repo_name, store_location=store_location)
402 return store.has_oid()
400 return store.has_oid()
403
401
404 return False
402 return False
405
403
406 @reraise_safe_exceptions
404 @reraise_safe_exceptions
407 def store_path(self, wire, oid):
405 def store_path(self, wire, oid):
408 conf = self._wire_to_config(wire)
406 conf = self._wire_to_config(wire)
409 repo_init = self._factory.repo_libgit2(wire)
407 repo_init = self._factory.repo_libgit2(wire)
410 with repo_init as repo:
408 with repo_init as repo:
411 repo_name = repo.path
409 repo_name = repo.path
412
410
413 store_location = conf.get('vcs_git_lfs_store_location')
411 store_location = conf.get('vcs_git_lfs_store_location')
414 if store_location:
412 if store_location:
415 store = LFSOidStore(
413 store = LFSOidStore(
416 oid=oid, repo=repo_name, store_location=store_location)
414 oid=oid, repo=repo_name, store_location=store_location)
417 return store.oid_path
415 return store.oid_path
418 raise ValueError(f'Unable to fetch oid with path {oid}')
416 raise ValueError(f'Unable to fetch oid with path {oid}')
419
417
420 @reraise_safe_exceptions
418 @reraise_safe_exceptions
421 def bulk_request(self, wire, rev, pre_load):
419 def bulk_request(self, wire, rev, pre_load):
422 cache_on, context_uid, repo_id = self._cache_on(wire)
420 cache_on, context_uid, repo_id = self._cache_on(wire)
423 region = self._region(wire)
421 region = self._region(wire)
424
422
425 @region.conditional_cache_on_arguments(condition=cache_on)
423 @region.conditional_cache_on_arguments(condition=cache_on)
426 def _bulk_request(_repo_id, _rev, _pre_load):
424 def _bulk_request(_repo_id, _rev, _pre_load):
427 result = {}
425 result = {}
428 for attr in pre_load:
426 for attr in pre_load:
429 try:
427 try:
430 method = self._bulk_methods[attr]
428 method = self._bulk_methods[attr]
431 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
429 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
432 args = [wire, rev]
430 args = [wire, rev]
433 result[attr] = method(*args)
431 result[attr] = method(*args)
434 except KeyError as e:
432 except KeyError as e:
435 raise exceptions.VcsException(e)(f"Unknown bulk attribute: {attr}")
433 raise exceptions.VcsException(e)(f"Unknown bulk attribute: {attr}")
436 return result
434 return result
437
435
438 return _bulk_request(repo_id, rev, sorted(pre_load))
436 return _bulk_request(repo_id, rev, sorted(pre_load))
439
437
440 @reraise_safe_exceptions
438 @reraise_safe_exceptions
441 def bulk_file_request(self, wire, commit_id, path, pre_load):
439 def bulk_file_request(self, wire, commit_id, path, pre_load):
442 cache_on, context_uid, repo_id = self._cache_on(wire)
440 cache_on, context_uid, repo_id = self._cache_on(wire)
443 region = self._region(wire)
441 region = self._region(wire)
444
442
445 @region.conditional_cache_on_arguments(condition=cache_on)
443 @region.conditional_cache_on_arguments(condition=cache_on)
446 def _bulk_file_request(_repo_id, _commit_id, _path, _pre_load):
444 def _bulk_file_request(_repo_id, _commit_id, _path, _pre_load):
447 result = {}
445 result = {}
448 for attr in pre_load:
446 for attr in pre_load:
449 try:
447 try:
450 method = self._bulk_file_methods[attr]
448 method = self._bulk_file_methods[attr]
451 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
449 wire.update({'cache': False}) # disable cache for bulk calls so we don't double cache
452 result[attr] = method(wire, _commit_id, _path)
450 result[attr] = method(wire, _commit_id, _path)
453 except KeyError as e:
451 except KeyError as e:
454 raise exceptions.VcsException(e)(f'Unknown bulk attribute: "{attr}"')
452 raise exceptions.VcsException(e)(f'Unknown bulk attribute: "{attr}"')
455 return result
453 return result
456
454
457 return BinaryEnvelope(_bulk_file_request(repo_id, commit_id, path, sorted(pre_load)))
455 return BinaryEnvelope(_bulk_file_request(repo_id, commit_id, path, sorted(pre_load)))
458
456
459 def _build_opener(self, url: str):
457 def _build_opener(self, url: str):
460 handlers = []
458 handlers = []
461 url_obj = url_parser(safe_bytes(url))
459 url_obj = url_parser(safe_bytes(url))
462 authinfo = url_obj.authinfo()[1]
460 authinfo = url_obj.authinfo()[1]
463
461
464 if authinfo:
462 if authinfo:
465 # create a password manager
463 # create a password manager
466 passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
464 passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
467 passmgr.add_password(*convert_to_str(authinfo))
465 passmgr.add_password(*convert_to_str(authinfo))
468
466
469 handlers.extend((httpbasicauthhandler(passmgr),
467 handlers.extend((httpbasicauthhandler(passmgr),
470 httpdigestauthhandler(passmgr)))
468 httpdigestauthhandler(passmgr)))
471
469
472 return urllib.request.build_opener(*handlers)
470 return urllib.request.build_opener(*handlers)
473
471
474 @reraise_safe_exceptions
472 @reraise_safe_exceptions
475 def check_url(self, url, config):
473 def check_url(self, url, config):
476 url_obj = url_parser(safe_bytes(url))
474 url_obj = url_parser(safe_bytes(url))
477
475
478 test_uri = safe_str(url_obj.authinfo()[0])
476 test_uri = safe_str(url_obj.authinfo()[0])
479 obfuscated_uri = get_obfuscated_url(url_obj)
477 obfuscated_uri = get_obfuscated_url(url_obj)
480
478
481 log.info("Checking URL for remote cloning/import: %s", obfuscated_uri)
479 log.info("Checking URL for remote cloning/import: %s", obfuscated_uri)
482
480
483 if not test_uri.endswith('info/refs'):
481 if not test_uri.endswith('info/refs'):
484 test_uri = test_uri.rstrip('/') + '/info/refs'
482 test_uri = test_uri.rstrip('/') + '/info/refs'
485
483
486 o = self._build_opener(url=url)
484 o = self._build_opener(url=url)
487 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
485 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
488
486
489 q = {"service": 'git-upload-pack'}
487 q = {"service": 'git-upload-pack'}
490 qs = f'?{urllib.parse.urlencode(q)}'
488 qs = f'?{urllib.parse.urlencode(q)}'
491 cu = f"{test_uri}{qs}"
489 cu = f"{test_uri}{qs}"
492
490
493 try:
491 try:
494 req = urllib.request.Request(cu, None, {})
492 req = urllib.request.Request(cu, None, {})
495 log.debug("Trying to open URL %s", obfuscated_uri)
493 log.debug("Trying to open URL %s", obfuscated_uri)
496 resp = o.open(req)
494 resp = o.open(req)
497 if resp.code != 200:
495 if resp.code != 200:
498 raise exceptions.URLError()('Return Code is not 200')
496 raise exceptions.URLError()('Return Code is not 200')
499 except Exception as e:
497 except Exception as e:
500 log.warning("URL cannot be opened: %s", obfuscated_uri, exc_info=True)
498 log.warning("URL cannot be opened: %s", obfuscated_uri, exc_info=True)
501 # means it cannot be cloned
499 # means it cannot be cloned
502 raise exceptions.URLError(e)(f"[{obfuscated_uri}] org_exc: {e}")
500 raise exceptions.URLError(e)(f"[{obfuscated_uri}] org_exc: {e}")
503
501
504 # now detect if it's proper git repo
502 # now detect if it's proper git repo
505 gitdata: bytes = resp.read()
503 gitdata: bytes = resp.read()
506
504
507 if b'service=git-upload-pack' in gitdata:
505 if b'service=git-upload-pack' in gitdata:
508 pass
506 pass
509 elif re.findall(br'[0-9a-fA-F]{40}\s+refs', gitdata):
507 elif re.findall(br'[0-9a-fA-F]{40}\s+refs', gitdata):
510 # old style git can return some other format!
508 # old style git can return some other format!
511 pass
509 pass
512 else:
510 else:
513 e = None
511 e = None
514 raise exceptions.URLError(e)(
512 raise exceptions.URLError(e)(
515 f"url [{obfuscated_uri}] does not look like an hg repo org_exc: {e}")
513 f"url [{obfuscated_uri}] does not look like an hg repo org_exc: {e}")
516
514
517 return True
515 return True
518
516
519 @reraise_safe_exceptions
517 @reraise_safe_exceptions
520 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
518 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
521 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
519 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
522 remote_refs = self.pull(wire, url, apply_refs=False)
520 remote_refs = self.pull(wire, url, apply_refs=False)
523 repo = self._factory.repo(wire)
521 repo = self._factory.repo(wire)
524 if isinstance(valid_refs, list):
522 if isinstance(valid_refs, list):
525 valid_refs = tuple(valid_refs)
523 valid_refs = tuple(valid_refs)
526
524
527 for k in remote_refs:
525 for k in remote_refs:
528 # only parse heads/tags and skip so called deferred tags
526 # only parse heads/tags and skip so called deferred tags
529 if k.startswith(valid_refs) and not k.endswith(deferred):
527 if k.startswith(valid_refs) and not k.endswith(deferred):
530 repo[k] = remote_refs[k]
528 repo[k] = remote_refs[k]
531
529
532 if update_after_clone:
530 if update_after_clone:
533 # we want to checkout HEAD
531 # we want to checkout HEAD
534 repo["HEAD"] = remote_refs["HEAD"]
532 repo["HEAD"] = remote_refs["HEAD"]
535 index.build_index_from_tree(repo.path, repo.index_path(),
533 index.build_index_from_tree(repo.path, repo.index_path(),
536 repo.object_store, repo["HEAD"].tree)
534 repo.object_store, repo["HEAD"].tree)
537
535
538 @reraise_safe_exceptions
536 @reraise_safe_exceptions
539 def branch(self, wire, commit_id):
537 def branch(self, wire, commit_id):
540 cache_on, context_uid, repo_id = self._cache_on(wire)
538 cache_on, context_uid, repo_id = self._cache_on(wire)
541 region = self._region(wire)
539 region = self._region(wire)
542
540
543 @region.conditional_cache_on_arguments(condition=cache_on)
541 @region.conditional_cache_on_arguments(condition=cache_on)
544 def _branch(_context_uid, _repo_id, _commit_id):
542 def _branch(_context_uid, _repo_id, _commit_id):
545 regex = re.compile('^refs/heads')
543 regex = re.compile('^refs/heads')
546
544
547 def filter_with(ref):
545 def filter_with(ref):
548 return regex.match(ref[0]) and ref[1] == _commit_id
546 return regex.match(ref[0]) and ref[1] == _commit_id
549
547
550 branches = list(filter(filter_with, list(self.get_refs(wire).items())))
548 branches = list(filter(filter_with, list(self.get_refs(wire).items())))
551 return [x[0].split('refs/heads/')[-1] for x in branches]
549 return [x[0].split('refs/heads/')[-1] for x in branches]
552
550
553 return _branch(context_uid, repo_id, commit_id)
551 return _branch(context_uid, repo_id, commit_id)
554
552
555 @reraise_safe_exceptions
553 @reraise_safe_exceptions
556 def commit_branches(self, wire, commit_id):
554 def commit_branches(self, wire, commit_id):
557 cache_on, context_uid, repo_id = self._cache_on(wire)
555 cache_on, context_uid, repo_id = self._cache_on(wire)
558 region = self._region(wire)
556 region = self._region(wire)
559
557
560 @region.conditional_cache_on_arguments(condition=cache_on)
558 @region.conditional_cache_on_arguments(condition=cache_on)
561 def _commit_branches(_context_uid, _repo_id, _commit_id):
559 def _commit_branches(_context_uid, _repo_id, _commit_id):
562 repo_init = self._factory.repo_libgit2(wire)
560 repo_init = self._factory.repo_libgit2(wire)
563 with repo_init as repo:
561 with repo_init as repo:
564 branches = [x for x in repo.branches.with_commit(_commit_id)]
562 branches = [x for x in repo.branches.with_commit(_commit_id)]
565 return branches
563 return branches
566
564
567 return _commit_branches(context_uid, repo_id, commit_id)
565 return _commit_branches(context_uid, repo_id, commit_id)
568
566
569 @reraise_safe_exceptions
567 @reraise_safe_exceptions
570 def add_object(self, wire, content):
568 def add_object(self, wire, content):
571 repo_init = self._factory.repo_libgit2(wire)
569 repo_init = self._factory.repo_libgit2(wire)
572 with repo_init as repo:
570 with repo_init as repo:
573 blob = objects.Blob()
571 blob = objects.Blob()
574 blob.set_raw_string(content)
572 blob.set_raw_string(content)
575 repo.object_store.add_object(blob)
573 repo.object_store.add_object(blob)
576 return blob.id
574 return blob.id
577
575
578 @reraise_safe_exceptions
576 @reraise_safe_exceptions
579 def create_commit(self, wire, author, committer, message, branch, new_tree_id,
577 def create_commit(self, wire, author, committer, message, branch, new_tree_id,
580 date_args: list[int, int] = None,
578 date_args: list[int, int] = None,
581 parents: list | None = None):
579 parents: list | None = None):
582
580
583 repo_init = self._factory.repo_libgit2(wire)
581 repo_init = self._factory.repo_libgit2(wire)
584 with repo_init as repo:
582 with repo_init as repo:
585
583
586 if date_args:
584 if date_args:
587 current_time, offset = date_args
585 current_time, offset = date_args
588
586
589 kw = {
587 kw = {
590 'time': current_time,
588 'time': current_time,
591 'offset': offset
589 'offset': offset
592 }
590 }
593 author = create_signature_from_string(author, **kw)
591 author = create_signature_from_string(author, **kw)
594 committer = create_signature_from_string(committer, **kw)
592 committer = create_signature_from_string(committer, **kw)
595
593
596 tree = new_tree_id
594 tree = new_tree_id
597 if isinstance(tree, (bytes, str)):
595 if isinstance(tree, (bytes, str)):
598 # validate this tree is in the repo...
596 # validate this tree is in the repo...
599 tree = repo[safe_str(tree)].id
597 tree = repo[safe_str(tree)].id
600
598
601 if parents:
599 if parents:
602 # run via sha's and validate them in repo
600 # run via sha's and validate them in repo
603 parents = [repo[c].id for c in parents]
601 parents = [repo[c].id for c in parents]
604 else:
602 else:
605 parents = []
603 parents = []
606 # ensure we COMMIT on top of given branch head
604 # ensure we COMMIT on top of given branch head
607 # check if this repo has ANY branches, otherwise it's a new branch case we need to make
605 # check if this repo has ANY branches, otherwise it's a new branch case we need to make
608 if branch in repo.branches.local:
606 if branch in repo.branches.local:
609 parents += [repo.branches[branch].target]
607 parents += [repo.branches[branch].target]
610 elif [x for x in repo.branches.local]:
608 elif [x for x in repo.branches.local]:
611 parents += [repo.head.target]
609 parents += [repo.head.target]
612 #else:
610 #else:
613 # in case we want to commit on new branch we create it on top of HEAD
611 # in case we want to commit on new branch we create it on top of HEAD
614 #repo.branches.local.create(branch, repo.revparse_single('HEAD'))
612 #repo.branches.local.create(branch, repo.revparse_single('HEAD'))
615
613
616 # # Create a new commit
614 # # Create a new commit
617 commit_oid = repo.create_commit(
615 commit_oid = repo.create_commit(
618 f'refs/heads/{branch}', # the name of the reference to update
616 f'refs/heads/{branch}', # the name of the reference to update
619 author, # the author of the commit
617 author, # the author of the commit
620 committer, # the committer of the commit
618 committer, # the committer of the commit
621 message, # the commit message
619 message, # the commit message
622 tree, # the tree produced by the index
620 tree, # the tree produced by the index
623 parents # list of parents for the new commit, usually just one,
621 parents # list of parents for the new commit, usually just one,
624 )
622 )
625
623
626 new_commit_id = safe_str(commit_oid)
624 new_commit_id = safe_str(commit_oid)
627
625
628 return new_commit_id
626 return new_commit_id
629
627
630 @reraise_safe_exceptions
628 @reraise_safe_exceptions
631 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
629 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
632
630
633 def mode2pygit(mode):
631 def mode2pygit(mode):
634 """
632 """
635 git only supports two filemode 644 and 755
633 git only supports two filemode 644 and 755
636
634
637 0o100755 -> 33261
635 0o100755 -> 33261
638 0o100644 -> 33188
636 0o100644 -> 33188
639 """
637 """
640 return {
638 return {
641 0o100644: pygit2.GIT_FILEMODE_BLOB,
639 0o100644: pygit2.GIT_FILEMODE_BLOB,
642 0o100755: pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
640 0o100755: pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
643 0o120000: pygit2.GIT_FILEMODE_LINK
641 0o120000: pygit2.GIT_FILEMODE_LINK
644 }.get(mode) or pygit2.GIT_FILEMODE_BLOB
642 }.get(mode) or pygit2.GIT_FILEMODE_BLOB
645
643
646 repo_init = self._factory.repo_libgit2(wire)
644 repo_init = self._factory.repo_libgit2(wire)
647 with repo_init as repo:
645 with repo_init as repo:
648 repo_index = repo.index
646 repo_index = repo.index
649
647
650 commit_parents = None
648 commit_parents = None
651 if commit_tree and commit_data['parents']:
649 if commit_tree and commit_data['parents']:
652 commit_parents = commit_data['parents']
650 commit_parents = commit_data['parents']
653 parent_commit = repo[commit_parents[0]]
651 parent_commit = repo[commit_parents[0]]
654 repo_index.read_tree(parent_commit.tree)
652 repo_index.read_tree(parent_commit.tree)
655
653
656 for pathspec in updated:
654 for pathspec in updated:
657 blob_id = repo.create_blob(pathspec['content'])
655 blob_id = repo.create_blob(pathspec['content'])
658 ie = pygit2.IndexEntry(pathspec['path'], blob_id, mode2pygit(pathspec['mode']))
656 ie = pygit2.IndexEntry(pathspec['path'], blob_id, mode2pygit(pathspec['mode']))
659 repo_index.add(ie)
657 repo_index.add(ie)
660
658
661 for pathspec in removed:
659 for pathspec in removed:
662 repo_index.remove(pathspec)
660 repo_index.remove(pathspec)
663
661
664 # Write changes to the index
662 # Write changes to the index
665 repo_index.write()
663 repo_index.write()
666
664
667 # Create a tree from the updated index
665 # Create a tree from the updated index
668 written_commit_tree = repo_index.write_tree()
666 written_commit_tree = repo_index.write_tree()
669
667
670 new_tree_id = written_commit_tree
668 new_tree_id = written_commit_tree
671
669
672 author = commit_data['author']
670 author = commit_data['author']
673 committer = commit_data['committer']
671 committer = commit_data['committer']
674 message = commit_data['message']
672 message = commit_data['message']
675
673
676 date_args = [int(commit_data['commit_time']), int(commit_data['commit_timezone'])]
674 date_args = [int(commit_data['commit_time']), int(commit_data['commit_timezone'])]
677
675
678 new_commit_id = self.create_commit(wire, author, committer, message, branch,
676 new_commit_id = self.create_commit(wire, author, committer, message, branch,
679 new_tree_id, date_args=date_args, parents=commit_parents)
677 new_tree_id, date_args=date_args, parents=commit_parents)
680
678
681 # libgit2, ensure the branch is there and exists
679 # libgit2, ensure the branch is there and exists
682 self.create_branch(wire, branch, new_commit_id)
680 self.create_branch(wire, branch, new_commit_id)
683
681
684 # libgit2, set new ref to this created commit
682 # libgit2, set new ref to this created commit
685 self.set_refs(wire, f'refs/heads/{branch}', new_commit_id)
683 self.set_refs(wire, f'refs/heads/{branch}', new_commit_id)
686
684
687 return new_commit_id
685 return new_commit_id
688
686
689 @reraise_safe_exceptions
687 @reraise_safe_exceptions
690 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
688 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
691 if url != 'default' and '://' not in url:
689 if url != 'default' and '://' not in url:
692 client = LocalGitClient(url)
690 client = LocalGitClient(url)
693 else:
691 else:
694 url_obj = url_parser(safe_bytes(url))
692 url_obj = url_parser(safe_bytes(url))
695 o = self._build_opener(url)
693 o = self._build_opener(url)
696 url = url_obj.authinfo()[0]
694 url = url_obj.authinfo()[0]
697 client = HttpGitClient(base_url=url, opener=o)
695 client = HttpGitClient(base_url=url, opener=o)
698 repo = self._factory.repo(wire)
696 repo = self._factory.repo(wire)
699
697
700 determine_wants = repo.object_store.determine_wants_all
698 determine_wants = repo.object_store.determine_wants_all
701
699
702 if refs:
700 if refs:
703 refs: list[bytes] = [ascii_bytes(x) for x in refs]
701 refs: list[bytes] = [ascii_bytes(x) for x in refs]
704
702
705 def determine_wants_requested(_remote_refs):
703 def determine_wants_requested(_remote_refs):
706 determined = []
704 determined = []
707 for ref_name, ref_hash in _remote_refs.items():
705 for ref_name, ref_hash in _remote_refs.items():
708 bytes_ref_name = safe_bytes(ref_name)
706 bytes_ref_name = safe_bytes(ref_name)
709
707
710 if bytes_ref_name in refs:
708 if bytes_ref_name in refs:
711 bytes_ref_hash = safe_bytes(ref_hash)
709 bytes_ref_hash = safe_bytes(ref_hash)
712 determined.append(bytes_ref_hash)
710 determined.append(bytes_ref_hash)
713 return determined
711 return determined
714
712
715 # swap with our custom requested wants
713 # swap with our custom requested wants
716 determine_wants = determine_wants_requested
714 determine_wants = determine_wants_requested
717
715
718 try:
716 try:
719 remote_refs = client.fetch(
717 remote_refs = client.fetch(
720 path=url, target=repo, determine_wants=determine_wants)
718 path=url, target=repo, determine_wants=determine_wants)
721
719
722 except NotGitRepository as e:
720 except NotGitRepository as e:
723 log.warning(
721 log.warning(
724 'Trying to fetch from "%s" failed, not a Git repository.', url)
722 'Trying to fetch from "%s" failed, not a Git repository.', url)
725 # Exception can contain unicode which we convert
723 # Exception can contain unicode which we convert
726 raise exceptions.AbortException(e)(repr(e))
724 raise exceptions.AbortException(e)(repr(e))
727
725
728 # mikhail: client.fetch() returns all the remote refs, but fetches only
726 # mikhail: client.fetch() returns all the remote refs, but fetches only
729 # refs filtered by `determine_wants` function. We need to filter result
727 # refs filtered by `determine_wants` function. We need to filter result
730 # as well
728 # as well
731 if refs:
729 if refs:
732 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
730 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
733
731
734 if apply_refs:
732 if apply_refs:
735 # TODO: johbo: Needs proper test coverage with a git repository
733 # TODO: johbo: Needs proper test coverage with a git repository
736 # that contains a tag object, so that we would end up with
734 # that contains a tag object, so that we would end up with
737 # a peeled ref at this point.
735 # a peeled ref at this point.
738 for k in remote_refs:
736 for k in remote_refs:
739 if k.endswith(PEELED_REF_MARKER):
737 if k.endswith(PEELED_REF_MARKER):
740 log.debug("Skipping peeled reference %s", k)
738 log.debug("Skipping peeled reference %s", k)
741 continue
739 continue
742 repo[k] = remote_refs[k]
740 repo[k] = remote_refs[k]
743
741
744 if refs and not update_after:
742 if refs and not update_after:
745 # update to ref
743 # update to ref
746 # mikhail: explicitly set the head to the last ref.
744 # mikhail: explicitly set the head to the last ref.
747 update_to_ref = refs[-1]
745 update_to_ref = refs[-1]
748 if isinstance(update_after, str):
746 if isinstance(update_after, str):
749 update_to_ref = update_after
747 update_to_ref = update_after
750
748
751 repo[HEAD_MARKER] = remote_refs[update_to_ref]
749 repo[HEAD_MARKER] = remote_refs[update_to_ref]
752
750
753 if update_after:
751 if update_after:
754 # we want to check out HEAD
752 # we want to check out HEAD
755 repo[HEAD_MARKER] = remote_refs[HEAD_MARKER]
753 repo[HEAD_MARKER] = remote_refs[HEAD_MARKER]
756 index.build_index_from_tree(repo.path, repo.index_path(),
754 index.build_index_from_tree(repo.path, repo.index_path(),
757 repo.object_store, repo[HEAD_MARKER].tree)
755 repo.object_store, repo[HEAD_MARKER].tree)
758
756
759 if isinstance(remote_refs, FetchPackResult):
757 if isinstance(remote_refs, FetchPackResult):
760 return remote_refs.refs
758 return remote_refs.refs
761 return remote_refs
759 return remote_refs
762
760
763 @reraise_safe_exceptions
761 @reraise_safe_exceptions
764 def sync_fetch(self, wire, url, refs=None, all_refs=False, **kwargs):
762 def sync_fetch(self, wire, url, refs=None, all_refs=False, **kwargs):
765 self._factory.repo(wire)
763 self._factory.repo(wire)
766 if refs and not isinstance(refs, (list, tuple)):
764 if refs and not isinstance(refs, (list, tuple)):
767 refs = [refs]
765 refs = [refs]
768
766
769 config = self._wire_to_config(wire)
767 config = self._wire_to_config(wire)
770 # get all remote refs we'll use to fetch later
768 # get all remote refs we'll use to fetch later
771 cmd = ['ls-remote']
769 cmd = ['ls-remote']
772 if not all_refs:
770 if not all_refs:
773 cmd += ['--heads', '--tags']
771 cmd += ['--heads', '--tags']
774 cmd += [url]
772 cmd += [url]
775 output, __ = self.run_git_command(
773 output, __ = self.run_git_command(
776 wire, cmd, fail_on_stderr=False,
774 wire, cmd, fail_on_stderr=False,
777 _copts=self._remote_conf(config),
775 _copts=self._remote_conf(config),
778 extra_env={'GIT_TERMINAL_PROMPT': '0'})
776 extra_env={'GIT_TERMINAL_PROMPT': '0'})
779
777
780 remote_refs = collections.OrderedDict()
778 remote_refs = collections.OrderedDict()
781 fetch_refs = []
779 fetch_refs = []
782
780
783 for ref_line in output.splitlines():
781 for ref_line in output.splitlines():
784 sha, ref = ref_line.split(b'\t')
782 sha, ref = ref_line.split(b'\t')
785 sha = sha.strip()
783 sha = sha.strip()
786 if ref in remote_refs:
784 if ref in remote_refs:
787 # duplicate, skip
785 # duplicate, skip
788 continue
786 continue
789 if ref.endswith(PEELED_REF_MARKER):
787 if ref.endswith(PEELED_REF_MARKER):
790 log.debug("Skipping peeled reference %s", ref)
788 log.debug("Skipping peeled reference %s", ref)
791 continue
789 continue
792 # don't sync HEAD
790 # don't sync HEAD
793 if ref in [HEAD_MARKER]:
791 if ref in [HEAD_MARKER]:
794 continue
792 continue
795
793
796 remote_refs[ref] = sha
794 remote_refs[ref] = sha
797
795
798 if refs and sha in refs:
796 if refs and sha in refs:
799 # we filter fetch using our specified refs
797 # we filter fetch using our specified refs
800 fetch_refs.append(f'{safe_str(ref)}:{safe_str(ref)}')
798 fetch_refs.append(f'{safe_str(ref)}:{safe_str(ref)}')
801 elif not refs:
799 elif not refs:
802 fetch_refs.append(f'{safe_str(ref)}:{safe_str(ref)}')
800 fetch_refs.append(f'{safe_str(ref)}:{safe_str(ref)}')
803 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
801 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
804
802
805 if fetch_refs:
803 if fetch_refs:
806 for chunk in more_itertools.chunked(fetch_refs, 128):
804 for chunk in more_itertools.chunked(fetch_refs, 128):
807 fetch_refs_chunks = list(chunk)
805 fetch_refs_chunks = list(chunk)
808 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
806 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
809 self.run_git_command(
807 self.run_git_command(
810 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
808 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
811 fail_on_stderr=False,
809 fail_on_stderr=False,
812 _copts=self._remote_conf(config),
810 _copts=self._remote_conf(config),
813 extra_env={'GIT_TERMINAL_PROMPT': '0'})
811 extra_env={'GIT_TERMINAL_PROMPT': '0'})
814 if kwargs.get('sync_large_objects'):
812 if kwargs.get('sync_large_objects'):
815 self.run_git_command(
813 self.run_git_command(
816 wire, ['lfs', 'fetch', url, '--all'],
814 wire, ['lfs', 'fetch', url, '--all'],
817 fail_on_stderr=False,
815 fail_on_stderr=False,
818 _copts=self._remote_conf(config),
816 _copts=self._remote_conf(config),
819 )
817 )
820
818
821 return remote_refs
819 return remote_refs
822
820
823 @reraise_safe_exceptions
821 @reraise_safe_exceptions
824 def sync_push(self, wire, url, refs=None, **kwargs):
822 def sync_push(self, wire, url, refs=None, **kwargs):
825 if not self.check_url(url, wire):
823 if not self.check_url(url, wire):
826 return
824 return
827 config = self._wire_to_config(wire)
825 config = self._wire_to_config(wire)
828 self._factory.repo(wire)
826 self._factory.repo(wire)
829 self.run_git_command(
827 self.run_git_command(
830 wire, ['push', url, '--mirror'], fail_on_stderr=False,
828 wire, ['push', url, '--mirror'], fail_on_stderr=False,
831 _copts=self._remote_conf(config),
829 _copts=self._remote_conf(config),
832 extra_env={'GIT_TERMINAL_PROMPT': '0'})
830 extra_env={'GIT_TERMINAL_PROMPT': '0'})
833 if kwargs.get('sync_large_objects'):
831 if kwargs.get('sync_large_objects'):
834 self.run_git_command(
832 self.run_git_command(
835 wire, ['lfs', 'push', url, '--all'],
833 wire, ['lfs', 'push', url, '--all'],
836 fail_on_stderr=False,
834 fail_on_stderr=False,
837 _copts=self._remote_conf(config),
835 _copts=self._remote_conf(config),
838 )
836 )
839
837
840 @reraise_safe_exceptions
838 @reraise_safe_exceptions
841 def get_remote_refs(self, wire, url):
839 def get_remote_refs(self, wire, url):
842 repo = Repo(url)
840 repo = Repo(url)
843 return repo.get_refs()
841 return repo.get_refs()
844
842
845 @reraise_safe_exceptions
843 @reraise_safe_exceptions
846 def get_description(self, wire):
844 def get_description(self, wire):
847 repo = self._factory.repo(wire)
845 repo = self._factory.repo(wire)
848 return repo.get_description()
846 return repo.get_description()
849
847
850 @reraise_safe_exceptions
848 @reraise_safe_exceptions
851 def get_missing_revs(self, wire, rev1, rev2, other_repo_path):
849 def get_missing_revs(self, wire, rev1, rev2, other_repo_path):
852 origin_repo_path = wire['path']
850 origin_repo_path = wire['path']
853 repo = self._factory.repo(wire)
851 repo = self._factory.repo(wire)
854 # fetch from other_repo_path to our origin repo
852 # fetch from other_repo_path to our origin repo
855 LocalGitClient(thin_packs=False).fetch(other_repo_path, repo)
853 LocalGitClient(thin_packs=False).fetch(other_repo_path, repo)
856
854
857 wire_remote = wire.copy()
855 wire_remote = wire.copy()
858 wire_remote['path'] = other_repo_path
856 wire_remote['path'] = other_repo_path
859 repo_remote = self._factory.repo(wire_remote)
857 repo_remote = self._factory.repo(wire_remote)
860
858
861 # fetch from origin_repo_path to our remote repo
859 # fetch from origin_repo_path to our remote repo
862 LocalGitClient(thin_packs=False).fetch(origin_repo_path, repo_remote)
860 LocalGitClient(thin_packs=False).fetch(origin_repo_path, repo_remote)
863
861
864 revs = [
862 revs = [
865 x.commit.id
863 x.commit.id
866 for x in repo_remote.get_walker(include=[safe_bytes(rev2)], exclude=[safe_bytes(rev1)])]
864 for x in repo_remote.get_walker(include=[safe_bytes(rev2)], exclude=[safe_bytes(rev1)])]
867 return revs
865 return revs
868
866
869 @reraise_safe_exceptions
867 @reraise_safe_exceptions
870 def get_object(self, wire, sha, maybe_unreachable=False):
868 def get_object(self, wire, sha, maybe_unreachable=False):
871 cache_on, context_uid, repo_id = self._cache_on(wire)
869 cache_on, context_uid, repo_id = self._cache_on(wire)
872 region = self._region(wire)
870 region = self._region(wire)
873
871
874 @region.conditional_cache_on_arguments(condition=cache_on)
872 @region.conditional_cache_on_arguments(condition=cache_on)
875 def _get_object(_context_uid, _repo_id, _sha):
873 def _get_object(_context_uid, _repo_id, _sha):
876 repo_init = self._factory.repo_libgit2(wire)
874 repo_init = self._factory.repo_libgit2(wire)
877 with repo_init as repo:
875 with repo_init as repo:
878
876
879 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
877 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
880 try:
878 try:
881 commit = repo.revparse_single(sha)
879 commit = repo.revparse_single(sha)
882 except KeyError:
880 except KeyError:
883 # NOTE(marcink): KeyError doesn't give us any meaningful information
881 # NOTE(marcink): KeyError doesn't give us any meaningful information
884 # here, we instead give something more explicit
882 # here, we instead give something more explicit
885 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
883 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
886 raise exceptions.LookupException(e)(missing_commit_err)
884 raise exceptions.LookupException(e)(missing_commit_err)
887 except ValueError as e:
885 except ValueError as e:
888 raise exceptions.LookupException(e)(missing_commit_err)
886 raise exceptions.LookupException(e)(missing_commit_err)
889
887
890 is_tag = False
888 is_tag = False
891 if isinstance(commit, pygit2.Tag):
889 if isinstance(commit, pygit2.Tag):
892 commit = repo.get(commit.target)
890 commit = repo.get(commit.target)
893 is_tag = True
891 is_tag = True
894
892
895 check_dangling = True
893 check_dangling = True
896 if is_tag:
894 if is_tag:
897 check_dangling = False
895 check_dangling = False
898
896
899 if check_dangling and maybe_unreachable:
897 if check_dangling and maybe_unreachable:
900 check_dangling = False
898 check_dangling = False
901
899
902 # we used a reference and it parsed means we're not having a dangling commit
900 # we used a reference and it parsed means we're not having a dangling commit
903 if sha != commit.hex:
901 if sha != commit.hex:
904 check_dangling = False
902 check_dangling = False
905
903
906 if check_dangling:
904 if check_dangling:
907 # check for dangling commit
905 # check for dangling commit
908 for branch in repo.branches.with_commit(commit.hex):
906 for branch in repo.branches.with_commit(commit.hex):
909 if branch:
907 if branch:
910 break
908 break
911 else:
909 else:
912 # NOTE(marcink): Empty error doesn't give us any meaningful information
910 # NOTE(marcink): Empty error doesn't give us any meaningful information
913 # here, we instead give something more explicit
911 # here, we instead give something more explicit
914 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
912 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
915 raise exceptions.LookupException(e)(missing_commit_err)
913 raise exceptions.LookupException(e)(missing_commit_err)
916
914
917 commit_id = commit.hex
915 commit_id = commit.hex
918 type_str = commit.type_str
916 type_str = commit.type_str
919
917
920 return {
918 return {
921 'id': commit_id,
919 'id': commit_id,
922 'type': type_str,
920 'type': type_str,
923 'commit_id': commit_id,
921 'commit_id': commit_id,
924 'idx': 0
922 'idx': 0
925 }
923 }
926
924
927 return _get_object(context_uid, repo_id, sha)
925 return _get_object(context_uid, repo_id, sha)
928
926
929 @reraise_safe_exceptions
927 @reraise_safe_exceptions
930 def get_refs(self, wire):
928 def get_refs(self, wire):
931 cache_on, context_uid, repo_id = self._cache_on(wire)
929 cache_on, context_uid, repo_id = self._cache_on(wire)
932 region = self._region(wire)
930 region = self._region(wire)
933
931
934 @region.conditional_cache_on_arguments(condition=cache_on)
932 @region.conditional_cache_on_arguments(condition=cache_on)
935 def _get_refs(_context_uid, _repo_id):
933 def _get_refs(_context_uid, _repo_id):
936
934
937 repo_init = self._factory.repo_libgit2(wire)
935 repo_init = self._factory.repo_libgit2(wire)
938 with repo_init as repo:
936 with repo_init as repo:
939 regex = re.compile('^refs/(heads|tags)/')
937 regex = re.compile('^refs/(heads|tags)/')
940 return {x.name: x.target.hex for x in
938 return {x.name: x.target.hex for x in
941 [ref for ref in repo.listall_reference_objects() if regex.match(ref.name)]}
939 [ref for ref in repo.listall_reference_objects() if regex.match(ref.name)]}
942
940
943 return _get_refs(context_uid, repo_id)
941 return _get_refs(context_uid, repo_id)
944
942
945 @reraise_safe_exceptions
943 @reraise_safe_exceptions
946 def get_branch_pointers(self, wire):
944 def get_branch_pointers(self, wire):
947 cache_on, context_uid, repo_id = self._cache_on(wire)
945 cache_on, context_uid, repo_id = self._cache_on(wire)
948 region = self._region(wire)
946 region = self._region(wire)
949
947
950 @region.conditional_cache_on_arguments(condition=cache_on)
948 @region.conditional_cache_on_arguments(condition=cache_on)
951 def _get_branch_pointers(_context_uid, _repo_id):
949 def _get_branch_pointers(_context_uid, _repo_id):
952
950
953 repo_init = self._factory.repo_libgit2(wire)
951 repo_init = self._factory.repo_libgit2(wire)
954 regex = re.compile('^refs/heads')
952 regex = re.compile('^refs/heads')
955 with repo_init as repo:
953 with repo_init as repo:
956 branches = [ref for ref in repo.listall_reference_objects() if regex.match(ref.name)]
954 branches = [ref for ref in repo.listall_reference_objects() if regex.match(ref.name)]
957 return {x.target.hex: x.shorthand for x in branches}
955 return {x.target.hex: x.shorthand for x in branches}
958
956
959 return _get_branch_pointers(context_uid, repo_id)
957 return _get_branch_pointers(context_uid, repo_id)
960
958
961 @reraise_safe_exceptions
959 @reraise_safe_exceptions
962 def head(self, wire, show_exc=True):
960 def head(self, wire, show_exc=True):
963 cache_on, context_uid, repo_id = self._cache_on(wire)
961 cache_on, context_uid, repo_id = self._cache_on(wire)
964 region = self._region(wire)
962 region = self._region(wire)
965
963
966 @region.conditional_cache_on_arguments(condition=cache_on)
964 @region.conditional_cache_on_arguments(condition=cache_on)
967 def _head(_context_uid, _repo_id, _show_exc):
965 def _head(_context_uid, _repo_id, _show_exc):
968 repo_init = self._factory.repo_libgit2(wire)
966 repo_init = self._factory.repo_libgit2(wire)
969 with repo_init as repo:
967 with repo_init as repo:
970 try:
968 try:
971 return repo.head.peel().hex
969 return repo.head.peel().hex
972 except Exception:
970 except Exception:
973 if show_exc:
971 if show_exc:
974 raise
972 raise
975 return _head(context_uid, repo_id, show_exc)
973 return _head(context_uid, repo_id, show_exc)
976
974
977 @reraise_safe_exceptions
975 @reraise_safe_exceptions
978 def init(self, wire):
976 def init(self, wire):
979 repo_path = safe_str(wire['path'])
977 repo_path = safe_str(wire['path'])
980 os.makedirs(repo_path, mode=0o755)
978 os.makedirs(repo_path, mode=0o755)
981 pygit2.init_repository(repo_path, bare=False)
979 pygit2.init_repository(repo_path, bare=False)
982
980
983 @reraise_safe_exceptions
981 @reraise_safe_exceptions
984 def init_bare(self, wire):
982 def init_bare(self, wire):
985 repo_path = safe_str(wire['path'])
983 repo_path = safe_str(wire['path'])
986 os.makedirs(repo_path, mode=0o755)
984 os.makedirs(repo_path, mode=0o755)
987 pygit2.init_repository(repo_path, bare=True)
985 pygit2.init_repository(repo_path, bare=True)
988
986
989 @reraise_safe_exceptions
987 @reraise_safe_exceptions
990 def revision(self, wire, rev):
988 def revision(self, wire, rev):
991
989
992 cache_on, context_uid, repo_id = self._cache_on(wire)
990 cache_on, context_uid, repo_id = self._cache_on(wire)
993 region = self._region(wire)
991 region = self._region(wire)
994
992
995 @region.conditional_cache_on_arguments(condition=cache_on)
993 @region.conditional_cache_on_arguments(condition=cache_on)
996 def _revision(_context_uid, _repo_id, _rev):
994 def _revision(_context_uid, _repo_id, _rev):
997 repo_init = self._factory.repo_libgit2(wire)
995 repo_init = self._factory.repo_libgit2(wire)
998 with repo_init as repo:
996 with repo_init as repo:
999 commit = repo[rev]
997 commit = repo[rev]
1000 obj_data = {
998 obj_data = {
1001 'id': commit.id.hex,
999 'id': commit.id.hex,
1002 }
1000 }
1003 # tree objects itself don't have tree_id attribute
1001 # tree objects itself don't have tree_id attribute
1004 if hasattr(commit, 'tree_id'):
1002 if hasattr(commit, 'tree_id'):
1005 obj_data['tree'] = commit.tree_id.hex
1003 obj_data['tree'] = commit.tree_id.hex
1006
1004
1007 return obj_data
1005 return obj_data
1008 return _revision(context_uid, repo_id, rev)
1006 return _revision(context_uid, repo_id, rev)
1009
1007
1010 @reraise_safe_exceptions
1008 @reraise_safe_exceptions
1011 def date(self, wire, commit_id):
1009 def date(self, wire, commit_id):
1012 cache_on, context_uid, repo_id = self._cache_on(wire)
1010 cache_on, context_uid, repo_id = self._cache_on(wire)
1013 region = self._region(wire)
1011 region = self._region(wire)
1014
1012
1015 @region.conditional_cache_on_arguments(condition=cache_on)
1013 @region.conditional_cache_on_arguments(condition=cache_on)
1016 def _date(_repo_id, _commit_id):
1014 def _date(_repo_id, _commit_id):
1017 repo_init = self._factory.repo_libgit2(wire)
1015 repo_init = self._factory.repo_libgit2(wire)
1018 with repo_init as repo:
1016 with repo_init as repo:
1019 commit = repo[commit_id]
1017 commit = repo[commit_id]
1020
1018
1021 if hasattr(commit, 'commit_time'):
1019 if hasattr(commit, 'commit_time'):
1022 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
1020 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
1023 else:
1021 else:
1024 commit = commit.get_object()
1022 commit = commit.get_object()
1025 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
1023 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
1026
1024
1027 # TODO(marcink): check dulwich difference of offset vs timezone
1025 # TODO(marcink): check dulwich difference of offset vs timezone
1028 return [commit_time, commit_time_offset]
1026 return [commit_time, commit_time_offset]
1029 return _date(repo_id, commit_id)
1027 return _date(repo_id, commit_id)
1030
1028
1031 @reraise_safe_exceptions
1029 @reraise_safe_exceptions
1032 def author(self, wire, commit_id):
1030 def author(self, wire, commit_id):
1033 cache_on, context_uid, repo_id = self._cache_on(wire)
1031 cache_on, context_uid, repo_id = self._cache_on(wire)
1034 region = self._region(wire)
1032 region = self._region(wire)
1035
1033
1036 @region.conditional_cache_on_arguments(condition=cache_on)
1034 @region.conditional_cache_on_arguments(condition=cache_on)
1037 def _author(_repo_id, _commit_id):
1035 def _author(_repo_id, _commit_id):
1038 repo_init = self._factory.repo_libgit2(wire)
1036 repo_init = self._factory.repo_libgit2(wire)
1039 with repo_init as repo:
1037 with repo_init as repo:
1040 commit = repo[commit_id]
1038 commit = repo[commit_id]
1041
1039
1042 if hasattr(commit, 'author'):
1040 if hasattr(commit, 'author'):
1043 author = commit.author
1041 author = commit.author
1044 else:
1042 else:
1045 author = commit.get_object().author
1043 author = commit.get_object().author
1046
1044
1047 if author.email:
1045 if author.email:
1048 return f"{author.name} <{author.email}>"
1046 return f"{author.name} <{author.email}>"
1049
1047
1050 try:
1048 try:
1051 return f"{author.name}"
1049 return f"{author.name}"
1052 except Exception:
1050 except Exception:
1053 return f"{safe_str(author.raw_name)}"
1051 return f"{safe_str(author.raw_name)}"
1054
1052
1055 return _author(repo_id, commit_id)
1053 return _author(repo_id, commit_id)
1056
1054
1057 @reraise_safe_exceptions
1055 @reraise_safe_exceptions
1058 def message(self, wire, commit_id):
1056 def message(self, wire, commit_id):
1059 cache_on, context_uid, repo_id = self._cache_on(wire)
1057 cache_on, context_uid, repo_id = self._cache_on(wire)
1060 region = self._region(wire)
1058 region = self._region(wire)
1061
1059
1062 @region.conditional_cache_on_arguments(condition=cache_on)
1060 @region.conditional_cache_on_arguments(condition=cache_on)
1063 def _message(_repo_id, _commit_id):
1061 def _message(_repo_id, _commit_id):
1064 repo_init = self._factory.repo_libgit2(wire)
1062 repo_init = self._factory.repo_libgit2(wire)
1065 with repo_init as repo:
1063 with repo_init as repo:
1066 commit = repo[commit_id]
1064 commit = repo[commit_id]
1067 return commit.message
1065 return commit.message
1068 return _message(repo_id, commit_id)
1066 return _message(repo_id, commit_id)
1069
1067
1070 @reraise_safe_exceptions
1068 @reraise_safe_exceptions
1071 def parents(self, wire, commit_id):
1069 def parents(self, wire, commit_id):
1072 cache_on, context_uid, repo_id = self._cache_on(wire)
1070 cache_on, context_uid, repo_id = self._cache_on(wire)
1073 region = self._region(wire)
1071 region = self._region(wire)
1074
1072
1075 @region.conditional_cache_on_arguments(condition=cache_on)
1073 @region.conditional_cache_on_arguments(condition=cache_on)
1076 def _parents(_repo_id, _commit_id):
1074 def _parents(_repo_id, _commit_id):
1077 repo_init = self._factory.repo_libgit2(wire)
1075 repo_init = self._factory.repo_libgit2(wire)
1078 with repo_init as repo:
1076 with repo_init as repo:
1079 commit = repo[commit_id]
1077 commit = repo[commit_id]
1080 if hasattr(commit, 'parent_ids'):
1078 if hasattr(commit, 'parent_ids'):
1081 parent_ids = commit.parent_ids
1079 parent_ids = commit.parent_ids
1082 else:
1080 else:
1083 parent_ids = commit.get_object().parent_ids
1081 parent_ids = commit.get_object().parent_ids
1084
1082
1085 return [x.hex for x in parent_ids]
1083 return [x.hex for x in parent_ids]
1086 return _parents(repo_id, commit_id)
1084 return _parents(repo_id, commit_id)
1087
1085
1088 @reraise_safe_exceptions
1086 @reraise_safe_exceptions
1089 def children(self, wire, commit_id):
1087 def children(self, wire, commit_id):
1090 cache_on, context_uid, repo_id = self._cache_on(wire)
1088 cache_on, context_uid, repo_id = self._cache_on(wire)
1091 region = self._region(wire)
1089 region = self._region(wire)
1092
1090
1093 head = self.head(wire)
1091 head = self.head(wire)
1094
1092
1095 @region.conditional_cache_on_arguments(condition=cache_on)
1093 @region.conditional_cache_on_arguments(condition=cache_on)
1096 def _children(_repo_id, _commit_id):
1094 def _children(_repo_id, _commit_id):
1097
1095
1098 output, __ = self.run_git_command(
1096 output, __ = self.run_git_command(
1099 wire, ['rev-list', '--all', '--children', f'{commit_id}^..{head}'])
1097 wire, ['rev-list', '--all', '--children', f'{commit_id}^..{head}'])
1100
1098
1101 child_ids = []
1099 child_ids = []
1102 pat = re.compile(fr'^{commit_id}')
1100 pat = re.compile(fr'^{commit_id}')
1103 for line in output.splitlines():
1101 for line in output.splitlines():
1104 line = safe_str(line)
1102 line = safe_str(line)
1105 if pat.match(line):
1103 if pat.match(line):
1106 found_ids = line.split(' ')[1:]
1104 found_ids = line.split(' ')[1:]
1107 child_ids.extend(found_ids)
1105 child_ids.extend(found_ids)
1108 break
1106 break
1109
1107
1110 return child_ids
1108 return child_ids
1111 return _children(repo_id, commit_id)
1109 return _children(repo_id, commit_id)
1112
1110
1113 @reraise_safe_exceptions
1111 @reraise_safe_exceptions
1114 def set_refs(self, wire, key, value):
1112 def set_refs(self, wire, key, value):
1115 repo_init = self._factory.repo_libgit2(wire)
1113 repo_init = self._factory.repo_libgit2(wire)
1116 with repo_init as repo:
1114 with repo_init as repo:
1117 repo.references.create(key, value, force=True)
1115 repo.references.create(key, value, force=True)
1118
1116
1119 @reraise_safe_exceptions
1117 @reraise_safe_exceptions
1120 def update_refs(self, wire, key, value):
1118 def update_refs(self, wire, key, value):
1121 repo_init = self._factory.repo_libgit2(wire)
1119 repo_init = self._factory.repo_libgit2(wire)
1122 with repo_init as repo:
1120 with repo_init as repo:
1123 if key not in repo.references:
1121 if key not in repo.references:
1124 raise ValueError(f'Reference {key} not found in the repository')
1122 raise ValueError(f'Reference {key} not found in the repository')
1125 repo.references.create(key, value, force=True)
1123 repo.references.create(key, value, force=True)
1126
1124
1127 @reraise_safe_exceptions
1125 @reraise_safe_exceptions
1128 def create_branch(self, wire, branch_name, commit_id, force=False):
1126 def create_branch(self, wire, branch_name, commit_id, force=False):
1129 repo_init = self._factory.repo_libgit2(wire)
1127 repo_init = self._factory.repo_libgit2(wire)
1130 with repo_init as repo:
1128 with repo_init as repo:
1131 if commit_id:
1129 if commit_id:
1132 commit = repo[commit_id]
1130 commit = repo[commit_id]
1133 else:
1131 else:
1134 # if commit is not given just use the HEAD
1132 # if commit is not given just use the HEAD
1135 commit = repo.head()
1133 commit = repo.head()
1136
1134
1137 if force:
1135 if force:
1138 repo.branches.local.create(branch_name, commit, force=force)
1136 repo.branches.local.create(branch_name, commit, force=force)
1139 elif not repo.branches.get(branch_name):
1137 elif not repo.branches.get(branch_name):
1140 # create only if that branch isn't existing
1138 # create only if that branch isn't existing
1141 repo.branches.local.create(branch_name, commit, force=force)
1139 repo.branches.local.create(branch_name, commit, force=force)
1142
1140
1143 @reraise_safe_exceptions
1141 @reraise_safe_exceptions
1144 def remove_ref(self, wire, key):
1142 def remove_ref(self, wire, key):
1145 repo_init = self._factory.repo_libgit2(wire)
1143 repo_init = self._factory.repo_libgit2(wire)
1146 with repo_init as repo:
1144 with repo_init as repo:
1147 repo.references.delete(key)
1145 repo.references.delete(key)
1148
1146
1149 @reraise_safe_exceptions
1147 @reraise_safe_exceptions
1150 def tag_remove(self, wire, tag_name):
1148 def tag_remove(self, wire, tag_name):
1151 repo_init = self._factory.repo_libgit2(wire)
1149 repo_init = self._factory.repo_libgit2(wire)
1152 with repo_init as repo:
1150 with repo_init as repo:
1153 key = f'refs/tags/{tag_name}'
1151 key = f'refs/tags/{tag_name}'
1154 repo.references.delete(key)
1152 repo.references.delete(key)
1155
1153
1156 @reraise_safe_exceptions
1154 @reraise_safe_exceptions
1157 def tree_changes(self, wire, source_id, target_id):
1155 def tree_changes(self, wire, source_id, target_id):
1158 repo = self._factory.repo(wire)
1156 repo = self._factory.repo(wire)
1159 # source can be empty
1157 # source can be empty
1160 source_id = safe_bytes(source_id if source_id else b'')
1158 source_id = safe_bytes(source_id if source_id else b'')
1161 target_id = safe_bytes(target_id)
1159 target_id = safe_bytes(target_id)
1162
1160
1163 source = repo[source_id].tree if source_id else None
1161 source = repo[source_id].tree if source_id else None
1164 target = repo[target_id].tree
1162 target = repo[target_id].tree
1165 result = repo.object_store.tree_changes(source, target)
1163 result = repo.object_store.tree_changes(source, target)
1166
1164
1167 added = set()
1165 added = set()
1168 modified = set()
1166 modified = set()
1169 deleted = set()
1167 deleted = set()
1170 for (old_path, new_path), (_, _), (_, _) in list(result):
1168 for (old_path, new_path), (_, _), (_, _) in list(result):
1171 if new_path and old_path:
1169 if new_path and old_path:
1172 modified.add(new_path)
1170 modified.add(new_path)
1173 elif new_path and not old_path:
1171 elif new_path and not old_path:
1174 added.add(new_path)
1172 added.add(new_path)
1175 elif not new_path and old_path:
1173 elif not new_path and old_path:
1176 deleted.add(old_path)
1174 deleted.add(old_path)
1177
1175
1178 return list(added), list(modified), list(deleted)
1176 return list(added), list(modified), list(deleted)
1179
1177
1180 @reraise_safe_exceptions
1178 @reraise_safe_exceptions
1181 def tree_and_type_for_path(self, wire, commit_id, path):
1179 def tree_and_type_for_path(self, wire, commit_id, path):
1182
1180
1183 cache_on, context_uid, repo_id = self._cache_on(wire)
1181 cache_on, context_uid, repo_id = self._cache_on(wire)
1184 region = self._region(wire)
1182 region = self._region(wire)
1185
1183
1186 @region.conditional_cache_on_arguments(condition=cache_on)
1184 @region.conditional_cache_on_arguments(condition=cache_on)
1187 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
1185 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
1188 repo_init = self._factory.repo_libgit2(wire)
1186 repo_init = self._factory.repo_libgit2(wire)
1189
1187
1190 with repo_init as repo:
1188 with repo_init as repo:
1191 commit = repo[commit_id]
1189 commit = repo[commit_id]
1192 try:
1190 try:
1193 tree = commit.tree[path]
1191 tree = commit.tree[path]
1194 except KeyError:
1192 except KeyError:
1195 return None, None, None
1193 return None, None, None
1196
1194
1197 return tree.id.hex, tree.type_str, tree.filemode
1195 return tree.id.hex, tree.type_str, tree.filemode
1198 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
1196 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
1199
1197
1200 @reraise_safe_exceptions
1198 @reraise_safe_exceptions
1201 def tree_items(self, wire, tree_id):
1199 def tree_items(self, wire, tree_id):
1202 cache_on, context_uid, repo_id = self._cache_on(wire)
1200 cache_on, context_uid, repo_id = self._cache_on(wire)
1203 region = self._region(wire)
1201 region = self._region(wire)
1204
1202
1205 @region.conditional_cache_on_arguments(condition=cache_on)
1203 @region.conditional_cache_on_arguments(condition=cache_on)
1206 def _tree_items(_repo_id, _tree_id):
1204 def _tree_items(_repo_id, _tree_id):
1207
1205
1208 repo_init = self._factory.repo_libgit2(wire)
1206 repo_init = self._factory.repo_libgit2(wire)
1209 with repo_init as repo:
1207 with repo_init as repo:
1210 try:
1208 try:
1211 tree = repo[tree_id]
1209 tree = repo[tree_id]
1212 except KeyError:
1210 except KeyError:
1213 raise ObjectMissing(f'No tree with id: {tree_id}')
1211 raise ObjectMissing(f'No tree with id: {tree_id}')
1214
1212
1215 result = []
1213 result = []
1216 for item in tree:
1214 for item in tree:
1217 item_sha = item.hex
1215 item_sha = item.hex
1218 item_mode = item.filemode
1216 item_mode = item.filemode
1219 item_type = item.type_str
1217 item_type = item.type_str
1220
1218
1221 if item_type == 'commit':
1219 if item_type == 'commit':
1222 # NOTE(marcink): submodules we translate to 'link' for backward compat
1220 # NOTE(marcink): submodules we translate to 'link' for backward compat
1223 item_type = 'link'
1221 item_type = 'link'
1224
1222
1225 result.append((item.name, item_mode, item_sha, item_type))
1223 result.append((item.name, item_mode, item_sha, item_type))
1226 return result
1224 return result
1227 return _tree_items(repo_id, tree_id)
1225 return _tree_items(repo_id, tree_id)
1228
1226
1229 @reraise_safe_exceptions
1227 @reraise_safe_exceptions
1230 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1228 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1231 """
1229 """
1232 Old version that uses subprocess to call diff
1230 Old version that uses subprocess to call diff
1233 """
1231 """
1234
1232
1235 flags = [
1233 flags = [
1236 f'-U{context}', '--patch',
1234 f'-U{context}', '--patch',
1237 '--binary',
1235 '--binary',
1238 '--find-renames',
1236 '--find-renames',
1239 '--no-indent-heuristic',
1237 '--no-indent-heuristic',
1240 # '--indent-heuristic',
1238 # '--indent-heuristic',
1241 #'--full-index',
1239 #'--full-index',
1242 #'--abbrev=40'
1240 #'--abbrev=40'
1243 ]
1241 ]
1244
1242
1245 if opt_ignorews:
1243 if opt_ignorews:
1246 flags.append('--ignore-all-space')
1244 flags.append('--ignore-all-space')
1247
1245
1248 if commit_id_1 == self.EMPTY_COMMIT:
1246 if commit_id_1 == self.EMPTY_COMMIT:
1249 cmd = ['show'] + flags + [commit_id_2]
1247 cmd = ['show'] + flags + [commit_id_2]
1250 else:
1248 else:
1251 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1249 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1252
1250
1253 if file_filter:
1251 if file_filter:
1254 cmd.extend(['--', file_filter])
1252 cmd.extend(['--', file_filter])
1255
1253
1256 diff, __ = self.run_git_command(wire, cmd)
1254 diff, __ = self.run_git_command(wire, cmd)
1257 # If we used 'show' command, strip first few lines (until actual diff
1255 # If we used 'show' command, strip first few lines (until actual diff
1258 # starts)
1256 # starts)
1259 if commit_id_1 == self.EMPTY_COMMIT:
1257 if commit_id_1 == self.EMPTY_COMMIT:
1260 lines = diff.splitlines()
1258 lines = diff.splitlines()
1261 x = 0
1259 x = 0
1262 for line in lines:
1260 for line in lines:
1263 if line.startswith(b'diff'):
1261 if line.startswith(b'diff'):
1264 break
1262 break
1265 x += 1
1263 x += 1
1266 # Append new line just like 'diff' command do
1264 # Append new line just like 'diff' command do
1267 diff = '\n'.join(lines[x:]) + '\n'
1265 diff = '\n'.join(lines[x:]) + '\n'
1268 return diff
1266 return diff
1269
1267
1270 @reraise_safe_exceptions
1268 @reraise_safe_exceptions
1271 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1269 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1272 repo_init = self._factory.repo_libgit2(wire)
1270 repo_init = self._factory.repo_libgit2(wire)
1273
1271
1274 with repo_init as repo:
1272 with repo_init as repo:
1275 swap = True
1273 swap = True
1276 flags = 0
1274 flags = 0
1277 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1275 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1278
1276
1279 if opt_ignorews:
1277 if opt_ignorews:
1280 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1278 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1281
1279
1282 if commit_id_1 == self.EMPTY_COMMIT:
1280 if commit_id_1 == self.EMPTY_COMMIT:
1283 comm1 = repo[commit_id_2]
1281 comm1 = repo[commit_id_2]
1284 diff_obj = comm1.tree.diff_to_tree(
1282 diff_obj = comm1.tree.diff_to_tree(
1285 flags=flags, context_lines=context, swap=swap)
1283 flags=flags, context_lines=context, swap=swap)
1286
1284
1287 else:
1285 else:
1288 comm1 = repo[commit_id_2]
1286 comm1 = repo[commit_id_2]
1289 comm2 = repo[commit_id_1]
1287 comm2 = repo[commit_id_1]
1290 diff_obj = comm1.tree.diff_to_tree(
1288 diff_obj = comm1.tree.diff_to_tree(
1291 comm2.tree, flags=flags, context_lines=context, swap=swap)
1289 comm2.tree, flags=flags, context_lines=context, swap=swap)
1292 similar_flags = 0
1290 similar_flags = 0
1293 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1291 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1294 diff_obj.find_similar(flags=similar_flags)
1292 diff_obj.find_similar(flags=similar_flags)
1295
1293
1296 if file_filter:
1294 if file_filter:
1297 for p in diff_obj:
1295 for p in diff_obj:
1298 if p.delta.old_file.path == file_filter:
1296 if p.delta.old_file.path == file_filter:
1299 return BytesEnvelope(p.data) or BytesEnvelope(b'')
1297 return BytesEnvelope(p.data) or BytesEnvelope(b'')
1300 # fo matching path == no diff
1298 # fo matching path == no diff
1301 return BytesEnvelope(b'')
1299 return BytesEnvelope(b'')
1302
1300
1303 return BytesEnvelope(safe_bytes(diff_obj.patch)) or BytesEnvelope(b'')
1301 return BytesEnvelope(safe_bytes(diff_obj.patch)) or BytesEnvelope(b'')
1304
1302
1305 @reraise_safe_exceptions
1303 @reraise_safe_exceptions
1306 def node_history(self, wire, commit_id, path, limit):
1304 def node_history(self, wire, commit_id, path, limit):
1307 cache_on, context_uid, repo_id = self._cache_on(wire)
1305 cache_on, context_uid, repo_id = self._cache_on(wire)
1308 region = self._region(wire)
1306 region = self._region(wire)
1309
1307
1310 @region.conditional_cache_on_arguments(condition=cache_on)
1308 @region.conditional_cache_on_arguments(condition=cache_on)
1311 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1309 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1312 # optimize for n==1, rev-list is much faster for that use-case
1310 # optimize for n==1, rev-list is much faster for that use-case
1313 if limit == 1:
1311 if limit == 1:
1314 cmd = ['rev-list', '-1', commit_id, '--', path]
1312 cmd = ['rev-list', '-1', commit_id, '--', path]
1315 else:
1313 else:
1316 cmd = ['log']
1314 cmd = ['log']
1317 if limit:
1315 if limit:
1318 cmd.extend(['-n', str(safe_int(limit, 0))])
1316 cmd.extend(['-n', str(safe_int(limit, 0))])
1319 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1317 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1320
1318
1321 output, __ = self.run_git_command(wire, cmd)
1319 output, __ = self.run_git_command(wire, cmd)
1322 commit_ids = re.findall(rb'[0-9a-fA-F]{40}', output)
1320 commit_ids = re.findall(rb'[0-9a-fA-F]{40}', output)
1323
1321
1324 return [x for x in commit_ids]
1322 return [x for x in commit_ids]
1325 return _node_history(context_uid, repo_id, commit_id, path, limit)
1323 return _node_history(context_uid, repo_id, commit_id, path, limit)
1326
1324
1327 @reraise_safe_exceptions
1325 @reraise_safe_exceptions
1328 def node_annotate_legacy(self, wire, commit_id, path):
1326 def node_annotate_legacy(self, wire, commit_id, path):
1329 # note: replaced by pygit2 implementation
1327 # note: replaced by pygit2 implementation
1330 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1328 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1331 # -l ==> outputs long shas (and we need all 40 characters)
1329 # -l ==> outputs long shas (and we need all 40 characters)
1332 # --root ==> doesn't put '^' character for boundaries
1330 # --root ==> doesn't put '^' character for boundaries
1333 # -r commit_id ==> blames for the given commit
1331 # -r commit_id ==> blames for the given commit
1334 output, __ = self.run_git_command(wire, cmd)
1332 output, __ = self.run_git_command(wire, cmd)
1335
1333
1336 result = []
1334 result = []
1337 for i, blame_line in enumerate(output.splitlines()[:-1]):
1335 for i, blame_line in enumerate(output.splitlines()[:-1]):
1338 line_no = i + 1
1336 line_no = i + 1
1339 blame_commit_id, line = re.split(rb' ', blame_line, 1)
1337 blame_commit_id, line = re.split(rb' ', blame_line, 1)
1340 result.append((line_no, blame_commit_id, line))
1338 result.append((line_no, blame_commit_id, line))
1341
1339
1342 return result
1340 return result
1343
1341
1344 @reraise_safe_exceptions
1342 @reraise_safe_exceptions
1345 def node_annotate(self, wire, commit_id, path):
1343 def node_annotate(self, wire, commit_id, path):
1346
1344
1347 result_libgit = []
1345 result_libgit = []
1348 repo_init = self._factory.repo_libgit2(wire)
1346 repo_init = self._factory.repo_libgit2(wire)
1349 with repo_init as repo:
1347 with repo_init as repo:
1350 commit = repo[commit_id]
1348 commit = repo[commit_id]
1351 blame_obj = repo.blame(path, newest_commit=commit_id)
1349 blame_obj = repo.blame(path, newest_commit=commit_id)
1352 for i, line in enumerate(commit.tree[path].data.splitlines()):
1350 for i, line in enumerate(commit.tree[path].data.splitlines()):
1353 line_no = i + 1
1351 line_no = i + 1
1354 hunk = blame_obj.for_line(line_no)
1352 hunk = blame_obj.for_line(line_no)
1355 blame_commit_id = hunk.final_commit_id.hex
1353 blame_commit_id = hunk.final_commit_id.hex
1356
1354
1357 result_libgit.append((line_no, blame_commit_id, line))
1355 result_libgit.append((line_no, blame_commit_id, line))
1358
1356
1359 return BinaryEnvelope(result_libgit)
1357 return BinaryEnvelope(result_libgit)
1360
1358
1361 @reraise_safe_exceptions
1359 @reraise_safe_exceptions
1362 def update_server_info(self, wire):
1360 def update_server_info(self, wire, force=False):
1363 repo = self._factory.repo(wire)
1361 cmd = ['update-server-info']
1364 update_server_info(repo)
1362 if force:
1363 cmd += ['--force']
1364 output, __ = self.run_git_command(wire, cmd)
1365 return output.splitlines()
1365
1366
1366 @reraise_safe_exceptions
1367 @reraise_safe_exceptions
1367 def get_all_commit_ids(self, wire):
1368 def get_all_commit_ids(self, wire):
1368
1369
1369 cache_on, context_uid, repo_id = self._cache_on(wire)
1370 cache_on, context_uid, repo_id = self._cache_on(wire)
1370 region = self._region(wire)
1371 region = self._region(wire)
1371
1372
1372 @region.conditional_cache_on_arguments(condition=cache_on)
1373 @region.conditional_cache_on_arguments(condition=cache_on)
1373 def _get_all_commit_ids(_context_uid, _repo_id):
1374 def _get_all_commit_ids(_context_uid, _repo_id):
1374
1375
1375 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1376 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1376 try:
1377 try:
1377 output, __ = self.run_git_command(wire, cmd)
1378 output, __ = self.run_git_command(wire, cmd)
1378 return output.splitlines()
1379 return output.splitlines()
1379 except Exception:
1380 except Exception:
1380 # Can be raised for empty repositories
1381 # Can be raised for empty repositories
1381 return []
1382 return []
1382
1383
1383 @region.conditional_cache_on_arguments(condition=cache_on)
1384 @region.conditional_cache_on_arguments(condition=cache_on)
1384 def _get_all_commit_ids_pygit2(_context_uid, _repo_id):
1385 def _get_all_commit_ids_pygit2(_context_uid, _repo_id):
1385 repo_init = self._factory.repo_libgit2(wire)
1386 repo_init = self._factory.repo_libgit2(wire)
1386 from pygit2 import GIT_SORT_REVERSE, GIT_SORT_TIME, GIT_BRANCH_ALL
1387 from pygit2 import GIT_SORT_REVERSE, GIT_SORT_TIME, GIT_BRANCH_ALL
1387 results = []
1388 results = []
1388 with repo_init as repo:
1389 with repo_init as repo:
1389 for commit in repo.walk(repo.head.target, GIT_SORT_TIME | GIT_BRANCH_ALL | GIT_SORT_REVERSE):
1390 for commit in repo.walk(repo.head.target, GIT_SORT_TIME | GIT_BRANCH_ALL | GIT_SORT_REVERSE):
1390 results.append(commit.id.hex)
1391 results.append(commit.id.hex)
1391
1392
1392 return _get_all_commit_ids(context_uid, repo_id)
1393 return _get_all_commit_ids(context_uid, repo_id)
1393
1394
1394 @reraise_safe_exceptions
1395 @reraise_safe_exceptions
1395 def run_git_command(self, wire, cmd, **opts):
1396 def run_git_command(self, wire, cmd, **opts):
1396 path = wire.get('path', None)
1397 path = wire.get('path', None)
1397 debug_mode = rhodecode.ConfigGet().get_bool('debug')
1398 debug_mode = rhodecode.ConfigGet().get_bool('debug')
1398
1399
1399 if path and os.path.isdir(path):
1400 if path and os.path.isdir(path):
1400 opts['cwd'] = path
1401 opts['cwd'] = path
1401
1402
1402 if '_bare' in opts:
1403 if '_bare' in opts:
1403 _copts = []
1404 _copts = []
1404 del opts['_bare']
1405 del opts['_bare']
1405 else:
1406 else:
1406 _copts = ['-c', 'core.quotepath=false', '-c', 'advice.diverging=false']
1407 _copts = ['-c', 'core.quotepath=false', '-c', 'advice.diverging=false']
1407 safe_call = False
1408 safe_call = False
1408 if '_safe' in opts:
1409 if '_safe' in opts:
1409 # no exc on failure
1410 # no exc on failure
1410 del opts['_safe']
1411 del opts['_safe']
1411 safe_call = True
1412 safe_call = True
1412
1413
1413 if '_copts' in opts:
1414 if '_copts' in opts:
1414 _copts.extend(opts['_copts'] or [])
1415 _copts.extend(opts['_copts'] or [])
1415 del opts['_copts']
1416 del opts['_copts']
1416
1417
1417 gitenv = os.environ.copy()
1418 gitenv = os.environ.copy()
1418 gitenv.update(opts.pop('extra_env', {}))
1419 gitenv.update(opts.pop('extra_env', {}))
1419 # need to clean fix GIT_DIR !
1420 # need to clean fix GIT_DIR !
1420 if 'GIT_DIR' in gitenv:
1421 if 'GIT_DIR' in gitenv:
1421 del gitenv['GIT_DIR']
1422 del gitenv['GIT_DIR']
1422 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1423 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1423 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1424 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1424
1425
1425 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1426 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1426 _opts = {'env': gitenv, 'shell': False}
1427 _opts = {'env': gitenv, 'shell': False}
1427
1428
1428 proc = None
1429 proc = None
1429 try:
1430 try:
1430 _opts.update(opts)
1431 _opts.update(opts)
1431 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1432 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1432
1433
1433 return b''.join(proc), b''.join(proc.stderr)
1434 return b''.join(proc), b''.join(proc.stderr)
1434 except OSError as err:
1435 except OSError as err:
1435 cmd = ' '.join(map(safe_str, cmd)) # human friendly CMD
1436 cmd = ' '.join(map(safe_str, cmd)) # human friendly CMD
1436 call_opts = {}
1437 call_opts = {}
1437 if debug_mode:
1438 if debug_mode:
1438 call_opts = _opts
1439 call_opts = _opts
1439
1440
1440 tb_err = ("Couldn't run git command ({}).\n"
1441 tb_err = ("Couldn't run git command ({}).\n"
1441 "Original error was:{}\n"
1442 "Original error was:{}\n"
1442 "Call options:{}\n"
1443 "Call options:{}\n"
1443 .format(cmd, err, call_opts))
1444 .format(cmd, err, call_opts))
1444 log.exception(tb_err)
1445 log.exception(tb_err)
1445 if safe_call:
1446 if safe_call:
1446 return '', err
1447 return '', err
1447 else:
1448 else:
1448 raise exceptions.VcsException()(tb_err)
1449 raise exceptions.VcsException()(tb_err)
1449 finally:
1450 finally:
1450 if proc:
1451 if proc:
1451 proc.close()
1452 proc.close()
1452
1453
1453 @reraise_safe_exceptions
1454 @reraise_safe_exceptions
1454 def install_hooks(self, wire, force=False):
1455 def install_hooks(self, wire, force=False):
1455 from vcsserver.hook_utils import install_git_hooks
1456 from vcsserver.hook_utils import install_git_hooks
1456 bare = self.bare(wire)
1457 bare = self.bare(wire)
1457 path = wire['path']
1458 path = wire['path']
1458 binary_dir = settings.BINARY_DIR
1459 binary_dir = settings.BINARY_DIR
1459 if binary_dir:
1460 if binary_dir:
1460 os.path.join(binary_dir, 'python3')
1461 os.path.join(binary_dir, 'python3')
1461 return install_git_hooks(path, bare, force_create=force)
1462 return install_git_hooks(path, bare, force_create=force)
1462
1463
1463 @reraise_safe_exceptions
1464 @reraise_safe_exceptions
1464 def get_hooks_info(self, wire):
1465 def get_hooks_info(self, wire):
1465 from vcsserver.hook_utils import (
1466 from vcsserver.hook_utils import (
1466 get_git_pre_hook_version, get_git_post_hook_version)
1467 get_git_pre_hook_version, get_git_post_hook_version)
1467 bare = self.bare(wire)
1468 bare = self.bare(wire)
1468 path = wire['path']
1469 path = wire['path']
1469 return {
1470 return {
1470 'pre_version': get_git_pre_hook_version(path, bare),
1471 'pre_version': get_git_pre_hook_version(path, bare),
1471 'post_version': get_git_post_hook_version(path, bare),
1472 'post_version': get_git_post_hook_version(path, bare),
1472 }
1473 }
1473
1474
1474 @reraise_safe_exceptions
1475 @reraise_safe_exceptions
1475 def set_head_ref(self, wire, head_name):
1476 def set_head_ref(self, wire, head_name):
1476 log.debug('Setting refs/head to `%s`', head_name)
1477 log.debug('Setting refs/head to `%s`', head_name)
1477 repo_init = self._factory.repo_libgit2(wire)
1478 repo_init = self._factory.repo_libgit2(wire)
1478 with repo_init as repo:
1479 with repo_init as repo:
1479 repo.set_head(f'refs/heads/{head_name}')
1480 repo.set_head(f'refs/heads/{head_name}')
1480
1481
1481 return [head_name] + [f'set HEAD to refs/heads/{head_name}']
1482 return [head_name] + [f'set HEAD to refs/heads/{head_name}']
1482
1483
1483 @reraise_safe_exceptions
1484 @reraise_safe_exceptions
1484 def archive_repo(self, wire, archive_name_key, kind, mtime, archive_at_path,
1485 def archive_repo(self, wire, archive_name_key, kind, mtime, archive_at_path,
1485 archive_dir_name, commit_id, cache_config):
1486 archive_dir_name, commit_id, cache_config):
1486
1487
1487 def file_walker(_commit_id, path):
1488 def file_walker(_commit_id, path):
1488 repo_init = self._factory.repo_libgit2(wire)
1489 repo_init = self._factory.repo_libgit2(wire)
1489
1490
1490 with repo_init as repo:
1491 with repo_init as repo:
1491 commit = repo[commit_id]
1492 commit = repo[commit_id]
1492
1493
1493 if path in ['', '/']:
1494 if path in ['', '/']:
1494 tree = commit.tree
1495 tree = commit.tree
1495 else:
1496 else:
1496 tree = commit.tree[path.rstrip('/')]
1497 tree = commit.tree[path.rstrip('/')]
1497 tree_id = tree.id.hex
1498 tree_id = tree.id.hex
1498 try:
1499 try:
1499 tree = repo[tree_id]
1500 tree = repo[tree_id]
1500 except KeyError:
1501 except KeyError:
1501 raise ObjectMissing(f'No tree with id: {tree_id}')
1502 raise ObjectMissing(f'No tree with id: {tree_id}')
1502
1503
1503 index = LibGit2Index.Index()
1504 index = LibGit2Index.Index()
1504 index.read_tree(tree)
1505 index.read_tree(tree)
1505 file_iter = index
1506 file_iter = index
1506
1507
1507 for file_node in file_iter:
1508 for file_node in file_iter:
1508 file_path = file_node.path
1509 file_path = file_node.path
1509 mode = file_node.mode
1510 mode = file_node.mode
1510 is_link = stat.S_ISLNK(mode)
1511 is_link = stat.S_ISLNK(mode)
1511 if mode == pygit2.GIT_FILEMODE_COMMIT:
1512 if mode == pygit2.GIT_FILEMODE_COMMIT:
1512 log.debug('Skipping path %s as a commit node', file_path)
1513 log.debug('Skipping path %s as a commit node', file_path)
1513 continue
1514 continue
1514 yield ArchiveNode(file_path, mode, is_link, repo[file_node.hex].read_raw)
1515 yield ArchiveNode(file_path, mode, is_link, repo[file_node.hex].read_raw)
1515
1516
1516 return store_archive_in_cache(
1517 return store_archive_in_cache(
1517 file_walker, archive_name_key, kind, mtime, archive_at_path, archive_dir_name, commit_id, cache_config=cache_config)
1518 file_walker, archive_name_key, kind, mtime, archive_at_path, archive_dir_name, commit_id, cache_config=cache_config)
General Comments 0
You need to be logged in to leave comments. Login now