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