##// END OF EJS Templates
libgit2: changed is_empty and introduced new way for checking tree_id for path in git
marcink -
r726:f20a6430 default
parent child Browse files
Show More
@@ -1,845 +1,863 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-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 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 import collections
17 import collections
18 import logging
18 import logging
19 import os
19 import os
20 import posixpath as vcspath
20 import posixpath as vcspath
21 import re
21 import re
22 import stat
22 import stat
23 import traceback
23 import traceback
24 import urllib
24 import urllib
25 import urllib2
25 import urllib2
26 from functools import wraps
26 from functools import wraps
27
27
28 import more_itertools
28 import more_itertools
29 import pygit2
29 import pygit2
30 from pygit2 import Repository as LibGit2Repo
30 from pygit2 import Repository as LibGit2Repo
31 from dulwich import index, objects
31 from dulwich import index, objects
32 from dulwich.client import HttpGitClient, LocalGitClient
32 from dulwich.client import HttpGitClient, LocalGitClient
33 from dulwich.errors import (
33 from dulwich.errors import (
34 NotGitRepository, ChecksumMismatch, WrongObjectException,
34 NotGitRepository, ChecksumMismatch, WrongObjectException,
35 MissingCommitError, ObjectMissing, HangupException,
35 MissingCommitError, ObjectMissing, HangupException,
36 UnexpectedCommandError)
36 UnexpectedCommandError)
37 from dulwich.repo import Repo as DulwichRepo
37 from dulwich.repo import Repo as DulwichRepo
38 from dulwich.server import update_server_info
38 from dulwich.server import update_server_info
39
39
40 from vcsserver import exceptions, settings, subprocessio
40 from vcsserver import exceptions, settings, subprocessio
41 from vcsserver.utils import safe_str
41 from vcsserver.utils import safe_str
42 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
42 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
43 from vcsserver.hgcompat import (
43 from vcsserver.hgcompat import (
44 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
44 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
45 from vcsserver.git_lfs.lib import LFSOidStore
45 from vcsserver.git_lfs.lib import LFSOidStore
46
46
47 DIR_STAT = stat.S_IFDIR
47 DIR_STAT = stat.S_IFDIR
48 FILE_MODE = stat.S_IFMT
48 FILE_MODE = stat.S_IFMT
49 GIT_LINK = objects.S_IFGITLINK
49 GIT_LINK = objects.S_IFGITLINK
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def reraise_safe_exceptions(func):
54 def reraise_safe_exceptions(func):
55 """Converts Dulwich exceptions to something neutral."""
55 """Converts Dulwich exceptions to something neutral."""
56
56
57 @wraps(func)
57 @wraps(func)
58 def wrapper(*args, **kwargs):
58 def wrapper(*args, **kwargs):
59 try:
59 try:
60 return func(*args, **kwargs)
60 return func(*args, **kwargs)
61 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
61 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
62 exc = exceptions.LookupException(org_exc=e)
62 exc = exceptions.LookupException(org_exc=e)
63 raise exc(safe_str(e))
63 raise exc(safe_str(e))
64 except (HangupException, UnexpectedCommandError) as e:
64 except (HangupException, UnexpectedCommandError) as e:
65 exc = exceptions.VcsException(org_exc=e)
65 exc = exceptions.VcsException(org_exc=e)
66 raise exc(safe_str(e))
66 raise exc(safe_str(e))
67 except Exception as e:
67 except Exception as e:
68 # NOTE(marcink): becuase of how dulwich handles some exceptions
68 # NOTE(marcink): becuase of how dulwich handles some exceptions
69 # (KeyError on empty repos), we cannot track this and catch all
69 # (KeyError on empty repos), we cannot track this and catch all
70 # exceptions, it's an exceptions from other handlers
70 # exceptions, it's an exceptions from other handlers
71 #if not hasattr(e, '_vcs_kind'):
71 #if not hasattr(e, '_vcs_kind'):
72 #log.exception("Unhandled exception in git remote call")
72 #log.exception("Unhandled exception in git remote call")
73 #raise_from_original(exceptions.UnhandledException)
73 #raise_from_original(exceptions.UnhandledException)
74 raise
74 raise
75 return wrapper
75 return wrapper
76
76
77
77
78 class Repo(DulwichRepo):
78 class Repo(DulwichRepo):
79 """
79 """
80 A wrapper for dulwich Repo class.
80 A wrapper for dulwich Repo class.
81
81
82 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
82 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
83 "Too many open files" error. We need to close all opened file descriptors
83 "Too many open files" error. We need to close all opened file descriptors
84 once the repo object is destroyed.
84 once the repo object is destroyed.
85 """
85 """
86 def __del__(self):
86 def __del__(self):
87 if hasattr(self, 'object_store'):
87 if hasattr(self, 'object_store'):
88 self.close()
88 self.close()
89
89
90
90
91 class Repository(LibGit2Repo):
91 class Repository(LibGit2Repo):
92
92
93 def __enter__(self):
93 def __enter__(self):
94 return self
94 return self
95
95
96 def __exit__(self, exc_type, exc_val, exc_tb):
96 def __exit__(self, exc_type, exc_val, exc_tb):
97 self.free()
97 self.free()
98
98
99
99
100 class GitFactory(RepoFactory):
100 class GitFactory(RepoFactory):
101 repo_type = 'git'
101 repo_type = 'git'
102
102
103 def _create_repo(self, wire, create, use_libgit2=False):
103 def _create_repo(self, wire, create, use_libgit2=False):
104 if use_libgit2:
104 if use_libgit2:
105 return Repository(wire['path'])
105 return Repository(wire['path'])
106 else:
106 else:
107 repo_path = str_to_dulwich(wire['path'])
107 repo_path = str_to_dulwich(wire['path'])
108 return Repo(repo_path)
108 return Repo(repo_path)
109
109
110 def repo(self, wire, create=False, use_libgit2=False):
110 def repo(self, wire, create=False, use_libgit2=False):
111 """
111 """
112 Get a repository instance for the given path.
112 Get a repository instance for the given path.
113 """
113 """
114 region = self._cache_region
114 region = self._cache_region
115 context = wire.get('context', None)
115 context = wire.get('context', None)
116 repo_path = wire.get('path', '')
116 repo_path = wire.get('path', '')
117 context_uid = '{}'.format(context)
117 context_uid = '{}'.format(context)
118 cache = wire.get('cache', True)
118 cache = wire.get('cache', True)
119 cache_on = context and cache
119 cache_on = context and cache
120
120
121 @region.conditional_cache_on_arguments(condition=cache_on)
121 @region.conditional_cache_on_arguments(condition=cache_on)
122 def create_new_repo(_repo_type, _repo_path, _context_uid, _use_libgit2):
122 def create_new_repo(_repo_type, _repo_path, _context_uid, _use_libgit2):
123 return self._create_repo(wire, create, use_libgit2)
123 return self._create_repo(wire, create, use_libgit2)
124
124
125 repo = create_new_repo(self.repo_type, repo_path, context_uid, use_libgit2)
125 repo = create_new_repo(self.repo_type, repo_path, context_uid, use_libgit2)
126 return repo
126 return repo
127
127
128 def repo_libgit2(self, wire):
128 def repo_libgit2(self, wire):
129 return self.repo(wire, use_libgit2=True)
129 return self.repo(wire, use_libgit2=True)
130
130
131
131
132 class GitRemote(object):
132 class GitRemote(object):
133
133
134 def __init__(self, factory):
134 def __init__(self, factory):
135 self._factory = factory
135 self._factory = factory
136 self.peeled_ref_marker = '^{}'
136 self.peeled_ref_marker = '^{}'
137 self._bulk_methods = {
137 self._bulk_methods = {
138 "date": self.date,
138 "date": self.date,
139 "author": self.author,
139 "author": self.author,
140 "message": self.message,
140 "message": self.message,
141 "parents": self.parents,
141 "parents": self.parents,
142 "_commit": self.revision,
142 "_commit": self.revision,
143 }
143 }
144
144
145 def _wire_to_config(self, wire):
145 def _wire_to_config(self, wire):
146 if 'config' in wire:
146 if 'config' in wire:
147 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
147 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
148 return {}
148 return {}
149
149
150 def _remote_conf(self, config):
150 def _remote_conf(self, config):
151 params = [
151 params = [
152 '-c', 'core.askpass=""',
152 '-c', 'core.askpass=""',
153 ]
153 ]
154 ssl_cert_dir = config.get('vcs_ssl_dir')
154 ssl_cert_dir = config.get('vcs_ssl_dir')
155 if ssl_cert_dir:
155 if ssl_cert_dir:
156 params.extend(['-c', 'http.sslCAinfo={}'.format(ssl_cert_dir)])
156 params.extend(['-c', 'http.sslCAinfo={}'.format(ssl_cert_dir)])
157 return params
157 return params
158
158
159 @reraise_safe_exceptions
159 @reraise_safe_exceptions
160 def is_empty(self, wire):
160 def is_empty(self, wire):
161 repo = self._factory.repo_libgit2(wire)
161 repo = self._factory.repo_libgit2(wire)
162
162
163 # NOTE(marcink): old solution as an alternative
163 try:
164 # try:
164 return not repo.head.name
165 # return not repo.head.name
165 except Exception:
166 # except Exception:
166 return True
167 # return True
168
169 return repo.is_empty
170
167
171 @reraise_safe_exceptions
168 @reraise_safe_exceptions
172 def add_object(self, wire, content):
169 def add_object(self, wire, content):
173 repo = self._factory.repo(wire)
170 repo = self._factory.repo(wire)
174 blob = objects.Blob()
171 blob = objects.Blob()
175 blob.set_raw_string(content)
172 blob.set_raw_string(content)
176 repo.object_store.add_object(blob)
173 repo.object_store.add_object(blob)
177 return blob.id
174 return blob.id
178
175
179 @reraise_safe_exceptions
176 @reraise_safe_exceptions
180 def assert_correct_path(self, wire):
177 def assert_correct_path(self, wire):
181 try:
178 try:
182 self._factory.repo_libgit2(wire)
179 self._factory.repo_libgit2(wire)
183 except pygit2.GitError:
180 except pygit2.GitError:
184 path = wire.get('path')
181 path = wire.get('path')
185 tb = traceback.format_exc()
182 tb = traceback.format_exc()
186 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
183 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
187 return False
184 return False
188
185
189 return True
186 return True
190
187
191 @reraise_safe_exceptions
188 @reraise_safe_exceptions
192 def bare(self, wire):
189 def bare(self, wire):
193 repo = self._factory.repo_libgit2(wire)
190 repo = self._factory.repo_libgit2(wire)
194 return repo.is_bare
191 return repo.is_bare
195
192
196 @reraise_safe_exceptions
193 @reraise_safe_exceptions
197 def blob_as_pretty_string(self, wire, sha):
194 def blob_as_pretty_string(self, wire, sha):
198 repo_init = self._factory.repo_libgit2(wire)
195 repo_init = self._factory.repo_libgit2(wire)
199 with repo_init as repo:
196 with repo_init as repo:
200 blob_obj = repo[sha]
197 blob_obj = repo[sha]
201 blob = blob_obj.data
198 blob = blob_obj.data
202 return blob
199 return blob
203
200
204 @reraise_safe_exceptions
201 @reraise_safe_exceptions
205 def blob_raw_length(self, wire, sha):
202 def blob_raw_length(self, wire, sha):
206 repo_init = self._factory.repo_libgit2(wire)
203 repo_init = self._factory.repo_libgit2(wire)
207 with repo_init as repo:
204 with repo_init as repo:
208 blob = repo[sha]
205 blob = repo[sha]
209 return blob.size
206 return blob.size
210
207
211 def _parse_lfs_pointer(self, raw_content):
208 def _parse_lfs_pointer(self, raw_content):
212
209
213 spec_string = 'version https://git-lfs.github.com/spec'
210 spec_string = 'version https://git-lfs.github.com/spec'
214 if raw_content and raw_content.startswith(spec_string):
211 if raw_content and raw_content.startswith(spec_string):
215 pattern = re.compile(r"""
212 pattern = re.compile(r"""
216 (?:\n)?
213 (?:\n)?
217 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
214 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
218 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
215 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
219 ^size[ ](?P<oid_size>[0-9]+)\n
216 ^size[ ](?P<oid_size>[0-9]+)\n
220 (?:\n)?
217 (?:\n)?
221 """, re.VERBOSE | re.MULTILINE)
218 """, re.VERBOSE | re.MULTILINE)
222 match = pattern.match(raw_content)
219 match = pattern.match(raw_content)
223 if match:
220 if match:
224 return match.groupdict()
221 return match.groupdict()
225
222
226 return {}
223 return {}
227
224
228 @reraise_safe_exceptions
225 @reraise_safe_exceptions
229 def is_large_file(self, wire, sha):
226 def is_large_file(self, wire, sha):
230 repo = self._factory.repo(wire)
227 repo_init = self._factory.repo_libgit2(wire)
231 blob = repo[sha]
228
232 return self._parse_lfs_pointer(blob.as_raw_string())
229 with repo_init as repo:
230 blob = repo[sha]
231 if blob.is_binary:
232 return {}
233
234 return self._parse_lfs_pointer(blob.data)
233
235
234 @reraise_safe_exceptions
236 @reraise_safe_exceptions
235 def in_largefiles_store(self, wire, oid):
237 def in_largefiles_store(self, wire, oid):
236 repo = self._factory.repo(wire)
238 repo = self._factory.repo_libgit2(wire)
237 conf = self._wire_to_config(wire)
239 conf = self._wire_to_config(wire)
238
240
239 store_location = conf.get('vcs_git_lfs_store_location')
241 store_location = conf.get('vcs_git_lfs_store_location')
240 if store_location:
242 if store_location:
241 repo_name = repo.path
243 repo_name = repo.path
242 store = LFSOidStore(
244 store = LFSOidStore(
243 oid=oid, repo=repo_name, store_location=store_location)
245 oid=oid, repo=repo_name, store_location=store_location)
244 return store.has_oid()
246 return store.has_oid()
245
247
246 return False
248 return False
247
249
248 @reraise_safe_exceptions
250 @reraise_safe_exceptions
249 def store_path(self, wire, oid):
251 def store_path(self, wire, oid):
250 repo = self._factory.repo(wire)
252 repo = self._factory.repo_libgit2(wire)
251 conf = self._wire_to_config(wire)
253 conf = self._wire_to_config(wire)
252
254
253 store_location = conf.get('vcs_git_lfs_store_location')
255 store_location = conf.get('vcs_git_lfs_store_location')
254 if store_location:
256 if store_location:
255 repo_name = repo.path
257 repo_name = repo.path
256 store = LFSOidStore(
258 store = LFSOidStore(
257 oid=oid, repo=repo_name, store_location=store_location)
259 oid=oid, repo=repo_name, store_location=store_location)
258 return store.oid_path
260 return store.oid_path
259 raise ValueError('Unable to fetch oid with path {}'.format(oid))
261 raise ValueError('Unable to fetch oid with path {}'.format(oid))
260
262
261 @reraise_safe_exceptions
263 @reraise_safe_exceptions
262 def bulk_request(self, wire, rev, pre_load):
264 def bulk_request(self, wire, rev, pre_load):
263 result = {}
265 result = {}
264 for attr in pre_load:
266 for attr in pre_load:
265 try:
267 try:
266 method = self._bulk_methods[attr]
268 method = self._bulk_methods[attr]
267 args = [wire, rev]
269 args = [wire, rev]
268 result[attr] = method(*args)
270 result[attr] = method(*args)
269 except KeyError as e:
271 except KeyError as e:
270 raise exceptions.VcsException(e)("Unknown bulk attribute: %s" % attr)
272 raise exceptions.VcsException(e)("Unknown bulk attribute: %s" % attr)
271 return result
273 return result
272
274
273 def _build_opener(self, url):
275 def _build_opener(self, url):
274 handlers = []
276 handlers = []
275 url_obj = url_parser(url)
277 url_obj = url_parser(url)
276 _, authinfo = url_obj.authinfo()
278 _, authinfo = url_obj.authinfo()
277
279
278 if authinfo:
280 if authinfo:
279 # create a password manager
281 # create a password manager
280 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
282 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
281 passmgr.add_password(*authinfo)
283 passmgr.add_password(*authinfo)
282
284
283 handlers.extend((httpbasicauthhandler(passmgr),
285 handlers.extend((httpbasicauthhandler(passmgr),
284 httpdigestauthhandler(passmgr)))
286 httpdigestauthhandler(passmgr)))
285
287
286 return urllib2.build_opener(*handlers)
288 return urllib2.build_opener(*handlers)
287
289
288 def _type_id_to_name(self, type_id):
290 def _type_id_to_name(self, type_id):
289 return {
291 return {
290 1: b'commit',
292 1: b'commit',
291 2: b'tree',
293 2: b'tree',
292 3: b'blob',
294 3: b'blob',
293 4: b'tag'
295 4: b'tag'
294 }[type_id]
296 }[type_id]
295
297
296 @reraise_safe_exceptions
298 @reraise_safe_exceptions
297 def check_url(self, url, config):
299 def check_url(self, url, config):
298 url_obj = url_parser(url)
300 url_obj = url_parser(url)
299 test_uri, _ = url_obj.authinfo()
301 test_uri, _ = url_obj.authinfo()
300 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
302 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
301 url_obj.query = obfuscate_qs(url_obj.query)
303 url_obj.query = obfuscate_qs(url_obj.query)
302 cleaned_uri = str(url_obj)
304 cleaned_uri = str(url_obj)
303 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
305 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
304
306
305 if not test_uri.endswith('info/refs'):
307 if not test_uri.endswith('info/refs'):
306 test_uri = test_uri.rstrip('/') + '/info/refs'
308 test_uri = test_uri.rstrip('/') + '/info/refs'
307
309
308 o = self._build_opener(url)
310 o = self._build_opener(url)
309 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
311 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
310
312
311 q = {"service": 'git-upload-pack'}
313 q = {"service": 'git-upload-pack'}
312 qs = '?%s' % urllib.urlencode(q)
314 qs = '?%s' % urllib.urlencode(q)
313 cu = "%s%s" % (test_uri, qs)
315 cu = "%s%s" % (test_uri, qs)
314 req = urllib2.Request(cu, None, {})
316 req = urllib2.Request(cu, None, {})
315
317
316 try:
318 try:
317 log.debug("Trying to open URL %s", cleaned_uri)
319 log.debug("Trying to open URL %s", cleaned_uri)
318 resp = o.open(req)
320 resp = o.open(req)
319 if resp.code != 200:
321 if resp.code != 200:
320 raise exceptions.URLError()('Return Code is not 200')
322 raise exceptions.URLError()('Return Code is not 200')
321 except Exception as e:
323 except Exception as e:
322 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
324 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
323 # means it cannot be cloned
325 # means it cannot be cloned
324 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
326 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
325
327
326 # now detect if it's proper git repo
328 # now detect if it's proper git repo
327 gitdata = resp.read()
329 gitdata = resp.read()
328 if 'service=git-upload-pack' in gitdata:
330 if 'service=git-upload-pack' in gitdata:
329 pass
331 pass
330 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
332 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
331 # old style git can return some other format !
333 # old style git can return some other format !
332 pass
334 pass
333 else:
335 else:
334 raise exceptions.URLError()(
336 raise exceptions.URLError()(
335 "url [%s] does not look like an git" % (cleaned_uri,))
337 "url [%s] does not look like an git" % (cleaned_uri,))
336
338
337 return True
339 return True
338
340
339 @reraise_safe_exceptions
341 @reraise_safe_exceptions
340 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
342 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
341 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
343 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
342 remote_refs = self.pull(wire, url, apply_refs=False)
344 remote_refs = self.pull(wire, url, apply_refs=False)
343 repo = self._factory.repo(wire)
345 repo = self._factory.repo(wire)
344 if isinstance(valid_refs, list):
346 if isinstance(valid_refs, list):
345 valid_refs = tuple(valid_refs)
347 valid_refs = tuple(valid_refs)
346
348
347 for k in remote_refs:
349 for k in remote_refs:
348 # only parse heads/tags and skip so called deferred tags
350 # only parse heads/tags and skip so called deferred tags
349 if k.startswith(valid_refs) and not k.endswith(deferred):
351 if k.startswith(valid_refs) and not k.endswith(deferred):
350 repo[k] = remote_refs[k]
352 repo[k] = remote_refs[k]
351
353
352 if update_after_clone:
354 if update_after_clone:
353 # we want to checkout HEAD
355 # we want to checkout HEAD
354 repo["HEAD"] = remote_refs["HEAD"]
356 repo["HEAD"] = remote_refs["HEAD"]
355 index.build_index_from_tree(repo.path, repo.index_path(),
357 index.build_index_from_tree(repo.path, repo.index_path(),
356 repo.object_store, repo["HEAD"].tree)
358 repo.object_store, repo["HEAD"].tree)
357
359
358 # TODO: this is quite complex, check if that can be simplified
360 # TODO: this is quite complex, check if that can be simplified
359 @reraise_safe_exceptions
361 @reraise_safe_exceptions
360 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
362 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
361 repo = self._factory.repo(wire)
363 repo = self._factory.repo(wire)
362 object_store = repo.object_store
364 object_store = repo.object_store
363
365
364 # Create tree and populates it with blobs
366 # Create tree and populates it with blobs
365 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
367 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
366
368
367 for node in updated:
369 for node in updated:
368 # Compute subdirs if needed
370 # Compute subdirs if needed
369 dirpath, nodename = vcspath.split(node['path'])
371 dirpath, nodename = vcspath.split(node['path'])
370 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
372 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
371 parent = commit_tree
373 parent = commit_tree
372 ancestors = [('', parent)]
374 ancestors = [('', parent)]
373
375
374 # Tries to dig for the deepest existing tree
376 # Tries to dig for the deepest existing tree
375 while dirnames:
377 while dirnames:
376 curdir = dirnames.pop(0)
378 curdir = dirnames.pop(0)
377 try:
379 try:
378 dir_id = parent[curdir][1]
380 dir_id = parent[curdir][1]
379 except KeyError:
381 except KeyError:
380 # put curdir back into dirnames and stops
382 # put curdir back into dirnames and stops
381 dirnames.insert(0, curdir)
383 dirnames.insert(0, curdir)
382 break
384 break
383 else:
385 else:
384 # If found, updates parent
386 # If found, updates parent
385 parent = repo[dir_id]
387 parent = repo[dir_id]
386 ancestors.append((curdir, parent))
388 ancestors.append((curdir, parent))
387 # Now parent is deepest existing tree and we need to create
389 # Now parent is deepest existing tree and we need to create
388 # subtrees for dirnames (in reverse order)
390 # subtrees for dirnames (in reverse order)
389 # [this only applies for nodes from added]
391 # [this only applies for nodes from added]
390 new_trees = []
392 new_trees = []
391
393
392 blob = objects.Blob.from_string(node['content'])
394 blob = objects.Blob.from_string(node['content'])
393
395
394 if dirnames:
396 if dirnames:
395 # If there are trees which should be created we need to build
397 # If there are trees which should be created we need to build
396 # them now (in reverse order)
398 # them now (in reverse order)
397 reversed_dirnames = list(reversed(dirnames))
399 reversed_dirnames = list(reversed(dirnames))
398 curtree = objects.Tree()
400 curtree = objects.Tree()
399 curtree[node['node_path']] = node['mode'], blob.id
401 curtree[node['node_path']] = node['mode'], blob.id
400 new_trees.append(curtree)
402 new_trees.append(curtree)
401 for dirname in reversed_dirnames[:-1]:
403 for dirname in reversed_dirnames[:-1]:
402 newtree = objects.Tree()
404 newtree = objects.Tree()
403 newtree[dirname] = (DIR_STAT, curtree.id)
405 newtree[dirname] = (DIR_STAT, curtree.id)
404 new_trees.append(newtree)
406 new_trees.append(newtree)
405 curtree = newtree
407 curtree = newtree
406 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
408 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
407 else:
409 else:
408 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
410 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
409
411
410 new_trees.append(parent)
412 new_trees.append(parent)
411 # Update ancestors
413 # Update ancestors
412 reversed_ancestors = reversed(
414 reversed_ancestors = reversed(
413 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
415 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
414 for parent, tree, path in reversed_ancestors:
416 for parent, tree, path in reversed_ancestors:
415 parent[path] = (DIR_STAT, tree.id)
417 parent[path] = (DIR_STAT, tree.id)
416 object_store.add_object(tree)
418 object_store.add_object(tree)
417
419
418 object_store.add_object(blob)
420 object_store.add_object(blob)
419 for tree in new_trees:
421 for tree in new_trees:
420 object_store.add_object(tree)
422 object_store.add_object(tree)
421
423
422 for node_path in removed:
424 for node_path in removed:
423 paths = node_path.split('/')
425 paths = node_path.split('/')
424 tree = commit_tree
426 tree = commit_tree
425 trees = [tree]
427 trees = [tree]
426 # Traverse deep into the forest...
428 # Traverse deep into the forest...
427 for path in paths:
429 for path in paths:
428 try:
430 try:
429 obj = repo[tree[path][1]]
431 obj = repo[tree[path][1]]
430 if isinstance(obj, objects.Tree):
432 if isinstance(obj, objects.Tree):
431 trees.append(obj)
433 trees.append(obj)
432 tree = obj
434 tree = obj
433 except KeyError:
435 except KeyError:
434 break
436 break
435 # Cut down the blob and all rotten trees on the way back...
437 # Cut down the blob and all rotten trees on the way back...
436 for path, tree in reversed(zip(paths, trees)):
438 for path, tree in reversed(zip(paths, trees)):
437 del tree[path]
439 del tree[path]
438 if tree:
440 if tree:
439 # This tree still has elements - don't remove it or any
441 # This tree still has elements - don't remove it or any
440 # of it's parents
442 # of it's parents
441 break
443 break
442
444
443 object_store.add_object(commit_tree)
445 object_store.add_object(commit_tree)
444
446
445 # Create commit
447 # Create commit
446 commit = objects.Commit()
448 commit = objects.Commit()
447 commit.tree = commit_tree.id
449 commit.tree = commit_tree.id
448 for k, v in commit_data.iteritems():
450 for k, v in commit_data.iteritems():
449 setattr(commit, k, v)
451 setattr(commit, k, v)
450 object_store.add_object(commit)
452 object_store.add_object(commit)
451
453
452 self.create_branch(wire, branch, commit.id)
454 self.create_branch(wire, branch, commit.id)
453
455
454 # dulwich set-ref
456 # dulwich set-ref
455 ref = 'refs/heads/%s' % branch
457 ref = 'refs/heads/%s' % branch
456 repo.refs[ref] = commit.id
458 repo.refs[ref] = commit.id
457
459
458 return commit.id
460 return commit.id
459
461
460 @reraise_safe_exceptions
462 @reraise_safe_exceptions
461 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
463 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
462 if url != 'default' and '://' not in url:
464 if url != 'default' and '://' not in url:
463 client = LocalGitClient(url)
465 client = LocalGitClient(url)
464 else:
466 else:
465 url_obj = url_parser(url)
467 url_obj = url_parser(url)
466 o = self._build_opener(url)
468 o = self._build_opener(url)
467 url, _ = url_obj.authinfo()
469 url, _ = url_obj.authinfo()
468 client = HttpGitClient(base_url=url, opener=o)
470 client = HttpGitClient(base_url=url, opener=o)
469 repo = self._factory.repo(wire)
471 repo = self._factory.repo(wire)
470
472
471 determine_wants = repo.object_store.determine_wants_all
473 determine_wants = repo.object_store.determine_wants_all
472 if refs:
474 if refs:
473 def determine_wants_requested(references):
475 def determine_wants_requested(references):
474 return [references[r] for r in references if r in refs]
476 return [references[r] for r in references if r in refs]
475 determine_wants = determine_wants_requested
477 determine_wants = determine_wants_requested
476
478
477 try:
479 try:
478 remote_refs = client.fetch(
480 remote_refs = client.fetch(
479 path=url, target=repo, determine_wants=determine_wants)
481 path=url, target=repo, determine_wants=determine_wants)
480 except NotGitRepository as e:
482 except NotGitRepository as e:
481 log.warning(
483 log.warning(
482 'Trying to fetch from "%s" failed, not a Git repository.', url)
484 'Trying to fetch from "%s" failed, not a Git repository.', url)
483 # Exception can contain unicode which we convert
485 # Exception can contain unicode which we convert
484 raise exceptions.AbortException(e)(repr(e))
486 raise exceptions.AbortException(e)(repr(e))
485
487
486 # mikhail: client.fetch() returns all the remote refs, but fetches only
488 # mikhail: client.fetch() returns all the remote refs, but fetches only
487 # refs filtered by `determine_wants` function. We need to filter result
489 # refs filtered by `determine_wants` function. We need to filter result
488 # as well
490 # as well
489 if refs:
491 if refs:
490 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
492 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
491
493
492 if apply_refs:
494 if apply_refs:
493 # TODO: johbo: Needs proper test coverage with a git repository
495 # TODO: johbo: Needs proper test coverage with a git repository
494 # that contains a tag object, so that we would end up with
496 # that contains a tag object, so that we would end up with
495 # a peeled ref at this point.
497 # a peeled ref at this point.
496 for k in remote_refs:
498 for k in remote_refs:
497 if k.endswith(self.peeled_ref_marker):
499 if k.endswith(self.peeled_ref_marker):
498 log.debug("Skipping peeled reference %s", k)
500 log.debug("Skipping peeled reference %s", k)
499 continue
501 continue
500 repo[k] = remote_refs[k]
502 repo[k] = remote_refs[k]
501
503
502 if refs and not update_after:
504 if refs and not update_after:
503 # mikhail: explicitly set the head to the last ref.
505 # mikhail: explicitly set the head to the last ref.
504 repo['HEAD'] = remote_refs[refs[-1]]
506 repo['HEAD'] = remote_refs[refs[-1]]
505
507
506 if update_after:
508 if update_after:
507 # we want to checkout HEAD
509 # we want to checkout HEAD
508 repo["HEAD"] = remote_refs["HEAD"]
510 repo["HEAD"] = remote_refs["HEAD"]
509 index.build_index_from_tree(repo.path, repo.index_path(),
511 index.build_index_from_tree(repo.path, repo.index_path(),
510 repo.object_store, repo["HEAD"].tree)
512 repo.object_store, repo["HEAD"].tree)
511 return remote_refs
513 return remote_refs
512
514
513 @reraise_safe_exceptions
515 @reraise_safe_exceptions
514 def sync_fetch(self, wire, url, refs=None):
516 def sync_fetch(self, wire, url, refs=None):
515 repo = self._factory.repo(wire)
517 repo = self._factory.repo(wire)
516 if refs and not isinstance(refs, (list, tuple)):
518 if refs and not isinstance(refs, (list, tuple)):
517 refs = [refs]
519 refs = [refs]
518 config = self._wire_to_config(wire)
520 config = self._wire_to_config(wire)
519 # get all remote refs we'll use to fetch later
521 # get all remote refs we'll use to fetch later
520 output, __ = self.run_git_command(
522 output, __ = self.run_git_command(
521 wire, ['ls-remote', url], fail_on_stderr=False,
523 wire, ['ls-remote', url], fail_on_stderr=False,
522 _copts=self._remote_conf(config),
524 _copts=self._remote_conf(config),
523 extra_env={'GIT_TERMINAL_PROMPT': '0'})
525 extra_env={'GIT_TERMINAL_PROMPT': '0'})
524
526
525 remote_refs = collections.OrderedDict()
527 remote_refs = collections.OrderedDict()
526 fetch_refs = []
528 fetch_refs = []
527
529
528 for ref_line in output.splitlines():
530 for ref_line in output.splitlines():
529 sha, ref = ref_line.split('\t')
531 sha, ref = ref_line.split('\t')
530 sha = sha.strip()
532 sha = sha.strip()
531 if ref in remote_refs:
533 if ref in remote_refs:
532 # duplicate, skip
534 # duplicate, skip
533 continue
535 continue
534 if ref.endswith(self.peeled_ref_marker):
536 if ref.endswith(self.peeled_ref_marker):
535 log.debug("Skipping peeled reference %s", ref)
537 log.debug("Skipping peeled reference %s", ref)
536 continue
538 continue
537 # don't sync HEAD
539 # don't sync HEAD
538 if ref in ['HEAD']:
540 if ref in ['HEAD']:
539 continue
541 continue
540
542
541 remote_refs[ref] = sha
543 remote_refs[ref] = sha
542
544
543 if refs and sha in refs:
545 if refs and sha in refs:
544 # we filter fetch using our specified refs
546 # we filter fetch using our specified refs
545 fetch_refs.append('{}:{}'.format(ref, ref))
547 fetch_refs.append('{}:{}'.format(ref, ref))
546 elif not refs:
548 elif not refs:
547 fetch_refs.append('{}:{}'.format(ref, ref))
549 fetch_refs.append('{}:{}'.format(ref, ref))
548 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
550 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
549 if fetch_refs:
551 if fetch_refs:
550 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
552 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
551 fetch_refs_chunks = list(chunk)
553 fetch_refs_chunks = list(chunk)
552 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
554 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
553 _out, _err = self.run_git_command(
555 _out, _err = self.run_git_command(
554 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
556 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
555 fail_on_stderr=False,
557 fail_on_stderr=False,
556 _copts=self._remote_conf(config),
558 _copts=self._remote_conf(config),
557 extra_env={'GIT_TERMINAL_PROMPT': '0'})
559 extra_env={'GIT_TERMINAL_PROMPT': '0'})
558
560
559 return remote_refs
561 return remote_refs
560
562
561 @reraise_safe_exceptions
563 @reraise_safe_exceptions
562 def sync_push(self, wire, url, refs=None):
564 def sync_push(self, wire, url, refs=None):
563 if not self.check_url(url, wire):
565 if not self.check_url(url, wire):
564 return
566 return
565 config = self._wire_to_config(wire)
567 config = self._wire_to_config(wire)
566 repo = self._factory.repo(wire)
568 repo = self._factory.repo(wire)
567 self.run_git_command(
569 self.run_git_command(
568 wire, ['push', url, '--mirror'], fail_on_stderr=False,
570 wire, ['push', url, '--mirror'], fail_on_stderr=False,
569 _copts=self._remote_conf(config),
571 _copts=self._remote_conf(config),
570 extra_env={'GIT_TERMINAL_PROMPT': '0'})
572 extra_env={'GIT_TERMINAL_PROMPT': '0'})
571
573
572 @reraise_safe_exceptions
574 @reraise_safe_exceptions
573 def get_remote_refs(self, wire, url):
575 def get_remote_refs(self, wire, url):
574 repo = Repo(url)
576 repo = Repo(url)
575 return repo.get_refs()
577 return repo.get_refs()
576
578
577 @reraise_safe_exceptions
579 @reraise_safe_exceptions
578 def get_description(self, wire):
580 def get_description(self, wire):
579 repo = self._factory.repo(wire)
581 repo = self._factory.repo(wire)
580 return repo.get_description()
582 return repo.get_description()
581
583
582 @reraise_safe_exceptions
584 @reraise_safe_exceptions
583 def get_missing_revs(self, wire, rev1, rev2, path2):
585 def get_missing_revs(self, wire, rev1, rev2, path2):
584 repo = self._factory.repo(wire)
586 repo = self._factory.repo(wire)
585 LocalGitClient(thin_packs=False).fetch(path2, repo)
587 LocalGitClient(thin_packs=False).fetch(path2, repo)
586
588
587 wire_remote = wire.copy()
589 wire_remote = wire.copy()
588 wire_remote['path'] = path2
590 wire_remote['path'] = path2
589 repo_remote = self._factory.repo(wire_remote)
591 repo_remote = self._factory.repo(wire_remote)
590 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
592 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
591
593
592 revs = [
594 revs = [
593 x.commit.id
595 x.commit.id
594 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
596 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
595 return revs
597 return revs
596
598
597 @reraise_safe_exceptions
599 @reraise_safe_exceptions
598 def get_object(self, wire, sha):
600 def get_object(self, wire, sha):
599 repo = self._factory.repo_libgit2(wire)
601 repo = self._factory.repo_libgit2(wire)
600
602
601 try:
603 try:
602 commit = repo.revparse_single(sha)
604 commit = repo.revparse_single(sha)
603 except (KeyError, ValueError) as e:
605 except (KeyError, ValueError) as e:
604 msg = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
606 msg = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
605 raise exceptions.LookupException(e)(msg)
607 raise exceptions.LookupException(e)(msg)
606
608
607 if isinstance(commit, pygit2.Tag):
609 if isinstance(commit, pygit2.Tag):
608 commit = repo.get(commit.target)
610 commit = repo.get(commit.target)
609
611
610 commit_id = commit.hex
612 commit_id = commit.hex
611 type_id = commit.type
613 type_id = commit.type
612
614
613 return {
615 return {
614 'id': commit_id,
616 'id': commit_id,
615 'type': self._type_id_to_name(type_id),
617 'type': self._type_id_to_name(type_id),
616 'commit_id': commit_id,
618 'commit_id': commit_id,
617 'idx': 0
619 'idx': 0
618 }
620 }
619
621
620 @reraise_safe_exceptions
622 @reraise_safe_exceptions
621 def get_refs(self, wire):
623 def get_refs(self, wire):
622 repo = self._factory.repo_libgit2(wire)
624 repo = self._factory.repo_libgit2(wire)
623
625
624 result = {}
626 result = {}
625 for ref in repo.references:
627 for ref in repo.references:
626 peeled_sha = repo.lookup_reference(ref).peel()
628 peeled_sha = repo.lookup_reference(ref).peel()
627 result[ref] = peeled_sha.hex
629 result[ref] = peeled_sha.hex
628
630
629 return result
631 return result
630
632
631 @reraise_safe_exceptions
633 @reraise_safe_exceptions
632 def head(self, wire, show_exc=True):
634 def head(self, wire, show_exc=True):
633 repo = self._factory.repo_libgit2(wire)
635 repo = self._factory.repo_libgit2(wire)
634 try:
636 try:
635 return repo.head.peel().hex
637 return repo.head.peel().hex
636 except Exception:
638 except Exception:
637 if show_exc:
639 if show_exc:
638 raise
640 raise
639
641
640 @reraise_safe_exceptions
642 @reraise_safe_exceptions
641 def init(self, wire):
643 def init(self, wire):
642 repo_path = str_to_dulwich(wire['path'])
644 repo_path = str_to_dulwich(wire['path'])
643 self.repo = Repo.init(repo_path)
645 self.repo = Repo.init(repo_path)
644
646
645 @reraise_safe_exceptions
647 @reraise_safe_exceptions
646 def init_bare(self, wire):
648 def init_bare(self, wire):
647 repo_path = str_to_dulwich(wire['path'])
649 repo_path = str_to_dulwich(wire['path'])
648 self.repo = Repo.init_bare(repo_path)
650 self.repo = Repo.init_bare(repo_path)
649
651
650 @reraise_safe_exceptions
652 @reraise_safe_exceptions
651 def revision(self, wire, rev):
653 def revision(self, wire, rev):
652 repo = self._factory.repo_libgit2(wire)
654 repo = self._factory.repo_libgit2(wire)
653 commit = repo[rev]
655 commit = repo[rev]
654 obj_data = {
656 obj_data = {
655 'id': commit.id.hex,
657 'id': commit.id.hex,
656 }
658 }
657 # tree objects itself don't have tree_id attribute
659 # tree objects itself don't have tree_id attribute
658 if hasattr(commit, 'tree_id'):
660 if hasattr(commit, 'tree_id'):
659 obj_data['tree'] = commit.tree_id.hex
661 obj_data['tree'] = commit.tree_id.hex
660
662
661 return obj_data
663 return obj_data
662
664
663 @reraise_safe_exceptions
665 @reraise_safe_exceptions
664 def date(self, wire, rev):
666 def date(self, wire, rev):
665 repo = self._factory.repo_libgit2(wire)
667 repo = self._factory.repo_libgit2(wire)
666 commit = repo[rev]
668 commit = repo[rev]
667 # TODO(marcink): check dulwich difference of offset vs timezone
669 # TODO(marcink): check dulwich difference of offset vs timezone
668 return [commit.commit_time, commit.commit_time_offset]
670 return [commit.commit_time, commit.commit_time_offset]
669
671
670 @reraise_safe_exceptions
672 @reraise_safe_exceptions
671 def author(self, wire, rev):
673 def author(self, wire, rev):
672 repo = self._factory.repo_libgit2(wire)
674 repo = self._factory.repo_libgit2(wire)
673 commit = repo[rev]
675 commit = repo[rev]
674 if commit.author.email:
676 if commit.author.email:
675 return u"{} <{}>".format(commit.author.name, commit.author.email)
677 return u"{} <{}>".format(commit.author.name, commit.author.email)
676
678
677 return u"{}".format(commit.author.raw_name)
679 return u"{}".format(commit.author.raw_name)
678
680
679 @reraise_safe_exceptions
681 @reraise_safe_exceptions
680 def message(self, wire, rev):
682 def message(self, wire, rev):
681 repo = self._factory.repo_libgit2(wire)
683 repo = self._factory.repo_libgit2(wire)
682 commit = repo[rev]
684 commit = repo[rev]
683 return commit.message
685 return commit.message
684
686
685 @reraise_safe_exceptions
687 @reraise_safe_exceptions
686 def parents(self, wire, rev):
688 def parents(self, wire, rev):
687 repo = self._factory.repo_libgit2(wire)
689 repo = self._factory.repo_libgit2(wire)
688 commit = repo[rev]
690 commit = repo[rev]
689 return [x.hex for x in commit.parent_ids]
691 return [x.hex for x in commit.parent_ids]
690
692
691 @reraise_safe_exceptions
693 @reraise_safe_exceptions
692 def set_refs(self, wire, key, value):
694 def set_refs(self, wire, key, value):
693 repo = self._factory.repo_libgit2(wire)
695 repo = self._factory.repo_libgit2(wire)
694 repo.references.create(key, value, force=True)
696 repo.references.create(key, value, force=True)
695
697
696 @reraise_safe_exceptions
698 @reraise_safe_exceptions
697 def create_branch(self, wire, branch_name, commit_id, force=False):
699 def create_branch(self, wire, branch_name, commit_id, force=False):
698 repo = self._factory.repo_libgit2(wire)
700 repo = self._factory.repo_libgit2(wire)
699 commit = repo[commit_id]
701 commit = repo[commit_id]
700
702
701 if force:
703 if force:
702 repo.branches.local.create(branch_name, commit, force=force)
704 repo.branches.local.create(branch_name, commit, force=force)
703 elif not repo.branches.get(branch_name):
705 elif not repo.branches.get(branch_name):
704 # create only if that branch isn't existing
706 # create only if that branch isn't existing
705 repo.branches.local.create(branch_name, commit, force=force)
707 repo.branches.local.create(branch_name, commit, force=force)
706
708
707 @reraise_safe_exceptions
709 @reraise_safe_exceptions
708 def remove_ref(self, wire, key):
710 def remove_ref(self, wire, key):
709 repo = self._factory.repo_libgit2(wire)
711 repo = self._factory.repo_libgit2(wire)
710 repo.references.delete(key)
712 repo.references.delete(key)
711
713
712 @reraise_safe_exceptions
714 @reraise_safe_exceptions
713 def tag_remove(self, wire, tag_name):
715 def tag_remove(self, wire, tag_name):
714 repo = self._factory.repo_libgit2(wire)
716 repo = self._factory.repo_libgit2(wire)
715 key = 'refs/tags/{}'.format(tag_name)
717 key = 'refs/tags/{}'.format(tag_name)
716 repo.references.delete(key)
718 repo.references.delete(key)
717
719
718 @reraise_safe_exceptions
720 @reraise_safe_exceptions
719 def tree_changes(self, wire, source_id, target_id):
721 def tree_changes(self, wire, source_id, target_id):
720 # TODO(marcink): remove this seems it's only used by tests
722 # TODO(marcink): remove this seems it's only used by tests
721 repo = self._factory.repo(wire)
723 repo = self._factory.repo(wire)
722 source = repo[source_id].tree if source_id else None
724 source = repo[source_id].tree if source_id else None
723 target = repo[target_id].tree
725 target = repo[target_id].tree
724 result = repo.object_store.tree_changes(source, target)
726 result = repo.object_store.tree_changes(source, target)
725 return list(result)
727 return list(result)
726
728
727 @reraise_safe_exceptions
729 @reraise_safe_exceptions
730 def tree_and_type_for_path(self, wire, commit_id, path):
731 repo_init = self._factory.repo_libgit2(wire)
732
733 with repo_init as repo:
734 commit = repo[commit_id]
735 try:
736 tree = commit.tree[path]
737 except KeyError:
738 return None, None, None
739
740 return tree.id.hex, tree.type, tree.filemode
741
742 @reraise_safe_exceptions
728 def tree_items(self, wire, tree_id):
743 def tree_items(self, wire, tree_id):
729 repo_init = self._factory.repo_libgit2(wire)
744 repo_init = self._factory.repo_libgit2(wire)
730
745
731 with repo_init as repo:
746 with repo_init as repo:
732 tree = repo[tree_id]
747 try:
748 tree = repo[tree_id]
749 except KeyError:
750 raise ObjectMissing('No tree with id: {}'.format(tree_id))
733
751
734 result = []
752 result = []
735 for item in tree:
753 for item in tree:
736 item_sha = item.hex
754 item_sha = item.hex
737 item_mode = item.filemode
755 item_mode = item.filemode
738 item_type = item.type
756 item_type = item.type
739
757
740 if item_type == 'commit':
758 if item_type == 'commit':
741 # NOTE(marcink): submodules we translate to 'link' for backward compat
759 # NOTE(marcink): submodules we translate to 'link' for backward compat
742 item_type = 'link'
760 item_type = 'link'
743
761
744 result.append((item.name, item_mode, item_sha, item_type))
762 result.append((item.name, item_mode, item_sha, item_type))
745 return result
763 return result
746
764
747 @reraise_safe_exceptions
765 @reraise_safe_exceptions
748 def update_server_info(self, wire):
766 def update_server_info(self, wire):
749 repo = self._factory.repo(wire)
767 repo = self._factory.repo(wire)
750 update_server_info(repo)
768 update_server_info(repo)
751
769
752 @reraise_safe_exceptions
770 @reraise_safe_exceptions
753 def discover_git_version(self):
771 def discover_git_version(self):
754 stdout, _ = self.run_git_command(
772 stdout, _ = self.run_git_command(
755 {}, ['--version'], _bare=True, _safe=True)
773 {}, ['--version'], _bare=True, _safe=True)
756 prefix = 'git version'
774 prefix = 'git version'
757 if stdout.startswith(prefix):
775 if stdout.startswith(prefix):
758 stdout = stdout[len(prefix):]
776 stdout = stdout[len(prefix):]
759 return stdout.strip()
777 return stdout.strip()
760
778
761 @reraise_safe_exceptions
779 @reraise_safe_exceptions
762 def get_all_commit_ids(self, wire):
780 def get_all_commit_ids(self, wire):
763 if self.is_empty(wire):
781 if self.is_empty(wire):
764 return []
782 return []
765
783
766 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
784 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
767 try:
785 try:
768 output, __ = self.run_git_command(wire, cmd)
786 output, __ = self.run_git_command(wire, cmd)
769 return output.splitlines()
787 return output.splitlines()
770 except Exception:
788 except Exception:
771 # Can be raised for empty repositories
789 # Can be raised for empty repositories
772 return []
790 return []
773
791
774 @reraise_safe_exceptions
792 @reraise_safe_exceptions
775 def run_git_command(self, wire, cmd, **opts):
793 def run_git_command(self, wire, cmd, **opts):
776 path = wire.get('path', None)
794 path = wire.get('path', None)
777
795
778 if path and os.path.isdir(path):
796 if path and os.path.isdir(path):
779 opts['cwd'] = path
797 opts['cwd'] = path
780
798
781 if '_bare' in opts:
799 if '_bare' in opts:
782 _copts = []
800 _copts = []
783 del opts['_bare']
801 del opts['_bare']
784 else:
802 else:
785 _copts = ['-c', 'core.quotepath=false', ]
803 _copts = ['-c', 'core.quotepath=false', ]
786 safe_call = False
804 safe_call = False
787 if '_safe' in opts:
805 if '_safe' in opts:
788 # no exc on failure
806 # no exc on failure
789 del opts['_safe']
807 del opts['_safe']
790 safe_call = True
808 safe_call = True
791
809
792 if '_copts' in opts:
810 if '_copts' in opts:
793 _copts.extend(opts['_copts'] or [])
811 _copts.extend(opts['_copts'] or [])
794 del opts['_copts']
812 del opts['_copts']
795
813
796 gitenv = os.environ.copy()
814 gitenv = os.environ.copy()
797 gitenv.update(opts.pop('extra_env', {}))
815 gitenv.update(opts.pop('extra_env', {}))
798 # need to clean fix GIT_DIR !
816 # need to clean fix GIT_DIR !
799 if 'GIT_DIR' in gitenv:
817 if 'GIT_DIR' in gitenv:
800 del gitenv['GIT_DIR']
818 del gitenv['GIT_DIR']
801 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
819 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
802 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
820 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
803
821
804 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
822 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
805 _opts = {'env': gitenv, 'shell': False}
823 _opts = {'env': gitenv, 'shell': False}
806
824
807 try:
825 try:
808 _opts.update(opts)
826 _opts.update(opts)
809 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
827 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
810
828
811 return ''.join(p), ''.join(p.error)
829 return ''.join(p), ''.join(p.error)
812 except (EnvironmentError, OSError) as err:
830 except (EnvironmentError, OSError) as err:
813 cmd = ' '.join(cmd) # human friendly CMD
831 cmd = ' '.join(cmd) # human friendly CMD
814 tb_err = ("Couldn't run git command (%s).\n"
832 tb_err = ("Couldn't run git command (%s).\n"
815 "Original error was:%s\n"
833 "Original error was:%s\n"
816 "Call options:%s\n"
834 "Call options:%s\n"
817 % (cmd, err, _opts))
835 % (cmd, err, _opts))
818 log.exception(tb_err)
836 log.exception(tb_err)
819 if safe_call:
837 if safe_call:
820 return '', err
838 return '', err
821 else:
839 else:
822 raise exceptions.VcsException()(tb_err)
840 raise exceptions.VcsException()(tb_err)
823
841
824 @reraise_safe_exceptions
842 @reraise_safe_exceptions
825 def install_hooks(self, wire, force=False):
843 def install_hooks(self, wire, force=False):
826 from vcsserver.hook_utils import install_git_hooks
844 from vcsserver.hook_utils import install_git_hooks
827 repo = self._factory.repo(wire)
845 repo = self._factory.repo(wire)
828 return install_git_hooks(repo.path, repo.bare, force_create=force)
846 return install_git_hooks(repo.path, repo.bare, force_create=force)
829
847
830 @reraise_safe_exceptions
848 @reraise_safe_exceptions
831 def get_hooks_info(self, wire):
849 def get_hooks_info(self, wire):
832 from vcsserver.hook_utils import (
850 from vcsserver.hook_utils import (
833 get_git_pre_hook_version, get_git_post_hook_version)
851 get_git_pre_hook_version, get_git_post_hook_version)
834 repo = self._factory.repo(wire)
852 repo = self._factory.repo(wire)
835 return {
853 return {
836 'pre_version': get_git_pre_hook_version(repo.path, repo.bare),
854 'pre_version': get_git_pre_hook_version(repo.path, repo.bare),
837 'post_version': get_git_post_hook_version(repo.path, repo.bare),
855 'post_version': get_git_post_hook_version(repo.path, repo.bare),
838 }
856 }
839
857
840
858
841 def str_to_dulwich(value):
859 def str_to_dulwich(value):
842 """
860 """
843 Dulwich 0.10.1a requires `unicode` objects to be passed in.
861 Dulwich 0.10.1a requires `unicode` objects to be passed in.
844 """
862 """
845 return value.decode(settings.WIRE_ENCODING)
863 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now