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