##// END OF EJS Templates
git-version: strip the info returned from subcall to remove newline character.
marcink -
r103:230198b7 default
parent child Browse files
Show More
@@ -1,576 +1,576 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 result = {}
434 result = {}
435 for ref, sha in repo.refs.as_dict().items():
435 for ref, sha in repo.refs.as_dict().items():
436 peeled_sha = repo.get_peeled(ref)
436 peeled_sha = repo.get_peeled(ref)
437 result[ref] = peeled_sha
437 result[ref] = peeled_sha
438 return result
438 return result
439
439
440 @reraise_safe_exceptions
440 @reraise_safe_exceptions
441 def get_refs_path(self, wire):
441 def get_refs_path(self, wire):
442 repo = self._factory.repo(wire)
442 repo = self._factory.repo(wire)
443 return repo.refs.path
443 return repo.refs.path
444
444
445 @reraise_safe_exceptions
445 @reraise_safe_exceptions
446 def head(self, wire):
446 def head(self, wire):
447 repo = self._factory.repo(wire)
447 repo = self._factory.repo(wire)
448 return repo.head()
448 return repo.head()
449
449
450 @reraise_safe_exceptions
450 @reraise_safe_exceptions
451 def init(self, wire):
451 def init(self, wire):
452 repo_path = str_to_dulwich(wire['path'])
452 repo_path = str_to_dulwich(wire['path'])
453 self.repo = Repo.init(repo_path)
453 self.repo = Repo.init(repo_path)
454
454
455 @reraise_safe_exceptions
455 @reraise_safe_exceptions
456 def init_bare(self, wire):
456 def init_bare(self, wire):
457 repo_path = str_to_dulwich(wire['path'])
457 repo_path = str_to_dulwich(wire['path'])
458 self.repo = Repo.init_bare(repo_path)
458 self.repo = Repo.init_bare(repo_path)
459
459
460 @reraise_safe_exceptions
460 @reraise_safe_exceptions
461 def revision(self, wire, rev):
461 def revision(self, wire, rev):
462 repo = self._factory.repo(wire)
462 repo = self._factory.repo(wire)
463 obj = repo[rev]
463 obj = repo[rev]
464 obj_data = {
464 obj_data = {
465 'id': obj.id,
465 'id': obj.id,
466 }
466 }
467 try:
467 try:
468 obj_data['tree'] = obj.tree
468 obj_data['tree'] = obj.tree
469 except AttributeError:
469 except AttributeError:
470 pass
470 pass
471 return obj_data
471 return obj_data
472
472
473 @reraise_safe_exceptions
473 @reraise_safe_exceptions
474 def commit_attribute(self, wire, rev, attr):
474 def commit_attribute(self, wire, rev, attr):
475 repo = self._factory.repo(wire)
475 repo = self._factory.repo(wire)
476 obj = repo[rev]
476 obj = repo[rev]
477 return getattr(obj, attr)
477 return getattr(obj, attr)
478
478
479 @reraise_safe_exceptions
479 @reraise_safe_exceptions
480 def set_refs(self, wire, key, value):
480 def set_refs(self, wire, key, value):
481 repo = self._factory.repo(wire)
481 repo = self._factory.repo(wire)
482 repo.refs[key] = value
482 repo.refs[key] = value
483
483
484 @reraise_safe_exceptions
484 @reraise_safe_exceptions
485 def remove_ref(self, wire, key):
485 def remove_ref(self, wire, key):
486 repo = self._factory.repo(wire)
486 repo = self._factory.repo(wire)
487 del repo.refs[key]
487 del repo.refs[key]
488
488
489 @reraise_safe_exceptions
489 @reraise_safe_exceptions
490 def tree_changes(self, wire, source_id, target_id):
490 def tree_changes(self, wire, source_id, target_id):
491 repo = self._factory.repo(wire)
491 repo = self._factory.repo(wire)
492 source = repo[source_id].tree if source_id else None
492 source = repo[source_id].tree if source_id else None
493 target = repo[target_id].tree
493 target = repo[target_id].tree
494 result = repo.object_store.tree_changes(source, target)
494 result = repo.object_store.tree_changes(source, target)
495 return list(result)
495 return list(result)
496
496
497 @reraise_safe_exceptions
497 @reraise_safe_exceptions
498 def tree_items(self, wire, tree_id):
498 def tree_items(self, wire, tree_id):
499 repo = self._factory.repo(wire)
499 repo = self._factory.repo(wire)
500 tree = repo[tree_id]
500 tree = repo[tree_id]
501
501
502 result = []
502 result = []
503 for item in tree.iteritems():
503 for item in tree.iteritems():
504 item_sha = item.sha
504 item_sha = item.sha
505 item_mode = item.mode
505 item_mode = item.mode
506
506
507 if FILE_MODE(item_mode) == GIT_LINK:
507 if FILE_MODE(item_mode) == GIT_LINK:
508 item_type = "link"
508 item_type = "link"
509 else:
509 else:
510 item_type = repo[item_sha].type_name
510 item_type = repo[item_sha].type_name
511
511
512 result.append((item.path, item_mode, item_sha, item_type))
512 result.append((item.path, item_mode, item_sha, item_type))
513 return result
513 return result
514
514
515 @reraise_safe_exceptions
515 @reraise_safe_exceptions
516 def update_server_info(self, wire):
516 def update_server_info(self, wire):
517 repo = self._factory.repo(wire)
517 repo = self._factory.repo(wire)
518 update_server_info(repo)
518 update_server_info(repo)
519
519
520 @reraise_safe_exceptions
520 @reraise_safe_exceptions
521 def discover_git_version(self):
521 def discover_git_version(self):
522 stdout, _ = self.run_git_command(
522 stdout, _ = self.run_git_command(
523 {}, ['--version'], _bare=True, _safe=True)
523 {}, ['--version'], _bare=True, _safe=True)
524 prefix = 'git version'
524 prefix = 'git version'
525 if stdout.startswith(prefix):
525 if stdout.startswith(prefix):
526 stdout = stdout[len(prefix):]
526 stdout = stdout[len(prefix):]
527 return stdout
527 return stdout.strip()
528
528
529 @reraise_safe_exceptions
529 @reraise_safe_exceptions
530 def run_git_command(self, wire, cmd, **opts):
530 def run_git_command(self, wire, cmd, **opts):
531 path = wire.get('path', None)
531 path = wire.get('path', None)
532
532
533 if path and os.path.isdir(path):
533 if path and os.path.isdir(path):
534 opts['cwd'] = path
534 opts['cwd'] = path
535
535
536 if '_bare' in opts:
536 if '_bare' in opts:
537 _copts = []
537 _copts = []
538 del opts['_bare']
538 del opts['_bare']
539 else:
539 else:
540 _copts = ['-c', 'core.quotepath=false', ]
540 _copts = ['-c', 'core.quotepath=false', ]
541 safe_call = False
541 safe_call = False
542 if '_safe' in opts:
542 if '_safe' in opts:
543 # no exc on failure
543 # no exc on failure
544 del opts['_safe']
544 del opts['_safe']
545 safe_call = True
545 safe_call = True
546
546
547 gitenv = os.environ.copy()
547 gitenv = os.environ.copy()
548 gitenv.update(opts.pop('extra_env', {}))
548 gitenv.update(opts.pop('extra_env', {}))
549 # need to clean fix GIT_DIR !
549 # need to clean fix GIT_DIR !
550 if 'GIT_DIR' in gitenv:
550 if 'GIT_DIR' in gitenv:
551 del gitenv['GIT_DIR']
551 del gitenv['GIT_DIR']
552 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
552 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
553
553
554 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
554 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
555
555
556 try:
556 try:
557 _opts = {'env': gitenv, 'shell': False}
557 _opts = {'env': gitenv, 'shell': False}
558 _opts.update(opts)
558 _opts.update(opts)
559 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
559 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
560
560
561 return ''.join(p), ''.join(p.error)
561 return ''.join(p), ''.join(p.error)
562 except (EnvironmentError, OSError) as err:
562 except (EnvironmentError, OSError) as err:
563 tb_err = ("Couldn't run git command (%s).\n"
563 tb_err = ("Couldn't run git command (%s).\n"
564 "Original error was:%s\n" % (cmd, err))
564 "Original error was:%s\n" % (cmd, err))
565 log.exception(tb_err)
565 log.exception(tb_err)
566 if safe_call:
566 if safe_call:
567 return '', err
567 return '', err
568 else:
568 else:
569 raise exceptions.VcsException(tb_err)
569 raise exceptions.VcsException(tb_err)
570
570
571
571
572 def str_to_dulwich(value):
572 def str_to_dulwich(value):
573 """
573 """
574 Dulwich 0.10.1a requires `unicode` objects to be passed in.
574 Dulwich 0.10.1a requires `unicode` objects to be passed in.
575 """
575 """
576 return value.decode(settings.WIRE_ENCODING)
576 return value.decode(settings.WIRE_ENCODING)
General Comments 0
You need to be logged in to leave comments. Login now