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