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