##// END OF EJS Templates
remote-clones: make sure we always use obfuscated url inside logs....
marcink -
r105:859d7a10 default
parent child Browse files
Show More
@@ -1,576 +1,579 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import logging
18 import logging
19 import os
19 import os
20 import posixpath as vcspath
20 import posixpath as vcspath
21 import re
21 import re
22 import stat
22 import stat
23 import urllib
23 import urllib
24 import urllib2
24 import urllib2
25 from functools import wraps
25 from functools import wraps
26
26
27 from dulwich import index, objects
27 from dulwich import index, objects
28 from dulwich.client import HttpGitClient, LocalGitClient
28 from dulwich.client import HttpGitClient, LocalGitClient
29 from dulwich.errors import (
29 from dulwich.errors import (
30 NotGitRepository, ChecksumMismatch, WrongObjectException,
30 NotGitRepository, ChecksumMismatch, WrongObjectException,
31 MissingCommitError, ObjectMissing, HangupException,
31 MissingCommitError, ObjectMissing, HangupException,
32 UnexpectedCommandError)
32 UnexpectedCommandError)
33 from dulwich.repo import Repo as DulwichRepo, Tag
33 from dulwich.repo import Repo as DulwichRepo, Tag
34 from dulwich.server import update_server_info
34 from dulwich.server import update_server_info
35
35
36 from vcsserver import exceptions, settings, subprocessio
36 from vcsserver import exceptions, settings, subprocessio
37 from vcsserver.utils import safe_str
37 from vcsserver.utils import safe_str
38 from vcsserver.base import RepoFactory
38 from vcsserver.base import RepoFactory
39 from vcsserver.hgcompat import (
39 from vcsserver.hgcompat import (
40 hg_url, httpbasicauthhandler, httpdigestauthhandler)
40 hg_url 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 = hg_url(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 = hg_url(url)
170 url_obj = url_parser(url)
171 test_uri, _ = url_obj.authinfo()
171 test_uri, _ = url_obj.authinfo()
172 url_obj.passwd = '*****'
172 url_obj.passwd = '*****'
173 cleaned_uri = str(url_obj)
173 cleaned_uri = str(url_obj)
174 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
174
175
175 if not test_uri.endswith('info/refs'):
176 if not test_uri.endswith('info/refs'):
176 test_uri = test_uri.rstrip('/') + '/info/refs'
177 test_uri = test_uri.rstrip('/') + '/info/refs'
177
178
178 o = self._build_opener(url)
179 o = self._build_opener(url)
179 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
180 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
180
181
181 q = {"service": 'git-upload-pack'}
182 q = {"service": 'git-upload-pack'}
182 qs = '?%s' % urllib.urlencode(q)
183 qs = '?%s' % urllib.urlencode(q)
183 cu = "%s%s" % (test_uri, qs)
184 cu = "%s%s" % (test_uri, qs)
184 req = urllib2.Request(cu, None, {})
185 req = urllib2.Request(cu, None, {})
185
186
186 try:
187 try:
188 log.debug("Trying to open URL %s", cleaned_uri)
187 resp = o.open(req)
189 resp = o.open(req)
188 if resp.code != 200:
190 if resp.code != 200:
189 raise Exception('Return Code is not 200')
191 raise exceptions.URLError('Return Code is not 200')
190 except Exception as e:
192 except Exception as e:
193 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
191 # means it cannot be cloned
194 # means it cannot be cloned
192 raise urllib2.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
195 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
193
196
194 # now detect if it's proper git repo
197 # now detect if it's proper git repo
195 gitdata = resp.read()
198 gitdata = resp.read()
196 if 'service=git-upload-pack' in gitdata:
199 if 'service=git-upload-pack' in gitdata:
197 pass
200 pass
198 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
201 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
199 # old style git can return some other format !
202 # old style git can return some other format !
200 pass
203 pass
201 else:
204 else:
202 raise urllib2.URLError(
205 raise exceptions.URLError(
203 "url [%s] does not look like an git" % (cleaned_uri,))
206 "url [%s] does not look like an git" % (cleaned_uri,))
204
207
205 return True
208 return True
206
209
207 @reraise_safe_exceptions
210 @reraise_safe_exceptions
208 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
211 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
209 remote_refs = self.fetch(wire, url, apply_refs=False)
212 remote_refs = self.fetch(wire, url, apply_refs=False)
210 repo = self._factory.repo(wire)
213 repo = self._factory.repo(wire)
211 if isinstance(valid_refs, list):
214 if isinstance(valid_refs, list):
212 valid_refs = tuple(valid_refs)
215 valid_refs = tuple(valid_refs)
213
216
214 for k in remote_refs:
217 for k in remote_refs:
215 # only parse heads/tags and skip so called deferred tags
218 # only parse heads/tags and skip so called deferred tags
216 if k.startswith(valid_refs) and not k.endswith(deferred):
219 if k.startswith(valid_refs) and not k.endswith(deferred):
217 repo[k] = remote_refs[k]
220 repo[k] = remote_refs[k]
218
221
219 if update_after_clone:
222 if update_after_clone:
220 # we want to checkout HEAD
223 # we want to checkout HEAD
221 repo["HEAD"] = remote_refs["HEAD"]
224 repo["HEAD"] = remote_refs["HEAD"]
222 index.build_index_from_tree(repo.path, repo.index_path(),
225 index.build_index_from_tree(repo.path, repo.index_path(),
223 repo.object_store, repo["HEAD"].tree)
226 repo.object_store, repo["HEAD"].tree)
224
227
225 # TODO: this is quite complex, check if that can be simplified
228 # TODO: this is quite complex, check if that can be simplified
226 @reraise_safe_exceptions
229 @reraise_safe_exceptions
227 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
230 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
228 repo = self._factory.repo(wire)
231 repo = self._factory.repo(wire)
229 object_store = repo.object_store
232 object_store = repo.object_store
230
233
231 # Create tree and populates it with blobs
234 # Create tree and populates it with blobs
232 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
235 commit_tree = commit_tree and repo[commit_tree] or objects.Tree()
233
236
234 for node in updated:
237 for node in updated:
235 # Compute subdirs if needed
238 # Compute subdirs if needed
236 dirpath, nodename = vcspath.split(node['path'])
239 dirpath, nodename = vcspath.split(node['path'])
237 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
240 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
238 parent = commit_tree
241 parent = commit_tree
239 ancestors = [('', parent)]
242 ancestors = [('', parent)]
240
243
241 # Tries to dig for the deepest existing tree
244 # Tries to dig for the deepest existing tree
242 while dirnames:
245 while dirnames:
243 curdir = dirnames.pop(0)
246 curdir = dirnames.pop(0)
244 try:
247 try:
245 dir_id = parent[curdir][1]
248 dir_id = parent[curdir][1]
246 except KeyError:
249 except KeyError:
247 # put curdir back into dirnames and stops
250 # put curdir back into dirnames and stops
248 dirnames.insert(0, curdir)
251 dirnames.insert(0, curdir)
249 break
252 break
250 else:
253 else:
251 # If found, updates parent
254 # If found, updates parent
252 parent = repo[dir_id]
255 parent = repo[dir_id]
253 ancestors.append((curdir, parent))
256 ancestors.append((curdir, parent))
254 # Now parent is deepest existing tree and we need to create
257 # Now parent is deepest existing tree and we need to create
255 # subtrees for dirnames (in reverse order)
258 # subtrees for dirnames (in reverse order)
256 # [this only applies for nodes from added]
259 # [this only applies for nodes from added]
257 new_trees = []
260 new_trees = []
258
261
259 blob = objects.Blob.from_string(node['content'])
262 blob = objects.Blob.from_string(node['content'])
260
263
261 if dirnames:
264 if dirnames:
262 # If there are trees which should be created we need to build
265 # If there are trees which should be created we need to build
263 # them now (in reverse order)
266 # them now (in reverse order)
264 reversed_dirnames = list(reversed(dirnames))
267 reversed_dirnames = list(reversed(dirnames))
265 curtree = objects.Tree()
268 curtree = objects.Tree()
266 curtree[node['node_path']] = node['mode'], blob.id
269 curtree[node['node_path']] = node['mode'], blob.id
267 new_trees.append(curtree)
270 new_trees.append(curtree)
268 for dirname in reversed_dirnames[:-1]:
271 for dirname in reversed_dirnames[:-1]:
269 newtree = objects.Tree()
272 newtree = objects.Tree()
270 newtree[dirname] = (DIR_STAT, curtree.id)
273 newtree[dirname] = (DIR_STAT, curtree.id)
271 new_trees.append(newtree)
274 new_trees.append(newtree)
272 curtree = newtree
275 curtree = newtree
273 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
276 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
274 else:
277 else:
275 parent.add(
278 parent.add(
276 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
279 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
277
280
278 new_trees.append(parent)
281 new_trees.append(parent)
279 # Update ancestors
282 # Update ancestors
280 reversed_ancestors = reversed(
283 reversed_ancestors = reversed(
281 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
284 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
282 for parent, tree, path in reversed_ancestors:
285 for parent, tree, path in reversed_ancestors:
283 parent[path] = (DIR_STAT, tree.id)
286 parent[path] = (DIR_STAT, tree.id)
284 object_store.add_object(tree)
287 object_store.add_object(tree)
285
288
286 object_store.add_object(blob)
289 object_store.add_object(blob)
287 for tree in new_trees:
290 for tree in new_trees:
288 object_store.add_object(tree)
291 object_store.add_object(tree)
289
292
290 for node_path in removed:
293 for node_path in removed:
291 paths = node_path.split('/')
294 paths = node_path.split('/')
292 tree = commit_tree
295 tree = commit_tree
293 trees = [tree]
296 trees = [tree]
294 # Traverse deep into the forest...
297 # Traverse deep into the forest...
295 for path in paths:
298 for path in paths:
296 try:
299 try:
297 obj = repo[tree[path][1]]
300 obj = repo[tree[path][1]]
298 if isinstance(obj, objects.Tree):
301 if isinstance(obj, objects.Tree):
299 trees.append(obj)
302 trees.append(obj)
300 tree = obj
303 tree = obj
301 except KeyError:
304 except KeyError:
302 break
305 break
303 # Cut down the blob and all rotten trees on the way back...
306 # Cut down the blob and all rotten trees on the way back...
304 for path, tree in reversed(zip(paths, trees)):
307 for path, tree in reversed(zip(paths, trees)):
305 del tree[path]
308 del tree[path]
306 if tree:
309 if tree:
307 # This tree still has elements - don't remove it or any
310 # This tree still has elements - don't remove it or any
308 # of it's parents
311 # of it's parents
309 break
312 break
310
313
311 object_store.add_object(commit_tree)
314 object_store.add_object(commit_tree)
312
315
313 # Create commit
316 # Create commit
314 commit = objects.Commit()
317 commit = objects.Commit()
315 commit.tree = commit_tree.id
318 commit.tree = commit_tree.id
316 for k, v in commit_data.iteritems():
319 for k, v in commit_data.iteritems():
317 setattr(commit, k, v)
320 setattr(commit, k, v)
318 object_store.add_object(commit)
321 object_store.add_object(commit)
319
322
320 ref = 'refs/heads/%s' % branch
323 ref = 'refs/heads/%s' % branch
321 repo.refs[ref] = commit.id
324 repo.refs[ref] = commit.id
322
325
323 return commit.id
326 return commit.id
324
327
325 @reraise_safe_exceptions
328 @reraise_safe_exceptions
326 def fetch(self, wire, url, apply_refs=True, refs=None):
329 def fetch(self, wire, url, apply_refs=True, refs=None):
327 if url != 'default' and '://' not in url:
330 if url != 'default' and '://' not in url:
328 client = LocalGitClient(url)
331 client = LocalGitClient(url)
329 else:
332 else:
330 url_obj = hg_url(url)
333 url_obj = url_parser(url)
331 o = self._build_opener(url)
334 o = self._build_opener(url)
332 url, _ = url_obj.authinfo()
335 url, _ = url_obj.authinfo()
333 client = HttpGitClient(base_url=url, opener=o)
336 client = HttpGitClient(base_url=url, opener=o)
334 repo = self._factory.repo(wire)
337 repo = self._factory.repo(wire)
335
338
336 determine_wants = repo.object_store.determine_wants_all
339 determine_wants = repo.object_store.determine_wants_all
337 if refs:
340 if refs:
338 def determine_wants_requested(references):
341 def determine_wants_requested(references):
339 return [references[r] for r in references if r in refs]
342 return [references[r] for r in references if r in refs]
340 determine_wants = determine_wants_requested
343 determine_wants = determine_wants_requested
341
344
342 try:
345 try:
343 remote_refs = client.fetch(
346 remote_refs = client.fetch(
344 path=url, target=repo, determine_wants=determine_wants)
347 path=url, target=repo, determine_wants=determine_wants)
345 except NotGitRepository:
348 except NotGitRepository:
346 log.warning(
349 log.warning(
347 'Trying to fetch from "%s" failed, not a Git repository.', url)
350 'Trying to fetch from "%s" failed, not a Git repository.', url)
348 raise exceptions.AbortException()
351 raise exceptions.AbortException()
349
352
350 # mikhail: client.fetch() returns all the remote refs, but fetches only
353 # mikhail: client.fetch() returns all the remote refs, but fetches only
351 # refs filtered by `determine_wants` function. We need to filter result
354 # refs filtered by `determine_wants` function. We need to filter result
352 # as well
355 # as well
353 if refs:
356 if refs:
354 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
357 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
355
358
356 if apply_refs:
359 if apply_refs:
357 # TODO: johbo: Needs proper test coverage with a git repository
360 # TODO: johbo: Needs proper test coverage with a git repository
358 # that contains a tag object, so that we would end up with
361 # that contains a tag object, so that we would end up with
359 # a peeled ref at this point.
362 # a peeled ref at this point.
360 PEELED_REF_MARKER = '^{}'
363 PEELED_REF_MARKER = '^{}'
361 for k in remote_refs:
364 for k in remote_refs:
362 if k.endswith(PEELED_REF_MARKER):
365 if k.endswith(PEELED_REF_MARKER):
363 log.info("Skipping peeled reference %s", k)
366 log.info("Skipping peeled reference %s", k)
364 continue
367 continue
365 repo[k] = remote_refs[k]
368 repo[k] = remote_refs[k]
366
369
367 if refs:
370 if refs:
368 # mikhail: explicitly set the head to the last ref.
371 # mikhail: explicitly set the head to the last ref.
369 repo['HEAD'] = remote_refs[refs[-1]]
372 repo['HEAD'] = remote_refs[refs[-1]]
370
373
371 # TODO: mikhail: should we return remote_refs here to be
374 # TODO: mikhail: should we return remote_refs here to be
372 # consistent?
375 # consistent?
373 else:
376 else:
374 return remote_refs
377 return remote_refs
375
378
376 @reraise_safe_exceptions
379 @reraise_safe_exceptions
377 def get_remote_refs(self, wire, url):
380 def get_remote_refs(self, wire, url):
378 repo = Repo(url)
381 repo = Repo(url)
379 return repo.get_refs()
382 return repo.get_refs()
380
383
381 @reraise_safe_exceptions
384 @reraise_safe_exceptions
382 def get_description(self, wire):
385 def get_description(self, wire):
383 repo = self._factory.repo(wire)
386 repo = self._factory.repo(wire)
384 return repo.get_description()
387 return repo.get_description()
385
388
386 @reraise_safe_exceptions
389 @reraise_safe_exceptions
387 def get_file_history(self, wire, file_path, commit_id, limit):
390 def get_file_history(self, wire, file_path, commit_id, limit):
388 repo = self._factory.repo(wire)
391 repo = self._factory.repo(wire)
389 include = [commit_id]
392 include = [commit_id]
390 paths = [file_path]
393 paths = [file_path]
391
394
392 walker = repo.get_walker(include, paths=paths, max_entries=limit)
395 walker = repo.get_walker(include, paths=paths, max_entries=limit)
393 return [x.commit.id for x in walker]
396 return [x.commit.id for x in walker]
394
397
395 @reraise_safe_exceptions
398 @reraise_safe_exceptions
396 def get_missing_revs(self, wire, rev1, rev2, path2):
399 def get_missing_revs(self, wire, rev1, rev2, path2):
397 repo = self._factory.repo(wire)
400 repo = self._factory.repo(wire)
398 LocalGitClient(thin_packs=False).fetch(path2, repo)
401 LocalGitClient(thin_packs=False).fetch(path2, repo)
399
402
400 wire_remote = wire.copy()
403 wire_remote = wire.copy()
401 wire_remote['path'] = path2
404 wire_remote['path'] = path2
402 repo_remote = self._factory.repo(wire_remote)
405 repo_remote = self._factory.repo(wire_remote)
403 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
406 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
404
407
405 revs = [
408 revs = [
406 x.commit.id
409 x.commit.id
407 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
410 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
408 return revs
411 return revs
409
412
410 @reraise_safe_exceptions
413 @reraise_safe_exceptions
411 def get_object(self, wire, sha):
414 def get_object(self, wire, sha):
412 repo = self._factory.repo(wire)
415 repo = self._factory.repo(wire)
413 obj = repo.get_object(sha)
416 obj = repo.get_object(sha)
414 commit_id = obj.id
417 commit_id = obj.id
415
418
416 if isinstance(obj, Tag):
419 if isinstance(obj, Tag):
417 commit_id = obj.object[1]
420 commit_id = obj.object[1]
418
421
419 return {
422 return {
420 'id': obj.id,
423 'id': obj.id,
421 'type': obj.type_name,
424 'type': obj.type_name,
422 'commit_id': commit_id
425 'commit_id': commit_id
423 }
426 }
424
427
425 @reraise_safe_exceptions
428 @reraise_safe_exceptions
426 def get_object_attrs(self, wire, sha, *attrs):
429 def get_object_attrs(self, wire, sha, *attrs):
427 repo = self._factory.repo(wire)
430 repo = self._factory.repo(wire)
428 obj = repo.get_object(sha)
431 obj = repo.get_object(sha)
429 return list(getattr(obj, a) for a in attrs)
432 return list(getattr(obj, a) for a in attrs)
430
433
431 @reraise_safe_exceptions
434 @reraise_safe_exceptions
432 def get_refs(self, wire):
435 def get_refs(self, wire):
433 repo = self._factory.repo(wire)
436 repo = self._factory.repo(wire)
434 result = {}
437 result = {}
435 for ref, sha in repo.refs.as_dict().items():
438 for ref, sha in repo.refs.as_dict().items():
436 peeled_sha = repo.get_peeled(ref)
439 peeled_sha = repo.get_peeled(ref)
437 result[ref] = peeled_sha
440 result[ref] = peeled_sha
438 return result
441 return result
439
442
440 @reraise_safe_exceptions
443 @reraise_safe_exceptions
441 def get_refs_path(self, wire):
444 def get_refs_path(self, wire):
442 repo = self._factory.repo(wire)
445 repo = self._factory.repo(wire)
443 return repo.refs.path
446 return repo.refs.path
444
447
445 @reraise_safe_exceptions
448 @reraise_safe_exceptions
446 def head(self, wire):
449 def head(self, wire):
447 repo = self._factory.repo(wire)
450 repo = self._factory.repo(wire)
448 return repo.head()
451 return repo.head()
449
452
450 @reraise_safe_exceptions
453 @reraise_safe_exceptions
451 def init(self, wire):
454 def init(self, wire):
452 repo_path = str_to_dulwich(wire['path'])
455 repo_path = str_to_dulwich(wire['path'])
453 self.repo = Repo.init(repo_path)
456 self.repo = Repo.init(repo_path)
454
457
455 @reraise_safe_exceptions
458 @reraise_safe_exceptions
456 def init_bare(self, wire):
459 def init_bare(self, wire):
457 repo_path = str_to_dulwich(wire['path'])
460 repo_path = str_to_dulwich(wire['path'])
458 self.repo = Repo.init_bare(repo_path)
461 self.repo = Repo.init_bare(repo_path)
459
462
460 @reraise_safe_exceptions
463 @reraise_safe_exceptions
461 def revision(self, wire, rev):
464 def revision(self, wire, rev):
462 repo = self._factory.repo(wire)
465 repo = self._factory.repo(wire)
463 obj = repo[rev]
466 obj = repo[rev]
464 obj_data = {
467 obj_data = {
465 'id': obj.id,
468 'id': obj.id,
466 }
469 }
467 try:
470 try:
468 obj_data['tree'] = obj.tree
471 obj_data['tree'] = obj.tree
469 except AttributeError:
472 except AttributeError:
470 pass
473 pass
471 return obj_data
474 return obj_data
472
475
473 @reraise_safe_exceptions
476 @reraise_safe_exceptions
474 def commit_attribute(self, wire, rev, attr):
477 def commit_attribute(self, wire, rev, attr):
475 repo = self._factory.repo(wire)
478 repo = self._factory.repo(wire)
476 obj = repo[rev]
479 obj = repo[rev]
477 return getattr(obj, attr)
480 return getattr(obj, attr)
478
481
479 @reraise_safe_exceptions
482 @reraise_safe_exceptions
480 def set_refs(self, wire, key, value):
483 def set_refs(self, wire, key, value):
481 repo = self._factory.repo(wire)
484 repo = self._factory.repo(wire)
482 repo.refs[key] = value
485 repo.refs[key] = value
483
486
484 @reraise_safe_exceptions
487 @reraise_safe_exceptions
485 def remove_ref(self, wire, key):
488 def remove_ref(self, wire, key):
486 repo = self._factory.repo(wire)
489 repo = self._factory.repo(wire)
487 del repo.refs[key]
490 del repo.refs[key]
488
491
489 @reraise_safe_exceptions
492 @reraise_safe_exceptions
490 def tree_changes(self, wire, source_id, target_id):
493 def tree_changes(self, wire, source_id, target_id):
491 repo = self._factory.repo(wire)
494 repo = self._factory.repo(wire)
492 source = repo[source_id].tree if source_id else None
495 source = repo[source_id].tree if source_id else None
493 target = repo[target_id].tree
496 target = repo[target_id].tree
494 result = repo.object_store.tree_changes(source, target)
497 result = repo.object_store.tree_changes(source, target)
495 return list(result)
498 return list(result)
496
499
497 @reraise_safe_exceptions
500 @reraise_safe_exceptions
498 def tree_items(self, wire, tree_id):
501 def tree_items(self, wire, tree_id):
499 repo = self._factory.repo(wire)
502 repo = self._factory.repo(wire)
500 tree = repo[tree_id]
503 tree = repo[tree_id]
501
504
502 result = []
505 result = []
503 for item in tree.iteritems():
506 for item in tree.iteritems():
504 item_sha = item.sha
507 item_sha = item.sha
505 item_mode = item.mode
508 item_mode = item.mode
506
509
507 if FILE_MODE(item_mode) == GIT_LINK:
510 if FILE_MODE(item_mode) == GIT_LINK:
508 item_type = "link"
511 item_type = "link"
509 else:
512 else:
510 item_type = repo[item_sha].type_name
513 item_type = repo[item_sha].type_name
511
514
512 result.append((item.path, item_mode, item_sha, item_type))
515 result.append((item.path, item_mode, item_sha, item_type))
513 return result
516 return result
514
517
515 @reraise_safe_exceptions
518 @reraise_safe_exceptions
516 def update_server_info(self, wire):
519 def update_server_info(self, wire):
517 repo = self._factory.repo(wire)
520 repo = self._factory.repo(wire)
518 update_server_info(repo)
521 update_server_info(repo)
519
522
520 @reraise_safe_exceptions
523 @reraise_safe_exceptions
521 def discover_git_version(self):
524 def discover_git_version(self):
522 stdout, _ = self.run_git_command(
525 stdout, _ = self.run_git_command(
523 {}, ['--version'], _bare=True, _safe=True)
526 {}, ['--version'], _bare=True, _safe=True)
524 prefix = 'git version'
527 prefix = 'git version'
525 if stdout.startswith(prefix):
528 if stdout.startswith(prefix):
526 stdout = stdout[len(prefix):]
529 stdout = stdout[len(prefix):]
527 return stdout.strip()
530 return stdout.strip()
528
531
529 @reraise_safe_exceptions
532 @reraise_safe_exceptions
530 def run_git_command(self, wire, cmd, **opts):
533 def run_git_command(self, wire, cmd, **opts):
531 path = wire.get('path', None)
534 path = wire.get('path', None)
532
535
533 if path and os.path.isdir(path):
536 if path and os.path.isdir(path):
534 opts['cwd'] = path
537 opts['cwd'] = path
535
538
536 if '_bare' in opts:
539 if '_bare' in opts:
537 _copts = []
540 _copts = []
538 del opts['_bare']
541 del opts['_bare']
539 else:
542 else:
540 _copts = ['-c', 'core.quotepath=false', ]
543 _copts = ['-c', 'core.quotepath=false', ]
541 safe_call = False
544 safe_call = False
542 if '_safe' in opts:
545 if '_safe' in opts:
543 # no exc on failure
546 # no exc on failure
544 del opts['_safe']
547 del opts['_safe']
545 safe_call = True
548 safe_call = True
546
549
547 gitenv = os.environ.copy()
550 gitenv = os.environ.copy()
548 gitenv.update(opts.pop('extra_env', {}))
551 gitenv.update(opts.pop('extra_env', {}))
549 # need to clean fix GIT_DIR !
552 # need to clean fix GIT_DIR !
550 if 'GIT_DIR' in gitenv:
553 if 'GIT_DIR' in gitenv:
551 del gitenv['GIT_DIR']
554 del gitenv['GIT_DIR']
552 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
555 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
553
556
554 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
557 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
555
558
556 try:
559 try:
557 _opts = {'env': gitenv, 'shell': False}
560 _opts = {'env': gitenv, 'shell': False}
558 _opts.update(opts)
561 _opts.update(opts)
559 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
562 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
560
563
561 return ''.join(p), ''.join(p.error)
564 return ''.join(p), ''.join(p.error)
562 except (EnvironmentError, OSError) as err:
565 except (EnvironmentError, OSError) as err:
563 tb_err = ("Couldn't run git command (%s).\n"
566 tb_err = ("Couldn't run git command (%s).\n"
564 "Original error was:%s\n" % (cmd, err))
567 "Original error was:%s\n" % (cmd, err))
565 log.exception(tb_err)
568 log.exception(tb_err)
566 if safe_call:
569 if safe_call:
567 return '', err
570 return '', err
568 else:
571 else:
569 raise exceptions.VcsException(tb_err)
572 raise exceptions.VcsException(tb_err)
570
573
571
574
572 def str_to_dulwich(value):
575 def str_to_dulwich(value):
573 """
576 """
574 Dulwich 0.10.1a requires `unicode` objects to be passed in.
577 Dulwich 0.10.1a requires `unicode` objects to be passed in.
575 """
578 """
576 return value.decode(settings.WIRE_ENCODING)
579 return value.decode(settings.WIRE_ENCODING)
@@ -1,719 +1,721 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
31 from vcsserver.base import RepoFactory
32 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex, hg_url,
33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 httpbasicauthhandler, httpdigestauthhandler, httppeer, localrepository,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 match, memctx, exchange, memfilectx, nullrev, patch, peer, revrange, ui,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 Abort, LookupError, RepoError, RepoLookupError, InterventionRequired,
36 patch, peer, revrange, ui, Abort, LookupError, RepoError, RepoLookupError,
37 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 log.info("Checking URL for remote cloning/import: %s", url)
325 _proto = None
324 _proto = None
326 if '+' in url[:url.find('://')]:
325 if '+' in url[:url.find('://')]:
327 _proto = url[0:url.find('+')]
326 _proto = url[0:url.find('+')]
328 url = url[url.find('+') + 1:]
327 url = url[url.find('+') + 1:]
329 handlers = []
328 handlers = []
330 url_obj = hg_url(url)
329 url_obj = url_parser(url)
331 test_uri, authinfo = url_obj.authinfo()
330 test_uri, authinfo = url_obj.authinfo()
332 url_obj.passwd = '*****'
331 url_obj.passwd = '*****'
333 cleaned_uri = str(url_obj)
332 cleaned_uri = str(url_obj)
333 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
334
334
335 if authinfo:
335 if authinfo:
336 # create a password manager
336 # create a password manager
337 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
337 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
338 passmgr.add_password(*authinfo)
338 passmgr.add_password(*authinfo)
339
339
340 handlers.extend((httpbasicauthhandler(passmgr),
340 handlers.extend((httpbasicauthhandler(passmgr),
341 httpdigestauthhandler(passmgr)))
341 httpdigestauthhandler(passmgr)))
342
342
343 o = urllib2.build_opener(*handlers)
343 o = urllib2.build_opener(*handlers)
344 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
344 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
345 ('Accept', 'application/mercurial-0.1')]
345 ('Accept', 'application/mercurial-0.1')]
346
346
347 q = {"cmd": 'between'}
347 q = {"cmd": 'between'}
348 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
348 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
349 qs = '?%s' % urllib.urlencode(q)
349 qs = '?%s' % urllib.urlencode(q)
350 cu = "%s%s" % (test_uri, qs)
350 cu = "%s%s" % (test_uri, qs)
351 req = urllib2.Request(cu, None, {})
351 req = urllib2.Request(cu, None, {})
352
352
353 try:
353 try:
354 log.debug("Trying to open URL %s", url)
354 log.debug("Trying to open URL %s", cleaned_uri)
355 resp = o.open(req)
355 resp = o.open(req)
356 if resp.code != 200:
356 if resp.code != 200:
357 raise exceptions.URLError('Return Code is not 200')
357 raise exceptions.URLError('Return Code is not 200')
358 except Exception as e:
358 except Exception as e:
359 log.warning("URL cannot be opened: %s", url, exc_info=True)
359 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
360 # means it cannot be cloned
360 # means it cannot be cloned
361 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
361 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
362
362
363 # now check if it's a proper hg repo, but don't do it for svn
363 # now check if it's a proper hg repo, but don't do it for svn
364 try:
364 try:
365 if _proto == 'svn':
365 if _proto == 'svn':
366 pass
366 pass
367 else:
367 else:
368 # check for pure hg repos
368 # check for pure hg repos
369 log.debug(
369 log.debug(
370 "Verifying if URL is a Mercurial repository: %s", url)
370 "Verifying if URL is a Mercurial repository: %s",
371 cleaned_uri)
371 httppeer(make_ui_from_config(config), url).lookup('tip')
372 httppeer(make_ui_from_config(config), url).lookup('tip')
372 except Exception as e:
373 except Exception as e:
373 log.warning("URL is not a valid Mercurial repository: %s", url)
374 log.warning("URL is not a valid Mercurial repository: %s",
375 cleaned_uri)
374 raise exceptions.URLError(
376 raise exceptions.URLError(
375 "url [%s] does not look like an hg repo org_exc: %s"
377 "url [%s] does not look like an hg repo org_exc: %s"
376 % (cleaned_uri, e))
378 % (cleaned_uri, e))
377
379
378 log.info("URL is a valid Mercurial repository: %s", url)
380 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
379 return True
381 return True
380
382
381 @reraise_safe_exceptions
383 @reraise_safe_exceptions
382 def diff(
384 def diff(
383 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
385 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
384 context):
386 context):
385 repo = self._factory.repo(wire)
387 repo = self._factory.repo(wire)
386
388
387 if file_filter:
389 if file_filter:
388 filter = match(file_filter[0], '', [file_filter[1]])
390 filter = match(file_filter[0], '', [file_filter[1]])
389 else:
391 else:
390 filter = file_filter
392 filter = file_filter
391 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
393 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
392
394
393 try:
395 try:
394 return "".join(patch.diff(
396 return "".join(patch.diff(
395 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
397 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
396 except RepoLookupError:
398 except RepoLookupError:
397 raise exceptions.LookupException()
399 raise exceptions.LookupException()
398
400
399 @reraise_safe_exceptions
401 @reraise_safe_exceptions
400 def file_history(self, wire, revision, path, limit):
402 def file_history(self, wire, revision, path, limit):
401 repo = self._factory.repo(wire)
403 repo = self._factory.repo(wire)
402
404
403 ctx = repo[revision]
405 ctx = repo[revision]
404 fctx = ctx.filectx(path)
406 fctx = ctx.filectx(path)
405
407
406 def history_iter():
408 def history_iter():
407 limit_rev = fctx.rev()
409 limit_rev = fctx.rev()
408 for obj in reversed(list(fctx.filelog())):
410 for obj in reversed(list(fctx.filelog())):
409 obj = fctx.filectx(obj)
411 obj = fctx.filectx(obj)
410 if limit_rev >= obj.rev():
412 if limit_rev >= obj.rev():
411 yield obj
413 yield obj
412
414
413 history = []
415 history = []
414 for cnt, obj in enumerate(history_iter()):
416 for cnt, obj in enumerate(history_iter()):
415 if limit and cnt >= limit:
417 if limit and cnt >= limit:
416 break
418 break
417 history.append(hex(obj.node()))
419 history.append(hex(obj.node()))
418
420
419 return [x for x in history]
421 return [x for x in history]
420
422
421 @reraise_safe_exceptions
423 @reraise_safe_exceptions
422 def file_history_untill(self, wire, revision, path, limit):
424 def file_history_untill(self, wire, revision, path, limit):
423 repo = self._factory.repo(wire)
425 repo = self._factory.repo(wire)
424 ctx = repo[revision]
426 ctx = repo[revision]
425 fctx = ctx.filectx(path)
427 fctx = ctx.filectx(path)
426
428
427 file_log = list(fctx.filelog())
429 file_log = list(fctx.filelog())
428 if limit:
430 if limit:
429 # Limit to the last n items
431 # Limit to the last n items
430 file_log = file_log[-limit:]
432 file_log = file_log[-limit:]
431
433
432 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
434 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
433
435
434 @reraise_safe_exceptions
436 @reraise_safe_exceptions
435 def fctx_annotate(self, wire, revision, path):
437 def fctx_annotate(self, wire, revision, path):
436 repo = self._factory.repo(wire)
438 repo = self._factory.repo(wire)
437 ctx = repo[revision]
439 ctx = repo[revision]
438 fctx = ctx.filectx(path)
440 fctx = ctx.filectx(path)
439
441
440 result = []
442 result = []
441 for i, annotate_data in enumerate(fctx.annotate()):
443 for i, annotate_data in enumerate(fctx.annotate()):
442 ln_no = i + 1
444 ln_no = i + 1
443 sha = hex(annotate_data[0].node())
445 sha = hex(annotate_data[0].node())
444 result.append((ln_no, sha, annotate_data[1]))
446 result.append((ln_no, sha, annotate_data[1]))
445 return result
447 return result
446
448
447 @reraise_safe_exceptions
449 @reraise_safe_exceptions
448 def fctx_data(self, wire, revision, path):
450 def fctx_data(self, wire, revision, path):
449 repo = self._factory.repo(wire)
451 repo = self._factory.repo(wire)
450 ctx = repo[revision]
452 ctx = repo[revision]
451 fctx = ctx.filectx(path)
453 fctx = ctx.filectx(path)
452 return fctx.data()
454 return fctx.data()
453
455
454 @reraise_safe_exceptions
456 @reraise_safe_exceptions
455 def fctx_flags(self, wire, revision, path):
457 def fctx_flags(self, wire, revision, path):
456 repo = self._factory.repo(wire)
458 repo = self._factory.repo(wire)
457 ctx = repo[revision]
459 ctx = repo[revision]
458 fctx = ctx.filectx(path)
460 fctx = ctx.filectx(path)
459 return fctx.flags()
461 return fctx.flags()
460
462
461 @reraise_safe_exceptions
463 @reraise_safe_exceptions
462 def fctx_size(self, wire, revision, path):
464 def fctx_size(self, wire, revision, path):
463 repo = self._factory.repo(wire)
465 repo = self._factory.repo(wire)
464 ctx = repo[revision]
466 ctx = repo[revision]
465 fctx = ctx.filectx(path)
467 fctx = ctx.filectx(path)
466 return fctx.size()
468 return fctx.size()
467
469
468 @reraise_safe_exceptions
470 @reraise_safe_exceptions
469 def get_all_commit_ids(self, wire, name):
471 def get_all_commit_ids(self, wire, name):
470 repo = self._factory.repo(wire)
472 repo = self._factory.repo(wire)
471 revs = repo.filtered(name).changelog.index
473 revs = repo.filtered(name).changelog.index
472 return map(lambda x: hex(x[7]), revs)[:-1]
474 return map(lambda x: hex(x[7]), revs)[:-1]
473
475
474 @reraise_safe_exceptions
476 @reraise_safe_exceptions
475 def get_config_value(self, wire, section, name, untrusted=False):
477 def get_config_value(self, wire, section, name, untrusted=False):
476 repo = self._factory.repo(wire)
478 repo = self._factory.repo(wire)
477 return repo.ui.config(section, name, untrusted=untrusted)
479 return repo.ui.config(section, name, untrusted=untrusted)
478
480
479 @reraise_safe_exceptions
481 @reraise_safe_exceptions
480 def get_config_bool(self, wire, section, name, untrusted=False):
482 def get_config_bool(self, wire, section, name, untrusted=False):
481 repo = self._factory.repo(wire)
483 repo = self._factory.repo(wire)
482 return repo.ui.configbool(section, name, untrusted=untrusted)
484 return repo.ui.configbool(section, name, untrusted=untrusted)
483
485
484 @reraise_safe_exceptions
486 @reraise_safe_exceptions
485 def get_config_list(self, wire, section, name, untrusted=False):
487 def get_config_list(self, wire, section, name, untrusted=False):
486 repo = self._factory.repo(wire)
488 repo = self._factory.repo(wire)
487 return repo.ui.configlist(section, name, untrusted=untrusted)
489 return repo.ui.configlist(section, name, untrusted=untrusted)
488
490
489 @reraise_safe_exceptions
491 @reraise_safe_exceptions
490 def is_large_file(self, wire, path):
492 def is_large_file(self, wire, path):
491 return largefiles.lfutil.isstandin(path)
493 return largefiles.lfutil.isstandin(path)
492
494
493 @reraise_safe_exceptions
495 @reraise_safe_exceptions
494 def in_store(self, wire, sha):
496 def in_store(self, wire, sha):
495 repo = self._factory.repo(wire)
497 repo = self._factory.repo(wire)
496 return largefiles.lfutil.instore(repo, sha)
498 return largefiles.lfutil.instore(repo, sha)
497
499
498 @reraise_safe_exceptions
500 @reraise_safe_exceptions
499 def in_user_cache(self, wire, sha):
501 def in_user_cache(self, wire, sha):
500 repo = self._factory.repo(wire)
502 repo = self._factory.repo(wire)
501 return largefiles.lfutil.inusercache(repo.ui, sha)
503 return largefiles.lfutil.inusercache(repo.ui, sha)
502
504
503 @reraise_safe_exceptions
505 @reraise_safe_exceptions
504 def store_path(self, wire, sha):
506 def store_path(self, wire, sha):
505 repo = self._factory.repo(wire)
507 repo = self._factory.repo(wire)
506 return largefiles.lfutil.storepath(repo, sha)
508 return largefiles.lfutil.storepath(repo, sha)
507
509
508 @reraise_safe_exceptions
510 @reraise_safe_exceptions
509 def link(self, wire, sha, path):
511 def link(self, wire, sha, path):
510 repo = self._factory.repo(wire)
512 repo = self._factory.repo(wire)
511 largefiles.lfutil.link(
513 largefiles.lfutil.link(
512 largefiles.lfutil.usercachepath(repo.ui, sha), path)
514 largefiles.lfutil.usercachepath(repo.ui, sha), path)
513
515
514 @reraise_safe_exceptions
516 @reraise_safe_exceptions
515 def localrepository(self, wire, create=False):
517 def localrepository(self, wire, create=False):
516 self._factory.repo(wire, create=create)
518 self._factory.repo(wire, create=create)
517
519
518 @reraise_safe_exceptions
520 @reraise_safe_exceptions
519 def lookup(self, wire, revision, both):
521 def lookup(self, wire, revision, both):
520 # TODO Paris: Ugly hack to "deserialize" long for msgpack
522 # TODO Paris: Ugly hack to "deserialize" long for msgpack
521 if isinstance(revision, float):
523 if isinstance(revision, float):
522 revision = long(revision)
524 revision = long(revision)
523 repo = self._factory.repo(wire)
525 repo = self._factory.repo(wire)
524 try:
526 try:
525 ctx = repo[revision]
527 ctx = repo[revision]
526 except RepoLookupError:
528 except RepoLookupError:
527 raise exceptions.LookupException(revision)
529 raise exceptions.LookupException(revision)
528 except LookupError as e:
530 except LookupError as e:
529 raise exceptions.LookupException(e.name)
531 raise exceptions.LookupException(e.name)
530
532
531 if not both:
533 if not both:
532 return ctx.hex()
534 return ctx.hex()
533
535
534 ctx = repo[ctx.hex()]
536 ctx = repo[ctx.hex()]
535 return ctx.hex(), ctx.rev()
537 return ctx.hex(), ctx.rev()
536
538
537 @reraise_safe_exceptions
539 @reraise_safe_exceptions
538 def pull(self, wire, url, commit_ids=None):
540 def pull(self, wire, url, commit_ids=None):
539 repo = self._factory.repo(wire)
541 repo = self._factory.repo(wire)
540 remote = peer(repo, {}, url)
542 remote = peer(repo, {}, url)
541 if commit_ids:
543 if commit_ids:
542 commit_ids = [bin(commit_id) for commit_id in commit_ids]
544 commit_ids = [bin(commit_id) for commit_id in commit_ids]
543
545
544 return exchange.pull(
546 return exchange.pull(
545 repo, remote, heads=commit_ids, force=None).cgresult
547 repo, remote, heads=commit_ids, force=None).cgresult
546
548
547 @reraise_safe_exceptions
549 @reraise_safe_exceptions
548 def revision(self, wire, rev):
550 def revision(self, wire, rev):
549 repo = self._factory.repo(wire)
551 repo = self._factory.repo(wire)
550 ctx = repo[rev]
552 ctx = repo[rev]
551 return ctx.rev()
553 return ctx.rev()
552
554
553 @reraise_safe_exceptions
555 @reraise_safe_exceptions
554 def rev_range(self, wire, filter):
556 def rev_range(self, wire, filter):
555 repo = self._factory.repo(wire)
557 repo = self._factory.repo(wire)
556 revisions = [rev for rev in revrange(repo, filter)]
558 revisions = [rev for rev in revrange(repo, filter)]
557 return revisions
559 return revisions
558
560
559 @reraise_safe_exceptions
561 @reraise_safe_exceptions
560 def rev_range_hash(self, wire, node):
562 def rev_range_hash(self, wire, node):
561 repo = self._factory.repo(wire)
563 repo = self._factory.repo(wire)
562
564
563 def get_revs(repo, rev_opt):
565 def get_revs(repo, rev_opt):
564 if rev_opt:
566 if rev_opt:
565 revs = revrange(repo, rev_opt)
567 revs = revrange(repo, rev_opt)
566 if len(revs) == 0:
568 if len(revs) == 0:
567 return (nullrev, nullrev)
569 return (nullrev, nullrev)
568 return max(revs), min(revs)
570 return max(revs), min(revs)
569 else:
571 else:
570 return len(repo) - 1, 0
572 return len(repo) - 1, 0
571
573
572 stop, start = get_revs(repo, [node + ':'])
574 stop, start = get_revs(repo, [node + ':'])
573 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
575 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
574 return revs
576 return revs
575
577
576 @reraise_safe_exceptions
578 @reraise_safe_exceptions
577 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
579 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
578 other_path = kwargs.pop('other_path', None)
580 other_path = kwargs.pop('other_path', None)
579
581
580 # case when we want to compare two independent repositories
582 # case when we want to compare two independent repositories
581 if other_path and other_path != wire["path"]:
583 if other_path and other_path != wire["path"]:
582 baseui = self._factory._create_config(wire["config"])
584 baseui = self._factory._create_config(wire["config"])
583 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
585 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
584 else:
586 else:
585 repo = self._factory.repo(wire)
587 repo = self._factory.repo(wire)
586 return list(repo.revs(rev_spec, *args))
588 return list(repo.revs(rev_spec, *args))
587
589
588 @reraise_safe_exceptions
590 @reraise_safe_exceptions
589 def strip(self, wire, revision, update, backup):
591 def strip(self, wire, revision, update, backup):
590 repo = self._factory.repo(wire)
592 repo = self._factory.repo(wire)
591 ctx = repo[revision]
593 ctx = repo[revision]
592 hgext_strip(
594 hgext_strip(
593 repo.baseui, repo, ctx.node(), update=update, backup=backup)
595 repo.baseui, repo, ctx.node(), update=update, backup=backup)
594
596
595 @reraise_safe_exceptions
597 @reraise_safe_exceptions
596 def tag(self, wire, name, revision, message, local, user,
598 def tag(self, wire, name, revision, message, local, user,
597 tag_time, tag_timezone):
599 tag_time, tag_timezone):
598 repo = self._factory.repo(wire)
600 repo = self._factory.repo(wire)
599 ctx = repo[revision]
601 ctx = repo[revision]
600 node = ctx.node()
602 node = ctx.node()
601
603
602 date = (tag_time, tag_timezone)
604 date = (tag_time, tag_timezone)
603 try:
605 try:
604 repo.tag(name, node, message, local, user, date)
606 repo.tag(name, node, message, local, user, date)
605 except Abort:
607 except Abort:
606 log.exception("Tag operation aborted")
608 log.exception("Tag operation aborted")
607 raise exceptions.AbortException()
609 raise exceptions.AbortException()
608
610
609 @reraise_safe_exceptions
611 @reraise_safe_exceptions
610 def tags(self, wire):
612 def tags(self, wire):
611 repo = self._factory.repo(wire)
613 repo = self._factory.repo(wire)
612 return repo.tags()
614 return repo.tags()
613
615
614 @reraise_safe_exceptions
616 @reraise_safe_exceptions
615 def update(self, wire, node=None, clean=False):
617 def update(self, wire, node=None, clean=False):
616 repo = self._factory.repo(wire)
618 repo = self._factory.repo(wire)
617 baseui = self._factory._create_config(wire['config'])
619 baseui = self._factory._create_config(wire['config'])
618 commands.update(baseui, repo, node=node, clean=clean)
620 commands.update(baseui, repo, node=node, clean=clean)
619
621
620 @reraise_safe_exceptions
622 @reraise_safe_exceptions
621 def identify(self, wire):
623 def identify(self, wire):
622 repo = self._factory.repo(wire)
624 repo = self._factory.repo(wire)
623 baseui = self._factory._create_config(wire['config'])
625 baseui = self._factory._create_config(wire['config'])
624 output = io.BytesIO()
626 output = io.BytesIO()
625 baseui.write = output.write
627 baseui.write = output.write
626 # This is required to get a full node id
628 # This is required to get a full node id
627 baseui.debugflag = True
629 baseui.debugflag = True
628 commands.identify(baseui, repo, id=True)
630 commands.identify(baseui, repo, id=True)
629
631
630 return output.getvalue()
632 return output.getvalue()
631
633
632 @reraise_safe_exceptions
634 @reraise_safe_exceptions
633 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
635 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
634 hooks=True):
636 hooks=True):
635 repo = self._factory.repo(wire)
637 repo = self._factory.repo(wire)
636 baseui = self._factory._create_config(wire['config'], hooks=hooks)
638 baseui = self._factory._create_config(wire['config'], hooks=hooks)
637
639
638 # Mercurial internally has a lot of logic that checks ONLY if
640 # Mercurial internally has a lot of logic that checks ONLY if
639 # option is defined, we just pass those if they are defined then
641 # option is defined, we just pass those if they are defined then
640 opts = {}
642 opts = {}
641 if bookmark:
643 if bookmark:
642 opts['bookmark'] = bookmark
644 opts['bookmark'] = bookmark
643 if branch:
645 if branch:
644 opts['branch'] = branch
646 opts['branch'] = branch
645 if revision:
647 if revision:
646 opts['rev'] = revision
648 opts['rev'] = revision
647
649
648 commands.pull(baseui, repo, source, **opts)
650 commands.pull(baseui, repo, source, **opts)
649
651
650 @reraise_safe_exceptions
652 @reraise_safe_exceptions
651 def heads(self, wire, branch=None):
653 def heads(self, wire, branch=None):
652 repo = self._factory.repo(wire)
654 repo = self._factory.repo(wire)
653 baseui = self._factory._create_config(wire['config'])
655 baseui = self._factory._create_config(wire['config'])
654 output = io.BytesIO()
656 output = io.BytesIO()
655
657
656 def write(data, **unused_kwargs):
658 def write(data, **unused_kwargs):
657 output.write(data)
659 output.write(data)
658
660
659 baseui.write = write
661 baseui.write = write
660 if branch:
662 if branch:
661 args = [branch]
663 args = [branch]
662 else:
664 else:
663 args = []
665 args = []
664 commands.heads(baseui, repo, template='{node} ', *args)
666 commands.heads(baseui, repo, template='{node} ', *args)
665
667
666 return output.getvalue()
668 return output.getvalue()
667
669
668 @reraise_safe_exceptions
670 @reraise_safe_exceptions
669 def ancestor(self, wire, revision1, revision2):
671 def ancestor(self, wire, revision1, revision2):
670 repo = self._factory.repo(wire)
672 repo = self._factory.repo(wire)
671 baseui = self._factory._create_config(wire['config'])
673 baseui = self._factory._create_config(wire['config'])
672 output = io.BytesIO()
674 output = io.BytesIO()
673 baseui.write = output.write
675 baseui.write = output.write
674 commands.debugancestor(baseui, repo, revision1, revision2)
676 commands.debugancestor(baseui, repo, revision1, revision2)
675
677
676 return output.getvalue()
678 return output.getvalue()
677
679
678 @reraise_safe_exceptions
680 @reraise_safe_exceptions
679 def push(self, wire, revisions, dest_path, hooks=True,
681 def push(self, wire, revisions, dest_path, hooks=True,
680 push_branches=False):
682 push_branches=False):
681 repo = self._factory.repo(wire)
683 repo = self._factory.repo(wire)
682 baseui = self._factory._create_config(wire['config'], hooks=hooks)
684 baseui = self._factory._create_config(wire['config'], hooks=hooks)
683 commands.push(baseui, repo, dest=dest_path, rev=revisions,
685 commands.push(baseui, repo, dest=dest_path, rev=revisions,
684 new_branch=push_branches)
686 new_branch=push_branches)
685
687
686 @reraise_safe_exceptions
688 @reraise_safe_exceptions
687 def merge(self, wire, revision):
689 def merge(self, wire, revision):
688 repo = self._factory.repo(wire)
690 repo = self._factory.repo(wire)
689 baseui = self._factory._create_config(wire['config'])
691 baseui = self._factory._create_config(wire['config'])
690 repo.ui.setconfig('ui', 'merge', 'internal:dump')
692 repo.ui.setconfig('ui', 'merge', 'internal:dump')
691
693
692 # In case of sub repositories are used mercurial prompts the user in
694 # In case of sub repositories are used mercurial prompts the user in
693 # case of merge conflicts or different sub repository sources. By
695 # case of merge conflicts or different sub repository sources. By
694 # setting the interactive flag to `False` mercurial doesn't prompt the
696 # setting the interactive flag to `False` mercurial doesn't prompt the
695 # used but instead uses a default value.
697 # used but instead uses a default value.
696 repo.ui.setconfig('ui', 'interactive', False)
698 repo.ui.setconfig('ui', 'interactive', False)
697
699
698 commands.merge(baseui, repo, rev=revision)
700 commands.merge(baseui, repo, rev=revision)
699
701
700 @reraise_safe_exceptions
702 @reraise_safe_exceptions
701 def commit(self, wire, message, username):
703 def commit(self, wire, message, username):
702 repo = self._factory.repo(wire)
704 repo = self._factory.repo(wire)
703 baseui = self._factory._create_config(wire['config'])
705 baseui = self._factory._create_config(wire['config'])
704 repo.ui.setconfig('ui', 'username', username)
706 repo.ui.setconfig('ui', 'username', username)
705 commands.commit(baseui, repo, message=message)
707 commands.commit(baseui, repo, message=message)
706
708
707 @reraise_safe_exceptions
709 @reraise_safe_exceptions
708 def rebase(self, wire, source=None, dest=None, abort=False):
710 def rebase(self, wire, source=None, dest=None, abort=False):
709 repo = self._factory.repo(wire)
711 repo = self._factory.repo(wire)
710 baseui = self._factory._create_config(wire['config'])
712 baseui = self._factory._create_config(wire['config'])
711 repo.ui.setconfig('ui', 'merge', 'internal:dump')
713 repo.ui.setconfig('ui', 'merge', 'internal:dump')
712 rebase.rebase(
714 rebase.rebase(
713 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
715 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
714
716
715 @reraise_safe_exceptions
717 @reraise_safe_exceptions
716 def bookmark(self, wire, bookmark, revision=None):
718 def bookmark(self, wire, bookmark, revision=None):
717 repo = self._factory.repo(wire)
719 repo = self._factory.repo(wire)
718 baseui = self._factory._create_config(wire['config'])
720 baseui = self._factory._create_config(wire['config'])
719 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
721 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
General Comments 0
You need to be logged in to leave comments. Login now