##// END OF EJS Templates
error-handlig: pass in Abort exception info to RhodeCode from vcsserver.
marcink -
r120:dcb043be default
parent child Browse files
Show More
@@ -1,580 +1,581 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, obfuscate_qs
38 from vcsserver.base import RepoFactory, obfuscate_qs
39 from vcsserver.hgcompat import (
39 from vcsserver.hgcompat import (
40 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
40 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
41
41
42
42
43 DIR_STAT = stat.S_IFDIR
43 DIR_STAT = stat.S_IFDIR
44 FILE_MODE = stat.S_IFMT
44 FILE_MODE = stat.S_IFMT
45 GIT_LINK = objects.S_IFGITLINK
45 GIT_LINK = objects.S_IFGITLINK
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def reraise_safe_exceptions(func):
50 def reraise_safe_exceptions(func):
51 """Converts Dulwich exceptions to something neutral."""
51 """Converts Dulwich exceptions to something neutral."""
52 @wraps(func)
52 @wraps(func)
53 def wrapper(*args, **kwargs):
53 def wrapper(*args, **kwargs):
54 try:
54 try:
55 return func(*args, **kwargs)
55 return func(*args, **kwargs)
56 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
56 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
57 ObjectMissing) as e:
57 ObjectMissing) as e:
58 raise exceptions.LookupException(e.message)
58 raise exceptions.LookupException(e.message)
59 except (HangupException, UnexpectedCommandError) as e:
59 except (HangupException, UnexpectedCommandError) as e:
60 raise exceptions.VcsException(e.message)
60 raise exceptions.VcsException(e.message)
61 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 = url_parser(url)
155 url_obj = url_parser(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 = url_parser(url)
170 url_obj = url_parser(url)
171 test_uri, _ = url_obj.authinfo()
171 test_uri, _ = url_obj.authinfo()
172 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
172 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
173 url_obj.query = obfuscate_qs(url_obj.query)
173 url_obj.query = obfuscate_qs(url_obj.query)
174 cleaned_uri = str(url_obj)
174 cleaned_uri = str(url_obj)
175 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
175 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
176
176
177 if not test_uri.endswith('info/refs'):
177 if not test_uri.endswith('info/refs'):
178 test_uri = test_uri.rstrip('/') + '/info/refs'
178 test_uri = test_uri.rstrip('/') + '/info/refs'
179
179
180 o = self._build_opener(url)
180 o = self._build_opener(url)
181 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
181 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
182
182
183 q = {"service": 'git-upload-pack'}
183 q = {"service": 'git-upload-pack'}
184 qs = '?%s' % urllib.urlencode(q)
184 qs = '?%s' % urllib.urlencode(q)
185 cu = "%s%s" % (test_uri, qs)
185 cu = "%s%s" % (test_uri, qs)
186 req = urllib2.Request(cu, None, {})
186 req = urllib2.Request(cu, None, {})
187
187
188 try:
188 try:
189 log.debug("Trying to open URL %s", cleaned_uri)
189 log.debug("Trying to open URL %s", cleaned_uri)
190 resp = o.open(req)
190 resp = o.open(req)
191 if resp.code != 200:
191 if resp.code != 200:
192 raise exceptions.URLError('Return Code is not 200')
192 raise exceptions.URLError('Return Code is not 200')
193 except Exception as e:
193 except Exception as e:
194 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
194 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
195 # means it cannot be cloned
195 # means it cannot be cloned
196 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
196 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
197
197
198 # now detect if it's proper git repo
198 # now detect if it's proper git repo
199 gitdata = resp.read()
199 gitdata = resp.read()
200 if 'service=git-upload-pack' in gitdata:
200 if 'service=git-upload-pack' in gitdata:
201 pass
201 pass
202 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
202 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
203 # old style git can return some other format !
203 # old style git can return some other format !
204 pass
204 pass
205 else:
205 else:
206 raise exceptions.URLError(
206 raise exceptions.URLError(
207 "url [%s] does not look like an git" % (cleaned_uri,))
207 "url [%s] does not look like an git" % (cleaned_uri,))
208
208
209 return True
209 return True
210
210
211 @reraise_safe_exceptions
211 @reraise_safe_exceptions
212 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
212 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
213 remote_refs = self.fetch(wire, url, apply_refs=False)
213 remote_refs = self.fetch(wire, url, apply_refs=False)
214 repo = self._factory.repo(wire)
214 repo = self._factory.repo(wire)
215 if isinstance(valid_refs, list):
215 if isinstance(valid_refs, list):
216 valid_refs = tuple(valid_refs)
216 valid_refs = tuple(valid_refs)
217
217
218 for k in remote_refs:
218 for k in remote_refs:
219 # only parse heads/tags and skip so called deferred tags
219 # only parse heads/tags and skip so called deferred tags
220 if k.startswith(valid_refs) and not k.endswith(deferred):
220 if k.startswith(valid_refs) and not k.endswith(deferred):
221 repo[k] = remote_refs[k]
221 repo[k] = remote_refs[k]
222
222
223 if update_after_clone:
223 if update_after_clone:
224 # we want to checkout HEAD
224 # we want to checkout HEAD
225 repo["HEAD"] = remote_refs["HEAD"]
225 repo["HEAD"] = remote_refs["HEAD"]
226 index.build_index_from_tree(repo.path, repo.index_path(),
226 index.build_index_from_tree(repo.path, repo.index_path(),
227 repo.object_store, repo["HEAD"].tree)
227 repo.object_store, repo["HEAD"].tree)
228
228
229 # TODO: this is quite complex, check if that can be simplified
229 # TODO: this is quite complex, check if that can be simplified
230 @reraise_safe_exceptions
230 @reraise_safe_exceptions
231 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
231 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
232 repo = self._factory.repo(wire)
232 repo = self._factory.repo(wire)
233 object_store = repo.object_store
233 object_store = repo.object_store
234
234
235 # Create tree and populates it with blobs
235 # Create tree and populates it with blobs
236 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
236 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
237
237
238 for node in updated:
238 for node in updated:
239 # Compute subdirs if needed
239 # Compute subdirs if needed
240 dirpath, nodename = vcspath.split(node['path'])
240 dirpath, nodename = vcspath.split(node['path'])
241 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
241 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
242 parent = commit_tree
242 parent = commit_tree
243 ancestors = [('', parent)]
243 ancestors = [('', parent)]
244
244
245 # Tries to dig for the deepest existing tree
245 # Tries to dig for the deepest existing tree
246 while dirnames:
246 while dirnames:
247 curdir = dirnames.pop(0)
247 curdir = dirnames.pop(0)
248 try:
248 try:
249 dir_id = parent[curdir][1]
249 dir_id = parent[curdir][1]
250 except KeyError:
250 except KeyError:
251 # put curdir back into dirnames and stops
251 # put curdir back into dirnames and stops
252 dirnames.insert(0, curdir)
252 dirnames.insert(0, curdir)
253 break
253 break
254 else:
254 else:
255 # If found, updates parent
255 # If found, updates parent
256 parent = repo[dir_id]
256 parent = repo[dir_id]
257 ancestors.append((curdir, parent))
257 ancestors.append((curdir, parent))
258 # Now parent is deepest existing tree and we need to create
258 # Now parent is deepest existing tree and we need to create
259 # subtrees for dirnames (in reverse order)
259 # subtrees for dirnames (in reverse order)
260 # [this only applies for nodes from added]
260 # [this only applies for nodes from added]
261 new_trees = []
261 new_trees = []
262
262
263 blob = objects.Blob.from_string(node['content'])
263 blob = objects.Blob.from_string(node['content'])
264
264
265 if dirnames:
265 if dirnames:
266 # If there are trees which should be created we need to build
266 # If there are trees which should be created we need to build
267 # them now (in reverse order)
267 # them now (in reverse order)
268 reversed_dirnames = list(reversed(dirnames))
268 reversed_dirnames = list(reversed(dirnames))
269 curtree = objects.Tree()
269 curtree = objects.Tree()
270 curtree[node['node_path']] = node['mode'], blob.id
270 curtree[node['node_path']] = node['mode'], blob.id
271 new_trees.append(curtree)
271 new_trees.append(curtree)
272 for dirname in reversed_dirnames[:-1]:
272 for dirname in reversed_dirnames[:-1]:
273 newtree = objects.Tree()
273 newtree = objects.Tree()
274 newtree[dirname] = (DIR_STAT, curtree.id)
274 newtree[dirname] = (DIR_STAT, curtree.id)
275 new_trees.append(newtree)
275 new_trees.append(newtree)
276 curtree = newtree
276 curtree = newtree
277 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
277 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
278 else:
278 else:
279 parent.add(
279 parent.add(
280 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
280 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
281
281
282 new_trees.append(parent)
282 new_trees.append(parent)
283 # Update ancestors
283 # Update ancestors
284 reversed_ancestors = reversed(
284 reversed_ancestors = reversed(
285 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
285 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
286 for parent, tree, path in reversed_ancestors:
286 for parent, tree, path in reversed_ancestors:
287 parent[path] = (DIR_STAT, tree.id)
287 parent[path] = (DIR_STAT, tree.id)
288 object_store.add_object(tree)
288 object_store.add_object(tree)
289
289
290 object_store.add_object(blob)
290 object_store.add_object(blob)
291 for tree in new_trees:
291 for tree in new_trees:
292 object_store.add_object(tree)
292 object_store.add_object(tree)
293
293
294 for node_path in removed:
294 for node_path in removed:
295 paths = node_path.split('/')
295 paths = node_path.split('/')
296 tree = commit_tree
296 tree = commit_tree
297 trees = [tree]
297 trees = [tree]
298 # Traverse deep into the forest...
298 # Traverse deep into the forest...
299 for path in paths:
299 for path in paths:
300 try:
300 try:
301 obj = repo[tree[path][1]]
301 obj = repo[tree[path][1]]
302 if isinstance(obj, objects.Tree):
302 if isinstance(obj, objects.Tree):
303 trees.append(obj)
303 trees.append(obj)
304 tree = obj
304 tree = obj
305 except KeyError:
305 except KeyError:
306 break
306 break
307 # Cut down the blob and all rotten trees on the way back...
307 # Cut down the blob and all rotten trees on the way back...
308 for path, tree in reversed(zip(paths, trees)):
308 for path, tree in reversed(zip(paths, trees)):
309 del tree[path]
309 del tree[path]
310 if tree:
310 if tree:
311 # This tree still has elements - don't remove it or any
311 # This tree still has elements - don't remove it or any
312 # of it's parents
312 # of it's parents
313 break
313 break
314
314
315 object_store.add_object(commit_tree)
315 object_store.add_object(commit_tree)
316
316
317 # Create commit
317 # Create commit
318 commit = objects.Commit()
318 commit = objects.Commit()
319 commit.tree = commit_tree.id
319 commit.tree = commit_tree.id
320 for k, v in commit_data.iteritems():
320 for k, v in commit_data.iteritems():
321 setattr(commit, k, v)
321 setattr(commit, k, v)
322 object_store.add_object(commit)
322 object_store.add_object(commit)
323
323
324 ref = 'refs/heads/%s' % branch
324 ref = 'refs/heads/%s' % branch
325 repo.refs[ref] = commit.id
325 repo.refs[ref] = commit.id
326
326
327 return commit.id
327 return commit.id
328
328
329 @reraise_safe_exceptions
329 @reraise_safe_exceptions
330 def fetch(self, wire, url, apply_refs=True, refs=None):
330 def fetch(self, wire, url, apply_refs=True, refs=None):
331 if url != 'default' and '://' not in url:
331 if url != 'default' and '://' not in url:
332 client = LocalGitClient(url)
332 client = LocalGitClient(url)
333 else:
333 else:
334 url_obj = url_parser(url)
334 url_obj = url_parser(url)
335 o = self._build_opener(url)
335 o = self._build_opener(url)
336 url, _ = url_obj.authinfo()
336 url, _ = url_obj.authinfo()
337 client = HttpGitClient(base_url=url, opener=o)
337 client = HttpGitClient(base_url=url, opener=o)
338 repo = self._factory.repo(wire)
338 repo = self._factory.repo(wire)
339
339
340 determine_wants = repo.object_store.determine_wants_all
340 determine_wants = repo.object_store.determine_wants_all
341 if refs:
341 if refs:
342 def determine_wants_requested(references):
342 def determine_wants_requested(references):
343 return [references[r] for r in references if r in refs]
343 return [references[r] for r in references if r in refs]
344 determine_wants = determine_wants_requested
344 determine_wants = determine_wants_requested
345
345
346 try:
346 try:
347 remote_refs = client.fetch(
347 remote_refs = client.fetch(
348 path=url, target=repo, determine_wants=determine_wants)
348 path=url, target=repo, determine_wants=determine_wants)
349 except NotGitRepository:
349 except NotGitRepository as e:
350 log.warning(
350 log.warning(
351 'Trying to fetch from "%s" failed, not a Git repository.', url)
351 'Trying to fetch from "%s" failed, not a Git repository.', url)
352 raise exceptions.AbortException()
352 # Exception can contain unicode which we convert
353 raise exceptions.AbortException(repr(e))
353
354
354 # mikhail: client.fetch() returns all the remote refs, but fetches only
355 # mikhail: client.fetch() returns all the remote refs, but fetches only
355 # refs filtered by `determine_wants` function. We need to filter result
356 # refs filtered by `determine_wants` function. We need to filter result
356 # as well
357 # as well
357 if refs:
358 if refs:
358 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
359 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
359
360
360 if apply_refs:
361 if apply_refs:
361 # TODO: johbo: Needs proper test coverage with a git repository
362 # TODO: johbo: Needs proper test coverage with a git repository
362 # that contains a tag object, so that we would end up with
363 # that contains a tag object, so that we would end up with
363 # a peeled ref at this point.
364 # a peeled ref at this point.
364 PEELED_REF_MARKER = '^{}'
365 PEELED_REF_MARKER = '^{}'
365 for k in remote_refs:
366 for k in remote_refs:
366 if k.endswith(PEELED_REF_MARKER):
367 if k.endswith(PEELED_REF_MARKER):
367 log.info("Skipping peeled reference %s", k)
368 log.info("Skipping peeled reference %s", k)
368 continue
369 continue
369 repo[k] = remote_refs[k]
370 repo[k] = remote_refs[k]
370
371
371 if refs:
372 if refs:
372 # mikhail: explicitly set the head to the last ref.
373 # mikhail: explicitly set the head to the last ref.
373 repo['HEAD'] = remote_refs[refs[-1]]
374 repo['HEAD'] = remote_refs[refs[-1]]
374
375
375 # TODO: mikhail: should we return remote_refs here to be
376 # TODO: mikhail: should we return remote_refs here to be
376 # consistent?
377 # consistent?
377 else:
378 else:
378 return remote_refs
379 return remote_refs
379
380
380 @reraise_safe_exceptions
381 @reraise_safe_exceptions
381 def get_remote_refs(self, wire, url):
382 def get_remote_refs(self, wire, url):
382 repo = Repo(url)
383 repo = Repo(url)
383 return repo.get_refs()
384 return repo.get_refs()
384
385
385 @reraise_safe_exceptions
386 @reraise_safe_exceptions
386 def get_description(self, wire):
387 def get_description(self, wire):
387 repo = self._factory.repo(wire)
388 repo = self._factory.repo(wire)
388 return repo.get_description()
389 return repo.get_description()
389
390
390 @reraise_safe_exceptions
391 @reraise_safe_exceptions
391 def get_file_history(self, wire, file_path, commit_id, limit):
392 def get_file_history(self, wire, file_path, commit_id, limit):
392 repo = self._factory.repo(wire)
393 repo = self._factory.repo(wire)
393 include = [commit_id]
394 include = [commit_id]
394 paths = [file_path]
395 paths = [file_path]
395
396
396 walker = repo.get_walker(include, paths=paths, max_entries=limit)
397 walker = repo.get_walker(include, paths=paths, max_entries=limit)
397 return [x.commit.id for x in walker]
398 return [x.commit.id for x in walker]
398
399
399 @reraise_safe_exceptions
400 @reraise_safe_exceptions
400 def get_missing_revs(self, wire, rev1, rev2, path2):
401 def get_missing_revs(self, wire, rev1, rev2, path2):
401 repo = self._factory.repo(wire)
402 repo = self._factory.repo(wire)
402 LocalGitClient(thin_packs=False).fetch(path2, repo)
403 LocalGitClient(thin_packs=False).fetch(path2, repo)
403
404
404 wire_remote = wire.copy()
405 wire_remote = wire.copy()
405 wire_remote['path'] = path2
406 wire_remote['path'] = path2
406 repo_remote = self._factory.repo(wire_remote)
407 repo_remote = self._factory.repo(wire_remote)
407 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
408 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
408
409
409 revs = [
410 revs = [
410 x.commit.id
411 x.commit.id
411 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
412 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
412 return revs
413 return revs
413
414
414 @reraise_safe_exceptions
415 @reraise_safe_exceptions
415 def get_object(self, wire, sha):
416 def get_object(self, wire, sha):
416 repo = self._factory.repo(wire)
417 repo = self._factory.repo(wire)
417 obj = repo.get_object(sha)
418 obj = repo.get_object(sha)
418 commit_id = obj.id
419 commit_id = obj.id
419
420
420 if isinstance(obj, Tag):
421 if isinstance(obj, Tag):
421 commit_id = obj.object[1]
422 commit_id = obj.object[1]
422
423
423 return {
424 return {
424 'id': obj.id,
425 'id': obj.id,
425 'type': obj.type_name,
426 'type': obj.type_name,
426 'commit_id': commit_id
427 'commit_id': commit_id
427 }
428 }
428
429
429 @reraise_safe_exceptions
430 @reraise_safe_exceptions
430 def get_object_attrs(self, wire, sha, *attrs):
431 def get_object_attrs(self, wire, sha, *attrs):
431 repo = self._factory.repo(wire)
432 repo = self._factory.repo(wire)
432 obj = repo.get_object(sha)
433 obj = repo.get_object(sha)
433 return list(getattr(obj, a) for a in attrs)
434 return list(getattr(obj, a) for a in attrs)
434
435
435 @reraise_safe_exceptions
436 @reraise_safe_exceptions
436 def get_refs(self, wire):
437 def get_refs(self, wire):
437 repo = self._factory.repo(wire)
438 repo = self._factory.repo(wire)
438 result = {}
439 result = {}
439 for ref, sha in repo.refs.as_dict().items():
440 for ref, sha in repo.refs.as_dict().items():
440 peeled_sha = repo.get_peeled(ref)
441 peeled_sha = repo.get_peeled(ref)
441 result[ref] = peeled_sha
442 result[ref] = peeled_sha
442 return result
443 return result
443
444
444 @reraise_safe_exceptions
445 @reraise_safe_exceptions
445 def get_refs_path(self, wire):
446 def get_refs_path(self, wire):
446 repo = self._factory.repo(wire)
447 repo = self._factory.repo(wire)
447 return repo.refs.path
448 return repo.refs.path
448
449
449 @reraise_safe_exceptions
450 @reraise_safe_exceptions
450 def head(self, wire):
451 def head(self, wire):
451 repo = self._factory.repo(wire)
452 repo = self._factory.repo(wire)
452 return repo.head()
453 return repo.head()
453
454
454 @reraise_safe_exceptions
455 @reraise_safe_exceptions
455 def init(self, wire):
456 def init(self, wire):
456 repo_path = str_to_dulwich(wire['path'])
457 repo_path = str_to_dulwich(wire['path'])
457 self.repo = Repo.init(repo_path)
458 self.repo = Repo.init(repo_path)
458
459
459 @reraise_safe_exceptions
460 @reraise_safe_exceptions
460 def init_bare(self, wire):
461 def init_bare(self, wire):
461 repo_path = str_to_dulwich(wire['path'])
462 repo_path = str_to_dulwich(wire['path'])
462 self.repo = Repo.init_bare(repo_path)
463 self.repo = Repo.init_bare(repo_path)
463
464
464 @reraise_safe_exceptions
465 @reraise_safe_exceptions
465 def revision(self, wire, rev):
466 def revision(self, wire, rev):
466 repo = self._factory.repo(wire)
467 repo = self._factory.repo(wire)
467 obj = repo[rev]
468 obj = repo[rev]
468 obj_data = {
469 obj_data = {
469 'id': obj.id,
470 'id': obj.id,
470 }
471 }
471 try:
472 try:
472 obj_data['tree'] = obj.tree
473 obj_data['tree'] = obj.tree
473 except AttributeError:
474 except AttributeError:
474 pass
475 pass
475 return obj_data
476 return obj_data
476
477
477 @reraise_safe_exceptions
478 @reraise_safe_exceptions
478 def commit_attribute(self, wire, rev, attr):
479 def commit_attribute(self, wire, rev, attr):
479 repo = self._factory.repo(wire)
480 repo = self._factory.repo(wire)
480 obj = repo[rev]
481 obj = repo[rev]
481 return getattr(obj, attr)
482 return getattr(obj, attr)
482
483
483 @reraise_safe_exceptions
484 @reraise_safe_exceptions
484 def set_refs(self, wire, key, value):
485 def set_refs(self, wire, key, value):
485 repo = self._factory.repo(wire)
486 repo = self._factory.repo(wire)
486 repo.refs[key] = value
487 repo.refs[key] = value
487
488
488 @reraise_safe_exceptions
489 @reraise_safe_exceptions
489 def remove_ref(self, wire, key):
490 def remove_ref(self, wire, key):
490 repo = self._factory.repo(wire)
491 repo = self._factory.repo(wire)
491 del repo.refs[key]
492 del repo.refs[key]
492
493
493 @reraise_safe_exceptions
494 @reraise_safe_exceptions
494 def tree_changes(self, wire, source_id, target_id):
495 def tree_changes(self, wire, source_id, target_id):
495 repo = self._factory.repo(wire)
496 repo = self._factory.repo(wire)
496 source = repo[source_id].tree if source_id else None
497 source = repo[source_id].tree if source_id else None
497 target = repo[target_id].tree
498 target = repo[target_id].tree
498 result = repo.object_store.tree_changes(source, target)
499 result = repo.object_store.tree_changes(source, target)
499 return list(result)
500 return list(result)
500
501
501 @reraise_safe_exceptions
502 @reraise_safe_exceptions
502 def tree_items(self, wire, tree_id):
503 def tree_items(self, wire, tree_id):
503 repo = self._factory.repo(wire)
504 repo = self._factory.repo(wire)
504 tree = repo[tree_id]
505 tree = repo[tree_id]
505
506
506 result = []
507 result = []
507 for item in tree.iteritems():
508 for item in tree.iteritems():
508 item_sha = item.sha
509 item_sha = item.sha
509 item_mode = item.mode
510 item_mode = item.mode
510
511
511 if FILE_MODE(item_mode) == GIT_LINK:
512 if FILE_MODE(item_mode) == GIT_LINK:
512 item_type = "link"
513 item_type = "link"
513 else:
514 else:
514 item_type = repo[item_sha].type_name
515 item_type = repo[item_sha].type_name
515
516
516 result.append((item.path, item_mode, item_sha, item_type))
517 result.append((item.path, item_mode, item_sha, item_type))
517 return result
518 return result
518
519
519 @reraise_safe_exceptions
520 @reraise_safe_exceptions
520 def update_server_info(self, wire):
521 def update_server_info(self, wire):
521 repo = self._factory.repo(wire)
522 repo = self._factory.repo(wire)
522 update_server_info(repo)
523 update_server_info(repo)
523
524
524 @reraise_safe_exceptions
525 @reraise_safe_exceptions
525 def discover_git_version(self):
526 def discover_git_version(self):
526 stdout, _ = self.run_git_command(
527 stdout, _ = self.run_git_command(
527 {}, ['--version'], _bare=True, _safe=True)
528 {}, ['--version'], _bare=True, _safe=True)
528 prefix = 'git version'
529 prefix = 'git version'
529 if stdout.startswith(prefix):
530 if stdout.startswith(prefix):
530 stdout = stdout[len(prefix):]
531 stdout = stdout[len(prefix):]
531 return stdout.strip()
532 return stdout.strip()
532
533
533 @reraise_safe_exceptions
534 @reraise_safe_exceptions
534 def run_git_command(self, wire, cmd, **opts):
535 def run_git_command(self, wire, cmd, **opts):
535 path = wire.get('path', None)
536 path = wire.get('path', None)
536
537
537 if path and os.path.isdir(path):
538 if path and os.path.isdir(path):
538 opts['cwd'] = path
539 opts['cwd'] = path
539
540
540 if '_bare' in opts:
541 if '_bare' in opts:
541 _copts = []
542 _copts = []
542 del opts['_bare']
543 del opts['_bare']
543 else:
544 else:
544 _copts = ['-c', 'core.quotepath=false', ]
545 _copts = ['-c', 'core.quotepath=false', ]
545 safe_call = False
546 safe_call = False
546 if '_safe' in opts:
547 if '_safe' in opts:
547 # no exc on failure
548 # no exc on failure
548 del opts['_safe']
549 del opts['_safe']
549 safe_call = True
550 safe_call = True
550
551
551 gitenv = os.environ.copy()
552 gitenv = os.environ.copy()
552 gitenv.update(opts.pop('extra_env', {}))
553 gitenv.update(opts.pop('extra_env', {}))
553 # need to clean fix GIT_DIR !
554 # need to clean fix GIT_DIR !
554 if 'GIT_DIR' in gitenv:
555 if 'GIT_DIR' in gitenv:
555 del gitenv['GIT_DIR']
556 del gitenv['GIT_DIR']
556 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
557 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
557
558
558 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
559 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
559
560
560 try:
561 try:
561 _opts = {'env': gitenv, 'shell': False}
562 _opts = {'env': gitenv, 'shell': False}
562 _opts.update(opts)
563 _opts.update(opts)
563 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
564 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
564
565
565 return ''.join(p), ''.join(p.error)
566 return ''.join(p), ''.join(p.error)
566 except (EnvironmentError, OSError) as err:
567 except (EnvironmentError, OSError) as err:
567 tb_err = ("Couldn't run git command (%s).\n"
568 tb_err = ("Couldn't run git command (%s).\n"
568 "Original error was:%s\n" % (cmd, err))
569 "Original error was:%s\n" % (cmd, err))
569 log.exception(tb_err)
570 log.exception(tb_err)
570 if safe_call:
571 if safe_call:
571 return '', err
572 return '', err
572 else:
573 else:
573 raise exceptions.VcsException(tb_err)
574 raise exceptions.VcsException(tb_err)
574
575
575
576
576 def str_to_dulwich(value):
577 def str_to_dulwich(value):
577 """
578 """
578 Dulwich 0.10.1a requires `unicode` objects to be passed in.
579 Dulwich 0.10.1a requires `unicode` objects to be passed in.
579 """
580 """
580 return value.decode(settings.WIRE_ENCODING)
581 return value.decode(settings.WIRE_ENCODING)
@@ -1,724 +1,725 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 io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import sys
21 import sys
22 import urllib
22 import urllib
23 import urllib2
23 import urllib2
24
24
25 from hgext import largefiles, rebase
25 from hgext import largefiles, rebase
26 from hgext.strip import strip as hgext_strip
26 from hgext.strip import strip as hgext_strip
27 from mercurial import commands
27 from mercurial import commands
28 from mercurial import unionrepo
28 from mercurial import unionrepo
29
29
30 from vcsserver import exceptions
30 from vcsserver import exceptions
31 from vcsserver.base import RepoFactory, obfuscate_qs
31 from vcsserver.base import RepoFactory, obfuscate_qs
32 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex,
33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
37 InterventionRequired, RequirementError)
37 InterventionRequired, RequirementError)
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 def make_ui_from_config(repo_config):
42 def make_ui_from_config(repo_config):
43 baseui = ui.ui()
43 baseui = ui.ui()
44
44
45 # clean the baseui object
45 # clean the baseui object
46 baseui._ocfg = hgconfig.config()
46 baseui._ocfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
49
49
50 for section, option, value in repo_config:
50 for section, option, value in repo_config:
51 baseui.setconfig(section, option, value)
51 baseui.setconfig(section, option, value)
52
52
53 # make our hgweb quiet so it doesn't print output
53 # make our hgweb quiet so it doesn't print output
54 baseui.setconfig('ui', 'quiet', 'true')
54 baseui.setconfig('ui', 'quiet', 'true')
55
55
56 # force mercurial to only use 1 thread, otherwise it may try to set a
56 # force mercurial to only use 1 thread, otherwise it may try to set a
57 # signal in a non-main thread, thus generating a ValueError.
57 # signal in a non-main thread, thus generating a ValueError.
58 baseui.setconfig('worker', 'numcpus', 1)
58 baseui.setconfig('worker', 'numcpus', 1)
59
59
60 # If there is no config for the largefiles extension, we explicitly disable
60 # If there is no config for the largefiles extension, we explicitly disable
61 # it here. This overrides settings from repositories hgrc file. Recent
61 # it here. This overrides settings from repositories hgrc file. Recent
62 # mercurial versions enable largefiles in hgrc on clone from largefile
62 # mercurial versions enable largefiles in hgrc on clone from largefile
63 # repo.
63 # repo.
64 if not baseui.hasconfig('extensions', 'largefiles'):
64 if not baseui.hasconfig('extensions', 'largefiles'):
65 log.debug('Explicitly disable largefiles extension for repo.')
65 log.debug('Explicitly disable largefiles extension for repo.')
66 baseui.setconfig('extensions', 'largefiles', '!')
66 baseui.setconfig('extensions', 'largefiles', '!')
67
67
68 return baseui
68 return baseui
69
69
70
70
71 def reraise_safe_exceptions(func):
71 def reraise_safe_exceptions(func):
72 """Decorator for converting mercurial exceptions to something neutral."""
72 """Decorator for converting mercurial exceptions to something neutral."""
73 def wrapper(*args, **kwargs):
73 def wrapper(*args, **kwargs):
74 try:
74 try:
75 return func(*args, **kwargs)
75 return func(*args, **kwargs)
76 except (Abort, InterventionRequired):
76 except (Abort, InterventionRequired):
77 raise_from_original(exceptions.AbortException)
77 raise_from_original(exceptions.AbortException)
78 except RepoLookupError:
78 except RepoLookupError:
79 raise_from_original(exceptions.LookupException)
79 raise_from_original(exceptions.LookupException)
80 except RequirementError:
80 except RequirementError:
81 raise_from_original(exceptions.RequirementException)
81 raise_from_original(exceptions.RequirementException)
82 except RepoError:
82 except RepoError:
83 raise_from_original(exceptions.VcsException)
83 raise_from_original(exceptions.VcsException)
84 except LookupError:
84 except LookupError:
85 raise_from_original(exceptions.LookupException)
85 raise_from_original(exceptions.LookupException)
86 except Exception as e:
86 except Exception as e:
87 if not hasattr(e, '_vcs_kind'):
87 if not hasattr(e, '_vcs_kind'):
88 log.exception("Unhandled exception in hg remote call")
88 log.exception("Unhandled exception in hg remote call")
89 raise_from_original(exceptions.UnhandledException)
89 raise_from_original(exceptions.UnhandledException)
90 raise
90 raise
91 return wrapper
91 return wrapper
92
92
93
93
94 def raise_from_original(new_type):
94 def raise_from_original(new_type):
95 """
95 """
96 Raise a new exception type with original args and traceback.
96 Raise a new exception type with original args and traceback.
97 """
97 """
98 _, original, traceback = sys.exc_info()
98 _, original, traceback = sys.exc_info()
99 try:
99 try:
100 raise new_type(*original.args), None, traceback
100 raise new_type(*original.args), None, traceback
101 finally:
101 finally:
102 del traceback
102 del traceback
103
103
104
104
105 class MercurialFactory(RepoFactory):
105 class MercurialFactory(RepoFactory):
106
106
107 def _create_config(self, config, hooks=True):
107 def _create_config(self, config, hooks=True):
108 if not hooks:
108 if not hooks:
109 hooks_to_clean = frozenset((
109 hooks_to_clean = frozenset((
110 'changegroup.repo_size', 'preoutgoing.pre_pull',
110 'changegroup.repo_size', 'preoutgoing.pre_pull',
111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
112 new_config = []
112 new_config = []
113 for section, option, value in config:
113 for section, option, value in config:
114 if section == 'hooks' and option in hooks_to_clean:
114 if section == 'hooks' and option in hooks_to_clean:
115 continue
115 continue
116 new_config.append((section, option, value))
116 new_config.append((section, option, value))
117 config = new_config
117 config = new_config
118
118
119 baseui = make_ui_from_config(config)
119 baseui = make_ui_from_config(config)
120 return baseui
120 return baseui
121
121
122 def _create_repo(self, wire, create):
122 def _create_repo(self, wire, create):
123 baseui = self._create_config(wire["config"])
123 baseui = self._create_config(wire["config"])
124 return localrepository(baseui, wire["path"], create)
124 return localrepository(baseui, wire["path"], create)
125
125
126
126
127 class HgRemote(object):
127 class HgRemote(object):
128
128
129 def __init__(self, factory):
129 def __init__(self, factory):
130 self._factory = factory
130 self._factory = factory
131
131
132 self._bulk_methods = {
132 self._bulk_methods = {
133 "affected_files": self.ctx_files,
133 "affected_files": self.ctx_files,
134 "author": self.ctx_user,
134 "author": self.ctx_user,
135 "branch": self.ctx_branch,
135 "branch": self.ctx_branch,
136 "children": self.ctx_children,
136 "children": self.ctx_children,
137 "date": self.ctx_date,
137 "date": self.ctx_date,
138 "message": self.ctx_description,
138 "message": self.ctx_description,
139 "parents": self.ctx_parents,
139 "parents": self.ctx_parents,
140 "status": self.ctx_status,
140 "status": self.ctx_status,
141 "_file_paths": self.ctx_list,
141 "_file_paths": self.ctx_list,
142 }
142 }
143
143
144 @reraise_safe_exceptions
144 @reraise_safe_exceptions
145 def discover_hg_version(self):
145 def discover_hg_version(self):
146 from mercurial import util
146 from mercurial import util
147 return util.version()
147 return util.version()
148
148
149 @reraise_safe_exceptions
149 @reraise_safe_exceptions
150 def archive_repo(self, archive_path, mtime, file_info, kind):
150 def archive_repo(self, archive_path, mtime, file_info, kind):
151 if kind == "tgz":
151 if kind == "tgz":
152 archiver = archival.tarit(archive_path, mtime, "gz")
152 archiver = archival.tarit(archive_path, mtime, "gz")
153 elif kind == "tbz2":
153 elif kind == "tbz2":
154 archiver = archival.tarit(archive_path, mtime, "bz2")
154 archiver = archival.tarit(archive_path, mtime, "bz2")
155 elif kind == 'zip':
155 elif kind == 'zip':
156 archiver = archival.zipit(archive_path, mtime)
156 archiver = archival.zipit(archive_path, mtime)
157 else:
157 else:
158 raise exceptions.ArchiveException(
158 raise exceptions.ArchiveException(
159 'Remote does not support: "%s".' % kind)
159 'Remote does not support: "%s".' % kind)
160
160
161 for f_path, f_mode, f_is_link, f_content in file_info:
161 for f_path, f_mode, f_is_link, f_content in file_info:
162 archiver.addfile(f_path, f_mode, f_is_link, f_content)
162 archiver.addfile(f_path, f_mode, f_is_link, f_content)
163 archiver.done()
163 archiver.done()
164
164
165 @reraise_safe_exceptions
165 @reraise_safe_exceptions
166 def bookmarks(self, wire):
166 def bookmarks(self, wire):
167 repo = self._factory.repo(wire)
167 repo = self._factory.repo(wire)
168 return dict(repo._bookmarks)
168 return dict(repo._bookmarks)
169
169
170 @reraise_safe_exceptions
170 @reraise_safe_exceptions
171 def branches(self, wire, normal, closed):
171 def branches(self, wire, normal, closed):
172 repo = self._factory.repo(wire)
172 repo = self._factory.repo(wire)
173 iter_branches = repo.branchmap().iterbranches()
173 iter_branches = repo.branchmap().iterbranches()
174 bt = {}
174 bt = {}
175 for branch_name, _heads, tip, is_closed in iter_branches:
175 for branch_name, _heads, tip, is_closed in iter_branches:
176 if normal and not is_closed:
176 if normal and not is_closed:
177 bt[branch_name] = tip
177 bt[branch_name] = tip
178 if closed and is_closed:
178 if closed and is_closed:
179 bt[branch_name] = tip
179 bt[branch_name] = tip
180
180
181 return bt
181 return bt
182
182
183 @reraise_safe_exceptions
183 @reraise_safe_exceptions
184 def bulk_request(self, wire, rev, pre_load):
184 def bulk_request(self, wire, rev, pre_load):
185 result = {}
185 result = {}
186 for attr in pre_load:
186 for attr in pre_load:
187 try:
187 try:
188 method = self._bulk_methods[attr]
188 method = self._bulk_methods[attr]
189 result[attr] = method(wire, rev)
189 result[attr] = method(wire, rev)
190 except KeyError:
190 except KeyError:
191 raise exceptions.VcsException(
191 raise exceptions.VcsException(
192 'Unknown bulk attribute: "%s"' % attr)
192 'Unknown bulk attribute: "%s"' % attr)
193 return result
193 return result
194
194
195 @reraise_safe_exceptions
195 @reraise_safe_exceptions
196 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
196 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
197 baseui = self._factory._create_config(wire["config"], hooks=hooks)
197 baseui = self._factory._create_config(wire["config"], hooks=hooks)
198 clone(baseui, source, dest, noupdate=not update_after_clone)
198 clone(baseui, source, dest, noupdate=not update_after_clone)
199
199
200 @reraise_safe_exceptions
200 @reraise_safe_exceptions
201 def commitctx(
201 def commitctx(
202 self, wire, message, parents, commit_time, commit_timezone,
202 self, wire, message, parents, commit_time, commit_timezone,
203 user, files, extra, removed, updated):
203 user, files, extra, removed, updated):
204
204
205 def _filectxfn(_repo, memctx, path):
205 def _filectxfn(_repo, memctx, path):
206 """
206 """
207 Marks given path as added/changed/removed in a given _repo. This is
207 Marks given path as added/changed/removed in a given _repo. This is
208 for internal mercurial commit function.
208 for internal mercurial commit function.
209 """
209 """
210
210
211 # check if this path is removed
211 # check if this path is removed
212 if path in removed:
212 if path in removed:
213 # returning None is a way to mark node for removal
213 # returning None is a way to mark node for removal
214 return None
214 return None
215
215
216 # check if this path is added
216 # check if this path is added
217 for node in updated:
217 for node in updated:
218 if node['path'] == path:
218 if node['path'] == path:
219 return memfilectx(
219 return memfilectx(
220 _repo,
220 _repo,
221 path=node['path'],
221 path=node['path'],
222 data=node['content'],
222 data=node['content'],
223 islink=False,
223 islink=False,
224 isexec=bool(node['mode'] & stat.S_IXUSR),
224 isexec=bool(node['mode'] & stat.S_IXUSR),
225 copied=False,
225 copied=False,
226 memctx=memctx)
226 memctx=memctx)
227
227
228 raise exceptions.AbortException(
228 raise exceptions.AbortException(
229 "Given path haven't been marked as added, "
229 "Given path haven't been marked as added, "
230 "changed or removed (%s)" % path)
230 "changed or removed (%s)" % path)
231
231
232 repo = self._factory.repo(wire)
232 repo = self._factory.repo(wire)
233
233
234 commit_ctx = memctx(
234 commit_ctx = memctx(
235 repo=repo,
235 repo=repo,
236 parents=parents,
236 parents=parents,
237 text=message,
237 text=message,
238 files=files,
238 files=files,
239 filectxfn=_filectxfn,
239 filectxfn=_filectxfn,
240 user=user,
240 user=user,
241 date=(commit_time, commit_timezone),
241 date=(commit_time, commit_timezone),
242 extra=extra)
242 extra=extra)
243
243
244 n = repo.commitctx(commit_ctx)
244 n = repo.commitctx(commit_ctx)
245 new_id = hex(n)
245 new_id = hex(n)
246
246
247 return new_id
247 return new_id
248
248
249 @reraise_safe_exceptions
249 @reraise_safe_exceptions
250 def ctx_branch(self, wire, revision):
250 def ctx_branch(self, wire, revision):
251 repo = self._factory.repo(wire)
251 repo = self._factory.repo(wire)
252 ctx = repo[revision]
252 ctx = repo[revision]
253 return ctx.branch()
253 return ctx.branch()
254
254
255 @reraise_safe_exceptions
255 @reraise_safe_exceptions
256 def ctx_children(self, wire, revision):
256 def ctx_children(self, wire, revision):
257 repo = self._factory.repo(wire)
257 repo = self._factory.repo(wire)
258 ctx = repo[revision]
258 ctx = repo[revision]
259 return [child.rev() for child in ctx.children()]
259 return [child.rev() for child in ctx.children()]
260
260
261 @reraise_safe_exceptions
261 @reraise_safe_exceptions
262 def ctx_date(self, wire, revision):
262 def ctx_date(self, wire, revision):
263 repo = self._factory.repo(wire)
263 repo = self._factory.repo(wire)
264 ctx = repo[revision]
264 ctx = repo[revision]
265 return ctx.date()
265 return ctx.date()
266
266
267 @reraise_safe_exceptions
267 @reraise_safe_exceptions
268 def ctx_description(self, wire, revision):
268 def ctx_description(self, wire, revision):
269 repo = self._factory.repo(wire)
269 repo = self._factory.repo(wire)
270 ctx = repo[revision]
270 ctx = repo[revision]
271 return ctx.description()
271 return ctx.description()
272
272
273 @reraise_safe_exceptions
273 @reraise_safe_exceptions
274 def ctx_diff(
274 def ctx_diff(
275 self, wire, revision, git=True, ignore_whitespace=True, context=3):
275 self, wire, revision, git=True, ignore_whitespace=True, context=3):
276 repo = self._factory.repo(wire)
276 repo = self._factory.repo(wire)
277 ctx = repo[revision]
277 ctx = repo[revision]
278 result = ctx.diff(
278 result = ctx.diff(
279 git=git, ignore_whitespace=ignore_whitespace, context=context)
279 git=git, ignore_whitespace=ignore_whitespace, context=context)
280 return list(result)
280 return list(result)
281
281
282 @reraise_safe_exceptions
282 @reraise_safe_exceptions
283 def ctx_files(self, wire, revision):
283 def ctx_files(self, wire, revision):
284 repo = self._factory.repo(wire)
284 repo = self._factory.repo(wire)
285 ctx = repo[revision]
285 ctx = repo[revision]
286 return ctx.files()
286 return ctx.files()
287
287
288 @reraise_safe_exceptions
288 @reraise_safe_exceptions
289 def ctx_list(self, path, revision):
289 def ctx_list(self, path, revision):
290 repo = self._factory.repo(path)
290 repo = self._factory.repo(path)
291 ctx = repo[revision]
291 ctx = repo[revision]
292 return list(ctx)
292 return list(ctx)
293
293
294 @reraise_safe_exceptions
294 @reraise_safe_exceptions
295 def ctx_parents(self, wire, revision):
295 def ctx_parents(self, wire, revision):
296 repo = self._factory.repo(wire)
296 repo = self._factory.repo(wire)
297 ctx = repo[revision]
297 ctx = repo[revision]
298 return [parent.rev() for parent in ctx.parents()]
298 return [parent.rev() for parent in ctx.parents()]
299
299
300 @reraise_safe_exceptions
300 @reraise_safe_exceptions
301 def ctx_substate(self, wire, revision):
301 def ctx_substate(self, wire, revision):
302 repo = self._factory.repo(wire)
302 repo = self._factory.repo(wire)
303 ctx = repo[revision]
303 ctx = repo[revision]
304 return ctx.substate
304 return ctx.substate
305
305
306 @reraise_safe_exceptions
306 @reraise_safe_exceptions
307 def ctx_status(self, wire, revision):
307 def ctx_status(self, wire, revision):
308 repo = self._factory.repo(wire)
308 repo = self._factory.repo(wire)
309 ctx = repo[revision]
309 ctx = repo[revision]
310 status = repo[ctx.p1().node()].status(other=ctx.node())
310 status = repo[ctx.p1().node()].status(other=ctx.node())
311 # object of status (odd, custom named tuple in mercurial) is not
311 # object of status (odd, custom named tuple in mercurial) is not
312 # correctly serializable via Pyro, we make it a list, as the underling
312 # correctly serializable via Pyro, we make it a list, as the underling
313 # API expects this to be a list
313 # API expects this to be a list
314 return list(status)
314 return list(status)
315
315
316 @reraise_safe_exceptions
316 @reraise_safe_exceptions
317 def ctx_user(self, wire, revision):
317 def ctx_user(self, wire, revision):
318 repo = self._factory.repo(wire)
318 repo = self._factory.repo(wire)
319 ctx = repo[revision]
319 ctx = repo[revision]
320 return ctx.user()
320 return ctx.user()
321
321
322 @reraise_safe_exceptions
322 @reraise_safe_exceptions
323 def check_url(self, url, config):
323 def check_url(self, url, config):
324 _proto = None
324 _proto = None
325 if '+' in url[:url.find('://')]:
325 if '+' in url[:url.find('://')]:
326 _proto = url[0:url.find('+')]
326 _proto = url[0:url.find('+')]
327 url = url[url.find('+') + 1:]
327 url = url[url.find('+') + 1:]
328 handlers = []
328 handlers = []
329 url_obj = url_parser(url)
329 url_obj = url_parser(url)
330 test_uri, authinfo = url_obj.authinfo()
330 test_uri, authinfo = url_obj.authinfo()
331 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
331 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
332 url_obj.query = obfuscate_qs(url_obj.query)
332 url_obj.query = obfuscate_qs(url_obj.query)
333
333
334 cleaned_uri = str(url_obj)
334 cleaned_uri = str(url_obj)
335 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
335 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
336
336
337 if authinfo:
337 if authinfo:
338 # create a password manager
338 # create a password manager
339 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
339 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
340 passmgr.add_password(*authinfo)
340 passmgr.add_password(*authinfo)
341
341
342 handlers.extend((httpbasicauthhandler(passmgr),
342 handlers.extend((httpbasicauthhandler(passmgr),
343 httpdigestauthhandler(passmgr)))
343 httpdigestauthhandler(passmgr)))
344
344
345 o = urllib2.build_opener(*handlers)
345 o = urllib2.build_opener(*handlers)
346 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
346 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
347 ('Accept', 'application/mercurial-0.1')]
347 ('Accept', 'application/mercurial-0.1')]
348
348
349 q = {"cmd": 'between'}
349 q = {"cmd": 'between'}
350 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
350 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
351 qs = '?%s' % urllib.urlencode(q)
351 qs = '?%s' % urllib.urlencode(q)
352 cu = "%s%s" % (test_uri, qs)
352 cu = "%s%s" % (test_uri, qs)
353 req = urllib2.Request(cu, None, {})
353 req = urllib2.Request(cu, None, {})
354
354
355 try:
355 try:
356 log.debug("Trying to open URL %s", cleaned_uri)
356 log.debug("Trying to open URL %s", cleaned_uri)
357 resp = o.open(req)
357 resp = o.open(req)
358 if resp.code != 200:
358 if resp.code != 200:
359 raise exceptions.URLError('Return Code is not 200')
359 raise exceptions.URLError('Return Code is not 200')
360 except Exception as e:
360 except Exception as e:
361 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
361 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
362 # means it cannot be cloned
362 # means it cannot be cloned
363 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
363 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
364
364
365 # now check if it's a proper hg repo, but don't do it for svn
365 # now check if it's a proper hg repo, but don't do it for svn
366 try:
366 try:
367 if _proto == 'svn':
367 if _proto == 'svn':
368 pass
368 pass
369 else:
369 else:
370 # check for pure hg repos
370 # check for pure hg repos
371 log.debug(
371 log.debug(
372 "Verifying if URL is a Mercurial repository: %s",
372 "Verifying if URL is a Mercurial repository: %s",
373 cleaned_uri)
373 cleaned_uri)
374 httppeer(make_ui_from_config(config), url).lookup('tip')
374 httppeer(make_ui_from_config(config), url).lookup('tip')
375 except Exception as e:
375 except Exception as e:
376 log.warning("URL is not a valid Mercurial repository: %s",
376 log.warning("URL is not a valid Mercurial repository: %s",
377 cleaned_uri)
377 cleaned_uri)
378 raise exceptions.URLError(
378 raise exceptions.URLError(
379 "url [%s] does not look like an hg repo org_exc: %s"
379 "url [%s] does not look like an hg repo org_exc: %s"
380 % (cleaned_uri, e))
380 % (cleaned_uri, e))
381
381
382 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
382 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
383 return True
383 return True
384
384
385 @reraise_safe_exceptions
385 @reraise_safe_exceptions
386 def diff(
386 def diff(
387 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
387 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
388 context):
388 context):
389 repo = self._factory.repo(wire)
389 repo = self._factory.repo(wire)
390
390
391 if file_filter:
391 if file_filter:
392 match_filter = match(file_filter[0], '', [file_filter[1]])
392 match_filter = match(file_filter[0], '', [file_filter[1]])
393 else:
393 else:
394 match_filter = file_filter
394 match_filter = file_filter
395 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
395 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
396
396
397 try:
397 try:
398 return "".join(patch.diff(
398 return "".join(patch.diff(
399 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
399 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
400 except RepoLookupError:
400 except RepoLookupError:
401 raise exceptions.LookupException()
401 raise exceptions.LookupException()
402
402
403 @reraise_safe_exceptions
403 @reraise_safe_exceptions
404 def file_history(self, wire, revision, path, limit):
404 def file_history(self, wire, revision, path, limit):
405 repo = self._factory.repo(wire)
405 repo = self._factory.repo(wire)
406
406
407 ctx = repo[revision]
407 ctx = repo[revision]
408 fctx = ctx.filectx(path)
408 fctx = ctx.filectx(path)
409
409
410 def history_iter():
410 def history_iter():
411 limit_rev = fctx.rev()
411 limit_rev = fctx.rev()
412 for obj in reversed(list(fctx.filelog())):
412 for obj in reversed(list(fctx.filelog())):
413 obj = fctx.filectx(obj)
413 obj = fctx.filectx(obj)
414 if limit_rev >= obj.rev():
414 if limit_rev >= obj.rev():
415 yield obj
415 yield obj
416
416
417 history = []
417 history = []
418 for cnt, obj in enumerate(history_iter()):
418 for cnt, obj in enumerate(history_iter()):
419 if limit and cnt >= limit:
419 if limit and cnt >= limit:
420 break
420 break
421 history.append(hex(obj.node()))
421 history.append(hex(obj.node()))
422
422
423 return [x for x in history]
423 return [x for x in history]
424
424
425 @reraise_safe_exceptions
425 @reraise_safe_exceptions
426 def file_history_untill(self, wire, revision, path, limit):
426 def file_history_untill(self, wire, revision, path, limit):
427 repo = self._factory.repo(wire)
427 repo = self._factory.repo(wire)
428 ctx = repo[revision]
428 ctx = repo[revision]
429 fctx = ctx.filectx(path)
429 fctx = ctx.filectx(path)
430
430
431 file_log = list(fctx.filelog())
431 file_log = list(fctx.filelog())
432 if limit:
432 if limit:
433 # Limit to the last n items
433 # Limit to the last n items
434 file_log = file_log[-limit:]
434 file_log = file_log[-limit:]
435
435
436 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
436 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
437
437
438 @reraise_safe_exceptions
438 @reraise_safe_exceptions
439 def fctx_annotate(self, wire, revision, path):
439 def fctx_annotate(self, wire, revision, path):
440 repo = self._factory.repo(wire)
440 repo = self._factory.repo(wire)
441 ctx = repo[revision]
441 ctx = repo[revision]
442 fctx = ctx.filectx(path)
442 fctx = ctx.filectx(path)
443
443
444 result = []
444 result = []
445 for i, annotate_data in enumerate(fctx.annotate()):
445 for i, annotate_data in enumerate(fctx.annotate()):
446 ln_no = i + 1
446 ln_no = i + 1
447 node_info, content = annotate_data
447 node_info, content = annotate_data
448 sha = hex(node_info[0].node())
448 sha = hex(node_info[0].node())
449 result.append((ln_no, sha, content))
449 result.append((ln_no, sha, content))
450 return result
450 return result
451
451
452 @reraise_safe_exceptions
452 @reraise_safe_exceptions
453 def fctx_data(self, wire, revision, path):
453 def fctx_data(self, wire, revision, path):
454 repo = self._factory.repo(wire)
454 repo = self._factory.repo(wire)
455 ctx = repo[revision]
455 ctx = repo[revision]
456 fctx = ctx.filectx(path)
456 fctx = ctx.filectx(path)
457 return fctx.data()
457 return fctx.data()
458
458
459 @reraise_safe_exceptions
459 @reraise_safe_exceptions
460 def fctx_flags(self, wire, revision, path):
460 def fctx_flags(self, wire, revision, path):
461 repo = self._factory.repo(wire)
461 repo = self._factory.repo(wire)
462 ctx = repo[revision]
462 ctx = repo[revision]
463 fctx = ctx.filectx(path)
463 fctx = ctx.filectx(path)
464 return fctx.flags()
464 return fctx.flags()
465
465
466 @reraise_safe_exceptions
466 @reraise_safe_exceptions
467 def fctx_size(self, wire, revision, path):
467 def fctx_size(self, wire, revision, path):
468 repo = self._factory.repo(wire)
468 repo = self._factory.repo(wire)
469 ctx = repo[revision]
469 ctx = repo[revision]
470 fctx = ctx.filectx(path)
470 fctx = ctx.filectx(path)
471 return fctx.size()
471 return fctx.size()
472
472
473 @reraise_safe_exceptions
473 @reraise_safe_exceptions
474 def get_all_commit_ids(self, wire, name):
474 def get_all_commit_ids(self, wire, name):
475 repo = self._factory.repo(wire)
475 repo = self._factory.repo(wire)
476 revs = repo.filtered(name).changelog.index
476 revs = repo.filtered(name).changelog.index
477 return map(lambda x: hex(x[7]), revs)[:-1]
477 return map(lambda x: hex(x[7]), revs)[:-1]
478
478
479 @reraise_safe_exceptions
479 @reraise_safe_exceptions
480 def get_config_value(self, wire, section, name, untrusted=False):
480 def get_config_value(self, wire, section, name, untrusted=False):
481 repo = self._factory.repo(wire)
481 repo = self._factory.repo(wire)
482 return repo.ui.config(section, name, untrusted=untrusted)
482 return repo.ui.config(section, name, untrusted=untrusted)
483
483
484 @reraise_safe_exceptions
484 @reraise_safe_exceptions
485 def get_config_bool(self, wire, section, name, untrusted=False):
485 def get_config_bool(self, wire, section, name, untrusted=False):
486 repo = self._factory.repo(wire)
486 repo = self._factory.repo(wire)
487 return repo.ui.configbool(section, name, untrusted=untrusted)
487 return repo.ui.configbool(section, name, untrusted=untrusted)
488
488
489 @reraise_safe_exceptions
489 @reraise_safe_exceptions
490 def get_config_list(self, wire, section, name, untrusted=False):
490 def get_config_list(self, wire, section, name, untrusted=False):
491 repo = self._factory.repo(wire)
491 repo = self._factory.repo(wire)
492 return repo.ui.configlist(section, name, untrusted=untrusted)
492 return repo.ui.configlist(section, name, untrusted=untrusted)
493
493
494 @reraise_safe_exceptions
494 @reraise_safe_exceptions
495 def is_large_file(self, wire, path):
495 def is_large_file(self, wire, path):
496 return largefiles.lfutil.isstandin(path)
496 return largefiles.lfutil.isstandin(path)
497
497
498 @reraise_safe_exceptions
498 @reraise_safe_exceptions
499 def in_store(self, wire, sha):
499 def in_store(self, wire, sha):
500 repo = self._factory.repo(wire)
500 repo = self._factory.repo(wire)
501 return largefiles.lfutil.instore(repo, sha)
501 return largefiles.lfutil.instore(repo, sha)
502
502
503 @reraise_safe_exceptions
503 @reraise_safe_exceptions
504 def in_user_cache(self, wire, sha):
504 def in_user_cache(self, wire, sha):
505 repo = self._factory.repo(wire)
505 repo = self._factory.repo(wire)
506 return largefiles.lfutil.inusercache(repo.ui, sha)
506 return largefiles.lfutil.inusercache(repo.ui, sha)
507
507
508 @reraise_safe_exceptions
508 @reraise_safe_exceptions
509 def store_path(self, wire, sha):
509 def store_path(self, wire, sha):
510 repo = self._factory.repo(wire)
510 repo = self._factory.repo(wire)
511 return largefiles.lfutil.storepath(repo, sha)
511 return largefiles.lfutil.storepath(repo, sha)
512
512
513 @reraise_safe_exceptions
513 @reraise_safe_exceptions
514 def link(self, wire, sha, path):
514 def link(self, wire, sha, path):
515 repo = self._factory.repo(wire)
515 repo = self._factory.repo(wire)
516 largefiles.lfutil.link(
516 largefiles.lfutil.link(
517 largefiles.lfutil.usercachepath(repo.ui, sha), path)
517 largefiles.lfutil.usercachepath(repo.ui, sha), path)
518
518
519 @reraise_safe_exceptions
519 @reraise_safe_exceptions
520 def localrepository(self, wire, create=False):
520 def localrepository(self, wire, create=False):
521 self._factory.repo(wire, create=create)
521 self._factory.repo(wire, create=create)
522
522
523 @reraise_safe_exceptions
523 @reraise_safe_exceptions
524 def lookup(self, wire, revision, both):
524 def lookup(self, wire, revision, both):
525 # TODO Paris: Ugly hack to "deserialize" long for msgpack
525 # TODO Paris: Ugly hack to "deserialize" long for msgpack
526 if isinstance(revision, float):
526 if isinstance(revision, float):
527 revision = long(revision)
527 revision = long(revision)
528 repo = self._factory.repo(wire)
528 repo = self._factory.repo(wire)
529 try:
529 try:
530 ctx = repo[revision]
530 ctx = repo[revision]
531 except RepoLookupError:
531 except RepoLookupError:
532 raise exceptions.LookupException(revision)
532 raise exceptions.LookupException(revision)
533 except LookupError as e:
533 except LookupError as e:
534 raise exceptions.LookupException(e.name)
534 raise exceptions.LookupException(e.name)
535
535
536 if not both:
536 if not both:
537 return ctx.hex()
537 return ctx.hex()
538
538
539 ctx = repo[ctx.hex()]
539 ctx = repo[ctx.hex()]
540 return ctx.hex(), ctx.rev()
540 return ctx.hex(), ctx.rev()
541
541
542 @reraise_safe_exceptions
542 @reraise_safe_exceptions
543 def pull(self, wire, url, commit_ids=None):
543 def pull(self, wire, url, commit_ids=None):
544 repo = self._factory.repo(wire)
544 repo = self._factory.repo(wire)
545 remote = peer(repo, {}, url)
545 remote = peer(repo, {}, url)
546 if commit_ids:
546 if commit_ids:
547 commit_ids = [bin(commit_id) for commit_id in commit_ids]
547 commit_ids = [bin(commit_id) for commit_id in commit_ids]
548
548
549 return exchange.pull(
549 return exchange.pull(
550 repo, remote, heads=commit_ids, force=None).cgresult
550 repo, remote, heads=commit_ids, force=None).cgresult
551
551
552 @reraise_safe_exceptions
552 @reraise_safe_exceptions
553 def revision(self, wire, rev):
553 def revision(self, wire, rev):
554 repo = self._factory.repo(wire)
554 repo = self._factory.repo(wire)
555 ctx = repo[rev]
555 ctx = repo[rev]
556 return ctx.rev()
556 return ctx.rev()
557
557
558 @reraise_safe_exceptions
558 @reraise_safe_exceptions
559 def rev_range(self, wire, filter):
559 def rev_range(self, wire, filter):
560 repo = self._factory.repo(wire)
560 repo = self._factory.repo(wire)
561 revisions = [rev for rev in revrange(repo, filter)]
561 revisions = [rev for rev in revrange(repo, filter)]
562 return revisions
562 return revisions
563
563
564 @reraise_safe_exceptions
564 @reraise_safe_exceptions
565 def rev_range_hash(self, wire, node):
565 def rev_range_hash(self, wire, node):
566 repo = self._factory.repo(wire)
566 repo = self._factory.repo(wire)
567
567
568 def get_revs(repo, rev_opt):
568 def get_revs(repo, rev_opt):
569 if rev_opt:
569 if rev_opt:
570 revs = revrange(repo, rev_opt)
570 revs = revrange(repo, rev_opt)
571 if len(revs) == 0:
571 if len(revs) == 0:
572 return (nullrev, nullrev)
572 return (nullrev, nullrev)
573 return max(revs), min(revs)
573 return max(revs), min(revs)
574 else:
574 else:
575 return len(repo) - 1, 0
575 return len(repo) - 1, 0
576
576
577 stop, start = get_revs(repo, [node + ':'])
577 stop, start = get_revs(repo, [node + ':'])
578 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
578 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
579 return revs
579 return revs
580
580
581 @reraise_safe_exceptions
581 @reraise_safe_exceptions
582 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
582 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
583 other_path = kwargs.pop('other_path', None)
583 other_path = kwargs.pop('other_path', None)
584
584
585 # case when we want to compare two independent repositories
585 # case when we want to compare two independent repositories
586 if other_path and other_path != wire["path"]:
586 if other_path and other_path != wire["path"]:
587 baseui = self._factory._create_config(wire["config"])
587 baseui = self._factory._create_config(wire["config"])
588 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
588 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
589 else:
589 else:
590 repo = self._factory.repo(wire)
590 repo = self._factory.repo(wire)
591 return list(repo.revs(rev_spec, *args))
591 return list(repo.revs(rev_spec, *args))
592
592
593 @reraise_safe_exceptions
593 @reraise_safe_exceptions
594 def strip(self, wire, revision, update, backup):
594 def strip(self, wire, revision, update, backup):
595 repo = self._factory.repo(wire)
595 repo = self._factory.repo(wire)
596 ctx = repo[revision]
596 ctx = repo[revision]
597 hgext_strip(
597 hgext_strip(
598 repo.baseui, repo, ctx.node(), update=update, backup=backup)
598 repo.baseui, repo, ctx.node(), update=update, backup=backup)
599
599
600 @reraise_safe_exceptions
600 @reraise_safe_exceptions
601 def tag(self, wire, name, revision, message, local, user,
601 def tag(self, wire, name, revision, message, local, user,
602 tag_time, tag_timezone):
602 tag_time, tag_timezone):
603 repo = self._factory.repo(wire)
603 repo = self._factory.repo(wire)
604 ctx = repo[revision]
604 ctx = repo[revision]
605 node = ctx.node()
605 node = ctx.node()
606
606
607 date = (tag_time, tag_timezone)
607 date = (tag_time, tag_timezone)
608 try:
608 try:
609 repo.tag(name, node, message, local, user, date)
609 repo.tag(name, node, message, local, user, date)
610 except Abort:
610 except Abort as e:
611 log.exception("Tag operation aborted")
611 log.exception("Tag operation aborted")
612 raise exceptions.AbortException()
612 # Exception can contain unicode which we convert
613 raise exceptions.AbortException(repr(e))
613
614
614 @reraise_safe_exceptions
615 @reraise_safe_exceptions
615 def tags(self, wire):
616 def tags(self, wire):
616 repo = self._factory.repo(wire)
617 repo = self._factory.repo(wire)
617 return repo.tags()
618 return repo.tags()
618
619
619 @reraise_safe_exceptions
620 @reraise_safe_exceptions
620 def update(self, wire, node=None, clean=False):
621 def update(self, wire, node=None, clean=False):
621 repo = self._factory.repo(wire)
622 repo = self._factory.repo(wire)
622 baseui = self._factory._create_config(wire['config'])
623 baseui = self._factory._create_config(wire['config'])
623 commands.update(baseui, repo, node=node, clean=clean)
624 commands.update(baseui, repo, node=node, clean=clean)
624
625
625 @reraise_safe_exceptions
626 @reraise_safe_exceptions
626 def identify(self, wire):
627 def identify(self, wire):
627 repo = self._factory.repo(wire)
628 repo = self._factory.repo(wire)
628 baseui = self._factory._create_config(wire['config'])
629 baseui = self._factory._create_config(wire['config'])
629 output = io.BytesIO()
630 output = io.BytesIO()
630 baseui.write = output.write
631 baseui.write = output.write
631 # This is required to get a full node id
632 # This is required to get a full node id
632 baseui.debugflag = True
633 baseui.debugflag = True
633 commands.identify(baseui, repo, id=True)
634 commands.identify(baseui, repo, id=True)
634
635
635 return output.getvalue()
636 return output.getvalue()
636
637
637 @reraise_safe_exceptions
638 @reraise_safe_exceptions
638 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
639 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
639 hooks=True):
640 hooks=True):
640 repo = self._factory.repo(wire)
641 repo = self._factory.repo(wire)
641 baseui = self._factory._create_config(wire['config'], hooks=hooks)
642 baseui = self._factory._create_config(wire['config'], hooks=hooks)
642
643
643 # Mercurial internally has a lot of logic that checks ONLY if
644 # Mercurial internally has a lot of logic that checks ONLY if
644 # option is defined, we just pass those if they are defined then
645 # option is defined, we just pass those if they are defined then
645 opts = {}
646 opts = {}
646 if bookmark:
647 if bookmark:
647 opts['bookmark'] = bookmark
648 opts['bookmark'] = bookmark
648 if branch:
649 if branch:
649 opts['branch'] = branch
650 opts['branch'] = branch
650 if revision:
651 if revision:
651 opts['rev'] = revision
652 opts['rev'] = revision
652
653
653 commands.pull(baseui, repo, source, **opts)
654 commands.pull(baseui, repo, source, **opts)
654
655
655 @reraise_safe_exceptions
656 @reraise_safe_exceptions
656 def heads(self, wire, branch=None):
657 def heads(self, wire, branch=None):
657 repo = self._factory.repo(wire)
658 repo = self._factory.repo(wire)
658 baseui = self._factory._create_config(wire['config'])
659 baseui = self._factory._create_config(wire['config'])
659 output = io.BytesIO()
660 output = io.BytesIO()
660
661
661 def write(data, **unused_kwargs):
662 def write(data, **unused_kwargs):
662 output.write(data)
663 output.write(data)
663
664
664 baseui.write = write
665 baseui.write = write
665 if branch:
666 if branch:
666 args = [branch]
667 args = [branch]
667 else:
668 else:
668 args = []
669 args = []
669 commands.heads(baseui, repo, template='{node} ', *args)
670 commands.heads(baseui, repo, template='{node} ', *args)
670
671
671 return output.getvalue()
672 return output.getvalue()
672
673
673 @reraise_safe_exceptions
674 @reraise_safe_exceptions
674 def ancestor(self, wire, revision1, revision2):
675 def ancestor(self, wire, revision1, revision2):
675 repo = self._factory.repo(wire)
676 repo = self._factory.repo(wire)
676 baseui = self._factory._create_config(wire['config'])
677 baseui = self._factory._create_config(wire['config'])
677 output = io.BytesIO()
678 output = io.BytesIO()
678 baseui.write = output.write
679 baseui.write = output.write
679 commands.debugancestor(baseui, repo, revision1, revision2)
680 commands.debugancestor(baseui, repo, revision1, revision2)
680
681
681 return output.getvalue()
682 return output.getvalue()
682
683
683 @reraise_safe_exceptions
684 @reraise_safe_exceptions
684 def push(self, wire, revisions, dest_path, hooks=True,
685 def push(self, wire, revisions, dest_path, hooks=True,
685 push_branches=False):
686 push_branches=False):
686 repo = self._factory.repo(wire)
687 repo = self._factory.repo(wire)
687 baseui = self._factory._create_config(wire['config'], hooks=hooks)
688 baseui = self._factory._create_config(wire['config'], hooks=hooks)
688 commands.push(baseui, repo, dest=dest_path, rev=revisions,
689 commands.push(baseui, repo, dest=dest_path, rev=revisions,
689 new_branch=push_branches)
690 new_branch=push_branches)
690
691
691 @reraise_safe_exceptions
692 @reraise_safe_exceptions
692 def merge(self, wire, revision):
693 def merge(self, wire, revision):
693 repo = self._factory.repo(wire)
694 repo = self._factory.repo(wire)
694 baseui = self._factory._create_config(wire['config'])
695 baseui = self._factory._create_config(wire['config'])
695 repo.ui.setconfig('ui', 'merge', 'internal:dump')
696 repo.ui.setconfig('ui', 'merge', 'internal:dump')
696
697
697 # In case of sub repositories are used mercurial prompts the user in
698 # In case of sub repositories are used mercurial prompts the user in
698 # case of merge conflicts or different sub repository sources. By
699 # case of merge conflicts or different sub repository sources. By
699 # setting the interactive flag to `False` mercurial doesn't prompt the
700 # setting the interactive flag to `False` mercurial doesn't prompt the
700 # used but instead uses a default value.
701 # used but instead uses a default value.
701 repo.ui.setconfig('ui', 'interactive', False)
702 repo.ui.setconfig('ui', 'interactive', False)
702
703
703 commands.merge(baseui, repo, rev=revision)
704 commands.merge(baseui, repo, rev=revision)
704
705
705 @reraise_safe_exceptions
706 @reraise_safe_exceptions
706 def commit(self, wire, message, username):
707 def commit(self, wire, message, username):
707 repo = self._factory.repo(wire)
708 repo = self._factory.repo(wire)
708 baseui = self._factory._create_config(wire['config'])
709 baseui = self._factory._create_config(wire['config'])
709 repo.ui.setconfig('ui', 'username', username)
710 repo.ui.setconfig('ui', 'username', username)
710 commands.commit(baseui, repo, message=message)
711 commands.commit(baseui, repo, message=message)
711
712
712 @reraise_safe_exceptions
713 @reraise_safe_exceptions
713 def rebase(self, wire, source=None, dest=None, abort=False):
714 def rebase(self, wire, source=None, dest=None, abort=False):
714 repo = self._factory.repo(wire)
715 repo = self._factory.repo(wire)
715 baseui = self._factory._create_config(wire['config'])
716 baseui = self._factory._create_config(wire['config'])
716 repo.ui.setconfig('ui', 'merge', 'internal:dump')
717 repo.ui.setconfig('ui', 'merge', 'internal:dump')
717 rebase.rebase(
718 rebase.rebase(
718 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
719 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
719
720
720 @reraise_safe_exceptions
721 @reraise_safe_exceptions
721 def bookmark(self, wire, bookmark, revision=None):
722 def bookmark(self, wire, bookmark, revision=None):
722 repo = self._factory.repo(wire)
723 repo = self._factory.repo(wire)
723 baseui = self._factory._create_config(wire['config'])
724 baseui = self._factory._create_config(wire['config'])
724 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
725 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
General Comments 0
You need to be logged in to leave comments. Login now