##// END OF EJS Templates
git: handle archive generation crash case when submodules are present.
super-admin -
r962:127b0594 default
parent child Browse files
Show More
@@ -1,1247 +1,1250 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-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import collections
18 import collections
19 import logging
19 import logging
20 import os
20 import os
21 import posixpath as vcspath
21 import posixpath as vcspath
22 import re
22 import re
23 import stat
23 import stat
24 import traceback
24 import traceback
25 import urllib
25 import urllib
26 import urllib2
26 import urllib2
27 from functools import wraps
27 from functools import wraps
28
28
29 import more_itertools
29 import more_itertools
30 import pygit2
30 import pygit2
31 from pygit2 import Repository as LibGit2Repo
31 from pygit2 import Repository as LibGit2Repo
32 from pygit2 import index as LibGit2Index
32 from pygit2 import index as LibGit2Index
33 from dulwich import index, objects
33 from dulwich import index, objects
34 from dulwich.client import HttpGitClient, LocalGitClient
34 from dulwich.client import HttpGitClient, LocalGitClient
35 from dulwich.errors import (
35 from dulwich.errors import (
36 NotGitRepository, ChecksumMismatch, WrongObjectException,
36 NotGitRepository, ChecksumMismatch, WrongObjectException,
37 MissingCommitError, ObjectMissing, HangupException,
37 MissingCommitError, ObjectMissing, HangupException,
38 UnexpectedCommandError)
38 UnexpectedCommandError)
39 from dulwich.repo import Repo as DulwichRepo
39 from dulwich.repo import Repo as DulwichRepo
40 from dulwich.server import update_server_info
40 from dulwich.server import update_server_info
41
41
42 from vcsserver import exceptions, settings, subprocessio
42 from vcsserver import exceptions, settings, subprocessio
43 from vcsserver.utils import safe_str, safe_int, safe_unicode
43 from vcsserver.utils import safe_str, safe_int, safe_unicode
44 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, archive_repo
44 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, archive_repo
45 from vcsserver.hgcompat import (
45 from vcsserver.hgcompat import (
46 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
46 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
47 from vcsserver.git_lfs.lib import LFSOidStore
47 from vcsserver.git_lfs.lib import LFSOidStore
48 from vcsserver.vcs_base import RemoteBase
48 from vcsserver.vcs_base import RemoteBase
49
49
50 DIR_STAT = stat.S_IFDIR
50 DIR_STAT = stat.S_IFDIR
51 FILE_MODE = stat.S_IFMT
51 FILE_MODE = stat.S_IFMT
52 GIT_LINK = objects.S_IFGITLINK
52 GIT_LINK = objects.S_IFGITLINK
53 PEELED_REF_MARKER = '^{}'
53 PEELED_REF_MARKER = '^{}'
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def str_to_dulwich(value):
59 def str_to_dulwich(value):
60 """
60 """
61 Dulwich 0.10.1a requires `unicode` objects to be passed in.
61 Dulwich 0.10.1a requires `unicode` objects to be passed in.
62 """
62 """
63 return value.decode(settings.WIRE_ENCODING)
63 return value.decode(settings.WIRE_ENCODING)
64
64
65
65
66 def reraise_safe_exceptions(func):
66 def reraise_safe_exceptions(func):
67 """Converts Dulwich exceptions to something neutral."""
67 """Converts Dulwich exceptions to something neutral."""
68
68
69 @wraps(func)
69 @wraps(func)
70 def wrapper(*args, **kwargs):
70 def wrapper(*args, **kwargs):
71 try:
71 try:
72 return func(*args, **kwargs)
72 return func(*args, **kwargs)
73 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
73 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
74 exc = exceptions.LookupException(org_exc=e)
74 exc = exceptions.LookupException(org_exc=e)
75 raise exc(safe_str(e))
75 raise exc(safe_str(e))
76 except (HangupException, UnexpectedCommandError) as e:
76 except (HangupException, UnexpectedCommandError) as e:
77 exc = exceptions.VcsException(org_exc=e)
77 exc = exceptions.VcsException(org_exc=e)
78 raise exc(safe_str(e))
78 raise exc(safe_str(e))
79 except Exception as e:
79 except Exception as e:
80 # NOTE(marcink): becuase of how dulwich handles some exceptions
80 # NOTE(marcink): becuase of how dulwich handles some exceptions
81 # (KeyError on empty repos), we cannot track this and catch all
81 # (KeyError on empty repos), we cannot track this and catch all
82 # exceptions, it's an exceptions from other handlers
82 # exceptions, it's an exceptions from other handlers
83 #if not hasattr(e, '_vcs_kind'):
83 #if not hasattr(e, '_vcs_kind'):
84 #log.exception("Unhandled exception in git remote call")
84 #log.exception("Unhandled exception in git remote call")
85 #raise_from_original(exceptions.UnhandledException)
85 #raise_from_original(exceptions.UnhandledException)
86 raise
86 raise
87 return wrapper
87 return wrapper
88
88
89
89
90 class Repo(DulwichRepo):
90 class Repo(DulwichRepo):
91 """
91 """
92 A wrapper for dulwich Repo class.
92 A wrapper for dulwich Repo class.
93
93
94 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
94 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
95 "Too many open files" error. We need to close all opened file descriptors
95 "Too many open files" error. We need to close all opened file descriptors
96 once the repo object is destroyed.
96 once the repo object is destroyed.
97 """
97 """
98 def __del__(self):
98 def __del__(self):
99 if hasattr(self, 'object_store'):
99 if hasattr(self, 'object_store'):
100 self.close()
100 self.close()
101
101
102
102
103 class Repository(LibGit2Repo):
103 class Repository(LibGit2Repo):
104
104
105 def __enter__(self):
105 def __enter__(self):
106 return self
106 return self
107
107
108 def __exit__(self, exc_type, exc_val, exc_tb):
108 def __exit__(self, exc_type, exc_val, exc_tb):
109 self.free()
109 self.free()
110
110
111
111
112 class GitFactory(RepoFactory):
112 class GitFactory(RepoFactory):
113 repo_type = 'git'
113 repo_type = 'git'
114
114
115 def _create_repo(self, wire, create, use_libgit2=False):
115 def _create_repo(self, wire, create, use_libgit2=False):
116 if use_libgit2:
116 if use_libgit2:
117 return Repository(wire['path'])
117 return Repository(wire['path'])
118 else:
118 else:
119 repo_path = str_to_dulwich(wire['path'])
119 repo_path = str_to_dulwich(wire['path'])
120 return Repo(repo_path)
120 return Repo(repo_path)
121
121
122 def repo(self, wire, create=False, use_libgit2=False):
122 def repo(self, wire, create=False, use_libgit2=False):
123 """
123 """
124 Get a repository instance for the given path.
124 Get a repository instance for the given path.
125 """
125 """
126 return self._create_repo(wire, create, use_libgit2)
126 return self._create_repo(wire, create, use_libgit2)
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(RemoteBase):
132 class GitRemote(RemoteBase):
133
133
134 def __init__(self, factory):
134 def __init__(self, factory):
135 self._factory = factory
135 self._factory = factory
136 self._bulk_methods = {
136 self._bulk_methods = {
137 "date": self.date,
137 "date": self.date,
138 "author": self.author,
138 "author": self.author,
139 "branch": self.branch,
139 "branch": self.branch,
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 discover_git_version(self):
160 def discover_git_version(self):
161 stdout, _ = self.run_git_command(
161 stdout, _ = self.run_git_command(
162 {}, ['--version'], _bare=True, _safe=True)
162 {}, ['--version'], _bare=True, _safe=True)
163 prefix = 'git version'
163 prefix = 'git version'
164 if stdout.startswith(prefix):
164 if stdout.startswith(prefix):
165 stdout = stdout[len(prefix):]
165 stdout = stdout[len(prefix):]
166 return stdout.strip()
166 return stdout.strip()
167
167
168 @reraise_safe_exceptions
168 @reraise_safe_exceptions
169 def is_empty(self, wire):
169 def is_empty(self, wire):
170 repo_init = self._factory.repo_libgit2(wire)
170 repo_init = self._factory.repo_libgit2(wire)
171 with repo_init as repo:
171 with repo_init as repo:
172
172
173 try:
173 try:
174 has_head = repo.head.name
174 has_head = repo.head.name
175 if has_head:
175 if has_head:
176 return False
176 return False
177
177
178 # NOTE(marcink): check again using more expensive method
178 # NOTE(marcink): check again using more expensive method
179 return repo.is_empty
179 return repo.is_empty
180 except Exception:
180 except Exception:
181 pass
181 pass
182
182
183 return True
183 return True
184
184
185 @reraise_safe_exceptions
185 @reraise_safe_exceptions
186 def assert_correct_path(self, wire):
186 def assert_correct_path(self, wire):
187 cache_on, context_uid, repo_id = self._cache_on(wire)
187 cache_on, context_uid, repo_id = self._cache_on(wire)
188 region = self.region(wire)
188 region = self.region(wire)
189 @region.conditional_cache_on_arguments(condition=cache_on)
189 @region.conditional_cache_on_arguments(condition=cache_on)
190 def _assert_correct_path(_context_uid, _repo_id):
190 def _assert_correct_path(_context_uid, _repo_id):
191 try:
191 try:
192 repo_init = self._factory.repo_libgit2(wire)
192 repo_init = self._factory.repo_libgit2(wire)
193 with repo_init as repo:
193 with repo_init as repo:
194 pass
194 pass
195 except pygit2.GitError:
195 except pygit2.GitError:
196 path = wire.get('path')
196 path = wire.get('path')
197 tb = traceback.format_exc()
197 tb = traceback.format_exc()
198 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
198 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
199 return False
199 return False
200
200
201 return True
201 return True
202 return _assert_correct_path(context_uid, repo_id)
202 return _assert_correct_path(context_uid, repo_id)
203
203
204 @reraise_safe_exceptions
204 @reraise_safe_exceptions
205 def bare(self, wire):
205 def bare(self, wire):
206 repo_init = self._factory.repo_libgit2(wire)
206 repo_init = self._factory.repo_libgit2(wire)
207 with repo_init as repo:
207 with repo_init as repo:
208 return repo.is_bare
208 return repo.is_bare
209
209
210 @reraise_safe_exceptions
210 @reraise_safe_exceptions
211 def blob_as_pretty_string(self, wire, sha):
211 def blob_as_pretty_string(self, wire, sha):
212 repo_init = self._factory.repo_libgit2(wire)
212 repo_init = self._factory.repo_libgit2(wire)
213 with repo_init as repo:
213 with repo_init as repo:
214 blob_obj = repo[sha]
214 blob_obj = repo[sha]
215 blob = blob_obj.data
215 blob = blob_obj.data
216 return blob
216 return blob
217
217
218 @reraise_safe_exceptions
218 @reraise_safe_exceptions
219 def blob_raw_length(self, wire, sha):
219 def blob_raw_length(self, wire, sha):
220 cache_on, context_uid, repo_id = self._cache_on(wire)
220 cache_on, context_uid, repo_id = self._cache_on(wire)
221 region = self.region(wire)
221 region = self.region(wire)
222 @region.conditional_cache_on_arguments(condition=cache_on)
222 @region.conditional_cache_on_arguments(condition=cache_on)
223 def _blob_raw_length(_repo_id, _sha):
223 def _blob_raw_length(_repo_id, _sha):
224
224
225 repo_init = self._factory.repo_libgit2(wire)
225 repo_init = self._factory.repo_libgit2(wire)
226 with repo_init as repo:
226 with repo_init as repo:
227 blob = repo[sha]
227 blob = repo[sha]
228 return blob.size
228 return blob.size
229
229
230 return _blob_raw_length(repo_id, sha)
230 return _blob_raw_length(repo_id, sha)
231
231
232 def _parse_lfs_pointer(self, raw_content):
232 def _parse_lfs_pointer(self, raw_content):
233
233
234 spec_string = 'version https://git-lfs.github.com/spec'
234 spec_string = 'version https://git-lfs.github.com/spec'
235 if raw_content and raw_content.startswith(spec_string):
235 if raw_content and raw_content.startswith(spec_string):
236 pattern = re.compile(r"""
236 pattern = re.compile(r"""
237 (?:\n)?
237 (?:\n)?
238 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
238 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
239 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
239 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
240 ^size[ ](?P<oid_size>[0-9]+)\n
240 ^size[ ](?P<oid_size>[0-9]+)\n
241 (?:\n)?
241 (?:\n)?
242 """, re.VERBOSE | re.MULTILINE)
242 """, re.VERBOSE | re.MULTILINE)
243 match = pattern.match(raw_content)
243 match = pattern.match(raw_content)
244 if match:
244 if match:
245 return match.groupdict()
245 return match.groupdict()
246
246
247 return {}
247 return {}
248
248
249 @reraise_safe_exceptions
249 @reraise_safe_exceptions
250 def is_large_file(self, wire, commit_id):
250 def is_large_file(self, wire, commit_id):
251 cache_on, context_uid, repo_id = self._cache_on(wire)
251 cache_on, context_uid, repo_id = self._cache_on(wire)
252
252
253 region = self.region(wire)
253 region = self.region(wire)
254 @region.conditional_cache_on_arguments(condition=cache_on)
254 @region.conditional_cache_on_arguments(condition=cache_on)
255 def _is_large_file(_repo_id, _sha):
255 def _is_large_file(_repo_id, _sha):
256 repo_init = self._factory.repo_libgit2(wire)
256 repo_init = self._factory.repo_libgit2(wire)
257 with repo_init as repo:
257 with repo_init as repo:
258 blob = repo[commit_id]
258 blob = repo[commit_id]
259 if blob.is_binary:
259 if blob.is_binary:
260 return {}
260 return {}
261
261
262 return self._parse_lfs_pointer(blob.data)
262 return self._parse_lfs_pointer(blob.data)
263
263
264 return _is_large_file(repo_id, commit_id)
264 return _is_large_file(repo_id, commit_id)
265
265
266 @reraise_safe_exceptions
266 @reraise_safe_exceptions
267 def is_binary(self, wire, tree_id):
267 def is_binary(self, wire, tree_id):
268 cache_on, context_uid, repo_id = self._cache_on(wire)
268 cache_on, context_uid, repo_id = self._cache_on(wire)
269
269
270 region = self.region(wire)
270 region = self.region(wire)
271 @region.conditional_cache_on_arguments(condition=cache_on)
271 @region.conditional_cache_on_arguments(condition=cache_on)
272 def _is_binary(_repo_id, _tree_id):
272 def _is_binary(_repo_id, _tree_id):
273 repo_init = self._factory.repo_libgit2(wire)
273 repo_init = self._factory.repo_libgit2(wire)
274 with repo_init as repo:
274 with repo_init as repo:
275 blob_obj = repo[tree_id]
275 blob_obj = repo[tree_id]
276 return blob_obj.is_binary
276 return blob_obj.is_binary
277
277
278 return _is_binary(repo_id, tree_id)
278 return _is_binary(repo_id, tree_id)
279
279
280 @reraise_safe_exceptions
280 @reraise_safe_exceptions
281 def in_largefiles_store(self, wire, oid):
281 def in_largefiles_store(self, wire, oid):
282 conf = self._wire_to_config(wire)
282 conf = self._wire_to_config(wire)
283 repo_init = self._factory.repo_libgit2(wire)
283 repo_init = self._factory.repo_libgit2(wire)
284 with repo_init as repo:
284 with repo_init as repo:
285 repo_name = repo.path
285 repo_name = repo.path
286
286
287 store_location = conf.get('vcs_git_lfs_store_location')
287 store_location = conf.get('vcs_git_lfs_store_location')
288 if store_location:
288 if store_location:
289
289
290 store = LFSOidStore(
290 store = LFSOidStore(
291 oid=oid, repo=repo_name, store_location=store_location)
291 oid=oid, repo=repo_name, store_location=store_location)
292 return store.has_oid()
292 return store.has_oid()
293
293
294 return False
294 return False
295
295
296 @reraise_safe_exceptions
296 @reraise_safe_exceptions
297 def store_path(self, wire, oid):
297 def store_path(self, wire, oid):
298 conf = self._wire_to_config(wire)
298 conf = self._wire_to_config(wire)
299 repo_init = self._factory.repo_libgit2(wire)
299 repo_init = self._factory.repo_libgit2(wire)
300 with repo_init as repo:
300 with repo_init as repo:
301 repo_name = repo.path
301 repo_name = repo.path
302
302
303 store_location = conf.get('vcs_git_lfs_store_location')
303 store_location = conf.get('vcs_git_lfs_store_location')
304 if store_location:
304 if store_location:
305 store = LFSOidStore(
305 store = LFSOidStore(
306 oid=oid, repo=repo_name, store_location=store_location)
306 oid=oid, repo=repo_name, store_location=store_location)
307 return store.oid_path
307 return store.oid_path
308 raise ValueError('Unable to fetch oid with path {}'.format(oid))
308 raise ValueError('Unable to fetch oid with path {}'.format(oid))
309
309
310 @reraise_safe_exceptions
310 @reraise_safe_exceptions
311 def bulk_request(self, wire, rev, pre_load):
311 def bulk_request(self, wire, rev, pre_load):
312 cache_on, context_uid, repo_id = self._cache_on(wire)
312 cache_on, context_uid, repo_id = self._cache_on(wire)
313 region = self.region(wire)
313 region = self.region(wire)
314 @region.conditional_cache_on_arguments(condition=cache_on)
314 @region.conditional_cache_on_arguments(condition=cache_on)
315 def _bulk_request(_repo_id, _rev, _pre_load):
315 def _bulk_request(_repo_id, _rev, _pre_load):
316 result = {}
316 result = {}
317 for attr in pre_load:
317 for attr in pre_load:
318 try:
318 try:
319 method = self._bulk_methods[attr]
319 method = self._bulk_methods[attr]
320 args = [wire, rev]
320 args = [wire, rev]
321 result[attr] = method(*args)
321 result[attr] = method(*args)
322 except KeyError as e:
322 except KeyError as e:
323 raise exceptions.VcsException(e)(
323 raise exceptions.VcsException(e)(
324 "Unknown bulk attribute: %s" % attr)
324 "Unknown bulk attribute: %s" % attr)
325 return result
325 return result
326
326
327 return _bulk_request(repo_id, rev, sorted(pre_load))
327 return _bulk_request(repo_id, rev, sorted(pre_load))
328
328
329 def _build_opener(self, url):
329 def _build_opener(self, url):
330 handlers = []
330 handlers = []
331 url_obj = url_parser(url)
331 url_obj = url_parser(url)
332 _, authinfo = url_obj.authinfo()
332 _, authinfo = url_obj.authinfo()
333
333
334 if authinfo:
334 if authinfo:
335 # create a password manager
335 # create a password manager
336 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
336 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
337 passmgr.add_password(*authinfo)
337 passmgr.add_password(*authinfo)
338
338
339 handlers.extend((httpbasicauthhandler(passmgr),
339 handlers.extend((httpbasicauthhandler(passmgr),
340 httpdigestauthhandler(passmgr)))
340 httpdigestauthhandler(passmgr)))
341
341
342 return urllib2.build_opener(*handlers)
342 return urllib2.build_opener(*handlers)
343
343
344 def _type_id_to_name(self, type_id):
344 def _type_id_to_name(self, type_id):
345 return {
345 return {
346 1: b'commit',
346 1: b'commit',
347 2: b'tree',
347 2: b'tree',
348 3: b'blob',
348 3: b'blob',
349 4: b'tag'
349 4: b'tag'
350 }[type_id]
350 }[type_id]
351
351
352 @reraise_safe_exceptions
352 @reraise_safe_exceptions
353 def check_url(self, url, config):
353 def check_url(self, url, config):
354 url_obj = url_parser(url)
354 url_obj = url_parser(url)
355 test_uri, _ = url_obj.authinfo()
355 test_uri, _ = url_obj.authinfo()
356 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
356 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
357 url_obj.query = obfuscate_qs(url_obj.query)
357 url_obj.query = obfuscate_qs(url_obj.query)
358 cleaned_uri = str(url_obj)
358 cleaned_uri = str(url_obj)
359 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
359 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
360
360
361 if not test_uri.endswith('info/refs'):
361 if not test_uri.endswith('info/refs'):
362 test_uri = test_uri.rstrip('/') + '/info/refs'
362 test_uri = test_uri.rstrip('/') + '/info/refs'
363
363
364 o = self._build_opener(url)
364 o = self._build_opener(url)
365 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
365 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
366
366
367 q = {"service": 'git-upload-pack'}
367 q = {"service": 'git-upload-pack'}
368 qs = '?%s' % urllib.urlencode(q)
368 qs = '?%s' % urllib.urlencode(q)
369 cu = "%s%s" % (test_uri, qs)
369 cu = "%s%s" % (test_uri, qs)
370 req = urllib2.Request(cu, None, {})
370 req = urllib2.Request(cu, None, {})
371
371
372 try:
372 try:
373 log.debug("Trying to open URL %s", cleaned_uri)
373 log.debug("Trying to open URL %s", cleaned_uri)
374 resp = o.open(req)
374 resp = o.open(req)
375 if resp.code != 200:
375 if resp.code != 200:
376 raise exceptions.URLError()('Return Code is not 200')
376 raise exceptions.URLError()('Return Code is not 200')
377 except Exception as e:
377 except Exception as e:
378 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
378 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
379 # means it cannot be cloned
379 # means it cannot be cloned
380 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
380 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
381
381
382 # now detect if it's proper git repo
382 # now detect if it's proper git repo
383 gitdata = resp.read()
383 gitdata = resp.read()
384 if 'service=git-upload-pack' in gitdata:
384 if 'service=git-upload-pack' in gitdata:
385 pass
385 pass
386 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
386 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
387 # old style git can return some other format !
387 # old style git can return some other format !
388 pass
388 pass
389 else:
389 else:
390 raise exceptions.URLError()(
390 raise exceptions.URLError()(
391 "url [%s] does not look like an git" % (cleaned_uri,))
391 "url [%s] does not look like an git" % (cleaned_uri,))
392
392
393 return True
393 return True
394
394
395 @reraise_safe_exceptions
395 @reraise_safe_exceptions
396 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
396 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
397 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
397 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
398 remote_refs = self.pull(wire, url, apply_refs=False)
398 remote_refs = self.pull(wire, url, apply_refs=False)
399 repo = self._factory.repo(wire)
399 repo = self._factory.repo(wire)
400 if isinstance(valid_refs, list):
400 if isinstance(valid_refs, list):
401 valid_refs = tuple(valid_refs)
401 valid_refs = tuple(valid_refs)
402
402
403 for k in remote_refs:
403 for k in remote_refs:
404 # only parse heads/tags and skip so called deferred tags
404 # only parse heads/tags and skip so called deferred tags
405 if k.startswith(valid_refs) and not k.endswith(deferred):
405 if k.startswith(valid_refs) and not k.endswith(deferred):
406 repo[k] = remote_refs[k]
406 repo[k] = remote_refs[k]
407
407
408 if update_after_clone:
408 if update_after_clone:
409 # we want to checkout HEAD
409 # we want to checkout HEAD
410 repo["HEAD"] = remote_refs["HEAD"]
410 repo["HEAD"] = remote_refs["HEAD"]
411 index.build_index_from_tree(repo.path, repo.index_path(),
411 index.build_index_from_tree(repo.path, repo.index_path(),
412 repo.object_store, repo["HEAD"].tree)
412 repo.object_store, repo["HEAD"].tree)
413
413
414 @reraise_safe_exceptions
414 @reraise_safe_exceptions
415 def branch(self, wire, commit_id):
415 def branch(self, wire, commit_id):
416 cache_on, context_uid, repo_id = self._cache_on(wire)
416 cache_on, context_uid, repo_id = self._cache_on(wire)
417 region = self.region(wire)
417 region = self.region(wire)
418 @region.conditional_cache_on_arguments(condition=cache_on)
418 @region.conditional_cache_on_arguments(condition=cache_on)
419 def _branch(_context_uid, _repo_id, _commit_id):
419 def _branch(_context_uid, _repo_id, _commit_id):
420 regex = re.compile('^refs/heads')
420 regex = re.compile('^refs/heads')
421
421
422 def filter_with(ref):
422 def filter_with(ref):
423 return regex.match(ref[0]) and ref[1] == _commit_id
423 return regex.match(ref[0]) and ref[1] == _commit_id
424
424
425 branches = filter(filter_with, self.get_refs(wire).items())
425 branches = filter(filter_with, self.get_refs(wire).items())
426 return [x[0].split('refs/heads/')[-1] for x in branches]
426 return [x[0].split('refs/heads/')[-1] for x in branches]
427
427
428 return _branch(context_uid, repo_id, commit_id)
428 return _branch(context_uid, repo_id, commit_id)
429
429
430 @reraise_safe_exceptions
430 @reraise_safe_exceptions
431 def commit_branches(self, wire, commit_id):
431 def commit_branches(self, wire, commit_id):
432 cache_on, context_uid, repo_id = self._cache_on(wire)
432 cache_on, context_uid, repo_id = self._cache_on(wire)
433 region = self.region(wire)
433 region = self.region(wire)
434 @region.conditional_cache_on_arguments(condition=cache_on)
434 @region.conditional_cache_on_arguments(condition=cache_on)
435 def _commit_branches(_context_uid, _repo_id, _commit_id):
435 def _commit_branches(_context_uid, _repo_id, _commit_id):
436 repo_init = self._factory.repo_libgit2(wire)
436 repo_init = self._factory.repo_libgit2(wire)
437 with repo_init as repo:
437 with repo_init as repo:
438 branches = [x for x in repo.branches.with_commit(_commit_id)]
438 branches = [x for x in repo.branches.with_commit(_commit_id)]
439 return branches
439 return branches
440
440
441 return _commit_branches(context_uid, repo_id, commit_id)
441 return _commit_branches(context_uid, repo_id, commit_id)
442
442
443 @reraise_safe_exceptions
443 @reraise_safe_exceptions
444 def add_object(self, wire, content):
444 def add_object(self, wire, content):
445 repo_init = self._factory.repo_libgit2(wire)
445 repo_init = self._factory.repo_libgit2(wire)
446 with repo_init as repo:
446 with repo_init as repo:
447 blob = objects.Blob()
447 blob = objects.Blob()
448 blob.set_raw_string(content)
448 blob.set_raw_string(content)
449 repo.object_store.add_object(blob)
449 repo.object_store.add_object(blob)
450 return blob.id
450 return blob.id
451
451
452 # TODO: this is quite complex, check if that can be simplified
452 # TODO: this is quite complex, check if that can be simplified
453 @reraise_safe_exceptions
453 @reraise_safe_exceptions
454 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
454 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
455 repo = self._factory.repo(wire)
455 repo = self._factory.repo(wire)
456 object_store = repo.object_store
456 object_store = repo.object_store
457
457
458 # Create tree and populates it with blobs
458 # Create tree and populates it with blobs
459 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
459 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
460
460
461 for node in updated:
461 for node in updated:
462 # Compute subdirs if needed
462 # Compute subdirs if needed
463 dirpath, nodename = vcspath.split(node['path'])
463 dirpath, nodename = vcspath.split(node['path'])
464 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
464 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
465 parent = commit_tree
465 parent = commit_tree
466 ancestors = [('', parent)]
466 ancestors = [('', parent)]
467
467
468 # Tries to dig for the deepest existing tree
468 # Tries to dig for the deepest existing tree
469 while dirnames:
469 while dirnames:
470 curdir = dirnames.pop(0)
470 curdir = dirnames.pop(0)
471 try:
471 try:
472 dir_id = parent[curdir][1]
472 dir_id = parent[curdir][1]
473 except KeyError:
473 except KeyError:
474 # put curdir back into dirnames and stops
474 # put curdir back into dirnames and stops
475 dirnames.insert(0, curdir)
475 dirnames.insert(0, curdir)
476 break
476 break
477 else:
477 else:
478 # If found, updates parent
478 # If found, updates parent
479 parent = repo[dir_id]
479 parent = repo[dir_id]
480 ancestors.append((curdir, parent))
480 ancestors.append((curdir, parent))
481 # Now parent is deepest existing tree and we need to create
481 # Now parent is deepest existing tree and we need to create
482 # subtrees for dirnames (in reverse order)
482 # subtrees for dirnames (in reverse order)
483 # [this only applies for nodes from added]
483 # [this only applies for nodes from added]
484 new_trees = []
484 new_trees = []
485
485
486 blob = objects.Blob.from_string(node['content'])
486 blob = objects.Blob.from_string(node['content'])
487
487
488 if dirnames:
488 if dirnames:
489 # If there are trees which should be created we need to build
489 # If there are trees which should be created we need to build
490 # them now (in reverse order)
490 # them now (in reverse order)
491 reversed_dirnames = list(reversed(dirnames))
491 reversed_dirnames = list(reversed(dirnames))
492 curtree = objects.Tree()
492 curtree = objects.Tree()
493 curtree[node['node_path']] = node['mode'], blob.id
493 curtree[node['node_path']] = node['mode'], blob.id
494 new_trees.append(curtree)
494 new_trees.append(curtree)
495 for dirname in reversed_dirnames[:-1]:
495 for dirname in reversed_dirnames[:-1]:
496 newtree = objects.Tree()
496 newtree = objects.Tree()
497 newtree[dirname] = (DIR_STAT, curtree.id)
497 newtree[dirname] = (DIR_STAT, curtree.id)
498 new_trees.append(newtree)
498 new_trees.append(newtree)
499 curtree = newtree
499 curtree = newtree
500 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
500 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
501 else:
501 else:
502 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
502 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
503
503
504 new_trees.append(parent)
504 new_trees.append(parent)
505 # Update ancestors
505 # Update ancestors
506 reversed_ancestors = reversed(
506 reversed_ancestors = reversed(
507 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
507 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
508 for parent, tree, path in reversed_ancestors:
508 for parent, tree, path in reversed_ancestors:
509 parent[path] = (DIR_STAT, tree.id)
509 parent[path] = (DIR_STAT, tree.id)
510 object_store.add_object(tree)
510 object_store.add_object(tree)
511
511
512 object_store.add_object(blob)
512 object_store.add_object(blob)
513 for tree in new_trees:
513 for tree in new_trees:
514 object_store.add_object(tree)
514 object_store.add_object(tree)
515
515
516 for node_path in removed:
516 for node_path in removed:
517 paths = node_path.split('/')
517 paths = node_path.split('/')
518 tree = commit_tree
518 tree = commit_tree
519 trees = [tree]
519 trees = [tree]
520 # Traverse deep into the forest...
520 # Traverse deep into the forest...
521 for path in paths:
521 for path in paths:
522 try:
522 try:
523 obj = repo[tree[path][1]]
523 obj = repo[tree[path][1]]
524 if isinstance(obj, objects.Tree):
524 if isinstance(obj, objects.Tree):
525 trees.append(obj)
525 trees.append(obj)
526 tree = obj
526 tree = obj
527 except KeyError:
527 except KeyError:
528 break
528 break
529 # Cut down the blob and all rotten trees on the way back...
529 # Cut down the blob and all rotten trees on the way back...
530 for path, tree in reversed(zip(paths, trees)):
530 for path, tree in reversed(zip(paths, trees)):
531 del tree[path]
531 del tree[path]
532 if tree:
532 if tree:
533 # This tree still has elements - don't remove it or any
533 # This tree still has elements - don't remove it or any
534 # of it's parents
534 # of it's parents
535 break
535 break
536
536
537 object_store.add_object(commit_tree)
537 object_store.add_object(commit_tree)
538
538
539 # Create commit
539 # Create commit
540 commit = objects.Commit()
540 commit = objects.Commit()
541 commit.tree = commit_tree.id
541 commit.tree = commit_tree.id
542 for k, v in commit_data.iteritems():
542 for k, v in commit_data.iteritems():
543 setattr(commit, k, v)
543 setattr(commit, k, v)
544 object_store.add_object(commit)
544 object_store.add_object(commit)
545
545
546 self.create_branch(wire, branch, commit.id)
546 self.create_branch(wire, branch, commit.id)
547
547
548 # dulwich set-ref
548 # dulwich set-ref
549 ref = 'refs/heads/%s' % branch
549 ref = 'refs/heads/%s' % branch
550 repo.refs[ref] = commit.id
550 repo.refs[ref] = commit.id
551
551
552 return commit.id
552 return commit.id
553
553
554 @reraise_safe_exceptions
554 @reraise_safe_exceptions
555 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
555 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
556 if url != 'default' and '://' not in url:
556 if url != 'default' and '://' not in url:
557 client = LocalGitClient(url)
557 client = LocalGitClient(url)
558 else:
558 else:
559 url_obj = url_parser(url)
559 url_obj = url_parser(url)
560 o = self._build_opener(url)
560 o = self._build_opener(url)
561 url, _ = url_obj.authinfo()
561 url, _ = url_obj.authinfo()
562 client = HttpGitClient(base_url=url, opener=o)
562 client = HttpGitClient(base_url=url, opener=o)
563 repo = self._factory.repo(wire)
563 repo = self._factory.repo(wire)
564
564
565 determine_wants = repo.object_store.determine_wants_all
565 determine_wants = repo.object_store.determine_wants_all
566 if refs:
566 if refs:
567 def determine_wants_requested(references):
567 def determine_wants_requested(references):
568 return [references[r] for r in references if r in refs]
568 return [references[r] for r in references if r in refs]
569 determine_wants = determine_wants_requested
569 determine_wants = determine_wants_requested
570
570
571 try:
571 try:
572 remote_refs = client.fetch(
572 remote_refs = client.fetch(
573 path=url, target=repo, determine_wants=determine_wants)
573 path=url, target=repo, determine_wants=determine_wants)
574 except NotGitRepository as e:
574 except NotGitRepository as e:
575 log.warning(
575 log.warning(
576 'Trying to fetch from "%s" failed, not a Git repository.', url)
576 'Trying to fetch from "%s" failed, not a Git repository.', url)
577 # Exception can contain unicode which we convert
577 # Exception can contain unicode which we convert
578 raise exceptions.AbortException(e)(repr(e))
578 raise exceptions.AbortException(e)(repr(e))
579
579
580 # mikhail: client.fetch() returns all the remote refs, but fetches only
580 # mikhail: client.fetch() returns all the remote refs, but fetches only
581 # refs filtered by `determine_wants` function. We need to filter result
581 # refs filtered by `determine_wants` function. We need to filter result
582 # as well
582 # as well
583 if refs:
583 if refs:
584 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
584 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
585
585
586 if apply_refs:
586 if apply_refs:
587 # TODO: johbo: Needs proper test coverage with a git repository
587 # TODO: johbo: Needs proper test coverage with a git repository
588 # that contains a tag object, so that we would end up with
588 # that contains a tag object, so that we would end up with
589 # a peeled ref at this point.
589 # a peeled ref at this point.
590 for k in remote_refs:
590 for k in remote_refs:
591 if k.endswith(PEELED_REF_MARKER):
591 if k.endswith(PEELED_REF_MARKER):
592 log.debug("Skipping peeled reference %s", k)
592 log.debug("Skipping peeled reference %s", k)
593 continue
593 continue
594 repo[k] = remote_refs[k]
594 repo[k] = remote_refs[k]
595
595
596 if refs and not update_after:
596 if refs and not update_after:
597 # mikhail: explicitly set the head to the last ref.
597 # mikhail: explicitly set the head to the last ref.
598 repo['HEAD'] = remote_refs[refs[-1]]
598 repo['HEAD'] = remote_refs[refs[-1]]
599
599
600 if update_after:
600 if update_after:
601 # we want to checkout HEAD
601 # we want to checkout HEAD
602 repo["HEAD"] = remote_refs["HEAD"]
602 repo["HEAD"] = remote_refs["HEAD"]
603 index.build_index_from_tree(repo.path, repo.index_path(),
603 index.build_index_from_tree(repo.path, repo.index_path(),
604 repo.object_store, repo["HEAD"].tree)
604 repo.object_store, repo["HEAD"].tree)
605 return remote_refs
605 return remote_refs
606
606
607 @reraise_safe_exceptions
607 @reraise_safe_exceptions
608 def sync_fetch(self, wire, url, refs=None, all_refs=False):
608 def sync_fetch(self, wire, url, refs=None, all_refs=False):
609 repo = self._factory.repo(wire)
609 repo = self._factory.repo(wire)
610 if refs and not isinstance(refs, (list, tuple)):
610 if refs and not isinstance(refs, (list, tuple)):
611 refs = [refs]
611 refs = [refs]
612
612
613 config = self._wire_to_config(wire)
613 config = self._wire_to_config(wire)
614 # get all remote refs we'll use to fetch later
614 # get all remote refs we'll use to fetch later
615 cmd = ['ls-remote']
615 cmd = ['ls-remote']
616 if not all_refs:
616 if not all_refs:
617 cmd += ['--heads', '--tags']
617 cmd += ['--heads', '--tags']
618 cmd += [url]
618 cmd += [url]
619 output, __ = self.run_git_command(
619 output, __ = self.run_git_command(
620 wire, cmd, fail_on_stderr=False,
620 wire, cmd, fail_on_stderr=False,
621 _copts=self._remote_conf(config),
621 _copts=self._remote_conf(config),
622 extra_env={'GIT_TERMINAL_PROMPT': '0'})
622 extra_env={'GIT_TERMINAL_PROMPT': '0'})
623
623
624 remote_refs = collections.OrderedDict()
624 remote_refs = collections.OrderedDict()
625 fetch_refs = []
625 fetch_refs = []
626
626
627 for ref_line in output.splitlines():
627 for ref_line in output.splitlines():
628 sha, ref = ref_line.split('\t')
628 sha, ref = ref_line.split('\t')
629 sha = sha.strip()
629 sha = sha.strip()
630 if ref in remote_refs:
630 if ref in remote_refs:
631 # duplicate, skip
631 # duplicate, skip
632 continue
632 continue
633 if ref.endswith(PEELED_REF_MARKER):
633 if ref.endswith(PEELED_REF_MARKER):
634 log.debug("Skipping peeled reference %s", ref)
634 log.debug("Skipping peeled reference %s", ref)
635 continue
635 continue
636 # don't sync HEAD
636 # don't sync HEAD
637 if ref in ['HEAD']:
637 if ref in ['HEAD']:
638 continue
638 continue
639
639
640 remote_refs[ref] = sha
640 remote_refs[ref] = sha
641
641
642 if refs and sha in refs:
642 if refs and sha in refs:
643 # we filter fetch using our specified refs
643 # we filter fetch using our specified refs
644 fetch_refs.append('{}:{}'.format(ref, ref))
644 fetch_refs.append('{}:{}'.format(ref, ref))
645 elif not refs:
645 elif not refs:
646 fetch_refs.append('{}:{}'.format(ref, ref))
646 fetch_refs.append('{}:{}'.format(ref, ref))
647 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
647 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
648
648
649 if fetch_refs:
649 if fetch_refs:
650 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
650 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
651 fetch_refs_chunks = list(chunk)
651 fetch_refs_chunks = list(chunk)
652 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
652 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
653 _out, _err = self.run_git_command(
653 _out, _err = self.run_git_command(
654 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
654 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
655 fail_on_stderr=False,
655 fail_on_stderr=False,
656 _copts=self._remote_conf(config),
656 _copts=self._remote_conf(config),
657 extra_env={'GIT_TERMINAL_PROMPT': '0'})
657 extra_env={'GIT_TERMINAL_PROMPT': '0'})
658
658
659 return remote_refs
659 return remote_refs
660
660
661 @reraise_safe_exceptions
661 @reraise_safe_exceptions
662 def sync_push(self, wire, url, refs=None):
662 def sync_push(self, wire, url, refs=None):
663 if not self.check_url(url, wire):
663 if not self.check_url(url, wire):
664 return
664 return
665 config = self._wire_to_config(wire)
665 config = self._wire_to_config(wire)
666 self._factory.repo(wire)
666 self._factory.repo(wire)
667 self.run_git_command(
667 self.run_git_command(
668 wire, ['push', url, '--mirror'], fail_on_stderr=False,
668 wire, ['push', url, '--mirror'], fail_on_stderr=False,
669 _copts=self._remote_conf(config),
669 _copts=self._remote_conf(config),
670 extra_env={'GIT_TERMINAL_PROMPT': '0'})
670 extra_env={'GIT_TERMINAL_PROMPT': '0'})
671
671
672 @reraise_safe_exceptions
672 @reraise_safe_exceptions
673 def get_remote_refs(self, wire, url):
673 def get_remote_refs(self, wire, url):
674 repo = Repo(url)
674 repo = Repo(url)
675 return repo.get_refs()
675 return repo.get_refs()
676
676
677 @reraise_safe_exceptions
677 @reraise_safe_exceptions
678 def get_description(self, wire):
678 def get_description(self, wire):
679 repo = self._factory.repo(wire)
679 repo = self._factory.repo(wire)
680 return repo.get_description()
680 return repo.get_description()
681
681
682 @reraise_safe_exceptions
682 @reraise_safe_exceptions
683 def get_missing_revs(self, wire, rev1, rev2, path2):
683 def get_missing_revs(self, wire, rev1, rev2, path2):
684 repo = self._factory.repo(wire)
684 repo = self._factory.repo(wire)
685 LocalGitClient(thin_packs=False).fetch(path2, repo)
685 LocalGitClient(thin_packs=False).fetch(path2, repo)
686
686
687 wire_remote = wire.copy()
687 wire_remote = wire.copy()
688 wire_remote['path'] = path2
688 wire_remote['path'] = path2
689 repo_remote = self._factory.repo(wire_remote)
689 repo_remote = self._factory.repo(wire_remote)
690 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
690 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
691
691
692 revs = [
692 revs = [
693 x.commit.id
693 x.commit.id
694 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
694 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
695 return revs
695 return revs
696
696
697 @reraise_safe_exceptions
697 @reraise_safe_exceptions
698 def get_object(self, wire, sha, maybe_unreachable=False):
698 def get_object(self, wire, sha, maybe_unreachable=False):
699 cache_on, context_uid, repo_id = self._cache_on(wire)
699 cache_on, context_uid, repo_id = self._cache_on(wire)
700 region = self.region(wire)
700 region = self.region(wire)
701 @region.conditional_cache_on_arguments(condition=cache_on)
701 @region.conditional_cache_on_arguments(condition=cache_on)
702 def _get_object(_context_uid, _repo_id, _sha):
702 def _get_object(_context_uid, _repo_id, _sha):
703 repo_init = self._factory.repo_libgit2(wire)
703 repo_init = self._factory.repo_libgit2(wire)
704 with repo_init as repo:
704 with repo_init as repo:
705
705
706 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
706 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
707 try:
707 try:
708 commit = repo.revparse_single(sha)
708 commit = repo.revparse_single(sha)
709 except KeyError:
709 except KeyError:
710 # NOTE(marcink): KeyError doesn't give us any meaningful information
710 # NOTE(marcink): KeyError doesn't give us any meaningful information
711 # here, we instead give something more explicit
711 # here, we instead give something more explicit
712 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
712 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
713 raise exceptions.LookupException(e)(missing_commit_err)
713 raise exceptions.LookupException(e)(missing_commit_err)
714 except ValueError as e:
714 except ValueError as e:
715 raise exceptions.LookupException(e)(missing_commit_err)
715 raise exceptions.LookupException(e)(missing_commit_err)
716
716
717 is_tag = False
717 is_tag = False
718 if isinstance(commit, pygit2.Tag):
718 if isinstance(commit, pygit2.Tag):
719 commit = repo.get(commit.target)
719 commit = repo.get(commit.target)
720 is_tag = True
720 is_tag = True
721
721
722 check_dangling = True
722 check_dangling = True
723 if is_tag:
723 if is_tag:
724 check_dangling = False
724 check_dangling = False
725
725
726 if check_dangling and maybe_unreachable:
726 if check_dangling and maybe_unreachable:
727 check_dangling = False
727 check_dangling = False
728
728
729 # we used a reference and it parsed means we're not having a dangling commit
729 # we used a reference and it parsed means we're not having a dangling commit
730 if sha != commit.hex:
730 if sha != commit.hex:
731 check_dangling = False
731 check_dangling = False
732
732
733 if check_dangling:
733 if check_dangling:
734 # check for dangling commit
734 # check for dangling commit
735 for branch in repo.branches.with_commit(commit.hex):
735 for branch in repo.branches.with_commit(commit.hex):
736 if branch:
736 if branch:
737 break
737 break
738 else:
738 else:
739 # NOTE(marcink): Empty error doesn't give us any meaningful information
739 # NOTE(marcink): Empty error doesn't give us any meaningful information
740 # here, we instead give something more explicit
740 # here, we instead give something more explicit
741 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
741 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
742 raise exceptions.LookupException(e)(missing_commit_err)
742 raise exceptions.LookupException(e)(missing_commit_err)
743
743
744 commit_id = commit.hex
744 commit_id = commit.hex
745 type_id = commit.type
745 type_id = commit.type
746
746
747 return {
747 return {
748 'id': commit_id,
748 'id': commit_id,
749 'type': self._type_id_to_name(type_id),
749 'type': self._type_id_to_name(type_id),
750 'commit_id': commit_id,
750 'commit_id': commit_id,
751 'idx': 0
751 'idx': 0
752 }
752 }
753
753
754 return _get_object(context_uid, repo_id, sha)
754 return _get_object(context_uid, repo_id, sha)
755
755
756 @reraise_safe_exceptions
756 @reraise_safe_exceptions
757 def get_refs(self, wire):
757 def get_refs(self, wire):
758 cache_on, context_uid, repo_id = self._cache_on(wire)
758 cache_on, context_uid, repo_id = self._cache_on(wire)
759 region = self.region(wire)
759 region = self.region(wire)
760 @region.conditional_cache_on_arguments(condition=cache_on)
760 @region.conditional_cache_on_arguments(condition=cache_on)
761 def _get_refs(_context_uid, _repo_id):
761 def _get_refs(_context_uid, _repo_id):
762
762
763 repo_init = self._factory.repo_libgit2(wire)
763 repo_init = self._factory.repo_libgit2(wire)
764 with repo_init as repo:
764 with repo_init as repo:
765 regex = re.compile('^refs/(heads|tags)/')
765 regex = re.compile('^refs/(heads|tags)/')
766 return {x.name: x.target.hex for x in
766 return {x.name: x.target.hex for x in
767 filter(lambda ref: regex.match(ref.name) ,repo.listall_reference_objects())}
767 filter(lambda ref: regex.match(ref.name) ,repo.listall_reference_objects())}
768
768
769 return _get_refs(context_uid, repo_id)
769 return _get_refs(context_uid, repo_id)
770
770
771 @reraise_safe_exceptions
771 @reraise_safe_exceptions
772 def get_branch_pointers(self, wire):
772 def get_branch_pointers(self, wire):
773 cache_on, context_uid, repo_id = self._cache_on(wire)
773 cache_on, context_uid, repo_id = self._cache_on(wire)
774 region = self.region(wire)
774 region = self.region(wire)
775 @region.conditional_cache_on_arguments(condition=cache_on)
775 @region.conditional_cache_on_arguments(condition=cache_on)
776 def _get_branch_pointers(_context_uid, _repo_id):
776 def _get_branch_pointers(_context_uid, _repo_id):
777
777
778 repo_init = self._factory.repo_libgit2(wire)
778 repo_init = self._factory.repo_libgit2(wire)
779 regex = re.compile('^refs/heads')
779 regex = re.compile('^refs/heads')
780 with repo_init as repo:
780 with repo_init as repo:
781 branches = filter(lambda ref: regex.match(ref.name), repo.listall_reference_objects())
781 branches = filter(lambda ref: regex.match(ref.name), repo.listall_reference_objects())
782 return {x.target.hex: x.shorthand for x in branches}
782 return {x.target.hex: x.shorthand for x in branches}
783
783
784 return _get_branch_pointers(context_uid, repo_id)
784 return _get_branch_pointers(context_uid, repo_id)
785
785
786 @reraise_safe_exceptions
786 @reraise_safe_exceptions
787 def head(self, wire, show_exc=True):
787 def head(self, wire, show_exc=True):
788 cache_on, context_uid, repo_id = self._cache_on(wire)
788 cache_on, context_uid, repo_id = self._cache_on(wire)
789 region = self.region(wire)
789 region = self.region(wire)
790 @region.conditional_cache_on_arguments(condition=cache_on)
790 @region.conditional_cache_on_arguments(condition=cache_on)
791 def _head(_context_uid, _repo_id, _show_exc):
791 def _head(_context_uid, _repo_id, _show_exc):
792 repo_init = self._factory.repo_libgit2(wire)
792 repo_init = self._factory.repo_libgit2(wire)
793 with repo_init as repo:
793 with repo_init as repo:
794 try:
794 try:
795 return repo.head.peel().hex
795 return repo.head.peel().hex
796 except Exception:
796 except Exception:
797 if show_exc:
797 if show_exc:
798 raise
798 raise
799 return _head(context_uid, repo_id, show_exc)
799 return _head(context_uid, repo_id, show_exc)
800
800
801 @reraise_safe_exceptions
801 @reraise_safe_exceptions
802 def init(self, wire):
802 def init(self, wire):
803 repo_path = str_to_dulwich(wire['path'])
803 repo_path = str_to_dulwich(wire['path'])
804 self.repo = Repo.init(repo_path)
804 self.repo = Repo.init(repo_path)
805
805
806 @reraise_safe_exceptions
806 @reraise_safe_exceptions
807 def init_bare(self, wire):
807 def init_bare(self, wire):
808 repo_path = str_to_dulwich(wire['path'])
808 repo_path = str_to_dulwich(wire['path'])
809 self.repo = Repo.init_bare(repo_path)
809 self.repo = Repo.init_bare(repo_path)
810
810
811 @reraise_safe_exceptions
811 @reraise_safe_exceptions
812 def revision(self, wire, rev):
812 def revision(self, wire, rev):
813
813
814 cache_on, context_uid, repo_id = self._cache_on(wire)
814 cache_on, context_uid, repo_id = self._cache_on(wire)
815 region = self.region(wire)
815 region = self.region(wire)
816 @region.conditional_cache_on_arguments(condition=cache_on)
816 @region.conditional_cache_on_arguments(condition=cache_on)
817 def _revision(_context_uid, _repo_id, _rev):
817 def _revision(_context_uid, _repo_id, _rev):
818 repo_init = self._factory.repo_libgit2(wire)
818 repo_init = self._factory.repo_libgit2(wire)
819 with repo_init as repo:
819 with repo_init as repo:
820 commit = repo[rev]
820 commit = repo[rev]
821 obj_data = {
821 obj_data = {
822 'id': commit.id.hex,
822 'id': commit.id.hex,
823 }
823 }
824 # tree objects itself don't have tree_id attribute
824 # tree objects itself don't have tree_id attribute
825 if hasattr(commit, 'tree_id'):
825 if hasattr(commit, 'tree_id'):
826 obj_data['tree'] = commit.tree_id.hex
826 obj_data['tree'] = commit.tree_id.hex
827
827
828 return obj_data
828 return obj_data
829 return _revision(context_uid, repo_id, rev)
829 return _revision(context_uid, repo_id, rev)
830
830
831 @reraise_safe_exceptions
831 @reraise_safe_exceptions
832 def date(self, wire, commit_id):
832 def date(self, wire, commit_id):
833 cache_on, context_uid, repo_id = self._cache_on(wire)
833 cache_on, context_uid, repo_id = self._cache_on(wire)
834 region = self.region(wire)
834 region = self.region(wire)
835 @region.conditional_cache_on_arguments(condition=cache_on)
835 @region.conditional_cache_on_arguments(condition=cache_on)
836 def _date(_repo_id, _commit_id):
836 def _date(_repo_id, _commit_id):
837 repo_init = self._factory.repo_libgit2(wire)
837 repo_init = self._factory.repo_libgit2(wire)
838 with repo_init as repo:
838 with repo_init as repo:
839 commit = repo[commit_id]
839 commit = repo[commit_id]
840
840
841 if hasattr(commit, 'commit_time'):
841 if hasattr(commit, 'commit_time'):
842 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
842 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
843 else:
843 else:
844 commit = commit.get_object()
844 commit = commit.get_object()
845 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
845 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
846
846
847 # TODO(marcink): check dulwich difference of offset vs timezone
847 # TODO(marcink): check dulwich difference of offset vs timezone
848 return [commit_time, commit_time_offset]
848 return [commit_time, commit_time_offset]
849 return _date(repo_id, commit_id)
849 return _date(repo_id, commit_id)
850
850
851 @reraise_safe_exceptions
851 @reraise_safe_exceptions
852 def author(self, wire, commit_id):
852 def author(self, wire, commit_id):
853 cache_on, context_uid, repo_id = self._cache_on(wire)
853 cache_on, context_uid, repo_id = self._cache_on(wire)
854 region = self.region(wire)
854 region = self.region(wire)
855 @region.conditional_cache_on_arguments(condition=cache_on)
855 @region.conditional_cache_on_arguments(condition=cache_on)
856 def _author(_repo_id, _commit_id):
856 def _author(_repo_id, _commit_id):
857 repo_init = self._factory.repo_libgit2(wire)
857 repo_init = self._factory.repo_libgit2(wire)
858 with repo_init as repo:
858 with repo_init as repo:
859 commit = repo[commit_id]
859 commit = repo[commit_id]
860
860
861 if hasattr(commit, 'author'):
861 if hasattr(commit, 'author'):
862 author = commit.author
862 author = commit.author
863 else:
863 else:
864 author = commit.get_object().author
864 author = commit.get_object().author
865
865
866 if author.email:
866 if author.email:
867 return u"{} <{}>".format(author.name, author.email)
867 return u"{} <{}>".format(author.name, author.email)
868
868
869 try:
869 try:
870 return u"{}".format(author.name)
870 return u"{}".format(author.name)
871 except Exception:
871 except Exception:
872 return u"{}".format(safe_unicode(author.raw_name))
872 return u"{}".format(safe_unicode(author.raw_name))
873
873
874 return _author(repo_id, commit_id)
874 return _author(repo_id, commit_id)
875
875
876 @reraise_safe_exceptions
876 @reraise_safe_exceptions
877 def message(self, wire, commit_id):
877 def message(self, wire, commit_id):
878 cache_on, context_uid, repo_id = self._cache_on(wire)
878 cache_on, context_uid, repo_id = self._cache_on(wire)
879 region = self.region(wire)
879 region = self.region(wire)
880 @region.conditional_cache_on_arguments(condition=cache_on)
880 @region.conditional_cache_on_arguments(condition=cache_on)
881 def _message(_repo_id, _commit_id):
881 def _message(_repo_id, _commit_id):
882 repo_init = self._factory.repo_libgit2(wire)
882 repo_init = self._factory.repo_libgit2(wire)
883 with repo_init as repo:
883 with repo_init as repo:
884 commit = repo[commit_id]
884 commit = repo[commit_id]
885 return commit.message
885 return commit.message
886 return _message(repo_id, commit_id)
886 return _message(repo_id, commit_id)
887
887
888 @reraise_safe_exceptions
888 @reraise_safe_exceptions
889 def parents(self, wire, commit_id):
889 def parents(self, wire, commit_id):
890 cache_on, context_uid, repo_id = self._cache_on(wire)
890 cache_on, context_uid, repo_id = self._cache_on(wire)
891 region = self.region(wire)
891 region = self.region(wire)
892 @region.conditional_cache_on_arguments(condition=cache_on)
892 @region.conditional_cache_on_arguments(condition=cache_on)
893 def _parents(_repo_id, _commit_id):
893 def _parents(_repo_id, _commit_id):
894 repo_init = self._factory.repo_libgit2(wire)
894 repo_init = self._factory.repo_libgit2(wire)
895 with repo_init as repo:
895 with repo_init as repo:
896 commit = repo[commit_id]
896 commit = repo[commit_id]
897 if hasattr(commit, 'parent_ids'):
897 if hasattr(commit, 'parent_ids'):
898 parent_ids = commit.parent_ids
898 parent_ids = commit.parent_ids
899 else:
899 else:
900 parent_ids = commit.get_object().parent_ids
900 parent_ids = commit.get_object().parent_ids
901
901
902 return [x.hex for x in parent_ids]
902 return [x.hex for x in parent_ids]
903 return _parents(repo_id, commit_id)
903 return _parents(repo_id, commit_id)
904
904
905 @reraise_safe_exceptions
905 @reraise_safe_exceptions
906 def children(self, wire, commit_id):
906 def children(self, wire, commit_id):
907 cache_on, context_uid, repo_id = self._cache_on(wire)
907 cache_on, context_uid, repo_id = self._cache_on(wire)
908 region = self.region(wire)
908 region = self.region(wire)
909 @region.conditional_cache_on_arguments(condition=cache_on)
909 @region.conditional_cache_on_arguments(condition=cache_on)
910 def _children(_repo_id, _commit_id):
910 def _children(_repo_id, _commit_id):
911 output, __ = self.run_git_command(
911 output, __ = self.run_git_command(
912 wire, ['rev-list', '--all', '--children'])
912 wire, ['rev-list', '--all', '--children'])
913
913
914 child_ids = []
914 child_ids = []
915 pat = re.compile(r'^%s' % commit_id)
915 pat = re.compile(r'^%s' % commit_id)
916 for l in output.splitlines():
916 for l in output.splitlines():
917 if pat.match(l):
917 if pat.match(l):
918 found_ids = l.split(' ')[1:]
918 found_ids = l.split(' ')[1:]
919 child_ids.extend(found_ids)
919 child_ids.extend(found_ids)
920
920
921 return child_ids
921 return child_ids
922 return _children(repo_id, commit_id)
922 return _children(repo_id, commit_id)
923
923
924 @reraise_safe_exceptions
924 @reraise_safe_exceptions
925 def set_refs(self, wire, key, value):
925 def set_refs(self, wire, key, value):
926 repo_init = self._factory.repo_libgit2(wire)
926 repo_init = self._factory.repo_libgit2(wire)
927 with repo_init as repo:
927 with repo_init as repo:
928 repo.references.create(key, value, force=True)
928 repo.references.create(key, value, force=True)
929
929
930 @reraise_safe_exceptions
930 @reraise_safe_exceptions
931 def create_branch(self, wire, branch_name, commit_id, force=False):
931 def create_branch(self, wire, branch_name, commit_id, force=False):
932 repo_init = self._factory.repo_libgit2(wire)
932 repo_init = self._factory.repo_libgit2(wire)
933 with repo_init as repo:
933 with repo_init as repo:
934 commit = repo[commit_id]
934 commit = repo[commit_id]
935
935
936 if force:
936 if force:
937 repo.branches.local.create(branch_name, commit, force=force)
937 repo.branches.local.create(branch_name, commit, force=force)
938 elif not repo.branches.get(branch_name):
938 elif not repo.branches.get(branch_name):
939 # create only if that branch isn't existing
939 # create only if that branch isn't existing
940 repo.branches.local.create(branch_name, commit, force=force)
940 repo.branches.local.create(branch_name, commit, force=force)
941
941
942 @reraise_safe_exceptions
942 @reraise_safe_exceptions
943 def remove_ref(self, wire, key):
943 def remove_ref(self, wire, key):
944 repo_init = self._factory.repo_libgit2(wire)
944 repo_init = self._factory.repo_libgit2(wire)
945 with repo_init as repo:
945 with repo_init as repo:
946 repo.references.delete(key)
946 repo.references.delete(key)
947
947
948 @reraise_safe_exceptions
948 @reraise_safe_exceptions
949 def tag_remove(self, wire, tag_name):
949 def tag_remove(self, wire, tag_name):
950 repo_init = self._factory.repo_libgit2(wire)
950 repo_init = self._factory.repo_libgit2(wire)
951 with repo_init as repo:
951 with repo_init as repo:
952 key = 'refs/tags/{}'.format(tag_name)
952 key = 'refs/tags/{}'.format(tag_name)
953 repo.references.delete(key)
953 repo.references.delete(key)
954
954
955 @reraise_safe_exceptions
955 @reraise_safe_exceptions
956 def tree_changes(self, wire, source_id, target_id):
956 def tree_changes(self, wire, source_id, target_id):
957 # TODO(marcink): remove this seems it's only used by tests
957 # TODO(marcink): remove this seems it's only used by tests
958 repo = self._factory.repo(wire)
958 repo = self._factory.repo(wire)
959 source = repo[source_id].tree if source_id else None
959 source = repo[source_id].tree if source_id else None
960 target = repo[target_id].tree
960 target = repo[target_id].tree
961 result = repo.object_store.tree_changes(source, target)
961 result = repo.object_store.tree_changes(source, target)
962 return list(result)
962 return list(result)
963
963
964 @reraise_safe_exceptions
964 @reraise_safe_exceptions
965 def tree_and_type_for_path(self, wire, commit_id, path):
965 def tree_and_type_for_path(self, wire, commit_id, path):
966
966
967 cache_on, context_uid, repo_id = self._cache_on(wire)
967 cache_on, context_uid, repo_id = self._cache_on(wire)
968 region = self.region(wire)
968 region = self.region(wire)
969 @region.conditional_cache_on_arguments(condition=cache_on)
969 @region.conditional_cache_on_arguments(condition=cache_on)
970 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
970 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
971 repo_init = self._factory.repo_libgit2(wire)
971 repo_init = self._factory.repo_libgit2(wire)
972
972
973 with repo_init as repo:
973 with repo_init as repo:
974 commit = repo[commit_id]
974 commit = repo[commit_id]
975 try:
975 try:
976 tree = commit.tree[path]
976 tree = commit.tree[path]
977 except KeyError:
977 except KeyError:
978 return None, None, None
978 return None, None, None
979
979
980 return tree.id.hex, tree.type, tree.filemode
980 return tree.id.hex, tree.type, tree.filemode
981 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
981 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
982
982
983 @reraise_safe_exceptions
983 @reraise_safe_exceptions
984 def tree_items(self, wire, tree_id):
984 def tree_items(self, wire, tree_id):
985 cache_on, context_uid, repo_id = self._cache_on(wire)
985 cache_on, context_uid, repo_id = self._cache_on(wire)
986 region = self.region(wire)
986 region = self.region(wire)
987 @region.conditional_cache_on_arguments(condition=cache_on)
987 @region.conditional_cache_on_arguments(condition=cache_on)
988 def _tree_items(_repo_id, _tree_id):
988 def _tree_items(_repo_id, _tree_id):
989
989
990 repo_init = self._factory.repo_libgit2(wire)
990 repo_init = self._factory.repo_libgit2(wire)
991 with repo_init as repo:
991 with repo_init as repo:
992 try:
992 try:
993 tree = repo[tree_id]
993 tree = repo[tree_id]
994 except KeyError:
994 except KeyError:
995 raise ObjectMissing('No tree with id: {}'.format(tree_id))
995 raise ObjectMissing('No tree with id: {}'.format(tree_id))
996
996
997 result = []
997 result = []
998 for item in tree:
998 for item in tree:
999 item_sha = item.hex
999 item_sha = item.hex
1000 item_mode = item.filemode
1000 item_mode = item.filemode
1001 item_type = item.type
1001 item_type = item.type
1002
1002
1003 if item_type == 'commit':
1003 if item_type == 'commit':
1004 # NOTE(marcink): submodules we translate to 'link' for backward compat
1004 # NOTE(marcink): submodules we translate to 'link' for backward compat
1005 item_type = 'link'
1005 item_type = 'link'
1006
1006
1007 result.append((item.name, item_mode, item_sha, item_type))
1007 result.append((item.name, item_mode, item_sha, item_type))
1008 return result
1008 return result
1009 return _tree_items(repo_id, tree_id)
1009 return _tree_items(repo_id, tree_id)
1010
1010
1011 @reraise_safe_exceptions
1011 @reraise_safe_exceptions
1012 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1012 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1013 """
1013 """
1014 Old version that uses subprocess to call diff
1014 Old version that uses subprocess to call diff
1015 """
1015 """
1016
1016
1017 flags = [
1017 flags = [
1018 '-U%s' % context, '--patch',
1018 '-U%s' % context, '--patch',
1019 '--binary',
1019 '--binary',
1020 '--find-renames',
1020 '--find-renames',
1021 '--no-indent-heuristic',
1021 '--no-indent-heuristic',
1022 # '--indent-heuristic',
1022 # '--indent-heuristic',
1023 #'--full-index',
1023 #'--full-index',
1024 #'--abbrev=40'
1024 #'--abbrev=40'
1025 ]
1025 ]
1026
1026
1027 if opt_ignorews:
1027 if opt_ignorews:
1028 flags.append('--ignore-all-space')
1028 flags.append('--ignore-all-space')
1029
1029
1030 if commit_id_1 == self.EMPTY_COMMIT:
1030 if commit_id_1 == self.EMPTY_COMMIT:
1031 cmd = ['show'] + flags + [commit_id_2]
1031 cmd = ['show'] + flags + [commit_id_2]
1032 else:
1032 else:
1033 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1033 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1034
1034
1035 if file_filter:
1035 if file_filter:
1036 cmd.extend(['--', file_filter])
1036 cmd.extend(['--', file_filter])
1037
1037
1038 diff, __ = self.run_git_command(wire, cmd)
1038 diff, __ = self.run_git_command(wire, cmd)
1039 # If we used 'show' command, strip first few lines (until actual diff
1039 # If we used 'show' command, strip first few lines (until actual diff
1040 # starts)
1040 # starts)
1041 if commit_id_1 == self.EMPTY_COMMIT:
1041 if commit_id_1 == self.EMPTY_COMMIT:
1042 lines = diff.splitlines()
1042 lines = diff.splitlines()
1043 x = 0
1043 x = 0
1044 for line in lines:
1044 for line in lines:
1045 if line.startswith('diff'):
1045 if line.startswith('diff'):
1046 break
1046 break
1047 x += 1
1047 x += 1
1048 # Append new line just like 'diff' command do
1048 # Append new line just like 'diff' command do
1049 diff = '\n'.join(lines[x:]) + '\n'
1049 diff = '\n'.join(lines[x:]) + '\n'
1050 return diff
1050 return diff
1051
1051
1052 @reraise_safe_exceptions
1052 @reraise_safe_exceptions
1053 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1053 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1054 repo_init = self._factory.repo_libgit2(wire)
1054 repo_init = self._factory.repo_libgit2(wire)
1055 with repo_init as repo:
1055 with repo_init as repo:
1056 swap = True
1056 swap = True
1057 flags = 0
1057 flags = 0
1058 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1058 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1059
1059
1060 if opt_ignorews:
1060 if opt_ignorews:
1061 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1061 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1062
1062
1063 if commit_id_1 == self.EMPTY_COMMIT:
1063 if commit_id_1 == self.EMPTY_COMMIT:
1064 comm1 = repo[commit_id_2]
1064 comm1 = repo[commit_id_2]
1065 diff_obj = comm1.tree.diff_to_tree(
1065 diff_obj = comm1.tree.diff_to_tree(
1066 flags=flags, context_lines=context, swap=swap)
1066 flags=flags, context_lines=context, swap=swap)
1067
1067
1068 else:
1068 else:
1069 comm1 = repo[commit_id_2]
1069 comm1 = repo[commit_id_2]
1070 comm2 = repo[commit_id_1]
1070 comm2 = repo[commit_id_1]
1071 diff_obj = comm1.tree.diff_to_tree(
1071 diff_obj = comm1.tree.diff_to_tree(
1072 comm2.tree, flags=flags, context_lines=context, swap=swap)
1072 comm2.tree, flags=flags, context_lines=context, swap=swap)
1073 similar_flags = 0
1073 similar_flags = 0
1074 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1074 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1075 diff_obj.find_similar(flags=similar_flags)
1075 diff_obj.find_similar(flags=similar_flags)
1076
1076
1077 if file_filter:
1077 if file_filter:
1078 for p in diff_obj:
1078 for p in diff_obj:
1079 if p.delta.old_file.path == file_filter:
1079 if p.delta.old_file.path == file_filter:
1080 return p.patch or ''
1080 return p.patch or ''
1081 # fo matching path == no diff
1081 # fo matching path == no diff
1082 return ''
1082 return ''
1083 return diff_obj.patch or ''
1083 return diff_obj.patch or ''
1084
1084
1085 @reraise_safe_exceptions
1085 @reraise_safe_exceptions
1086 def node_history(self, wire, commit_id, path, limit):
1086 def node_history(self, wire, commit_id, path, limit):
1087 cache_on, context_uid, repo_id = self._cache_on(wire)
1087 cache_on, context_uid, repo_id = self._cache_on(wire)
1088 region = self.region(wire)
1088 region = self.region(wire)
1089 @region.conditional_cache_on_arguments(condition=cache_on)
1089 @region.conditional_cache_on_arguments(condition=cache_on)
1090 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1090 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1091 # optimize for n==1, rev-list is much faster for that use-case
1091 # optimize for n==1, rev-list is much faster for that use-case
1092 if limit == 1:
1092 if limit == 1:
1093 cmd = ['rev-list', '-1', commit_id, '--', path]
1093 cmd = ['rev-list', '-1', commit_id, '--', path]
1094 else:
1094 else:
1095 cmd = ['log']
1095 cmd = ['log']
1096 if limit:
1096 if limit:
1097 cmd.extend(['-n', str(safe_int(limit, 0))])
1097 cmd.extend(['-n', str(safe_int(limit, 0))])
1098 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1098 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1099
1099
1100 output, __ = self.run_git_command(wire, cmd)
1100 output, __ = self.run_git_command(wire, cmd)
1101 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
1101 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
1102
1102
1103 return [x for x in commit_ids]
1103 return [x for x in commit_ids]
1104 return _node_history(context_uid, repo_id, commit_id, path, limit)
1104 return _node_history(context_uid, repo_id, commit_id, path, limit)
1105
1105
1106 @reraise_safe_exceptions
1106 @reraise_safe_exceptions
1107 def node_annotate(self, wire, commit_id, path):
1107 def node_annotate(self, wire, commit_id, path):
1108
1108
1109 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1109 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1110 # -l ==> outputs long shas (and we need all 40 characters)
1110 # -l ==> outputs long shas (and we need all 40 characters)
1111 # --root ==> doesn't put '^' character for boundaries
1111 # --root ==> doesn't put '^' character for boundaries
1112 # -r commit_id ==> blames for the given commit
1112 # -r commit_id ==> blames for the given commit
1113 output, __ = self.run_git_command(wire, cmd)
1113 output, __ = self.run_git_command(wire, cmd)
1114
1114
1115 result = []
1115 result = []
1116 for i, blame_line in enumerate(output.split('\n')[:-1]):
1116 for i, blame_line in enumerate(output.split('\n')[:-1]):
1117 line_no = i + 1
1117 line_no = i + 1
1118 commit_id, line = re.split(r' ', blame_line, 1)
1118 commit_id, line = re.split(r' ', blame_line, 1)
1119 result.append((line_no, commit_id, line))
1119 result.append((line_no, commit_id, line))
1120 return result
1120 return result
1121
1121
1122 @reraise_safe_exceptions
1122 @reraise_safe_exceptions
1123 def update_server_info(self, wire):
1123 def update_server_info(self, wire):
1124 repo = self._factory.repo(wire)
1124 repo = self._factory.repo(wire)
1125 update_server_info(repo)
1125 update_server_info(repo)
1126
1126
1127 @reraise_safe_exceptions
1127 @reraise_safe_exceptions
1128 def get_all_commit_ids(self, wire):
1128 def get_all_commit_ids(self, wire):
1129
1129
1130 cache_on, context_uid, repo_id = self._cache_on(wire)
1130 cache_on, context_uid, repo_id = self._cache_on(wire)
1131 region = self.region(wire)
1131 region = self.region(wire)
1132 @region.conditional_cache_on_arguments(condition=cache_on)
1132 @region.conditional_cache_on_arguments(condition=cache_on)
1133 def _get_all_commit_ids(_context_uid, _repo_id):
1133 def _get_all_commit_ids(_context_uid, _repo_id):
1134
1134
1135 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1135 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1136 try:
1136 try:
1137 output, __ = self.run_git_command(wire, cmd)
1137 output, __ = self.run_git_command(wire, cmd)
1138 return output.splitlines()
1138 return output.splitlines()
1139 except Exception:
1139 except Exception:
1140 # Can be raised for empty repositories
1140 # Can be raised for empty repositories
1141 return []
1141 return []
1142 return _get_all_commit_ids(context_uid, repo_id)
1142 return _get_all_commit_ids(context_uid, repo_id)
1143
1143
1144 @reraise_safe_exceptions
1144 @reraise_safe_exceptions
1145 def run_git_command(self, wire, cmd, **opts):
1145 def run_git_command(self, wire, cmd, **opts):
1146 path = wire.get('path', None)
1146 path = wire.get('path', None)
1147
1147
1148 if path and os.path.isdir(path):
1148 if path and os.path.isdir(path):
1149 opts['cwd'] = path
1149 opts['cwd'] = path
1150
1150
1151 if '_bare' in opts:
1151 if '_bare' in opts:
1152 _copts = []
1152 _copts = []
1153 del opts['_bare']
1153 del opts['_bare']
1154 else:
1154 else:
1155 _copts = ['-c', 'core.quotepath=false', ]
1155 _copts = ['-c', 'core.quotepath=false', ]
1156 safe_call = False
1156 safe_call = False
1157 if '_safe' in opts:
1157 if '_safe' in opts:
1158 # no exc on failure
1158 # no exc on failure
1159 del opts['_safe']
1159 del opts['_safe']
1160 safe_call = True
1160 safe_call = True
1161
1161
1162 if '_copts' in opts:
1162 if '_copts' in opts:
1163 _copts.extend(opts['_copts'] or [])
1163 _copts.extend(opts['_copts'] or [])
1164 del opts['_copts']
1164 del opts['_copts']
1165
1165
1166 gitenv = os.environ.copy()
1166 gitenv = os.environ.copy()
1167 gitenv.update(opts.pop('extra_env', {}))
1167 gitenv.update(opts.pop('extra_env', {}))
1168 # need to clean fix GIT_DIR !
1168 # need to clean fix GIT_DIR !
1169 if 'GIT_DIR' in gitenv:
1169 if 'GIT_DIR' in gitenv:
1170 del gitenv['GIT_DIR']
1170 del gitenv['GIT_DIR']
1171 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1171 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1172 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1172 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1173
1173
1174 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1174 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1175 _opts = {'env': gitenv, 'shell': False}
1175 _opts = {'env': gitenv, 'shell': False}
1176
1176
1177 proc = None
1177 proc = None
1178 try:
1178 try:
1179 _opts.update(opts)
1179 _opts.update(opts)
1180 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1180 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1181
1181
1182 return ''.join(proc), ''.join(proc.error)
1182 return ''.join(proc), ''.join(proc.error)
1183 except (EnvironmentError, OSError) as err:
1183 except (EnvironmentError, OSError) as err:
1184 cmd = ' '.join(cmd) # human friendly CMD
1184 cmd = ' '.join(cmd) # human friendly CMD
1185 tb_err = ("Couldn't run git command (%s).\n"
1185 tb_err = ("Couldn't run git command (%s).\n"
1186 "Original error was:%s\n"
1186 "Original error was:%s\n"
1187 "Call options:%s\n"
1187 "Call options:%s\n"
1188 % (cmd, err, _opts))
1188 % (cmd, err, _opts))
1189 log.exception(tb_err)
1189 log.exception(tb_err)
1190 if safe_call:
1190 if safe_call:
1191 return '', err
1191 return '', err
1192 else:
1192 else:
1193 raise exceptions.VcsException()(tb_err)
1193 raise exceptions.VcsException()(tb_err)
1194 finally:
1194 finally:
1195 if proc:
1195 if proc:
1196 proc.close()
1196 proc.close()
1197
1197
1198 @reraise_safe_exceptions
1198 @reraise_safe_exceptions
1199 def install_hooks(self, wire, force=False):
1199 def install_hooks(self, wire, force=False):
1200 from vcsserver.hook_utils import install_git_hooks
1200 from vcsserver.hook_utils import install_git_hooks
1201 bare = self.bare(wire)
1201 bare = self.bare(wire)
1202 path = wire['path']
1202 path = wire['path']
1203 return install_git_hooks(path, bare, force_create=force)
1203 return install_git_hooks(path, bare, force_create=force)
1204
1204
1205 @reraise_safe_exceptions
1205 @reraise_safe_exceptions
1206 def get_hooks_info(self, wire):
1206 def get_hooks_info(self, wire):
1207 from vcsserver.hook_utils import (
1207 from vcsserver.hook_utils import (
1208 get_git_pre_hook_version, get_git_post_hook_version)
1208 get_git_pre_hook_version, get_git_post_hook_version)
1209 bare = self.bare(wire)
1209 bare = self.bare(wire)
1210 path = wire['path']
1210 path = wire['path']
1211 return {
1211 return {
1212 'pre_version': get_git_pre_hook_version(path, bare),
1212 'pre_version': get_git_pre_hook_version(path, bare),
1213 'post_version': get_git_post_hook_version(path, bare),
1213 'post_version': get_git_post_hook_version(path, bare),
1214 }
1214 }
1215
1215
1216 @reraise_safe_exceptions
1216 @reraise_safe_exceptions
1217 def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
1217 def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
1218 archive_dir_name, commit_id):
1218 archive_dir_name, commit_id):
1219
1219
1220 def file_walker(_commit_id, path):
1220 def file_walker(_commit_id, path):
1221 repo_init = self._factory.repo_libgit2(wire)
1221 repo_init = self._factory.repo_libgit2(wire)
1222
1222
1223 with repo_init as repo:
1223 with repo_init as repo:
1224 commit = repo[commit_id]
1224 commit = repo[commit_id]
1225
1225
1226 if path in ['', '/']:
1226 if path in ['', '/']:
1227 tree = commit.tree
1227 tree = commit.tree
1228 else:
1228 else:
1229 tree = commit.tree[path.rstrip('/')]
1229 tree = commit.tree[path.rstrip('/')]
1230 tree_id = tree.id.hex
1230 tree_id = tree.id.hex
1231 try:
1231 try:
1232 tree = repo[tree_id]
1232 tree = repo[tree_id]
1233 except KeyError:
1233 except KeyError:
1234 raise ObjectMissing('No tree with id: {}'.format(tree_id))
1234 raise ObjectMissing('No tree with id: {}'.format(tree_id))
1235
1235
1236 index = LibGit2Index.Index()
1236 index = LibGit2Index.Index()
1237 index.read_tree(tree)
1237 index.read_tree(tree)
1238 file_iter = index
1238 file_iter = index
1239
1239
1240 for fn in file_iter:
1240 for fn in file_iter:
1241 file_path = fn.path
1241 file_path = fn.path
1242 mode = fn.mode
1242 mode = fn.mode
1243 is_link = stat.S_ISLNK(mode)
1243 is_link = stat.S_ISLNK(mode)
1244 yield ArchiveNode(file_path, mode, is_link, repo[fn.id].read_raw)
1244 if mode == pygit2.GIT_FILEMODE_COMMIT:
1245 log.debug('Skipping path %s as a commit node', file_path)
1246 continue
1247 yield ArchiveNode(file_path, mode, is_link, repo[fn.hex].read_raw)
1245
1248
1246 return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
1249 return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
1247 archive_dir_name, commit_id)
1250 archive_dir_name, commit_id)
General Comments 0
You need to be logged in to leave comments. Login now