##// END OF EJS Templates
sync: disable prompts to not allow in any case to block processes on input.
marcink -
r381:d27ef2a8 default
parent child Browse files
Show More
@@ -1,658 +1,663 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode 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):
450 def sync_push(self, wire, url, refs=None):
451 if self.check_url(url, wire):
451 if self.check_url(url, wire):
452 repo = self._factory.repo(wire)
452 repo = self._factory.repo(wire)
453 self.run_git_command(
453 self.run_git_command(
454 wire, ['push', url, '--mirror'], fail_on_stderr=False)
454 wire, ['push', url, '--mirror'], fail_on_stderr=False,
455
455 _copts=['-c', 'core.askpass=""'],
456 extra_env={'GIT_TERMINAL_PROMPT': '0'})
456
457
457 @reraise_safe_exceptions
458 @reraise_safe_exceptions
458 def get_remote_refs(self, wire, url):
459 def get_remote_refs(self, wire, url):
459 repo = Repo(url)
460 repo = Repo(url)
460 return repo.get_refs()
461 return repo.get_refs()
461
462
462 @reraise_safe_exceptions
463 @reraise_safe_exceptions
463 def get_description(self, wire):
464 def get_description(self, wire):
464 repo = self._factory.repo(wire)
465 repo = self._factory.repo(wire)
465 return repo.get_description()
466 return repo.get_description()
466
467
467 @reraise_safe_exceptions
468 @reraise_safe_exceptions
468 def get_file_history(self, wire, file_path, commit_id, limit):
469 def get_file_history(self, wire, file_path, commit_id, limit):
469 repo = self._factory.repo(wire)
470 repo = self._factory.repo(wire)
470 include = [commit_id]
471 include = [commit_id]
471 paths = [file_path]
472 paths = [file_path]
472
473
473 walker = repo.get_walker(include, paths=paths, max_entries=limit)
474 walker = repo.get_walker(include, paths=paths, max_entries=limit)
474 return [x.commit.id for x in walker]
475 return [x.commit.id for x in walker]
475
476
476 @reraise_safe_exceptions
477 @reraise_safe_exceptions
477 def get_missing_revs(self, wire, rev1, rev2, path2):
478 def get_missing_revs(self, wire, rev1, rev2, path2):
478 repo = self._factory.repo(wire)
479 repo = self._factory.repo(wire)
479 LocalGitClient(thin_packs=False).fetch(path2, repo)
480 LocalGitClient(thin_packs=False).fetch(path2, repo)
480
481
481 wire_remote = wire.copy()
482 wire_remote = wire.copy()
482 wire_remote['path'] = path2
483 wire_remote['path'] = path2
483 repo_remote = self._factory.repo(wire_remote)
484 repo_remote = self._factory.repo(wire_remote)
484 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
485 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
485
486
486 revs = [
487 revs = [
487 x.commit.id
488 x.commit.id
488 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
489 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
489 return revs
490 return revs
490
491
491 @reraise_safe_exceptions
492 @reraise_safe_exceptions
492 def get_object(self, wire, sha):
493 def get_object(self, wire, sha):
493 repo = self._factory.repo(wire)
494 repo = self._factory.repo(wire)
494 obj = repo.get_object(sha)
495 obj = repo.get_object(sha)
495 commit_id = obj.id
496 commit_id = obj.id
496
497
497 if isinstance(obj, Tag):
498 if isinstance(obj, Tag):
498 commit_id = obj.object[1]
499 commit_id = obj.object[1]
499
500
500 return {
501 return {
501 'id': obj.id,
502 'id': obj.id,
502 'type': obj.type_name,
503 'type': obj.type_name,
503 'commit_id': commit_id
504 'commit_id': commit_id
504 }
505 }
505
506
506 @reraise_safe_exceptions
507 @reraise_safe_exceptions
507 def get_object_attrs(self, wire, sha, *attrs):
508 def get_object_attrs(self, wire, sha, *attrs):
508 repo = self._factory.repo(wire)
509 repo = self._factory.repo(wire)
509 obj = repo.get_object(sha)
510 obj = repo.get_object(sha)
510 return list(getattr(obj, a) for a in attrs)
511 return list(getattr(obj, a) for a in attrs)
511
512
512 @reraise_safe_exceptions
513 @reraise_safe_exceptions
513 def get_refs(self, wire):
514 def get_refs(self, wire):
514 repo = self._factory.repo(wire)
515 repo = self._factory.repo(wire)
515 result = {}
516 result = {}
516 for ref, sha in repo.refs.as_dict().items():
517 for ref, sha in repo.refs.as_dict().items():
517 peeled_sha = repo.get_peeled(ref)
518 peeled_sha = repo.get_peeled(ref)
518 result[ref] = peeled_sha
519 result[ref] = peeled_sha
519 return result
520 return result
520
521
521 @reraise_safe_exceptions
522 @reraise_safe_exceptions
522 def get_refs_path(self, wire):
523 def get_refs_path(self, wire):
523 repo = self._factory.repo(wire)
524 repo = self._factory.repo(wire)
524 return repo.refs.path
525 return repo.refs.path
525
526
526 @reraise_safe_exceptions
527 @reraise_safe_exceptions
527 def head(self, wire):
528 def head(self, wire):
528 repo = self._factory.repo(wire)
529 repo = self._factory.repo(wire)
529 return repo.head()
530 return repo.head()
530
531
531 @reraise_safe_exceptions
532 @reraise_safe_exceptions
532 def init(self, wire):
533 def init(self, wire):
533 repo_path = str_to_dulwich(wire['path'])
534 repo_path = str_to_dulwich(wire['path'])
534 self.repo = Repo.init(repo_path)
535 self.repo = Repo.init(repo_path)
535
536
536 @reraise_safe_exceptions
537 @reraise_safe_exceptions
537 def init_bare(self, wire):
538 def init_bare(self, wire):
538 repo_path = str_to_dulwich(wire['path'])
539 repo_path = str_to_dulwich(wire['path'])
539 self.repo = Repo.init_bare(repo_path)
540 self.repo = Repo.init_bare(repo_path)
540
541
541 @reraise_safe_exceptions
542 @reraise_safe_exceptions
542 def revision(self, wire, rev):
543 def revision(self, wire, rev):
543 repo = self._factory.repo(wire)
544 repo = self._factory.repo(wire)
544 obj = repo[rev]
545 obj = repo[rev]
545 obj_data = {
546 obj_data = {
546 'id': obj.id,
547 'id': obj.id,
547 }
548 }
548 try:
549 try:
549 obj_data['tree'] = obj.tree
550 obj_data['tree'] = obj.tree
550 except AttributeError:
551 except AttributeError:
551 pass
552 pass
552 return obj_data
553 return obj_data
553
554
554 @reraise_safe_exceptions
555 @reraise_safe_exceptions
555 def commit_attribute(self, wire, rev, attr):
556 def commit_attribute(self, wire, rev, attr):
556 repo = self._factory.repo(wire)
557 repo = self._factory.repo(wire)
557 obj = repo[rev]
558 obj = repo[rev]
558 return getattr(obj, attr)
559 return getattr(obj, attr)
559
560
560 @reraise_safe_exceptions
561 @reraise_safe_exceptions
561 def set_refs(self, wire, key, value):
562 def set_refs(self, wire, key, value):
562 repo = self._factory.repo(wire)
563 repo = self._factory.repo(wire)
563 repo.refs[key] = value
564 repo.refs[key] = value
564
565
565 @reraise_safe_exceptions
566 @reraise_safe_exceptions
566 def remove_ref(self, wire, key):
567 def remove_ref(self, wire, key):
567 repo = self._factory.repo(wire)
568 repo = self._factory.repo(wire)
568 del repo.refs[key]
569 del repo.refs[key]
569
570
570 @reraise_safe_exceptions
571 @reraise_safe_exceptions
571 def tree_changes(self, wire, source_id, target_id):
572 def tree_changes(self, wire, source_id, target_id):
572 repo = self._factory.repo(wire)
573 repo = self._factory.repo(wire)
573 source = repo[source_id].tree if source_id else None
574 source = repo[source_id].tree if source_id else None
574 target = repo[target_id].tree
575 target = repo[target_id].tree
575 result = repo.object_store.tree_changes(source, target)
576 result = repo.object_store.tree_changes(source, target)
576 return list(result)
577 return list(result)
577
578
578 @reraise_safe_exceptions
579 @reraise_safe_exceptions
579 def tree_items(self, wire, tree_id):
580 def tree_items(self, wire, tree_id):
580 repo = self._factory.repo(wire)
581 repo = self._factory.repo(wire)
581 tree = repo[tree_id]
582 tree = repo[tree_id]
582
583
583 result = []
584 result = []
584 for item in tree.iteritems():
585 for item in tree.iteritems():
585 item_sha = item.sha
586 item_sha = item.sha
586 item_mode = item.mode
587 item_mode = item.mode
587
588
588 if FILE_MODE(item_mode) == GIT_LINK:
589 if FILE_MODE(item_mode) == GIT_LINK:
589 item_type = "link"
590 item_type = "link"
590 else:
591 else:
591 item_type = repo[item_sha].type_name
592 item_type = repo[item_sha].type_name
592
593
593 result.append((item.path, item_mode, item_sha, item_type))
594 result.append((item.path, item_mode, item_sha, item_type))
594 return result
595 return result
595
596
596 @reraise_safe_exceptions
597 @reraise_safe_exceptions
597 def update_server_info(self, wire):
598 def update_server_info(self, wire):
598 repo = self._factory.repo(wire)
599 repo = self._factory.repo(wire)
599 update_server_info(repo)
600 update_server_info(repo)
600
601
601 @reraise_safe_exceptions
602 @reraise_safe_exceptions
602 def discover_git_version(self):
603 def discover_git_version(self):
603 stdout, _ = self.run_git_command(
604 stdout, _ = self.run_git_command(
604 {}, ['--version'], _bare=True, _safe=True)
605 {}, ['--version'], _bare=True, _safe=True)
605 prefix = 'git version'
606 prefix = 'git version'
606 if stdout.startswith(prefix):
607 if stdout.startswith(prefix):
607 stdout = stdout[len(prefix):]
608 stdout = stdout[len(prefix):]
608 return stdout.strip()
609 return stdout.strip()
609
610
610 @reraise_safe_exceptions
611 @reraise_safe_exceptions
611 def run_git_command(self, wire, cmd, **opts):
612 def run_git_command(self, wire, cmd, **opts):
612 path = wire.get('path', None)
613 path = wire.get('path', None)
613
614
614 if path and os.path.isdir(path):
615 if path and os.path.isdir(path):
615 opts['cwd'] = path
616 opts['cwd'] = path
616
617
617 if '_bare' in opts:
618 if '_bare' in opts:
618 _copts = []
619 _copts = []
619 del opts['_bare']
620 del opts['_bare']
620 else:
621 else:
621 _copts = ['-c', 'core.quotepath=false', ]
622 _copts = ['-c', 'core.quotepath=false', ]
622 safe_call = False
623 safe_call = False
623 if '_safe' in opts:
624 if '_safe' in opts:
624 # no exc on failure
625 # no exc on failure
625 del opts['_safe']
626 del opts['_safe']
626 safe_call = True
627 safe_call = True
627
628
629 if '_copts' in opts:
630 _copts.extend(opts['_copts'] or [])
631 del opts['_copts']
632
628 gitenv = os.environ.copy()
633 gitenv = os.environ.copy()
629 gitenv.update(opts.pop('extra_env', {}))
634 gitenv.update(opts.pop('extra_env', {}))
630 # need to clean fix GIT_DIR !
635 # need to clean fix GIT_DIR !
631 if 'GIT_DIR' in gitenv:
636 if 'GIT_DIR' in gitenv:
632 del gitenv['GIT_DIR']
637 del gitenv['GIT_DIR']
633 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
638 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
634
639
635 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
640 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
636
641
637 try:
642 try:
638 _opts = {'env': gitenv, 'shell': False}
643 _opts = {'env': gitenv, 'shell': False}
639 _opts.update(opts)
644 _opts.update(opts)
640 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
645 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
641
646
642 return ''.join(p), ''.join(p.error)
647 return ''.join(p), ''.join(p.error)
643 except (EnvironmentError, OSError) as err:
648 except (EnvironmentError, OSError) as err:
644 cmd = ' '.join(cmd) # human friendly CMD
649 cmd = ' '.join(cmd) # human friendly CMD
645 tb_err = ("Couldn't run git command (%s).\n"
650 tb_err = ("Couldn't run git command (%s).\n"
646 "Original error was:%s\n" % (cmd, err))
651 "Original error was:%s\n" % (cmd, err))
647 log.exception(tb_err)
652 log.exception(tb_err)
648 if safe_call:
653 if safe_call:
649 return '', err
654 return '', err
650 else:
655 else:
651 raise exceptions.VcsException(tb_err)
656 raise exceptions.VcsException(tb_err)
652
657
653
658
654 def str_to_dulwich(value):
659 def str_to_dulwich(value):
655 """
660 """
656 Dulwich 0.10.1a requires `unicode` objects to be passed in.
661 Dulwich 0.10.1a requires `unicode` objects to be passed in.
657 """
662 """
658 return value.decode(settings.WIRE_ENCODING)
663 return value.decode(settings.WIRE_ENCODING)
@@ -1,758 +1,771 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode 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 # Disable any prompts for this repo
557 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
558
556 remote = peer(repo, {}, url)
559 remote = peer(repo, {}, url)
560 # Disable any prompts for this remote
561 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
562
557 if commit_ids:
563 if commit_ids:
558 commit_ids = [bin(commit_id) for commit_id in commit_ids]
564 commit_ids = [bin(commit_id) for commit_id in commit_ids]
559
565
560 return exchange.pull(
566 return exchange.pull(
561 repo, remote, heads=commit_ids, force=None).cgresult
567 repo, remote, heads=commit_ids, force=None).cgresult
562
568
563 @reraise_safe_exceptions
569 @reraise_safe_exceptions
564 def sync_push(self, wire, url):
570 def sync_push(self, wire, url):
565 if self.check_url(url, wire['config']):
571 if self.check_url(url, wire['config']):
566 repo = self._factory.repo(wire)
572 repo = self._factory.repo(wire)
573
574 # Disable any prompts for this repo
575 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
576
567 bookmarks = dict(repo._bookmarks).keys()
577 bookmarks = dict(repo._bookmarks).keys()
568 remote = peer(repo, {}, url)
578 remote = peer(repo, {}, url)
579 # Disable any prompts for this remote
580 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
581
569 return exchange.push(
582 return exchange.push(
570 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
583 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
571
584
572 @reraise_safe_exceptions
585 @reraise_safe_exceptions
573 def revision(self, wire, rev):
586 def revision(self, wire, rev):
574 repo = self._factory.repo(wire)
587 repo = self._factory.repo(wire)
575 ctx = repo[rev]
588 ctx = repo[rev]
576 return ctx.rev()
589 return ctx.rev()
577
590
578 @reraise_safe_exceptions
591 @reraise_safe_exceptions
579 def rev_range(self, wire, filter):
592 def rev_range(self, wire, filter):
580 repo = self._factory.repo(wire)
593 repo = self._factory.repo(wire)
581 revisions = [rev for rev in revrange(repo, filter)]
594 revisions = [rev for rev in revrange(repo, filter)]
582 return revisions
595 return revisions
583
596
584 @reraise_safe_exceptions
597 @reraise_safe_exceptions
585 def rev_range_hash(self, wire, node):
598 def rev_range_hash(self, wire, node):
586 repo = self._factory.repo(wire)
599 repo = self._factory.repo(wire)
587
600
588 def get_revs(repo, rev_opt):
601 def get_revs(repo, rev_opt):
589 if rev_opt:
602 if rev_opt:
590 revs = revrange(repo, rev_opt)
603 revs = revrange(repo, rev_opt)
591 if len(revs) == 0:
604 if len(revs) == 0:
592 return (nullrev, nullrev)
605 return (nullrev, nullrev)
593 return max(revs), min(revs)
606 return max(revs), min(revs)
594 else:
607 else:
595 return len(repo) - 1, 0
608 return len(repo) - 1, 0
596
609
597 stop, start = get_revs(repo, [node + ':'])
610 stop, start = get_revs(repo, [node + ':'])
598 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
611 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
599 return revs
612 return revs
600
613
601 @reraise_safe_exceptions
614 @reraise_safe_exceptions
602 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
615 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
603 other_path = kwargs.pop('other_path', None)
616 other_path = kwargs.pop('other_path', None)
604
617
605 # case when we want to compare two independent repositories
618 # case when we want to compare two independent repositories
606 if other_path and other_path != wire["path"]:
619 if other_path and other_path != wire["path"]:
607 baseui = self._factory._create_config(wire["config"])
620 baseui = self._factory._create_config(wire["config"])
608 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
621 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
609 else:
622 else:
610 repo = self._factory.repo(wire)
623 repo = self._factory.repo(wire)
611 return list(repo.revs(rev_spec, *args))
624 return list(repo.revs(rev_spec, *args))
612
625
613 @reraise_safe_exceptions
626 @reraise_safe_exceptions
614 def strip(self, wire, revision, update, backup):
627 def strip(self, wire, revision, update, backup):
615 repo = self._factory.repo(wire)
628 repo = self._factory.repo(wire)
616 ctx = repo[revision]
629 ctx = repo[revision]
617 hgext_strip(
630 hgext_strip(
618 repo.baseui, repo, ctx.node(), update=update, backup=backup)
631 repo.baseui, repo, ctx.node(), update=update, backup=backup)
619
632
620 @reraise_safe_exceptions
633 @reraise_safe_exceptions
621 def verify(self, wire,):
634 def verify(self, wire,):
622 repo = self._factory.repo(wire)
635 repo = self._factory.repo(wire)
623 baseui = self._factory._create_config(wire['config'])
636 baseui = self._factory._create_config(wire['config'])
624 baseui.setconfig('ui', 'quiet', 'false')
637 baseui.setconfig('ui', 'quiet', 'false')
625 output = io.BytesIO()
638 output = io.BytesIO()
626
639
627 def write(data, **unused_kwargs):
640 def write(data, **unused_kwargs):
628 output.write(data)
641 output.write(data)
629 baseui.write = write
642 baseui.write = write
630
643
631 repo.ui = baseui
644 repo.ui = baseui
632 verify.verify(repo)
645 verify.verify(repo)
633 return output.getvalue()
646 return output.getvalue()
634
647
635 @reraise_safe_exceptions
648 @reraise_safe_exceptions
636 def tag(self, wire, name, revision, message, local, user,
649 def tag(self, wire, name, revision, message, local, user,
637 tag_time, tag_timezone):
650 tag_time, tag_timezone):
638 repo = self._factory.repo(wire)
651 repo = self._factory.repo(wire)
639 ctx = repo[revision]
652 ctx = repo[revision]
640 node = ctx.node()
653 node = ctx.node()
641
654
642 date = (tag_time, tag_timezone)
655 date = (tag_time, tag_timezone)
643 try:
656 try:
644 hg_tag.tag(repo, name, node, message, local, user, date)
657 hg_tag.tag(repo, name, node, message, local, user, date)
645 except Abort as e:
658 except Abort as e:
646 log.exception("Tag operation aborted")
659 log.exception("Tag operation aborted")
647 # Exception can contain unicode which we convert
660 # Exception can contain unicode which we convert
648 raise exceptions.AbortException(repr(e))
661 raise exceptions.AbortException(repr(e))
649
662
650 @reraise_safe_exceptions
663 @reraise_safe_exceptions
651 def tags(self, wire):
664 def tags(self, wire):
652 repo = self._factory.repo(wire)
665 repo = self._factory.repo(wire)
653 return repo.tags()
666 return repo.tags()
654
667
655 @reraise_safe_exceptions
668 @reraise_safe_exceptions
656 def update(self, wire, node=None, clean=False):
669 def update(self, wire, node=None, clean=False):
657 repo = self._factory.repo(wire)
670 repo = self._factory.repo(wire)
658 baseui = self._factory._create_config(wire['config'])
671 baseui = self._factory._create_config(wire['config'])
659 commands.update(baseui, repo, node=node, clean=clean)
672 commands.update(baseui, repo, node=node, clean=clean)
660
673
661 @reraise_safe_exceptions
674 @reraise_safe_exceptions
662 def identify(self, wire):
675 def identify(self, wire):
663 repo = self._factory.repo(wire)
676 repo = self._factory.repo(wire)
664 baseui = self._factory._create_config(wire['config'])
677 baseui = self._factory._create_config(wire['config'])
665 output = io.BytesIO()
678 output = io.BytesIO()
666 baseui.write = output.write
679 baseui.write = output.write
667 # This is required to get a full node id
680 # This is required to get a full node id
668 baseui.debugflag = True
681 baseui.debugflag = True
669 commands.identify(baseui, repo, id=True)
682 commands.identify(baseui, repo, id=True)
670
683
671 return output.getvalue()
684 return output.getvalue()
672
685
673 @reraise_safe_exceptions
686 @reraise_safe_exceptions
674 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
687 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
675 hooks=True):
688 hooks=True):
676 repo = self._factory.repo(wire)
689 repo = self._factory.repo(wire)
677 baseui = self._factory._create_config(wire['config'], hooks=hooks)
690 baseui = self._factory._create_config(wire['config'], hooks=hooks)
678
691
679 # Mercurial internally has a lot of logic that checks ONLY if
692 # Mercurial internally has a lot of logic that checks ONLY if
680 # option is defined, we just pass those if they are defined then
693 # option is defined, we just pass those if they are defined then
681 opts = {}
694 opts = {}
682 if bookmark:
695 if bookmark:
683 opts['bookmark'] = bookmark
696 opts['bookmark'] = bookmark
684 if branch:
697 if branch:
685 opts['branch'] = branch
698 opts['branch'] = branch
686 if revision:
699 if revision:
687 opts['rev'] = revision
700 opts['rev'] = revision
688
701
689 commands.pull(baseui, repo, source, **opts)
702 commands.pull(baseui, repo, source, **opts)
690
703
691 @reraise_safe_exceptions
704 @reraise_safe_exceptions
692 def heads(self, wire, branch=None):
705 def heads(self, wire, branch=None):
693 repo = self._factory.repo(wire)
706 repo = self._factory.repo(wire)
694 baseui = self._factory._create_config(wire['config'])
707 baseui = self._factory._create_config(wire['config'])
695 output = io.BytesIO()
708 output = io.BytesIO()
696
709
697 def write(data, **unused_kwargs):
710 def write(data, **unused_kwargs):
698 output.write(data)
711 output.write(data)
699
712
700 baseui.write = write
713 baseui.write = write
701 if branch:
714 if branch:
702 args = [branch]
715 args = [branch]
703 else:
716 else:
704 args = []
717 args = []
705 commands.heads(baseui, repo, template='{node} ', *args)
718 commands.heads(baseui, repo, template='{node} ', *args)
706
719
707 return output.getvalue()
720 return output.getvalue()
708
721
709 @reraise_safe_exceptions
722 @reraise_safe_exceptions
710 def ancestor(self, wire, revision1, revision2):
723 def ancestor(self, wire, revision1, revision2):
711 repo = self._factory.repo(wire)
724 repo = self._factory.repo(wire)
712 changelog = repo.changelog
725 changelog = repo.changelog
713 lookup = repo.lookup
726 lookup = repo.lookup
714 a = changelog.ancestor(lookup(revision1), lookup(revision2))
727 a = changelog.ancestor(lookup(revision1), lookup(revision2))
715 return hex(a)
728 return hex(a)
716
729
717 @reraise_safe_exceptions
730 @reraise_safe_exceptions
718 def push(self, wire, revisions, dest_path, hooks=True,
731 def push(self, wire, revisions, dest_path, hooks=True,
719 push_branches=False):
732 push_branches=False):
720 repo = self._factory.repo(wire)
733 repo = self._factory.repo(wire)
721 baseui = self._factory._create_config(wire['config'], hooks=hooks)
734 baseui = self._factory._create_config(wire['config'], hooks=hooks)
722 commands.push(baseui, repo, dest=dest_path, rev=revisions,
735 commands.push(baseui, repo, dest=dest_path, rev=revisions,
723 new_branch=push_branches)
736 new_branch=push_branches)
724
737
725 @reraise_safe_exceptions
738 @reraise_safe_exceptions
726 def merge(self, wire, revision):
739 def merge(self, wire, revision):
727 repo = self._factory.repo(wire)
740 repo = self._factory.repo(wire)
728 baseui = self._factory._create_config(wire['config'])
741 baseui = self._factory._create_config(wire['config'])
729 repo.ui.setconfig('ui', 'merge', 'internal:dump')
742 repo.ui.setconfig('ui', 'merge', 'internal:dump')
730
743
731 # In case of sub repositories are used mercurial prompts the user in
744 # In case of sub repositories are used mercurial prompts the user in
732 # case of merge conflicts or different sub repository sources. By
745 # case of merge conflicts or different sub repository sources. By
733 # setting the interactive flag to `False` mercurial doesn't prompt the
746 # setting the interactive flag to `False` mercurial doesn't prompt the
734 # used but instead uses a default value.
747 # used but instead uses a default value.
735 repo.ui.setconfig('ui', 'interactive', False)
748 repo.ui.setconfig('ui', 'interactive', False)
736
749
737 commands.merge(baseui, repo, rev=revision)
750 commands.merge(baseui, repo, rev=revision)
738
751
739 @reraise_safe_exceptions
752 @reraise_safe_exceptions
740 def commit(self, wire, message, username, close_branch=False):
753 def commit(self, wire, message, username, close_branch=False):
741 repo = self._factory.repo(wire)
754 repo = self._factory.repo(wire)
742 baseui = self._factory._create_config(wire['config'])
755 baseui = self._factory._create_config(wire['config'])
743 repo.ui.setconfig('ui', 'username', username)
756 repo.ui.setconfig('ui', 'username', username)
744 commands.commit(baseui, repo, message=message, close_branch=close_branch)
757 commands.commit(baseui, repo, message=message, close_branch=close_branch)
745
758
746 @reraise_safe_exceptions
759 @reraise_safe_exceptions
747 def rebase(self, wire, source=None, dest=None, abort=False):
760 def rebase(self, wire, source=None, dest=None, abort=False):
748 repo = self._factory.repo(wire)
761 repo = self._factory.repo(wire)
749 baseui = self._factory._create_config(wire['config'])
762 baseui = self._factory._create_config(wire['config'])
750 repo.ui.setconfig('ui', 'merge', 'internal:dump')
763 repo.ui.setconfig('ui', 'merge', 'internal:dump')
751 rebase.rebase(
764 rebase.rebase(
752 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
765 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
753
766
754 @reraise_safe_exceptions
767 @reraise_safe_exceptions
755 def bookmark(self, wire, bookmark, revision=None):
768 def bookmark(self, wire, bookmark, revision=None):
756 repo = self._factory.repo(wire)
769 repo = self._factory.repo(wire)
757 baseui = self._factory._create_config(wire['config'])
770 baseui = self._factory._create_config(wire['config'])
758 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
771 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
@@ -1,72 +1,75 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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode 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 import logging
18
19 log = logging.getLogger(__name__)
17
20
18
21
19 def safe_int(val, default=None):
22 def safe_int(val, default=None):
20 """
23 """
21 Returns int() of val if val is not convertable to int use default
24 Returns int() of val if val is not convertable to int use default
22 instead
25 instead
23
26
24 :param val:
27 :param val:
25 :param default:
28 :param default:
26 """
29 """
27
30
28 try:
31 try:
29 val = int(val)
32 val = int(val)
30 except (ValueError, TypeError):
33 except (ValueError, TypeError):
31 val = default
34 val = default
32
35
33 return val
36 return val
34
37
35
38
36 def safe_str(unicode_, to_encoding=['utf8']):
39 def safe_str(unicode_, to_encoding=['utf8']):
37 """
40 """
38 safe str function. Does few trick to turn unicode_ into string
41 safe str function. Does few trick to turn unicode_ into string
39
42
40 In case of UnicodeEncodeError, we try to return it with encoding detected
43 In case of UnicodeEncodeError, we try to return it with encoding detected
41 by chardet library if it fails fallback to string with errors replaced
44 by chardet library if it fails fallback to string with errors replaced
42
45
43 :param unicode_: unicode to encode
46 :param unicode_: unicode to encode
44 :rtype: str
47 :rtype: str
45 :returns: str object
48 :returns: str object
46 """
49 """
47
50
48 # if it's not basestr cast to str
51 # if it's not basestr cast to str
49 if not isinstance(unicode_, basestring):
52 if not isinstance(unicode_, basestring):
50 return str(unicode_)
53 return str(unicode_)
51
54
52 if isinstance(unicode_, str):
55 if isinstance(unicode_, str):
53 return unicode_
56 return unicode_
54
57
55 if not isinstance(to_encoding, (list, tuple)):
58 if not isinstance(to_encoding, (list, tuple)):
56 to_encoding = [to_encoding]
59 to_encoding = [to_encoding]
57
60
58 for enc in to_encoding:
61 for enc in to_encoding:
59 try:
62 try:
60 return unicode_.encode(enc)
63 return unicode_.encode(enc)
61 except UnicodeEncodeError:
64 except UnicodeEncodeError:
62 pass
65 pass
63
66
64 try:
67 try:
65 import chardet
68 import chardet
66 encoding = chardet.detect(unicode_)['encoding']
69 encoding = chardet.detect(unicode_)['encoding']
67 if encoding is None:
70 if encoding is None:
68 raise UnicodeEncodeError()
71 raise UnicodeEncodeError()
69
72
70 return unicode_.encode(encoding)
73 return unicode_.encode(encoding)
71 except (ImportError, UnicodeEncodeError):
74 except (ImportError, UnicodeEncodeError):
72 return unicode_.encode(to_encoding[0], 'replace')
75 return unicode_.encode(to_encoding[0], 'replace')
General Comments 0
You need to be logged in to leave comments. Login now