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