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