##// END OF EJS Templates
git: allow head command to not raise exception is we wish to have only return value.
marcink -
r504:caa31d8f default
parent child Browse files
Show More
@@ -1,671 +1,675 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 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 logging
18 import logging
19 import os
19 import os
20 import posixpath as vcspath
20 import posixpath as vcspath
21 import re
21 import re
22 import stat
22 import stat
23 import traceback
23 import traceback
24 import urllib
24 import urllib
25 import urllib2
25 import urllib2
26 from functools import wraps
26 from functools import wraps
27
27
28 from dulwich import index, objects
28 from dulwich import index, objects
29 from dulwich.client import HttpGitClient, LocalGitClient
29 from dulwich.client import HttpGitClient, LocalGitClient
30 from dulwich.errors import (
30 from dulwich.errors import (
31 NotGitRepository, ChecksumMismatch, WrongObjectException,
31 NotGitRepository, ChecksumMismatch, WrongObjectException,
32 MissingCommitError, ObjectMissing, HangupException,
32 MissingCommitError, ObjectMissing, HangupException,
33 UnexpectedCommandError)
33 UnexpectedCommandError)
34 from dulwich.repo import Repo as DulwichRepo, Tag
34 from dulwich.repo import Repo as DulwichRepo, Tag
35 from dulwich.server import update_server_info
35 from dulwich.server import update_server_info
36
36
37 from vcsserver import exceptions, settings, subprocessio
37 from vcsserver import exceptions, settings, subprocessio
38 from vcsserver.utils import safe_str
38 from vcsserver.utils import safe_str
39 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
39 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
40 from vcsserver.hgcompat import (
40 from vcsserver.hgcompat import (
41 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
41 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
42 from vcsserver.git_lfs.lib import LFSOidStore
42 from vcsserver.git_lfs.lib import LFSOidStore
43
43
44 DIR_STAT = stat.S_IFDIR
44 DIR_STAT = stat.S_IFDIR
45 FILE_MODE = stat.S_IFMT
45 FILE_MODE = stat.S_IFMT
46 GIT_LINK = objects.S_IFGITLINK
46 GIT_LINK = objects.S_IFGITLINK
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def reraise_safe_exceptions(func):
51 def reraise_safe_exceptions(func):
52 """Converts Dulwich exceptions to something neutral."""
52 """Converts Dulwich exceptions to something neutral."""
53 @wraps(func)
53 @wraps(func)
54 def wrapper(*args, **kwargs):
54 def wrapper(*args, **kwargs):
55 try:
55 try:
56 return func(*args, **kwargs)
56 return func(*args, **kwargs)
57 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
57 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
58 ObjectMissing) as e:
58 ObjectMissing) as e:
59 raise exceptions.LookupException(e)(e.message)
59 raise exceptions.LookupException(e)(e.message)
60 except (HangupException, UnexpectedCommandError) as e:
60 except (HangupException, UnexpectedCommandError) as e:
61 raise exceptions.VcsException(e)(e.message)
61 raise exceptions.VcsException(e)(e.message)
62 except Exception as e:
62 except Exception as e:
63 # NOTE(marcink): becuase of how dulwich handles some exceptions
63 # NOTE(marcink): becuase of how dulwich handles some exceptions
64 # (KeyError on empty repos), we cannot track this and catch all
64 # (KeyError on empty repos), we cannot track this and catch all
65 # exceptions, it's an exceptions from other handlers
65 # exceptions, it's an exceptions from other handlers
66 #if not hasattr(e, '_vcs_kind'):
66 #if not hasattr(e, '_vcs_kind'):
67 #log.exception("Unhandled exception in git remote call")
67 #log.exception("Unhandled exception in git remote call")
68 #raise_from_original(exceptions.UnhandledException)
68 #raise_from_original(exceptions.UnhandledException)
69 raise
69 raise
70 return wrapper
70 return wrapper
71
71
72
72
73 class Repo(DulwichRepo):
73 class Repo(DulwichRepo):
74 """
74 """
75 A wrapper for dulwich Repo class.
75 A wrapper for dulwich Repo class.
76
76
77 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
77 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
78 "Too many open files" error. We need to close all opened file descriptors
78 "Too many open files" error. We need to close all opened file descriptors
79 once the repo object is destroyed.
79 once the repo object is destroyed.
80
80
81 TODO: mikhail: please check if we need this wrapper after updating dulwich
81 TODO: mikhail: please check if we need this wrapper after updating dulwich
82 to 0.12.0 +
82 to 0.12.0 +
83 """
83 """
84 def __del__(self):
84 def __del__(self):
85 if hasattr(self, 'object_store'):
85 if hasattr(self, 'object_store'):
86 self.close()
86 self.close()
87
87
88
88
89 class GitFactory(RepoFactory):
89 class GitFactory(RepoFactory):
90 repo_type = 'git'
90 repo_type = 'git'
91
91
92 def _create_repo(self, wire, create):
92 def _create_repo(self, wire, create):
93 repo_path = str_to_dulwich(wire['path'])
93 repo_path = str_to_dulwich(wire['path'])
94 return Repo(repo_path)
94 return Repo(repo_path)
95
95
96
96
97 class GitRemote(object):
97 class GitRemote(object):
98
98
99 def __init__(self, factory):
99 def __init__(self, factory):
100 self._factory = factory
100 self._factory = factory
101
101
102 self._bulk_methods = {
102 self._bulk_methods = {
103 "author": self.commit_attribute,
103 "author": self.commit_attribute,
104 "date": self.get_object_attrs,
104 "date": self.get_object_attrs,
105 "message": self.commit_attribute,
105 "message": self.commit_attribute,
106 "parents": self.commit_attribute,
106 "parents": self.commit_attribute,
107 "_commit": self.revision,
107 "_commit": self.revision,
108 }
108 }
109
109
110 def _wire_to_config(self, wire):
110 def _wire_to_config(self, wire):
111 if 'config' in wire:
111 if 'config' in wire:
112 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
112 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
113 return {}
113 return {}
114
114
115 def _assign_ref(self, wire, ref, commit_id):
115 def _assign_ref(self, wire, ref, commit_id):
116 repo = self._factory.repo(wire)
116 repo = self._factory.repo(wire)
117 repo[ref] = commit_id
117 repo[ref] = commit_id
118
118
119 @reraise_safe_exceptions
119 @reraise_safe_exceptions
120 def add_object(self, wire, content):
120 def add_object(self, wire, content):
121 repo = self._factory.repo(wire)
121 repo = self._factory.repo(wire)
122 blob = objects.Blob()
122 blob = objects.Blob()
123 blob.set_raw_string(content)
123 blob.set_raw_string(content)
124 repo.object_store.add_object(blob)
124 repo.object_store.add_object(blob)
125 return blob.id
125 return blob.id
126
126
127 @reraise_safe_exceptions
127 @reraise_safe_exceptions
128 def assert_correct_path(self, wire):
128 def assert_correct_path(self, wire):
129 path = wire.get('path')
129 path = wire.get('path')
130 try:
130 try:
131 self._factory.repo(wire)
131 self._factory.repo(wire)
132 except NotGitRepository as e:
132 except NotGitRepository as e:
133 tb = traceback.format_exc()
133 tb = traceback.format_exc()
134 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
134 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
135 return False
135 return False
136
136
137 return True
137 return True
138
138
139 @reraise_safe_exceptions
139 @reraise_safe_exceptions
140 def bare(self, wire):
140 def bare(self, wire):
141 repo = self._factory.repo(wire)
141 repo = self._factory.repo(wire)
142 return repo.bare
142 return repo.bare
143
143
144 @reraise_safe_exceptions
144 @reraise_safe_exceptions
145 def blob_as_pretty_string(self, wire, sha):
145 def blob_as_pretty_string(self, wire, sha):
146 repo = self._factory.repo(wire)
146 repo = self._factory.repo(wire)
147 return repo[sha].as_pretty_string()
147 return repo[sha].as_pretty_string()
148
148
149 @reraise_safe_exceptions
149 @reraise_safe_exceptions
150 def blob_raw_length(self, wire, sha):
150 def blob_raw_length(self, wire, sha):
151 repo = self._factory.repo(wire)
151 repo = self._factory.repo(wire)
152 blob = repo[sha]
152 blob = repo[sha]
153 return blob.raw_length()
153 return blob.raw_length()
154
154
155 def _parse_lfs_pointer(self, raw_content):
155 def _parse_lfs_pointer(self, raw_content):
156
156
157 spec_string = 'version https://git-lfs.github.com/spec'
157 spec_string = 'version https://git-lfs.github.com/spec'
158 if raw_content and raw_content.startswith(spec_string):
158 if raw_content and raw_content.startswith(spec_string):
159 pattern = re.compile(r"""
159 pattern = re.compile(r"""
160 (?:\n)?
160 (?:\n)?
161 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
161 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
162 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
162 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
163 ^size[ ](?P<oid_size>[0-9]+)\n
163 ^size[ ](?P<oid_size>[0-9]+)\n
164 (?:\n)?
164 (?:\n)?
165 """, re.VERBOSE | re.MULTILINE)
165 """, re.VERBOSE | re.MULTILINE)
166 match = pattern.match(raw_content)
166 match = pattern.match(raw_content)
167 if match:
167 if match:
168 return match.groupdict()
168 return match.groupdict()
169
169
170 return {}
170 return {}
171
171
172 @reraise_safe_exceptions
172 @reraise_safe_exceptions
173 def is_large_file(self, wire, sha):
173 def is_large_file(self, wire, sha):
174 repo = self._factory.repo(wire)
174 repo = self._factory.repo(wire)
175 blob = repo[sha]
175 blob = repo[sha]
176 return self._parse_lfs_pointer(blob.as_raw_string())
176 return self._parse_lfs_pointer(blob.as_raw_string())
177
177
178 @reraise_safe_exceptions
178 @reraise_safe_exceptions
179 def in_largefiles_store(self, wire, oid):
179 def in_largefiles_store(self, wire, oid):
180 repo = self._factory.repo(wire)
180 repo = self._factory.repo(wire)
181 conf = self._wire_to_config(wire)
181 conf = self._wire_to_config(wire)
182
182
183 store_location = conf.get('vcs_git_lfs_store_location')
183 store_location = conf.get('vcs_git_lfs_store_location')
184 if store_location:
184 if store_location:
185 repo_name = repo.path
185 repo_name = repo.path
186 store = LFSOidStore(
186 store = LFSOidStore(
187 oid=oid, repo=repo_name, store_location=store_location)
187 oid=oid, repo=repo_name, store_location=store_location)
188 return store.has_oid()
188 return store.has_oid()
189
189
190 return False
190 return False
191
191
192 @reraise_safe_exceptions
192 @reraise_safe_exceptions
193 def store_path(self, wire, oid):
193 def store_path(self, wire, oid):
194 repo = self._factory.repo(wire)
194 repo = self._factory.repo(wire)
195 conf = self._wire_to_config(wire)
195 conf = self._wire_to_config(wire)
196
196
197 store_location = conf.get('vcs_git_lfs_store_location')
197 store_location = conf.get('vcs_git_lfs_store_location')
198 if store_location:
198 if store_location:
199 repo_name = repo.path
199 repo_name = repo.path
200 store = LFSOidStore(
200 store = LFSOidStore(
201 oid=oid, repo=repo_name, store_location=store_location)
201 oid=oid, repo=repo_name, store_location=store_location)
202 return store.oid_path
202 return store.oid_path
203 raise ValueError('Unable to fetch oid with path {}'.format(oid))
203 raise ValueError('Unable to fetch oid with path {}'.format(oid))
204
204
205 @reraise_safe_exceptions
205 @reraise_safe_exceptions
206 def bulk_request(self, wire, rev, pre_load):
206 def bulk_request(self, wire, rev, pre_load):
207 result = {}
207 result = {}
208 for attr in pre_load:
208 for attr in pre_load:
209 try:
209 try:
210 method = self._bulk_methods[attr]
210 method = self._bulk_methods[attr]
211 args = [wire, rev]
211 args = [wire, rev]
212 if attr == "date":
212 if attr == "date":
213 args.extend(["commit_time", "commit_timezone"])
213 args.extend(["commit_time", "commit_timezone"])
214 elif attr in ["author", "message", "parents"]:
214 elif attr in ["author", "message", "parents"]:
215 args.append(attr)
215 args.append(attr)
216 result[attr] = method(*args)
216 result[attr] = method(*args)
217 except KeyError as e:
217 except KeyError as e:
218 raise exceptions.VcsException(e)(
218 raise exceptions.VcsException(e)(
219 "Unknown bulk attribute: %s" % attr)
219 "Unknown bulk attribute: %s" % attr)
220 return result
220 return result
221
221
222 def _build_opener(self, url):
222 def _build_opener(self, url):
223 handlers = []
223 handlers = []
224 url_obj = url_parser(url)
224 url_obj = url_parser(url)
225 _, authinfo = url_obj.authinfo()
225 _, authinfo = url_obj.authinfo()
226
226
227 if authinfo:
227 if authinfo:
228 # create a password manager
228 # create a password manager
229 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
229 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
230 passmgr.add_password(*authinfo)
230 passmgr.add_password(*authinfo)
231
231
232 handlers.extend((httpbasicauthhandler(passmgr),
232 handlers.extend((httpbasicauthhandler(passmgr),
233 httpdigestauthhandler(passmgr)))
233 httpdigestauthhandler(passmgr)))
234
234
235 return urllib2.build_opener(*handlers)
235 return urllib2.build_opener(*handlers)
236
236
237 @reraise_safe_exceptions
237 @reraise_safe_exceptions
238 def check_url(self, url, config):
238 def check_url(self, url, config):
239 url_obj = url_parser(url)
239 url_obj = url_parser(url)
240 test_uri, _ = url_obj.authinfo()
240 test_uri, _ = url_obj.authinfo()
241 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
241 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
242 url_obj.query = obfuscate_qs(url_obj.query)
242 url_obj.query = obfuscate_qs(url_obj.query)
243 cleaned_uri = str(url_obj)
243 cleaned_uri = str(url_obj)
244 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
244 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
245
245
246 if not test_uri.endswith('info/refs'):
246 if not test_uri.endswith('info/refs'):
247 test_uri = test_uri.rstrip('/') + '/info/refs'
247 test_uri = test_uri.rstrip('/') + '/info/refs'
248
248
249 o = self._build_opener(url)
249 o = self._build_opener(url)
250 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
250 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
251
251
252 q = {"service": 'git-upload-pack'}
252 q = {"service": 'git-upload-pack'}
253 qs = '?%s' % urllib.urlencode(q)
253 qs = '?%s' % urllib.urlencode(q)
254 cu = "%s%s" % (test_uri, qs)
254 cu = "%s%s" % (test_uri, qs)
255 req = urllib2.Request(cu, None, {})
255 req = urllib2.Request(cu, None, {})
256
256
257 try:
257 try:
258 log.debug("Trying to open URL %s", cleaned_uri)
258 log.debug("Trying to open URL %s", cleaned_uri)
259 resp = o.open(req)
259 resp = o.open(req)
260 if resp.code != 200:
260 if resp.code != 200:
261 raise exceptions.URLError()('Return Code is not 200')
261 raise exceptions.URLError()('Return Code is not 200')
262 except Exception as e:
262 except Exception as e:
263 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
263 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
264 # means it cannot be cloned
264 # means it cannot be cloned
265 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
265 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
266
266
267 # now detect if it's proper git repo
267 # now detect if it's proper git repo
268 gitdata = resp.read()
268 gitdata = resp.read()
269 if 'service=git-upload-pack' in gitdata:
269 if 'service=git-upload-pack' in gitdata:
270 pass
270 pass
271 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
271 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
272 # old style git can return some other format !
272 # old style git can return some other format !
273 pass
273 pass
274 else:
274 else:
275 raise exceptions.URLError()(
275 raise exceptions.URLError()(
276 "url [%s] does not look like an git" % (cleaned_uri,))
276 "url [%s] does not look like an git" % (cleaned_uri,))
277
277
278 return True
278 return True
279
279
280 @reraise_safe_exceptions
280 @reraise_safe_exceptions
281 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
281 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
282 remote_refs = self.fetch(wire, url, apply_refs=False)
282 remote_refs = self.fetch(wire, url, apply_refs=False)
283 repo = self._factory.repo(wire)
283 repo = self._factory.repo(wire)
284 if isinstance(valid_refs, list):
284 if isinstance(valid_refs, list):
285 valid_refs = tuple(valid_refs)
285 valid_refs = tuple(valid_refs)
286
286
287 for k in remote_refs:
287 for k in remote_refs:
288 # only parse heads/tags and skip so called deferred tags
288 # only parse heads/tags and skip so called deferred tags
289 if k.startswith(valid_refs) and not k.endswith(deferred):
289 if k.startswith(valid_refs) and not k.endswith(deferred):
290 repo[k] = remote_refs[k]
290 repo[k] = remote_refs[k]
291
291
292 if update_after_clone:
292 if update_after_clone:
293 # we want to checkout HEAD
293 # we want to checkout HEAD
294 repo["HEAD"] = remote_refs["HEAD"]
294 repo["HEAD"] = remote_refs["HEAD"]
295 index.build_index_from_tree(repo.path, repo.index_path(),
295 index.build_index_from_tree(repo.path, repo.index_path(),
296 repo.object_store, repo["HEAD"].tree)
296 repo.object_store, repo["HEAD"].tree)
297
297
298 # TODO: this is quite complex, check if that can be simplified
298 # TODO: this is quite complex, check if that can be simplified
299 @reraise_safe_exceptions
299 @reraise_safe_exceptions
300 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
300 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
301 repo = self._factory.repo(wire)
301 repo = self._factory.repo(wire)
302 object_store = repo.object_store
302 object_store = repo.object_store
303
303
304 # Create tree and populates it with blobs
304 # Create tree and populates it with blobs
305 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
305 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
306
306
307 for node in updated:
307 for node in updated:
308 # Compute subdirs if needed
308 # Compute subdirs if needed
309 dirpath, nodename = vcspath.split(node['path'])
309 dirpath, nodename = vcspath.split(node['path'])
310 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
310 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
311 parent = commit_tree
311 parent = commit_tree
312 ancestors = [('', parent)]
312 ancestors = [('', parent)]
313
313
314 # Tries to dig for the deepest existing tree
314 # Tries to dig for the deepest existing tree
315 while dirnames:
315 while dirnames:
316 curdir = dirnames.pop(0)
316 curdir = dirnames.pop(0)
317 try:
317 try:
318 dir_id = parent[curdir][1]
318 dir_id = parent[curdir][1]
319 except KeyError:
319 except KeyError:
320 # put curdir back into dirnames and stops
320 # put curdir back into dirnames and stops
321 dirnames.insert(0, curdir)
321 dirnames.insert(0, curdir)
322 break
322 break
323 else:
323 else:
324 # If found, updates parent
324 # If found, updates parent
325 parent = repo[dir_id]
325 parent = repo[dir_id]
326 ancestors.append((curdir, parent))
326 ancestors.append((curdir, parent))
327 # Now parent is deepest existing tree and we need to create
327 # Now parent is deepest existing tree and we need to create
328 # subtrees for dirnames (in reverse order)
328 # subtrees for dirnames (in reverse order)
329 # [this only applies for nodes from added]
329 # [this only applies for nodes from added]
330 new_trees = []
330 new_trees = []
331
331
332 blob = objects.Blob.from_string(node['content'])
332 blob = objects.Blob.from_string(node['content'])
333
333
334 if dirnames:
334 if dirnames:
335 # If there are trees which should be created we need to build
335 # If there are trees which should be created we need to build
336 # them now (in reverse order)
336 # them now (in reverse order)
337 reversed_dirnames = list(reversed(dirnames))
337 reversed_dirnames = list(reversed(dirnames))
338 curtree = objects.Tree()
338 curtree = objects.Tree()
339 curtree[node['node_path']] = node['mode'], blob.id
339 curtree[node['node_path']] = node['mode'], blob.id
340 new_trees.append(curtree)
340 new_trees.append(curtree)
341 for dirname in reversed_dirnames[:-1]:
341 for dirname in reversed_dirnames[:-1]:
342 newtree = objects.Tree()
342 newtree = objects.Tree()
343 newtree[dirname] = (DIR_STAT, curtree.id)
343 newtree[dirname] = (DIR_STAT, curtree.id)
344 new_trees.append(newtree)
344 new_trees.append(newtree)
345 curtree = newtree
345 curtree = newtree
346 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
346 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
347 else:
347 else:
348 parent.add(
348 parent.add(
349 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
349 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
350
350
351 new_trees.append(parent)
351 new_trees.append(parent)
352 # Update ancestors
352 # Update ancestors
353 reversed_ancestors = reversed(
353 reversed_ancestors = reversed(
354 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
354 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
355 for parent, tree, path in reversed_ancestors:
355 for parent, tree, path in reversed_ancestors:
356 parent[path] = (DIR_STAT, tree.id)
356 parent[path] = (DIR_STAT, tree.id)
357 object_store.add_object(tree)
357 object_store.add_object(tree)
358
358
359 object_store.add_object(blob)
359 object_store.add_object(blob)
360 for tree in new_trees:
360 for tree in new_trees:
361 object_store.add_object(tree)
361 object_store.add_object(tree)
362
362
363 for node_path in removed:
363 for node_path in removed:
364 paths = node_path.split('/')
364 paths = node_path.split('/')
365 tree = commit_tree
365 tree = commit_tree
366 trees = [tree]
366 trees = [tree]
367 # Traverse deep into the forest...
367 # Traverse deep into the forest...
368 for path in paths:
368 for path in paths:
369 try:
369 try:
370 obj = repo[tree[path][1]]
370 obj = repo[tree[path][1]]
371 if isinstance(obj, objects.Tree):
371 if isinstance(obj, objects.Tree):
372 trees.append(obj)
372 trees.append(obj)
373 tree = obj
373 tree = obj
374 except KeyError:
374 except KeyError:
375 break
375 break
376 # Cut down the blob and all rotten trees on the way back...
376 # Cut down the blob and all rotten trees on the way back...
377 for path, tree in reversed(zip(paths, trees)):
377 for path, tree in reversed(zip(paths, trees)):
378 del tree[path]
378 del tree[path]
379 if tree:
379 if tree:
380 # This tree still has elements - don't remove it or any
380 # This tree still has elements - don't remove it or any
381 # of it's parents
381 # of it's parents
382 break
382 break
383
383
384 object_store.add_object(commit_tree)
384 object_store.add_object(commit_tree)
385
385
386 # Create commit
386 # Create commit
387 commit = objects.Commit()
387 commit = objects.Commit()
388 commit.tree = commit_tree.id
388 commit.tree = commit_tree.id
389 for k, v in commit_data.iteritems():
389 for k, v in commit_data.iteritems():
390 setattr(commit, k, v)
390 setattr(commit, k, v)
391 object_store.add_object(commit)
391 object_store.add_object(commit)
392
392
393 ref = 'refs/heads/%s' % branch
393 ref = 'refs/heads/%s' % branch
394 repo.refs[ref] = commit.id
394 repo.refs[ref] = commit.id
395
395
396 return commit.id
396 return commit.id
397
397
398 @reraise_safe_exceptions
398 @reraise_safe_exceptions
399 def fetch(self, wire, url, apply_refs=True, refs=None):
399 def fetch(self, wire, url, apply_refs=True, refs=None):
400 if url != 'default' and '://' not in url:
400 if url != 'default' and '://' not in url:
401 client = LocalGitClient(url)
401 client = LocalGitClient(url)
402 else:
402 else:
403 url_obj = url_parser(url)
403 url_obj = url_parser(url)
404 o = self._build_opener(url)
404 o = self._build_opener(url)
405 url, _ = url_obj.authinfo()
405 url, _ = url_obj.authinfo()
406 client = HttpGitClient(base_url=url, opener=o)
406 client = HttpGitClient(base_url=url, opener=o)
407 repo = self._factory.repo(wire)
407 repo = self._factory.repo(wire)
408
408
409 determine_wants = repo.object_store.determine_wants_all
409 determine_wants = repo.object_store.determine_wants_all
410 if refs:
410 if refs:
411 def determine_wants_requested(references):
411 def determine_wants_requested(references):
412 return [references[r] for r in references if r in refs]
412 return [references[r] for r in references if r in refs]
413 determine_wants = determine_wants_requested
413 determine_wants = determine_wants_requested
414
414
415 try:
415 try:
416 remote_refs = client.fetch(
416 remote_refs = client.fetch(
417 path=url, target=repo, determine_wants=determine_wants)
417 path=url, target=repo, determine_wants=determine_wants)
418 except NotGitRepository as e:
418 except NotGitRepository as e:
419 log.warning(
419 log.warning(
420 'Trying to fetch from "%s" failed, not a Git repository.', url)
420 'Trying to fetch from "%s" failed, not a Git repository.', url)
421 # Exception can contain unicode which we convert
421 # Exception can contain unicode which we convert
422 raise exceptions.AbortException(e)(repr(e))
422 raise exceptions.AbortException(e)(repr(e))
423
423
424 # mikhail: client.fetch() returns all the remote refs, but fetches only
424 # mikhail: client.fetch() returns all the remote refs, but fetches only
425 # refs filtered by `determine_wants` function. We need to filter result
425 # refs filtered by `determine_wants` function. We need to filter result
426 # as well
426 # as well
427 if refs:
427 if refs:
428 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
428 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
429
429
430 if apply_refs:
430 if apply_refs:
431 # TODO: johbo: Needs proper test coverage with a git repository
431 # TODO: johbo: Needs proper test coverage with a git repository
432 # that contains a tag object, so that we would end up with
432 # that contains a tag object, so that we would end up with
433 # a peeled ref at this point.
433 # a peeled ref at this point.
434 PEELED_REF_MARKER = '^{}'
434 PEELED_REF_MARKER = '^{}'
435 for k in remote_refs:
435 for k in remote_refs:
436 if k.endswith(PEELED_REF_MARKER):
436 if k.endswith(PEELED_REF_MARKER):
437 log.info("Skipping peeled reference %s", k)
437 log.info("Skipping peeled reference %s", k)
438 continue
438 continue
439 repo[k] = remote_refs[k]
439 repo[k] = remote_refs[k]
440
440
441 if refs:
441 if refs:
442 # mikhail: explicitly set the head to the last ref.
442 # mikhail: explicitly set the head to the last ref.
443 repo['HEAD'] = remote_refs[refs[-1]]
443 repo['HEAD'] = remote_refs[refs[-1]]
444
444
445 # TODO: mikhail: should we return remote_refs here to be
445 # TODO: mikhail: should we return remote_refs here to be
446 # consistent?
446 # consistent?
447 else:
447 else:
448 return remote_refs
448 return remote_refs
449
449
450 @reraise_safe_exceptions
450 @reraise_safe_exceptions
451 def sync_push(self, wire, url, refs=None):
451 def sync_push(self, wire, url, refs=None):
452 if self.check_url(url, wire):
452 if self.check_url(url, wire):
453 repo = self._factory.repo(wire)
453 repo = self._factory.repo(wire)
454 self.run_git_command(
454 self.run_git_command(
455 wire, ['push', url, '--mirror'], fail_on_stderr=False,
455 wire, ['push', url, '--mirror'], fail_on_stderr=False,
456 _copts=['-c', 'core.askpass=""'],
456 _copts=['-c', 'core.askpass=""'],
457 extra_env={'GIT_TERMINAL_PROMPT': '0'})
457 extra_env={'GIT_TERMINAL_PROMPT': '0'})
458
458
459 @reraise_safe_exceptions
459 @reraise_safe_exceptions
460 def get_remote_refs(self, wire, url):
460 def get_remote_refs(self, wire, url):
461 repo = Repo(url)
461 repo = Repo(url)
462 return repo.get_refs()
462 return repo.get_refs()
463
463
464 @reraise_safe_exceptions
464 @reraise_safe_exceptions
465 def get_description(self, wire):
465 def get_description(self, wire):
466 repo = self._factory.repo(wire)
466 repo = self._factory.repo(wire)
467 return repo.get_description()
467 return repo.get_description()
468
468
469 @reraise_safe_exceptions
469 @reraise_safe_exceptions
470 def get_file_history(self, wire, file_path, commit_id, limit):
470 def get_file_history(self, wire, file_path, commit_id, limit):
471 repo = self._factory.repo(wire)
471 repo = self._factory.repo(wire)
472 include = [commit_id]
472 include = [commit_id]
473 paths = [file_path]
473 paths = [file_path]
474
474
475 walker = repo.get_walker(include, paths=paths, max_entries=limit)
475 walker = repo.get_walker(include, paths=paths, max_entries=limit)
476 return [x.commit.id for x in walker]
476 return [x.commit.id for x in walker]
477
477
478 @reraise_safe_exceptions
478 @reraise_safe_exceptions
479 def get_missing_revs(self, wire, rev1, rev2, path2):
479 def get_missing_revs(self, wire, rev1, rev2, path2):
480 repo = self._factory.repo(wire)
480 repo = self._factory.repo(wire)
481 LocalGitClient(thin_packs=False).fetch(path2, repo)
481 LocalGitClient(thin_packs=False).fetch(path2, repo)
482
482
483 wire_remote = wire.copy()
483 wire_remote = wire.copy()
484 wire_remote['path'] = path2
484 wire_remote['path'] = path2
485 repo_remote = self._factory.repo(wire_remote)
485 repo_remote = self._factory.repo(wire_remote)
486 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
486 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
487
487
488 revs = [
488 revs = [
489 x.commit.id
489 x.commit.id
490 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
490 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
491 return revs
491 return revs
492
492
493 @reraise_safe_exceptions
493 @reraise_safe_exceptions
494 def get_object(self, wire, sha):
494 def get_object(self, wire, sha):
495 repo = self._factory.repo(wire)
495 repo = self._factory.repo(wire)
496 obj = repo.get_object(sha)
496 obj = repo.get_object(sha)
497 commit_id = obj.id
497 commit_id = obj.id
498
498
499 if isinstance(obj, Tag):
499 if isinstance(obj, Tag):
500 commit_id = obj.object[1]
500 commit_id = obj.object[1]
501
501
502 return {
502 return {
503 'id': obj.id,
503 'id': obj.id,
504 'type': obj.type_name,
504 'type': obj.type_name,
505 'commit_id': commit_id
505 'commit_id': commit_id
506 }
506 }
507
507
508 @reraise_safe_exceptions
508 @reraise_safe_exceptions
509 def get_object_attrs(self, wire, sha, *attrs):
509 def get_object_attrs(self, wire, sha, *attrs):
510 repo = self._factory.repo(wire)
510 repo = self._factory.repo(wire)
511 obj = repo.get_object(sha)
511 obj = repo.get_object(sha)
512 return list(getattr(obj, a) for a in attrs)
512 return list(getattr(obj, a) for a in attrs)
513
513
514 @reraise_safe_exceptions
514 @reraise_safe_exceptions
515 def get_refs(self, wire):
515 def get_refs(self, wire):
516 repo = self._factory.repo(wire)
516 repo = self._factory.repo(wire)
517 result = {}
517 result = {}
518 for ref, sha in repo.refs.as_dict().items():
518 for ref, sha in repo.refs.as_dict().items():
519 peeled_sha = repo.get_peeled(ref)
519 peeled_sha = repo.get_peeled(ref)
520 result[ref] = peeled_sha
520 result[ref] = peeled_sha
521 return result
521 return result
522
522
523 @reraise_safe_exceptions
523 @reraise_safe_exceptions
524 def get_refs_path(self, wire):
524 def get_refs_path(self, wire):
525 repo = self._factory.repo(wire)
525 repo = self._factory.repo(wire)
526 return repo.refs.path
526 return repo.refs.path
527
527
528 @reraise_safe_exceptions
528 @reraise_safe_exceptions
529 def head(self, wire):
529 def head(self, wire, show_exc=True):
530 repo = self._factory.repo(wire)
530 repo = self._factory.repo(wire)
531 return repo.head()
531 try:
532 return repo.head()
533 except Exception:
534 if show_exc:
535 raise
532
536
533 @reraise_safe_exceptions
537 @reraise_safe_exceptions
534 def init(self, wire):
538 def init(self, wire):
535 repo_path = str_to_dulwich(wire['path'])
539 repo_path = str_to_dulwich(wire['path'])
536 self.repo = Repo.init(repo_path)
540 self.repo = Repo.init(repo_path)
537
541
538 @reraise_safe_exceptions
542 @reraise_safe_exceptions
539 def init_bare(self, wire):
543 def init_bare(self, wire):
540 repo_path = str_to_dulwich(wire['path'])
544 repo_path = str_to_dulwich(wire['path'])
541 self.repo = Repo.init_bare(repo_path)
545 self.repo = Repo.init_bare(repo_path)
542
546
543 @reraise_safe_exceptions
547 @reraise_safe_exceptions
544 def revision(self, wire, rev):
548 def revision(self, wire, rev):
545 repo = self._factory.repo(wire)
549 repo = self._factory.repo(wire)
546 obj = repo[rev]
550 obj = repo[rev]
547 obj_data = {
551 obj_data = {
548 'id': obj.id,
552 'id': obj.id,
549 }
553 }
550 try:
554 try:
551 obj_data['tree'] = obj.tree
555 obj_data['tree'] = obj.tree
552 except AttributeError:
556 except AttributeError:
553 pass
557 pass
554 return obj_data
558 return obj_data
555
559
556 @reraise_safe_exceptions
560 @reraise_safe_exceptions
557 def commit_attribute(self, wire, rev, attr):
561 def commit_attribute(self, wire, rev, attr):
558 repo = self._factory.repo(wire)
562 repo = self._factory.repo(wire)
559 obj = repo[rev]
563 obj = repo[rev]
560 return getattr(obj, attr)
564 return getattr(obj, attr)
561
565
562 @reraise_safe_exceptions
566 @reraise_safe_exceptions
563 def set_refs(self, wire, key, value):
567 def set_refs(self, wire, key, value):
564 repo = self._factory.repo(wire)
568 repo = self._factory.repo(wire)
565 repo.refs[key] = value
569 repo.refs[key] = value
566
570
567 @reraise_safe_exceptions
571 @reraise_safe_exceptions
568 def remove_ref(self, wire, key):
572 def remove_ref(self, wire, key):
569 repo = self._factory.repo(wire)
573 repo = self._factory.repo(wire)
570 del repo.refs[key]
574 del repo.refs[key]
571
575
572 @reraise_safe_exceptions
576 @reraise_safe_exceptions
573 def tree_changes(self, wire, source_id, target_id):
577 def tree_changes(self, wire, source_id, target_id):
574 repo = self._factory.repo(wire)
578 repo = self._factory.repo(wire)
575 source = repo[source_id].tree if source_id else None
579 source = repo[source_id].tree if source_id else None
576 target = repo[target_id].tree
580 target = repo[target_id].tree
577 result = repo.object_store.tree_changes(source, target)
581 result = repo.object_store.tree_changes(source, target)
578 return list(result)
582 return list(result)
579
583
580 @reraise_safe_exceptions
584 @reraise_safe_exceptions
581 def tree_items(self, wire, tree_id):
585 def tree_items(self, wire, tree_id):
582 repo = self._factory.repo(wire)
586 repo = self._factory.repo(wire)
583 tree = repo[tree_id]
587 tree = repo[tree_id]
584
588
585 result = []
589 result = []
586 for item in tree.iteritems():
590 for item in tree.iteritems():
587 item_sha = item.sha
591 item_sha = item.sha
588 item_mode = item.mode
592 item_mode = item.mode
589
593
590 if FILE_MODE(item_mode) == GIT_LINK:
594 if FILE_MODE(item_mode) == GIT_LINK:
591 item_type = "link"
595 item_type = "link"
592 else:
596 else:
593 item_type = repo[item_sha].type_name
597 item_type = repo[item_sha].type_name
594
598
595 result.append((item.path, item_mode, item_sha, item_type))
599 result.append((item.path, item_mode, item_sha, item_type))
596 return result
600 return result
597
601
598 @reraise_safe_exceptions
602 @reraise_safe_exceptions
599 def update_server_info(self, wire):
603 def update_server_info(self, wire):
600 repo = self._factory.repo(wire)
604 repo = self._factory.repo(wire)
601 update_server_info(repo)
605 update_server_info(repo)
602
606
603 @reraise_safe_exceptions
607 @reraise_safe_exceptions
604 def discover_git_version(self):
608 def discover_git_version(self):
605 stdout, _ = self.run_git_command(
609 stdout, _ = self.run_git_command(
606 {}, ['--version'], _bare=True, _safe=True)
610 {}, ['--version'], _bare=True, _safe=True)
607 prefix = 'git version'
611 prefix = 'git version'
608 if stdout.startswith(prefix):
612 if stdout.startswith(prefix):
609 stdout = stdout[len(prefix):]
613 stdout = stdout[len(prefix):]
610 return stdout.strip()
614 return stdout.strip()
611
615
612 @reraise_safe_exceptions
616 @reraise_safe_exceptions
613 def run_git_command(self, wire, cmd, **opts):
617 def run_git_command(self, wire, cmd, **opts):
614 path = wire.get('path', None)
618 path = wire.get('path', None)
615
619
616 if path and os.path.isdir(path):
620 if path and os.path.isdir(path):
617 opts['cwd'] = path
621 opts['cwd'] = path
618
622
619 if '_bare' in opts:
623 if '_bare' in opts:
620 _copts = []
624 _copts = []
621 del opts['_bare']
625 del opts['_bare']
622 else:
626 else:
623 _copts = ['-c', 'core.quotepath=false', ]
627 _copts = ['-c', 'core.quotepath=false', ]
624 safe_call = False
628 safe_call = False
625 if '_safe' in opts:
629 if '_safe' in opts:
626 # no exc on failure
630 # no exc on failure
627 del opts['_safe']
631 del opts['_safe']
628 safe_call = True
632 safe_call = True
629
633
630 if '_copts' in opts:
634 if '_copts' in opts:
631 _copts.extend(opts['_copts'] or [])
635 _copts.extend(opts['_copts'] or [])
632 del opts['_copts']
636 del opts['_copts']
633
637
634 gitenv = os.environ.copy()
638 gitenv = os.environ.copy()
635 gitenv.update(opts.pop('extra_env', {}))
639 gitenv.update(opts.pop('extra_env', {}))
636 # need to clean fix GIT_DIR !
640 # need to clean fix GIT_DIR !
637 if 'GIT_DIR' in gitenv:
641 if 'GIT_DIR' in gitenv:
638 del gitenv['GIT_DIR']
642 del gitenv['GIT_DIR']
639 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
643 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
640 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
644 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
641
645
642 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
646 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
643
647
644 try:
648 try:
645 _opts = {'env': gitenv, 'shell': False}
649 _opts = {'env': gitenv, 'shell': False}
646 _opts.update(opts)
650 _opts.update(opts)
647 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
651 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
648
652
649 return ''.join(p), ''.join(p.error)
653 return ''.join(p), ''.join(p.error)
650 except (EnvironmentError, OSError) as err:
654 except (EnvironmentError, OSError) as err:
651 cmd = ' '.join(cmd) # human friendly CMD
655 cmd = ' '.join(cmd) # human friendly CMD
652 tb_err = ("Couldn't run git command (%s).\n"
656 tb_err = ("Couldn't run git command (%s).\n"
653 "Original error was:%s\n" % (cmd, err))
657 "Original error was:%s\n" % (cmd, err))
654 log.exception(tb_err)
658 log.exception(tb_err)
655 if safe_call:
659 if safe_call:
656 return '', err
660 return '', err
657 else:
661 else:
658 raise exceptions.VcsException()(tb_err)
662 raise exceptions.VcsException()(tb_err)
659
663
660 @reraise_safe_exceptions
664 @reraise_safe_exceptions
661 def install_hooks(self, wire, force=False):
665 def install_hooks(self, wire, force=False):
662 from vcsserver.hook_utils import install_git_hooks
666 from vcsserver.hook_utils import install_git_hooks
663 repo = self._factory.repo(wire)
667 repo = self._factory.repo(wire)
664 return install_git_hooks(repo.path, repo.bare, force_create=force)
668 return install_git_hooks(repo.path, repo.bare, force_create=force)
665
669
666
670
667 def str_to_dulwich(value):
671 def str_to_dulwich(value):
668 """
672 """
669 Dulwich 0.10.1a requires `unicode` objects to be passed in.
673 Dulwich 0.10.1a requires `unicode` objects to be passed in.
670 """
674 """
671 return value.decode(settings.WIRE_ENCODING)
675 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now