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