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