##// END OF EJS Templates

Compare Commits r97:e424cba775fe...r100:2582dee7dd4c

Target:

Source:

Time Author Commit Description
Martin Bornhold
r97:e424cba775fe
subrepo: Add patch to turn off mercurial subrepo handling.
Martin Bornhold
r98:2fa39df61008
subrepo: Add custom exception to indicate subrepo merge conflict.
Martin Bornhold
r99:e1008af8f033
subrepo: Turn off interactive mode when merging mercurial repo. If there is a merge conflict in the sub repositories mercurial will prompt the used to decide which version to use.
Martin Bornhold
r100:2582dee7dd4c
subrepo: Apply mercurial sub repository patch.
@@ -1,68 +1,70 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 """
18 """
19 Special exception handling over the wire.
19 Special exception handling over the wire.
20
20
21 Since we cannot assume that our client is able to import our exception classes,
21 Since we cannot assume that our client is able to import our exception classes,
22 this module provides a "wrapping" mechanism to raise plain exceptions
22 this module provides a "wrapping" mechanism to raise plain exceptions
23 which contain an extra attribute `_vcs_kind` to allow a client to distinguish
23 which contain an extra attribute `_vcs_kind` to allow a client to distinguish
24 different error conditions.
24 different error conditions.
25 """
25 """
26
26
27 import functools
27 import functools
28 from pyramid.httpexceptions import HTTPLocked
28 from pyramid.httpexceptions import HTTPLocked
29
29
30
30
31 def _make_exception(kind, *args):
31 def _make_exception(kind, *args):
32 """
32 """
33 Prepares a base `Exception` instance to be sent over the wire.
33 Prepares a base `Exception` instance to be sent over the wire.
34
34
35 To give our caller a hint what this is about, it will attach an attribute
35 To give our caller a hint what this is about, it will attach an attribute
36 `_vcs_kind` to the exception.
36 `_vcs_kind` to the exception.
37 """
37 """
38 exc = Exception(*args)
38 exc = Exception(*args)
39 exc._vcs_kind = kind
39 exc._vcs_kind = kind
40 return exc
40 return exc
41
41
42
42
43 AbortException = functools.partial(_make_exception, 'abort')
43 AbortException = functools.partial(_make_exception, 'abort')
44
44
45 ArchiveException = functools.partial(_make_exception, 'archive')
45 ArchiveException = functools.partial(_make_exception, 'archive')
46
46
47 LookupException = functools.partial(_make_exception, 'lookup')
47 LookupException = functools.partial(_make_exception, 'lookup')
48
48
49 VcsException = functools.partial(_make_exception, 'error')
49 VcsException = functools.partial(_make_exception, 'error')
50
50
51 RepositoryLockedException = functools.partial(_make_exception, 'repo_locked')
51 RepositoryLockedException = functools.partial(_make_exception, 'repo_locked')
52
52
53 RequirementException = functools.partial(_make_exception, 'requirement')
53 RequirementException = functools.partial(_make_exception, 'requirement')
54
54
55 UnhandledException = functools.partial(_make_exception, 'unhandled')
55 UnhandledException = functools.partial(_make_exception, 'unhandled')
56
56
57 URLError = functools.partial(_make_exception, 'url_error')
57 URLError = functools.partial(_make_exception, 'url_error')
58
58
59 SubrepoMergeException = functools.partial(_make_exception, 'subrepo_merge_error')
60
59
61
60 class HTTPRepoLocked(HTTPLocked):
62 class HTTPRepoLocked(HTTPLocked):
61 """
63 """
62 Subclass of HTTPLocked response that allows to set the title and status
64 Subclass of HTTPLocked response that allows to set the title and status
63 code via constructor arguments.
65 code via constructor arguments.
64 """
66 """
65 def __init__(self, title, status_code=None, **kwargs):
67 def __init__(self, title, status_code=None, **kwargs):
66 self.code = status_code or HTTPLocked.code
68 self.code = status_code or HTTPLocked.code
67 self.title = title
69 self.title = title
68 super(HTTPRepoLocked, self).__init__(**kwargs)
70 super(HTTPRepoLocked, self).__init__(**kwargs)
@@ -1,707 +1,714 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import sys
21 import sys
22 import urllib
22 import urllib
23 import urllib2
23 import urllib2
24
24
25 from hgext import largefiles, rebase
25 from hgext import largefiles, rebase
26 from hgext.strip import strip as hgext_strip
26 from hgext.strip import strip as hgext_strip
27 from mercurial import commands
27 from mercurial import commands
28 from mercurial import unionrepo
28 from mercurial import unionrepo
29
29
30 from vcsserver import exceptions
30 from vcsserver import exceptions
31 from vcsserver.base import RepoFactory
31 from vcsserver.base import RepoFactory
32 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex, hg_url,
33 archival, bin, clone, config as hgconfig, diffopts, hex, hg_url,
34 httpbasicauthhandler, httpdigestauthhandler, httppeer, localrepository,
34 httpbasicauthhandler, httpdigestauthhandler, httppeer, localrepository,
35 match, memctx, exchange, memfilectx, nullrev, patch, peer, revrange, ui,
35 match, memctx, exchange, memfilectx, nullrev, patch, peer, revrange, ui,
36 Abort, LookupError, RepoError, RepoLookupError, InterventionRequired,
36 Abort, LookupError, RepoError, RepoLookupError, InterventionRequired,
37 RequirementError)
37 RequirementError)
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 def make_ui_from_config(repo_config):
42 def make_ui_from_config(repo_config):
43 baseui = ui.ui()
43 baseui = ui.ui()
44
44
45 # clean the baseui object
45 # clean the baseui object
46 baseui._ocfg = hgconfig.config()
46 baseui._ocfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
49
49
50 for section, option, value in repo_config:
50 for section, option, value in repo_config:
51 baseui.setconfig(section, option, value)
51 baseui.setconfig(section, option, value)
52
52
53 # make our hgweb quiet so it doesn't print output
53 # make our hgweb quiet so it doesn't print output
54 baseui.setconfig('ui', 'quiet', 'true')
54 baseui.setconfig('ui', 'quiet', 'true')
55
55
56 # force mercurial to only use 1 thread, otherwise it may try to set a
56 # force mercurial to only use 1 thread, otherwise it may try to set a
57 # signal in a non-main thread, thus generating a ValueError.
57 # signal in a non-main thread, thus generating a ValueError.
58 baseui.setconfig('worker', 'numcpus', 1)
58 baseui.setconfig('worker', 'numcpus', 1)
59
59
60 # If there is no config for the largefiles extension, we explicitly disable
60 # If there is no config for the largefiles extension, we explicitly disable
61 # it here. This overrides settings from repositories hgrc file. Recent
61 # it here. This overrides settings from repositories hgrc file. Recent
62 # mercurial versions enable largefiles in hgrc on clone from largefile
62 # mercurial versions enable largefiles in hgrc on clone from largefile
63 # repo.
63 # repo.
64 if not baseui.hasconfig('extensions', 'largefiles'):
64 if not baseui.hasconfig('extensions', 'largefiles'):
65 log.debug('Explicitly disable largefiles extension for repo.')
65 log.debug('Explicitly disable largefiles extension for repo.')
66 baseui.setconfig('extensions', 'largefiles', '!')
66 baseui.setconfig('extensions', 'largefiles', '!')
67
67
68 return baseui
68 return baseui
69
69
70
70
71 def reraise_safe_exceptions(func):
71 def reraise_safe_exceptions(func):
72 """Decorator for converting mercurial exceptions to something neutral."""
72 """Decorator for converting mercurial exceptions to something neutral."""
73 def wrapper(*args, **kwargs):
73 def wrapper(*args, **kwargs):
74 try:
74 try:
75 return func(*args, **kwargs)
75 return func(*args, **kwargs)
76 except (Abort, InterventionRequired):
76 except (Abort, InterventionRequired):
77 raise_from_original(exceptions.AbortException)
77 raise_from_original(exceptions.AbortException)
78 except RepoLookupError:
78 except RepoLookupError:
79 raise_from_original(exceptions.LookupException)
79 raise_from_original(exceptions.LookupException)
80 except RequirementError:
80 except RequirementError:
81 raise_from_original(exceptions.RequirementException)
81 raise_from_original(exceptions.RequirementException)
82 except RepoError:
82 except RepoError:
83 raise_from_original(exceptions.VcsException)
83 raise_from_original(exceptions.VcsException)
84 except LookupError:
84 except LookupError:
85 raise_from_original(exceptions.LookupException)
85 raise_from_original(exceptions.LookupException)
86 except Exception as e:
86 except Exception as e:
87 if not hasattr(e, '_vcs_kind'):
87 if not hasattr(e, '_vcs_kind'):
88 log.exception("Unhandled exception in hg remote call")
88 log.exception("Unhandled exception in hg remote call")
89 raise_from_original(exceptions.UnhandledException)
89 raise_from_original(exceptions.UnhandledException)
90 raise
90 raise
91 return wrapper
91 return wrapper
92
92
93
93
94 def raise_from_original(new_type):
94 def raise_from_original(new_type):
95 """
95 """
96 Raise a new exception type with original args and traceback.
96 Raise a new exception type with original args and traceback.
97 """
97 """
98 _, original, traceback = sys.exc_info()
98 _, original, traceback = sys.exc_info()
99 try:
99 try:
100 raise new_type(*original.args), None, traceback
100 raise new_type(*original.args), None, traceback
101 finally:
101 finally:
102 del traceback
102 del traceback
103
103
104
104
105 class MercurialFactory(RepoFactory):
105 class MercurialFactory(RepoFactory):
106
106
107 def _create_config(self, config, hooks=True):
107 def _create_config(self, config, hooks=True):
108 if not hooks:
108 if not hooks:
109 hooks_to_clean = frozenset((
109 hooks_to_clean = frozenset((
110 'changegroup.repo_size', 'preoutgoing.pre_pull',
110 'changegroup.repo_size', 'preoutgoing.pre_pull',
111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
111 'outgoing.pull_logger', 'prechangegroup.pre_push'))
112 new_config = []
112 new_config = []
113 for section, option, value in config:
113 for section, option, value in config:
114 if section == 'hooks' and option in hooks_to_clean:
114 if section == 'hooks' and option in hooks_to_clean:
115 continue
115 continue
116 new_config.append((section, option, value))
116 new_config.append((section, option, value))
117 config = new_config
117 config = new_config
118
118
119 baseui = make_ui_from_config(config)
119 baseui = make_ui_from_config(config)
120 return baseui
120 return baseui
121
121
122 def _create_repo(self, wire, create):
122 def _create_repo(self, wire, create):
123 baseui = self._create_config(wire["config"])
123 baseui = self._create_config(wire["config"])
124 return localrepository(baseui, wire["path"], create)
124 return localrepository(baseui, wire["path"], create)
125
125
126
126
127 class HgRemote(object):
127 class HgRemote(object):
128
128
129 def __init__(self, factory):
129 def __init__(self, factory):
130 self._factory = factory
130 self._factory = factory
131
131
132 self._bulk_methods = {
132 self._bulk_methods = {
133 "affected_files": self.ctx_files,
133 "affected_files": self.ctx_files,
134 "author": self.ctx_user,
134 "author": self.ctx_user,
135 "branch": self.ctx_branch,
135 "branch": self.ctx_branch,
136 "children": self.ctx_children,
136 "children": self.ctx_children,
137 "date": self.ctx_date,
137 "date": self.ctx_date,
138 "message": self.ctx_description,
138 "message": self.ctx_description,
139 "parents": self.ctx_parents,
139 "parents": self.ctx_parents,
140 "status": self.ctx_status,
140 "status": self.ctx_status,
141 "_file_paths": self.ctx_list,
141 "_file_paths": self.ctx_list,
142 }
142 }
143
143
144 @reraise_safe_exceptions
144 @reraise_safe_exceptions
145 def archive_repo(self, archive_path, mtime, file_info, kind):
145 def archive_repo(self, archive_path, mtime, file_info, kind):
146 if kind == "tgz":
146 if kind == "tgz":
147 archiver = archival.tarit(archive_path, mtime, "gz")
147 archiver = archival.tarit(archive_path, mtime, "gz")
148 elif kind == "tbz2":
148 elif kind == "tbz2":
149 archiver = archival.tarit(archive_path, mtime, "bz2")
149 archiver = archival.tarit(archive_path, mtime, "bz2")
150 elif kind == 'zip':
150 elif kind == 'zip':
151 archiver = archival.zipit(archive_path, mtime)
151 archiver = archival.zipit(archive_path, mtime)
152 else:
152 else:
153 raise exceptions.ArchiveException(
153 raise exceptions.ArchiveException(
154 'Remote does not support: "%s".' % kind)
154 'Remote does not support: "%s".' % kind)
155
155
156 for f_path, f_mode, f_is_link, f_content in file_info:
156 for f_path, f_mode, f_is_link, f_content in file_info:
157 archiver.addfile(f_path, f_mode, f_is_link, f_content)
157 archiver.addfile(f_path, f_mode, f_is_link, f_content)
158 archiver.done()
158 archiver.done()
159
159
160 @reraise_safe_exceptions
160 @reraise_safe_exceptions
161 def bookmarks(self, wire):
161 def bookmarks(self, wire):
162 repo = self._factory.repo(wire)
162 repo = self._factory.repo(wire)
163 return dict(repo._bookmarks)
163 return dict(repo._bookmarks)
164
164
165 @reraise_safe_exceptions
165 @reraise_safe_exceptions
166 def branches(self, wire, normal, closed):
166 def branches(self, wire, normal, closed):
167 repo = self._factory.repo(wire)
167 repo = self._factory.repo(wire)
168 iter_branches = repo.branchmap().iterbranches()
168 iter_branches = repo.branchmap().iterbranches()
169 bt = {}
169 bt = {}
170 for branch_name, _heads, tip, is_closed in iter_branches:
170 for branch_name, _heads, tip, is_closed in iter_branches:
171 if normal and not is_closed:
171 if normal and not is_closed:
172 bt[branch_name] = tip
172 bt[branch_name] = tip
173 if closed and is_closed:
173 if closed and is_closed:
174 bt[branch_name] = tip
174 bt[branch_name] = tip
175
175
176 return bt
176 return bt
177
177
178 @reraise_safe_exceptions
178 @reraise_safe_exceptions
179 def bulk_request(self, wire, rev, pre_load):
179 def bulk_request(self, wire, rev, pre_load):
180 result = {}
180 result = {}
181 for attr in pre_load:
181 for attr in pre_load:
182 try:
182 try:
183 method = self._bulk_methods[attr]
183 method = self._bulk_methods[attr]
184 result[attr] = method(wire, rev)
184 result[attr] = method(wire, rev)
185 except KeyError:
185 except KeyError:
186 raise exceptions.VcsException(
186 raise exceptions.VcsException(
187 'Unknown bulk attribute: "%s"' % attr)
187 'Unknown bulk attribute: "%s"' % attr)
188 return result
188 return result
189
189
190 @reraise_safe_exceptions
190 @reraise_safe_exceptions
191 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
191 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
192 baseui = self._factory._create_config(wire["config"], hooks=hooks)
192 baseui = self._factory._create_config(wire["config"], hooks=hooks)
193 clone(baseui, source, dest, noupdate=not update_after_clone)
193 clone(baseui, source, dest, noupdate=not update_after_clone)
194
194
195 @reraise_safe_exceptions
195 @reraise_safe_exceptions
196 def commitctx(
196 def commitctx(
197 self, wire, message, parents, commit_time, commit_timezone,
197 self, wire, message, parents, commit_time, commit_timezone,
198 user, files, extra, removed, updated):
198 user, files, extra, removed, updated):
199
199
200 def _filectxfn(_repo, memctx, path):
200 def _filectxfn(_repo, memctx, path):
201 """
201 """
202 Marks given path as added/changed/removed in a given _repo. This is
202 Marks given path as added/changed/removed in a given _repo. This is
203 for internal mercurial commit function.
203 for internal mercurial commit function.
204 """
204 """
205
205
206 # check if this path is removed
206 # check if this path is removed
207 if path in removed:
207 if path in removed:
208 # returning None is a way to mark node for removal
208 # returning None is a way to mark node for removal
209 return None
209 return None
210
210
211 # check if this path is added
211 # check if this path is added
212 for node in updated:
212 for node in updated:
213 if node['path'] == path:
213 if node['path'] == path:
214 return memfilectx(
214 return memfilectx(
215 _repo,
215 _repo,
216 path=node['path'],
216 path=node['path'],
217 data=node['content'],
217 data=node['content'],
218 islink=False,
218 islink=False,
219 isexec=bool(node['mode'] & stat.S_IXUSR),
219 isexec=bool(node['mode'] & stat.S_IXUSR),
220 copied=False,
220 copied=False,
221 memctx=memctx)
221 memctx=memctx)
222
222
223 raise exceptions.AbortException(
223 raise exceptions.AbortException(
224 "Given path haven't been marked as added, "
224 "Given path haven't been marked as added, "
225 "changed or removed (%s)" % path)
225 "changed or removed (%s)" % path)
226
226
227 repo = self._factory.repo(wire)
227 repo = self._factory.repo(wire)
228
228
229 commit_ctx = memctx(
229 commit_ctx = memctx(
230 repo=repo,
230 repo=repo,
231 parents=parents,
231 parents=parents,
232 text=message,
232 text=message,
233 files=files,
233 files=files,
234 filectxfn=_filectxfn,
234 filectxfn=_filectxfn,
235 user=user,
235 user=user,
236 date=(commit_time, commit_timezone),
236 date=(commit_time, commit_timezone),
237 extra=extra)
237 extra=extra)
238
238
239 n = repo.commitctx(commit_ctx)
239 n = repo.commitctx(commit_ctx)
240 new_id = hex(n)
240 new_id = hex(n)
241
241
242 return new_id
242 return new_id
243
243
244 @reraise_safe_exceptions
244 @reraise_safe_exceptions
245 def ctx_branch(self, wire, revision):
245 def ctx_branch(self, wire, revision):
246 repo = self._factory.repo(wire)
246 repo = self._factory.repo(wire)
247 ctx = repo[revision]
247 ctx = repo[revision]
248 return ctx.branch()
248 return ctx.branch()
249
249
250 @reraise_safe_exceptions
250 @reraise_safe_exceptions
251 def ctx_children(self, wire, revision):
251 def ctx_children(self, wire, revision):
252 repo = self._factory.repo(wire)
252 repo = self._factory.repo(wire)
253 ctx = repo[revision]
253 ctx = repo[revision]
254 return [child.rev() for child in ctx.children()]
254 return [child.rev() for child in ctx.children()]
255
255
256 @reraise_safe_exceptions
256 @reraise_safe_exceptions
257 def ctx_date(self, wire, revision):
257 def ctx_date(self, wire, revision):
258 repo = self._factory.repo(wire)
258 repo = self._factory.repo(wire)
259 ctx = repo[revision]
259 ctx = repo[revision]
260 return ctx.date()
260 return ctx.date()
261
261
262 @reraise_safe_exceptions
262 @reraise_safe_exceptions
263 def ctx_description(self, wire, revision):
263 def ctx_description(self, wire, revision):
264 repo = self._factory.repo(wire)
264 repo = self._factory.repo(wire)
265 ctx = repo[revision]
265 ctx = repo[revision]
266 return ctx.description()
266 return ctx.description()
267
267
268 @reraise_safe_exceptions
268 @reraise_safe_exceptions
269 def ctx_diff(
269 def ctx_diff(
270 self, wire, revision, git=True, ignore_whitespace=True, context=3):
270 self, wire, revision, git=True, ignore_whitespace=True, context=3):
271 repo = self._factory.repo(wire)
271 repo = self._factory.repo(wire)
272 ctx = repo[revision]
272 ctx = repo[revision]
273 result = ctx.diff(
273 result = ctx.diff(
274 git=git, ignore_whitespace=ignore_whitespace, context=context)
274 git=git, ignore_whitespace=ignore_whitespace, context=context)
275 return list(result)
275 return list(result)
276
276
277 @reraise_safe_exceptions
277 @reraise_safe_exceptions
278 def ctx_files(self, wire, revision):
278 def ctx_files(self, wire, revision):
279 repo = self._factory.repo(wire)
279 repo = self._factory.repo(wire)
280 ctx = repo[revision]
280 ctx = repo[revision]
281 return ctx.files()
281 return ctx.files()
282
282
283 @reraise_safe_exceptions
283 @reraise_safe_exceptions
284 def ctx_list(self, path, revision):
284 def ctx_list(self, path, revision):
285 repo = self._factory.repo(path)
285 repo = self._factory.repo(path)
286 ctx = repo[revision]
286 ctx = repo[revision]
287 return list(ctx)
287 return list(ctx)
288
288
289 @reraise_safe_exceptions
289 @reraise_safe_exceptions
290 def ctx_parents(self, wire, revision):
290 def ctx_parents(self, wire, revision):
291 repo = self._factory.repo(wire)
291 repo = self._factory.repo(wire)
292 ctx = repo[revision]
292 ctx = repo[revision]
293 return [parent.rev() for parent in ctx.parents()]
293 return [parent.rev() for parent in ctx.parents()]
294
294
295 @reraise_safe_exceptions
295 @reraise_safe_exceptions
296 def ctx_substate(self, wire, revision):
296 def ctx_substate(self, wire, revision):
297 repo = self._factory.repo(wire)
297 repo = self._factory.repo(wire)
298 ctx = repo[revision]
298 ctx = repo[revision]
299 return ctx.substate
299 return ctx.substate
300
300
301 @reraise_safe_exceptions
301 @reraise_safe_exceptions
302 def ctx_status(self, wire, revision):
302 def ctx_status(self, wire, revision):
303 repo = self._factory.repo(wire)
303 repo = self._factory.repo(wire)
304 ctx = repo[revision]
304 ctx = repo[revision]
305 status = repo[ctx.p1().node()].status(other=ctx.node())
305 status = repo[ctx.p1().node()].status(other=ctx.node())
306 # object of status (odd, custom named tuple in mercurial) is not
306 # object of status (odd, custom named tuple in mercurial) is not
307 # correctly serializable via Pyro, we make it a list, as the underling
307 # correctly serializable via Pyro, we make it a list, as the underling
308 # API expects this to be a list
308 # API expects this to be a list
309 return list(status)
309 return list(status)
310
310
311 @reraise_safe_exceptions
311 @reraise_safe_exceptions
312 def ctx_user(self, wire, revision):
312 def ctx_user(self, wire, revision):
313 repo = self._factory.repo(wire)
313 repo = self._factory.repo(wire)
314 ctx = repo[revision]
314 ctx = repo[revision]
315 return ctx.user()
315 return ctx.user()
316
316
317 @reraise_safe_exceptions
317 @reraise_safe_exceptions
318 def check_url(self, url, config):
318 def check_url(self, url, config):
319 log.info("Checking URL for remote cloning/import: %s", url)
319 log.info("Checking URL for remote cloning/import: %s", url)
320 _proto = None
320 _proto = None
321 if '+' in url[:url.find('://')]:
321 if '+' in url[:url.find('://')]:
322 _proto = url[0:url.find('+')]
322 _proto = url[0:url.find('+')]
323 url = url[url.find('+') + 1:]
323 url = url[url.find('+') + 1:]
324 handlers = []
324 handlers = []
325 url_obj = hg_url(url)
325 url_obj = hg_url(url)
326 test_uri, authinfo = url_obj.authinfo()
326 test_uri, authinfo = url_obj.authinfo()
327 url_obj.passwd = '*****'
327 url_obj.passwd = '*****'
328 cleaned_uri = str(url_obj)
328 cleaned_uri = str(url_obj)
329
329
330 if authinfo:
330 if authinfo:
331 # create a password manager
331 # create a password manager
332 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
332 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
333 passmgr.add_password(*authinfo)
333 passmgr.add_password(*authinfo)
334
334
335 handlers.extend((httpbasicauthhandler(passmgr),
335 handlers.extend((httpbasicauthhandler(passmgr),
336 httpdigestauthhandler(passmgr)))
336 httpdigestauthhandler(passmgr)))
337
337
338 o = urllib2.build_opener(*handlers)
338 o = urllib2.build_opener(*handlers)
339 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
339 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
340 ('Accept', 'application/mercurial-0.1')]
340 ('Accept', 'application/mercurial-0.1')]
341
341
342 q = {"cmd": 'between'}
342 q = {"cmd": 'between'}
343 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
343 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
344 qs = '?%s' % urllib.urlencode(q)
344 qs = '?%s' % urllib.urlencode(q)
345 cu = "%s%s" % (test_uri, qs)
345 cu = "%s%s" % (test_uri, qs)
346 req = urllib2.Request(cu, None, {})
346 req = urllib2.Request(cu, None, {})
347
347
348 try:
348 try:
349 log.debug("Trying to open URL %s", url)
349 log.debug("Trying to open URL %s", url)
350 resp = o.open(req)
350 resp = o.open(req)
351 if resp.code != 200:
351 if resp.code != 200:
352 raise exceptions.URLError('Return Code is not 200')
352 raise exceptions.URLError('Return Code is not 200')
353 except Exception as e:
353 except Exception as e:
354 log.warning("URL cannot be opened: %s", url, exc_info=True)
354 log.warning("URL cannot be opened: %s", url, exc_info=True)
355 # means it cannot be cloned
355 # means it cannot be cloned
356 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
356 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
357
357
358 # now check if it's a proper hg repo, but don't do it for svn
358 # now check if it's a proper hg repo, but don't do it for svn
359 try:
359 try:
360 if _proto == 'svn':
360 if _proto == 'svn':
361 pass
361 pass
362 else:
362 else:
363 # check for pure hg repos
363 # check for pure hg repos
364 log.debug(
364 log.debug(
365 "Verifying if URL is a Mercurial repository: %s", url)
365 "Verifying if URL is a Mercurial repository: %s", url)
366 httppeer(make_ui_from_config(config), url).lookup('tip')
366 httppeer(make_ui_from_config(config), url).lookup('tip')
367 except Exception as e:
367 except Exception as e:
368 log.warning("URL is not a valid Mercurial repository: %s", url)
368 log.warning("URL is not a valid Mercurial repository: %s", url)
369 raise exceptions.URLError(
369 raise exceptions.URLError(
370 "url [%s] does not look like an hg repo org_exc: %s"
370 "url [%s] does not look like an hg repo org_exc: %s"
371 % (cleaned_uri, e))
371 % (cleaned_uri, e))
372
372
373 log.info("URL is a valid Mercurial repository: %s", url)
373 log.info("URL is a valid Mercurial repository: %s", url)
374 return True
374 return True
375
375
376 @reraise_safe_exceptions
376 @reraise_safe_exceptions
377 def diff(
377 def diff(
378 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
378 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
379 context):
379 context):
380 repo = self._factory.repo(wire)
380 repo = self._factory.repo(wire)
381
381
382 if file_filter:
382 if file_filter:
383 filter = match(file_filter[0], '', [file_filter[1]])
383 filter = match(file_filter[0], '', [file_filter[1]])
384 else:
384 else:
385 filter = file_filter
385 filter = file_filter
386 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
386 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
387
387
388 try:
388 try:
389 return "".join(patch.diff(
389 return "".join(patch.diff(
390 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
390 repo, node1=rev1, node2=rev2, match=filter, opts=opts))
391 except RepoLookupError:
391 except RepoLookupError:
392 raise exceptions.LookupException()
392 raise exceptions.LookupException()
393
393
394 @reraise_safe_exceptions
394 @reraise_safe_exceptions
395 def file_history(self, wire, revision, path, limit):
395 def file_history(self, wire, revision, path, limit):
396 repo = self._factory.repo(wire)
396 repo = self._factory.repo(wire)
397
397
398 ctx = repo[revision]
398 ctx = repo[revision]
399 fctx = ctx.filectx(path)
399 fctx = ctx.filectx(path)
400
400
401 def history_iter():
401 def history_iter():
402 limit_rev = fctx.rev()
402 limit_rev = fctx.rev()
403 for obj in reversed(list(fctx.filelog())):
403 for obj in reversed(list(fctx.filelog())):
404 obj = fctx.filectx(obj)
404 obj = fctx.filectx(obj)
405 if limit_rev >= obj.rev():
405 if limit_rev >= obj.rev():
406 yield obj
406 yield obj
407
407
408 history = []
408 history = []
409 for cnt, obj in enumerate(history_iter()):
409 for cnt, obj in enumerate(history_iter()):
410 if limit and cnt >= limit:
410 if limit and cnt >= limit:
411 break
411 break
412 history.append(hex(obj.node()))
412 history.append(hex(obj.node()))
413
413
414 return [x for x in history]
414 return [x for x in history]
415
415
416 @reraise_safe_exceptions
416 @reraise_safe_exceptions
417 def file_history_untill(self, wire, revision, path, limit):
417 def file_history_untill(self, wire, revision, path, limit):
418 repo = self._factory.repo(wire)
418 repo = self._factory.repo(wire)
419 ctx = repo[revision]
419 ctx = repo[revision]
420 fctx = ctx.filectx(path)
420 fctx = ctx.filectx(path)
421
421
422 file_log = list(fctx.filelog())
422 file_log = list(fctx.filelog())
423 if limit:
423 if limit:
424 # Limit to the last n items
424 # Limit to the last n items
425 file_log = file_log[-limit:]
425 file_log = file_log[-limit:]
426
426
427 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
427 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
428
428
429 @reraise_safe_exceptions
429 @reraise_safe_exceptions
430 def fctx_annotate(self, wire, revision, path):
430 def fctx_annotate(self, wire, revision, path):
431 repo = self._factory.repo(wire)
431 repo = self._factory.repo(wire)
432 ctx = repo[revision]
432 ctx = repo[revision]
433 fctx = ctx.filectx(path)
433 fctx = ctx.filectx(path)
434
434
435 result = []
435 result = []
436 for i, annotate_data in enumerate(fctx.annotate()):
436 for i, annotate_data in enumerate(fctx.annotate()):
437 ln_no = i + 1
437 ln_no = i + 1
438 sha = hex(annotate_data[0].node())
438 sha = hex(annotate_data[0].node())
439 result.append((ln_no, sha, annotate_data[1]))
439 result.append((ln_no, sha, annotate_data[1]))
440 return result
440 return result
441
441
442 @reraise_safe_exceptions
442 @reraise_safe_exceptions
443 def fctx_data(self, wire, revision, path):
443 def fctx_data(self, wire, revision, path):
444 repo = self._factory.repo(wire)
444 repo = self._factory.repo(wire)
445 ctx = repo[revision]
445 ctx = repo[revision]
446 fctx = ctx.filectx(path)
446 fctx = ctx.filectx(path)
447 return fctx.data()
447 return fctx.data()
448
448
449 @reraise_safe_exceptions
449 @reraise_safe_exceptions
450 def fctx_flags(self, wire, revision, path):
450 def fctx_flags(self, wire, revision, path):
451 repo = self._factory.repo(wire)
451 repo = self._factory.repo(wire)
452 ctx = repo[revision]
452 ctx = repo[revision]
453 fctx = ctx.filectx(path)
453 fctx = ctx.filectx(path)
454 return fctx.flags()
454 return fctx.flags()
455
455
456 @reraise_safe_exceptions
456 @reraise_safe_exceptions
457 def fctx_size(self, wire, revision, path):
457 def fctx_size(self, wire, revision, path):
458 repo = self._factory.repo(wire)
458 repo = self._factory.repo(wire)
459 ctx = repo[revision]
459 ctx = repo[revision]
460 fctx = ctx.filectx(path)
460 fctx = ctx.filectx(path)
461 return fctx.size()
461 return fctx.size()
462
462
463 @reraise_safe_exceptions
463 @reraise_safe_exceptions
464 def get_all_commit_ids(self, wire, name):
464 def get_all_commit_ids(self, wire, name):
465 repo = self._factory.repo(wire)
465 repo = self._factory.repo(wire)
466 revs = repo.filtered(name).changelog.index
466 revs = repo.filtered(name).changelog.index
467 return map(lambda x: hex(x[7]), revs)[:-1]
467 return map(lambda x: hex(x[7]), revs)[:-1]
468
468
469 @reraise_safe_exceptions
469 @reraise_safe_exceptions
470 def get_config_value(self, wire, section, name, untrusted=False):
470 def get_config_value(self, wire, section, name, untrusted=False):
471 repo = self._factory.repo(wire)
471 repo = self._factory.repo(wire)
472 return repo.ui.config(section, name, untrusted=untrusted)
472 return repo.ui.config(section, name, untrusted=untrusted)
473
473
474 @reraise_safe_exceptions
474 @reraise_safe_exceptions
475 def get_config_bool(self, wire, section, name, untrusted=False):
475 def get_config_bool(self, wire, section, name, untrusted=False):
476 repo = self._factory.repo(wire)
476 repo = self._factory.repo(wire)
477 return repo.ui.configbool(section, name, untrusted=untrusted)
477 return repo.ui.configbool(section, name, untrusted=untrusted)
478
478
479 @reraise_safe_exceptions
479 @reraise_safe_exceptions
480 def get_config_list(self, wire, section, name, untrusted=False):
480 def get_config_list(self, wire, section, name, untrusted=False):
481 repo = self._factory.repo(wire)
481 repo = self._factory.repo(wire)
482 return repo.ui.configlist(section, name, untrusted=untrusted)
482 return repo.ui.configlist(section, name, untrusted=untrusted)
483
483
484 @reraise_safe_exceptions
484 @reraise_safe_exceptions
485 def is_large_file(self, wire, path):
485 def is_large_file(self, wire, path):
486 return largefiles.lfutil.isstandin(path)
486 return largefiles.lfutil.isstandin(path)
487
487
488 @reraise_safe_exceptions
488 @reraise_safe_exceptions
489 def in_store(self, wire, sha):
489 def in_store(self, wire, sha):
490 repo = self._factory.repo(wire)
490 repo = self._factory.repo(wire)
491 return largefiles.lfutil.instore(repo, sha)
491 return largefiles.lfutil.instore(repo, sha)
492
492
493 @reraise_safe_exceptions
493 @reraise_safe_exceptions
494 def in_user_cache(self, wire, sha):
494 def in_user_cache(self, wire, sha):
495 repo = self._factory.repo(wire)
495 repo = self._factory.repo(wire)
496 return largefiles.lfutil.inusercache(repo.ui, sha)
496 return largefiles.lfutil.inusercache(repo.ui, sha)
497
497
498 @reraise_safe_exceptions
498 @reraise_safe_exceptions
499 def store_path(self, wire, sha):
499 def store_path(self, wire, sha):
500 repo = self._factory.repo(wire)
500 repo = self._factory.repo(wire)
501 return largefiles.lfutil.storepath(repo, sha)
501 return largefiles.lfutil.storepath(repo, sha)
502
502
503 @reraise_safe_exceptions
503 @reraise_safe_exceptions
504 def link(self, wire, sha, path):
504 def link(self, wire, sha, path):
505 repo = self._factory.repo(wire)
505 repo = self._factory.repo(wire)
506 largefiles.lfutil.link(
506 largefiles.lfutil.link(
507 largefiles.lfutil.usercachepath(repo.ui, sha), path)
507 largefiles.lfutil.usercachepath(repo.ui, sha), path)
508
508
509 @reraise_safe_exceptions
509 @reraise_safe_exceptions
510 def localrepository(self, wire, create=False):
510 def localrepository(self, wire, create=False):
511 self._factory.repo(wire, create=create)
511 self._factory.repo(wire, create=create)
512
512
513 @reraise_safe_exceptions
513 @reraise_safe_exceptions
514 def lookup(self, wire, revision, both):
514 def lookup(self, wire, revision, both):
515 # TODO Paris: Ugly hack to "deserialize" long for msgpack
515 # TODO Paris: Ugly hack to "deserialize" long for msgpack
516 if isinstance(revision, float):
516 if isinstance(revision, float):
517 revision = long(revision)
517 revision = long(revision)
518 repo = self._factory.repo(wire)
518 repo = self._factory.repo(wire)
519 try:
519 try:
520 ctx = repo[revision]
520 ctx = repo[revision]
521 except RepoLookupError:
521 except RepoLookupError:
522 raise exceptions.LookupException(revision)
522 raise exceptions.LookupException(revision)
523 except LookupError as e:
523 except LookupError as e:
524 raise exceptions.LookupException(e.name)
524 raise exceptions.LookupException(e.name)
525
525
526 if not both:
526 if not both:
527 return ctx.hex()
527 return ctx.hex()
528
528
529 ctx = repo[ctx.hex()]
529 ctx = repo[ctx.hex()]
530 return ctx.hex(), ctx.rev()
530 return ctx.hex(), ctx.rev()
531
531
532 @reraise_safe_exceptions
532 @reraise_safe_exceptions
533 def pull(self, wire, url, commit_ids=None):
533 def pull(self, wire, url, commit_ids=None):
534 repo = self._factory.repo(wire)
534 repo = self._factory.repo(wire)
535 remote = peer(repo, {}, url)
535 remote = peer(repo, {}, url)
536 if commit_ids:
536 if commit_ids:
537 commit_ids = [bin(commit_id) for commit_id in commit_ids]
537 commit_ids = [bin(commit_id) for commit_id in commit_ids]
538
538
539 return exchange.pull(
539 return exchange.pull(
540 repo, remote, heads=commit_ids, force=None).cgresult
540 repo, remote, heads=commit_ids, force=None).cgresult
541
541
542 @reraise_safe_exceptions
542 @reraise_safe_exceptions
543 def revision(self, wire, rev):
543 def revision(self, wire, rev):
544 repo = self._factory.repo(wire)
544 repo = self._factory.repo(wire)
545 ctx = repo[rev]
545 ctx = repo[rev]
546 return ctx.rev()
546 return ctx.rev()
547
547
548 @reraise_safe_exceptions
548 @reraise_safe_exceptions
549 def rev_range(self, wire, filter):
549 def rev_range(self, wire, filter):
550 repo = self._factory.repo(wire)
550 repo = self._factory.repo(wire)
551 revisions = [rev for rev in revrange(repo, filter)]
551 revisions = [rev for rev in revrange(repo, filter)]
552 return revisions
552 return revisions
553
553
554 @reraise_safe_exceptions
554 @reraise_safe_exceptions
555 def rev_range_hash(self, wire, node):
555 def rev_range_hash(self, wire, node):
556 repo = self._factory.repo(wire)
556 repo = self._factory.repo(wire)
557
557
558 def get_revs(repo, rev_opt):
558 def get_revs(repo, rev_opt):
559 if rev_opt:
559 if rev_opt:
560 revs = revrange(repo, rev_opt)
560 revs = revrange(repo, rev_opt)
561 if len(revs) == 0:
561 if len(revs) == 0:
562 return (nullrev, nullrev)
562 return (nullrev, nullrev)
563 return max(revs), min(revs)
563 return max(revs), min(revs)
564 else:
564 else:
565 return len(repo) - 1, 0
565 return len(repo) - 1, 0
566
566
567 stop, start = get_revs(repo, [node + ':'])
567 stop, start = get_revs(repo, [node + ':'])
568 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
568 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
569 return revs
569 return revs
570
570
571 @reraise_safe_exceptions
571 @reraise_safe_exceptions
572 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
572 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
573 other_path = kwargs.pop('other_path', None)
573 other_path = kwargs.pop('other_path', None)
574
574
575 # case when we want to compare two independent repositories
575 # case when we want to compare two independent repositories
576 if other_path and other_path != wire["path"]:
576 if other_path and other_path != wire["path"]:
577 baseui = self._factory._create_config(wire["config"])
577 baseui = self._factory._create_config(wire["config"])
578 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
578 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
579 else:
579 else:
580 repo = self._factory.repo(wire)
580 repo = self._factory.repo(wire)
581 return list(repo.revs(rev_spec, *args))
581 return list(repo.revs(rev_spec, *args))
582
582
583 @reraise_safe_exceptions
583 @reraise_safe_exceptions
584 def strip(self, wire, revision, update, backup):
584 def strip(self, wire, revision, update, backup):
585 repo = self._factory.repo(wire)
585 repo = self._factory.repo(wire)
586 ctx = repo[revision]
586 ctx = repo[revision]
587 hgext_strip(
587 hgext_strip(
588 repo.baseui, repo, ctx.node(), update=update, backup=backup)
588 repo.baseui, repo, ctx.node(), update=update, backup=backup)
589
589
590 @reraise_safe_exceptions
590 @reraise_safe_exceptions
591 def tag(self, wire, name, revision, message, local, user,
591 def tag(self, wire, name, revision, message, local, user,
592 tag_time, tag_timezone):
592 tag_time, tag_timezone):
593 repo = self._factory.repo(wire)
593 repo = self._factory.repo(wire)
594 ctx = repo[revision]
594 ctx = repo[revision]
595 node = ctx.node()
595 node = ctx.node()
596
596
597 date = (tag_time, tag_timezone)
597 date = (tag_time, tag_timezone)
598 try:
598 try:
599 repo.tag(name, node, message, local, user, date)
599 repo.tag(name, node, message, local, user, date)
600 except Abort:
600 except Abort:
601 log.exception("Tag operation aborted")
601 log.exception("Tag operation aborted")
602 raise exceptions.AbortException()
602 raise exceptions.AbortException()
603
603
604 @reraise_safe_exceptions
604 @reraise_safe_exceptions
605 def tags(self, wire):
605 def tags(self, wire):
606 repo = self._factory.repo(wire)
606 repo = self._factory.repo(wire)
607 return repo.tags()
607 return repo.tags()
608
608
609 @reraise_safe_exceptions
609 @reraise_safe_exceptions
610 def update(self, wire, node=None, clean=False):
610 def update(self, wire, node=None, clean=False):
611 repo = self._factory.repo(wire)
611 repo = self._factory.repo(wire)
612 baseui = self._factory._create_config(wire['config'])
612 baseui = self._factory._create_config(wire['config'])
613 commands.update(baseui, repo, node=node, clean=clean)
613 commands.update(baseui, repo, node=node, clean=clean)
614
614
615 @reraise_safe_exceptions
615 @reraise_safe_exceptions
616 def identify(self, wire):
616 def identify(self, wire):
617 repo = self._factory.repo(wire)
617 repo = self._factory.repo(wire)
618 baseui = self._factory._create_config(wire['config'])
618 baseui = self._factory._create_config(wire['config'])
619 output = io.BytesIO()
619 output = io.BytesIO()
620 baseui.write = output.write
620 baseui.write = output.write
621 # This is required to get a full node id
621 # This is required to get a full node id
622 baseui.debugflag = True
622 baseui.debugflag = True
623 commands.identify(baseui, repo, id=True)
623 commands.identify(baseui, repo, id=True)
624
624
625 return output.getvalue()
625 return output.getvalue()
626
626
627 @reraise_safe_exceptions
627 @reraise_safe_exceptions
628 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
628 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
629 hooks=True):
629 hooks=True):
630 repo = self._factory.repo(wire)
630 repo = self._factory.repo(wire)
631 baseui = self._factory._create_config(wire['config'], hooks=hooks)
631 baseui = self._factory._create_config(wire['config'], hooks=hooks)
632
632
633 # Mercurial internally has a lot of logic that checks ONLY if
633 # Mercurial internally has a lot of logic that checks ONLY if
634 # option is defined, we just pass those if they are defined then
634 # option is defined, we just pass those if they are defined then
635 opts = {}
635 opts = {}
636 if bookmark:
636 if bookmark:
637 opts['bookmark'] = bookmark
637 opts['bookmark'] = bookmark
638 if branch:
638 if branch:
639 opts['branch'] = branch
639 opts['branch'] = branch
640 if revision:
640 if revision:
641 opts['rev'] = revision
641 opts['rev'] = revision
642
642
643 commands.pull(baseui, repo, source, **opts)
643 commands.pull(baseui, repo, source, **opts)
644
644
645 @reraise_safe_exceptions
645 @reraise_safe_exceptions
646 def heads(self, wire, branch=None):
646 def heads(self, wire, branch=None):
647 repo = self._factory.repo(wire)
647 repo = self._factory.repo(wire)
648 baseui = self._factory._create_config(wire['config'])
648 baseui = self._factory._create_config(wire['config'])
649 output = io.BytesIO()
649 output = io.BytesIO()
650
650
651 def write(data, **unused_kwargs):
651 def write(data, **unused_kwargs):
652 output.write(data)
652 output.write(data)
653
653
654 baseui.write = write
654 baseui.write = write
655 if branch:
655 if branch:
656 args = [branch]
656 args = [branch]
657 else:
657 else:
658 args = []
658 args = []
659 commands.heads(baseui, repo, template='{node} ', *args)
659 commands.heads(baseui, repo, template='{node} ', *args)
660
660
661 return output.getvalue()
661 return output.getvalue()
662
662
663 @reraise_safe_exceptions
663 @reraise_safe_exceptions
664 def ancestor(self, wire, revision1, revision2):
664 def ancestor(self, wire, revision1, revision2):
665 repo = self._factory.repo(wire)
665 repo = self._factory.repo(wire)
666 baseui = self._factory._create_config(wire['config'])
666 baseui = self._factory._create_config(wire['config'])
667 output = io.BytesIO()
667 output = io.BytesIO()
668 baseui.write = output.write
668 baseui.write = output.write
669 commands.debugancestor(baseui, repo, revision1, revision2)
669 commands.debugancestor(baseui, repo, revision1, revision2)
670
670
671 return output.getvalue()
671 return output.getvalue()
672
672
673 @reraise_safe_exceptions
673 @reraise_safe_exceptions
674 def push(self, wire, revisions, dest_path, hooks=True,
674 def push(self, wire, revisions, dest_path, hooks=True,
675 push_branches=False):
675 push_branches=False):
676 repo = self._factory.repo(wire)
676 repo = self._factory.repo(wire)
677 baseui = self._factory._create_config(wire['config'], hooks=hooks)
677 baseui = self._factory._create_config(wire['config'], hooks=hooks)
678 commands.push(baseui, repo, dest=dest_path, rev=revisions,
678 commands.push(baseui, repo, dest=dest_path, rev=revisions,
679 new_branch=push_branches)
679 new_branch=push_branches)
680
680
681 @reraise_safe_exceptions
681 @reraise_safe_exceptions
682 def merge(self, wire, revision):
682 def merge(self, wire, revision):
683 repo = self._factory.repo(wire)
683 repo = self._factory.repo(wire)
684 baseui = self._factory._create_config(wire['config'])
684 baseui = self._factory._create_config(wire['config'])
685 repo.ui.setconfig('ui', 'merge', 'internal:dump')
685 repo.ui.setconfig('ui', 'merge', 'internal:dump')
686
687 # In case of sub repositories are used mercurial prompts the user in
688 # case of merge conflicts or different sub repository sources. By
689 # setting the interactive flag to `False` mercurial doesn't prompt the
690 # used but instead uses a default value.
691 repo.ui.setconfig('ui', 'interactive', False)
692
686 commands.merge(baseui, repo, rev=revision)
693 commands.merge(baseui, repo, rev=revision)
687
694
688 @reraise_safe_exceptions
695 @reraise_safe_exceptions
689 def commit(self, wire, message, username):
696 def commit(self, wire, message, username):
690 repo = self._factory.repo(wire)
697 repo = self._factory.repo(wire)
691 baseui = self._factory._create_config(wire['config'])
698 baseui = self._factory._create_config(wire['config'])
692 repo.ui.setconfig('ui', 'username', username)
699 repo.ui.setconfig('ui', 'username', username)
693 commands.commit(baseui, repo, message=message)
700 commands.commit(baseui, repo, message=message)
694
701
695 @reraise_safe_exceptions
702 @reraise_safe_exceptions
696 def rebase(self, wire, source=None, dest=None, abort=False):
703 def rebase(self, wire, source=None, dest=None, abort=False):
697 repo = self._factory.repo(wire)
704 repo = self._factory.repo(wire)
698 baseui = self._factory._create_config(wire['config'])
705 baseui = self._factory._create_config(wire['config'])
699 repo.ui.setconfig('ui', 'merge', 'internal:dump')
706 repo.ui.setconfig('ui', 'merge', 'internal:dump')
700 rebase.rebase(
707 rebase.rebase(
701 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
708 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
702
709
703 @reraise_safe_exceptions
710 @reraise_safe_exceptions
704 def bookmark(self, wire, bookmark, revision=None):
711 def bookmark(self, wire, bookmark, revision=None):
705 repo = self._factory.repo(wire)
712 repo = self._factory.repo(wire)
706 baseui = self._factory._create_config(wire['config'])
713 baseui = self._factory._create_config(wire['config'])
707 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
714 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
@@ -1,61 +1,62 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 """
18 """
19 Mercurial libs compatibility
19 Mercurial libs compatibility
20 """
20 """
21
21
22 import mercurial
22 import mercurial
23 import mercurial.demandimport
23 import mercurial.demandimport
24 # patch demandimport, due to bug in mercurial when it always triggers
24 # patch demandimport, due to bug in mercurial when it always triggers
25 # demandimport.enable()
25 # demandimport.enable()
26 mercurial.demandimport.enable = lambda *args, **kwargs: 1
26 mercurial.demandimport.enable = lambda *args, **kwargs: 1
27
27
28 from mercurial import ui
28 from mercurial import ui
29 from mercurial import patch
29 from mercurial import patch
30 from mercurial import config
30 from mercurial import config
31 from mercurial import extensions
31 from mercurial import extensions
32 from mercurial import scmutil
32 from mercurial import scmutil
33 from mercurial import archival
33 from mercurial import archival
34 from mercurial import discovery
34 from mercurial import discovery
35 from mercurial import unionrepo
35 from mercurial import unionrepo
36 from mercurial import localrepo
36 from mercurial import localrepo
37 from mercurial import merge as hg_merge
37 from mercurial import merge as hg_merge
38 from mercurial import subrepo
38
39
39 from mercurial.commands import clone, nullid, pull
40 from mercurial.commands import clone, nullid, pull
40 from mercurial.context import memctx, memfilectx
41 from mercurial.context import memctx, memfilectx
41 from mercurial.error import (
42 from mercurial.error import (
42 LookupError, RepoError, RepoLookupError, Abort, InterventionRequired,
43 LookupError, RepoError, RepoLookupError, Abort, InterventionRequired,
43 RequirementError)
44 RequirementError)
44 from mercurial.hgweb import hgweb_mod
45 from mercurial.hgweb import hgweb_mod
45 from mercurial.localrepo import localrepository
46 from mercurial.localrepo import localrepository
46 from mercurial.match import match
47 from mercurial.match import match
47 from mercurial.mdiff import diffopts
48 from mercurial.mdiff import diffopts
48 from mercurial.node import bin, hex
49 from mercurial.node import bin, hex
49 from mercurial.encoding import tolocal
50 from mercurial.encoding import tolocal
50 from mercurial.discovery import findcommonoutgoing
51 from mercurial.discovery import findcommonoutgoing
51 from mercurial.hg import peer
52 from mercurial.hg import peer
52 from mercurial.httppeer import httppeer
53 from mercurial.httppeer import httppeer
53 from mercurial.util import url as hg_url
54 from mercurial.util import url as hg_url
54 from mercurial.scmutil import revrange
55 from mercurial.scmutil import revrange
55 from mercurial.node import nullrev
56 from mercurial.node import nullrev
56 from mercurial import exchange
57 from mercurial import exchange
57 from hgext import largefiles
58 from hgext import largefiles
58
59
59 # those authnadlers are patched for python 2.6.5 bug an
60 # those authnadlers are patched for python 2.6.5 bug an
60 # infinit looping when given invalid resources
61 # infinit looping when given invalid resources
61 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
62 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
@@ -1,60 +1,134 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 """
18 """
19 Adjustments to Mercurial
19 Adjustments to Mercurial
20
20
21 Intentionally kept separate from `hgcompat` and `hg`, so that these patches can
21 Intentionally kept separate from `hgcompat` and `hg`, so that these patches can
22 be applied without having to import the whole Mercurial machinery.
22 be applied without having to import the whole Mercurial machinery.
23
23
24 Imports are function local, so that just importing this module does not cause
24 Imports are function local, so that just importing this module does not cause
25 side-effects other than these functions being defined.
25 side-effects other than these functions being defined.
26 """
26 """
27
27
28 import logging
28 import logging
29
29
30
30
31 def patch_largefiles_capabilities():
31 def patch_largefiles_capabilities():
32 """
32 """
33 Patches the capabilities function in the largefiles extension.
33 Patches the capabilities function in the largefiles extension.
34 """
34 """
35 from vcsserver import hgcompat
35 from vcsserver import hgcompat
36 lfproto = hgcompat.largefiles.proto
36 lfproto = hgcompat.largefiles.proto
37 wrapper = _dynamic_capabilities_wrapper(
37 wrapper = _dynamic_capabilities_wrapper(
38 lfproto, hgcompat.extensions.extensions)
38 lfproto, hgcompat.extensions.extensions)
39 lfproto.capabilities = wrapper
39 lfproto.capabilities = wrapper
40
40
41
41
42 def _dynamic_capabilities_wrapper(lfproto, extensions):
42 def _dynamic_capabilities_wrapper(lfproto, extensions):
43
43
44 wrapped_capabilities = lfproto.capabilities
44 wrapped_capabilities = lfproto.capabilities
45 logger = logging.getLogger('vcsserver.hg')
45 logger = logging.getLogger('vcsserver.hg')
46
46
47 def _dynamic_capabilities(repo, proto):
47 def _dynamic_capabilities(repo, proto):
48 """
48 """
49 Adds dynamic behavior, so that the capability is only added if the
49 Adds dynamic behavior, so that the capability is only added if the
50 extension is enabled in the current ui object.
50 extension is enabled in the current ui object.
51 """
51 """
52 if 'largefiles' in dict(extensions(repo.ui)):
52 if 'largefiles' in dict(extensions(repo.ui)):
53 logger.debug('Extension largefiles enabled')
53 logger.debug('Extension largefiles enabled')
54 calc_capabilities = wrapped_capabilities
54 calc_capabilities = wrapped_capabilities
55 else:
55 else:
56 logger.debug('Extension largefiles disabled')
56 logger.debug('Extension largefiles disabled')
57 calc_capabilities = lfproto.capabilitiesorig
57 calc_capabilities = lfproto.capabilitiesorig
58 return calc_capabilities(repo, proto)
58 return calc_capabilities(repo, proto)
59
59
60 return _dynamic_capabilities
60 return _dynamic_capabilities
61
62
63 def patch_subrepo_type_mapping():
64 from collections import defaultdict
65 from hgcompat import subrepo
66 from exceptions import SubrepoMergeException
67
68 class NoOpSubrepo(subrepo.abstractsubrepo):
69
70 def __init__(self, ctx, path, *args, **kwargs):
71 """Initialize abstractsubrepo part
72
73 ``ctx`` is the context referring this subrepository in the
74 parent repository.
75
76 ``path`` is the path to this subrepository as seen from
77 innermost repository.
78 """
79 self.ui = ctx.repo().ui
80 self._ctx = ctx
81 self._path = path
82
83 def storeclean(self, path):
84 """
85 returns true if the repository has not changed since it was last
86 cloned from or pushed to a given repository.
87 """
88 return True
89
90 def dirty(self, ignoreupdate=False):
91 """returns true if the dirstate of the subrepo is dirty or does not
92 match current stored state. If ignoreupdate is true, only check
93 whether the subrepo has uncommitted changes in its dirstate.
94 """
95 return False
96
97 def basestate(self):
98 """current working directory base state, disregarding .hgsubstate
99 state and working directory modifications"""
100 substate = subrepo.state(self._ctx, self.ui)
101 file_system_path, rev, repotype = substate.get(self._path)
102 return rev
103
104 def remove(self):
105 """remove the subrepo
106
107 (should verify the dirstate is not dirty first)
108 """
109 pass
110
111 def get(self, state, overwrite=False):
112 """run whatever commands are needed to put the subrepo into
113 this state
114 """
115 pass
116
117 def merge(self, state):
118 """merge currently-saved state with the new state."""
119 raise SubrepoMergeException()
120
121 def push(self, opts):
122 """perform whatever action is analogous to 'hg push'
123
124 This may be a no-op on some systems.
125 """
126 pass
127
128 # Patch subrepo type mapping to always return our NoOpSubrepo class
129 # whenever a subrepo class is looked up.
130 subrepo.types = {
131 'hg': NoOpSubrepo,
132 'git': NoOpSubrepo,
133 'svn': NoOpSubrepo
134 }
@@ -1,358 +1,359 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import base64
18 import base64
19 import locale
19 import locale
20 import logging
20 import logging
21 import uuid
21 import uuid
22 import wsgiref.util
22 import wsgiref.util
23 from itertools import chain
23 from itertools import chain
24
24
25 import msgpack
25 import msgpack
26 from beaker.cache import CacheManager
26 from beaker.cache import CacheManager
27 from beaker.util import parse_cache_config_options
27 from beaker.util import parse_cache_config_options
28 from pyramid.config import Configurator
28 from pyramid.config import Configurator
29 from pyramid.wsgi import wsgiapp
29 from pyramid.wsgi import wsgiapp
30
30
31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 from vcsserver.echo_stub.echo_app import EchoApp
33 from vcsserver.echo_stub.echo_app import EchoApp
34 from vcsserver.exceptions import HTTPRepoLocked
34 from vcsserver.exceptions import HTTPRepoLocked
35 from vcsserver.server import VcsServer
35 from vcsserver.server import VcsServer
36
36
37 try:
37 try:
38 from vcsserver.git import GitFactory, GitRemote
38 from vcsserver.git import GitFactory, GitRemote
39 except ImportError:
39 except ImportError:
40 GitFactory = None
40 GitFactory = None
41 GitRemote = None
41 GitRemote = None
42 try:
42 try:
43 from vcsserver.hg import MercurialFactory, HgRemote
43 from vcsserver.hg import MercurialFactory, HgRemote
44 except ImportError:
44 except ImportError:
45 MercurialFactory = None
45 MercurialFactory = None
46 HgRemote = None
46 HgRemote = None
47 try:
47 try:
48 from vcsserver.svn import SubversionFactory, SvnRemote
48 from vcsserver.svn import SubversionFactory, SvnRemote
49 except ImportError:
49 except ImportError:
50 SubversionFactory = None
50 SubversionFactory = None
51 SvnRemote = None
51 SvnRemote = None
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class VCS(object):
56 class VCS(object):
57 def __init__(self, locale=None, cache_config=None):
57 def __init__(self, locale=None, cache_config=None):
58 self.locale = locale
58 self.locale = locale
59 self.cache_config = cache_config
59 self.cache_config = cache_config
60 self._configure_locale()
60 self._configure_locale()
61 self._initialize_cache()
61 self._initialize_cache()
62
62
63 if GitFactory and GitRemote:
63 if GitFactory and GitRemote:
64 git_repo_cache = self.cache.get_cache_region(
64 git_repo_cache = self.cache.get_cache_region(
65 'git', region='repo_object')
65 'git', region='repo_object')
66 git_factory = GitFactory(git_repo_cache)
66 git_factory = GitFactory(git_repo_cache)
67 self._git_remote = GitRemote(git_factory)
67 self._git_remote = GitRemote(git_factory)
68 else:
68 else:
69 log.info("Git client import failed")
69 log.info("Git client import failed")
70
70
71 if MercurialFactory and HgRemote:
71 if MercurialFactory and HgRemote:
72 hg_repo_cache = self.cache.get_cache_region(
72 hg_repo_cache = self.cache.get_cache_region(
73 'hg', region='repo_object')
73 'hg', region='repo_object')
74 hg_factory = MercurialFactory(hg_repo_cache)
74 hg_factory = MercurialFactory(hg_repo_cache)
75 self._hg_remote = HgRemote(hg_factory)
75 self._hg_remote = HgRemote(hg_factory)
76 else:
76 else:
77 log.info("Mercurial client import failed")
77 log.info("Mercurial client import failed")
78
78
79 if SubversionFactory and SvnRemote:
79 if SubversionFactory and SvnRemote:
80 svn_repo_cache = self.cache.get_cache_region(
80 svn_repo_cache = self.cache.get_cache_region(
81 'svn', region='repo_object')
81 'svn', region='repo_object')
82 svn_factory = SubversionFactory(svn_repo_cache)
82 svn_factory = SubversionFactory(svn_repo_cache)
83 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
83 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 else:
84 else:
85 log.info("Subversion client import failed")
85 log.info("Subversion client import failed")
86
86
87 self._vcsserver = VcsServer()
87 self._vcsserver = VcsServer()
88
88
89 def _initialize_cache(self):
89 def _initialize_cache(self):
90 cache_config = parse_cache_config_options(self.cache_config)
90 cache_config = parse_cache_config_options(self.cache_config)
91 log.info('Initializing beaker cache: %s' % cache_config)
91 log.info('Initializing beaker cache: %s' % cache_config)
92 self.cache = CacheManager(**cache_config)
92 self.cache = CacheManager(**cache_config)
93
93
94 def _configure_locale(self):
94 def _configure_locale(self):
95 if self.locale:
95 if self.locale:
96 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
96 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 else:
97 else:
98 log.info(
98 log.info(
99 'Configuring locale subsystem based on environment variables')
99 'Configuring locale subsystem based on environment variables')
100 try:
100 try:
101 # If self.locale is the empty string, then the locale
101 # If self.locale is the empty string, then the locale
102 # module will use the environment variables. See the
102 # module will use the environment variables. See the
103 # documentation of the package `locale`.
103 # documentation of the package `locale`.
104 locale.setlocale(locale.LC_ALL, self.locale)
104 locale.setlocale(locale.LC_ALL, self.locale)
105
105
106 language_code, encoding = locale.getlocale()
106 language_code, encoding = locale.getlocale()
107 log.info(
107 log.info(
108 'Locale set to language code "%s" with encoding "%s".',
108 'Locale set to language code "%s" with encoding "%s".',
109 language_code, encoding)
109 language_code, encoding)
110 except locale.Error:
110 except locale.Error:
111 log.exception(
111 log.exception(
112 'Cannot set locale, not configuring the locale system')
112 'Cannot set locale, not configuring the locale system')
113
113
114
114
115 class WsgiProxy(object):
115 class WsgiProxy(object):
116 def __init__(self, wsgi):
116 def __init__(self, wsgi):
117 self.wsgi = wsgi
117 self.wsgi = wsgi
118
118
119 def __call__(self, environ, start_response):
119 def __call__(self, environ, start_response):
120 input_data = environ['wsgi.input'].read()
120 input_data = environ['wsgi.input'].read()
121 input_data = msgpack.unpackb(input_data)
121 input_data = msgpack.unpackb(input_data)
122
122
123 error = None
123 error = None
124 try:
124 try:
125 data, status, headers = self.wsgi.handle(
125 data, status, headers = self.wsgi.handle(
126 input_data['environment'], input_data['input_data'],
126 input_data['environment'], input_data['input_data'],
127 *input_data['args'], **input_data['kwargs'])
127 *input_data['args'], **input_data['kwargs'])
128 except Exception as e:
128 except Exception as e:
129 data, status, headers = [], None, None
129 data, status, headers = [], None, None
130 error = {
130 error = {
131 'message': str(e),
131 'message': str(e),
132 '_vcs_kind': getattr(e, '_vcs_kind', None)
132 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 }
133 }
134
134
135 start_response(200, {})
135 start_response(200, {})
136 return self._iterator(error, status, headers, data)
136 return self._iterator(error, status, headers, data)
137
137
138 def _iterator(self, error, status, headers, data):
138 def _iterator(self, error, status, headers, data):
139 initial_data = [
139 initial_data = [
140 error,
140 error,
141 status,
141 status,
142 headers,
142 headers,
143 ]
143 ]
144
144
145 for d in chain(initial_data, data):
145 for d in chain(initial_data, data):
146 yield msgpack.packb(d)
146 yield msgpack.packb(d)
147
147
148
148
149 class HTTPApplication(object):
149 class HTTPApplication(object):
150 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
150 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151
151
152 remote_wsgi = remote_wsgi
152 remote_wsgi = remote_wsgi
153 _use_echo_app = False
153 _use_echo_app = False
154
154
155 def __init__(self, settings=None):
155 def __init__(self, settings=None):
156 self.config = Configurator(settings=settings)
156 self.config = Configurator(settings=settings)
157 locale = settings.get('', 'en_US.UTF-8')
157 locale = settings.get('', 'en_US.UTF-8')
158 vcs = VCS(locale=locale, cache_config=settings)
158 vcs = VCS(locale=locale, cache_config=settings)
159 self._remotes = {
159 self._remotes = {
160 'hg': vcs._hg_remote,
160 'hg': vcs._hg_remote,
161 'git': vcs._git_remote,
161 'git': vcs._git_remote,
162 'svn': vcs._svn_remote,
162 'svn': vcs._svn_remote,
163 'server': vcs._vcsserver,
163 'server': vcs._vcsserver,
164 }
164 }
165 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
165 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 self._use_echo_app = True
166 self._use_echo_app = True
167 log.warning("Using EchoApp for VCS operations.")
167 log.warning("Using EchoApp for VCS operations.")
168 self.remote_wsgi = remote_wsgi_stub
168 self.remote_wsgi = remote_wsgi_stub
169 self._configure_settings(settings)
169 self._configure_settings(settings)
170 self._configure()
170 self._configure()
171
171
172 def _configure_settings(self, app_settings):
172 def _configure_settings(self, app_settings):
173 """
173 """
174 Configure the settings module.
174 Configure the settings module.
175 """
175 """
176 git_path = app_settings.get('git_path', None)
176 git_path = app_settings.get('git_path', None)
177 if git_path:
177 if git_path:
178 settings.GIT_EXECUTABLE = git_path
178 settings.GIT_EXECUTABLE = git_path
179
179
180 def _configure(self):
180 def _configure(self):
181 self.config.add_renderer(
181 self.config.add_renderer(
182 name='msgpack',
182 name='msgpack',
183 factory=self._msgpack_renderer_factory)
183 factory=self._msgpack_renderer_factory)
184
184
185 self.config.add_route('status', '/status')
185 self.config.add_route('status', '/status')
186 self.config.add_route('hg_proxy', '/proxy/hg')
186 self.config.add_route('hg_proxy', '/proxy/hg')
187 self.config.add_route('git_proxy', '/proxy/git')
187 self.config.add_route('git_proxy', '/proxy/git')
188 self.config.add_route('vcs', '/{backend}')
188 self.config.add_route('vcs', '/{backend}')
189 self.config.add_route('stream_git', '/stream/git/*repo_name')
189 self.config.add_route('stream_git', '/stream/git/*repo_name')
190 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
190 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
191
191
192 self.config.add_view(
192 self.config.add_view(
193 self.status_view, route_name='status', renderer='json')
193 self.status_view, route_name='status', renderer='json')
194 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
194 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
195 self.config.add_view(self.git_proxy(), route_name='git_proxy')
195 self.config.add_view(self.git_proxy(), route_name='git_proxy')
196 self.config.add_view(
196 self.config.add_view(
197 self.vcs_view, route_name='vcs', renderer='msgpack')
197 self.vcs_view, route_name='vcs', renderer='msgpack')
198
198
199 self.config.add_view(self.hg_stream(), route_name='stream_hg')
199 self.config.add_view(self.hg_stream(), route_name='stream_hg')
200 self.config.add_view(self.git_stream(), route_name='stream_git')
200 self.config.add_view(self.git_stream(), route_name='stream_git')
201 self.config.add_view(
201 self.config.add_view(
202 self.handle_vcs_exception, context=Exception,
202 self.handle_vcs_exception, context=Exception,
203 custom_predicates=[self.is_vcs_exception])
203 custom_predicates=[self.is_vcs_exception])
204
204
205 def wsgi_app(self):
205 def wsgi_app(self):
206 return self.config.make_wsgi_app()
206 return self.config.make_wsgi_app()
207
207
208 def vcs_view(self, request):
208 def vcs_view(self, request):
209 remote = self._remotes[request.matchdict['backend']]
209 remote = self._remotes[request.matchdict['backend']]
210 payload = msgpack.unpackb(request.body, use_list=True)
210 payload = msgpack.unpackb(request.body, use_list=True)
211 method = payload.get('method')
211 method = payload.get('method')
212 params = payload.get('params')
212 params = payload.get('params')
213 wire = params.get('wire')
213 wire = params.get('wire')
214 args = params.get('args')
214 args = params.get('args')
215 kwargs = params.get('kwargs')
215 kwargs = params.get('kwargs')
216 if wire:
216 if wire:
217 try:
217 try:
218 wire['context'] = uuid.UUID(wire['context'])
218 wire['context'] = uuid.UUID(wire['context'])
219 except KeyError:
219 except KeyError:
220 pass
220 pass
221 args.insert(0, wire)
221 args.insert(0, wire)
222
222
223 try:
223 try:
224 resp = getattr(remote, method)(*args, **kwargs)
224 resp = getattr(remote, method)(*args, **kwargs)
225 except Exception as e:
225 except Exception as e:
226 type_ = e.__class__.__name__
226 type_ = e.__class__.__name__
227 if type_ not in self.ALLOWED_EXCEPTIONS:
227 if type_ not in self.ALLOWED_EXCEPTIONS:
228 type_ = None
228 type_ = None
229
229
230 resp = {
230 resp = {
231 'id': payload.get('id'),
231 'id': payload.get('id'),
232 'error': {
232 'error': {
233 'message': e.message,
233 'message': e.message,
234 'type': type_
234 'type': type_
235 }
235 }
236 }
236 }
237 try:
237 try:
238 resp['error']['_vcs_kind'] = e._vcs_kind
238 resp['error']['_vcs_kind'] = e._vcs_kind
239 except AttributeError:
239 except AttributeError:
240 pass
240 pass
241 else:
241 else:
242 resp = {
242 resp = {
243 'id': payload.get('id'),
243 'id': payload.get('id'),
244 'result': resp
244 'result': resp
245 }
245 }
246
246
247 return resp
247 return resp
248
248
249 def status_view(self, request):
249 def status_view(self, request):
250 return {'status': 'OK'}
250 return {'status': 'OK'}
251
251
252 def _msgpack_renderer_factory(self, info):
252 def _msgpack_renderer_factory(self, info):
253 def _render(value, system):
253 def _render(value, system):
254 value = msgpack.packb(value)
254 value = msgpack.packb(value)
255 request = system.get('request')
255 request = system.get('request')
256 if request is not None:
256 if request is not None:
257 response = request.response
257 response = request.response
258 ct = response.content_type
258 ct = response.content_type
259 if ct == response.default_content_type:
259 if ct == response.default_content_type:
260 response.content_type = 'application/x-msgpack'
260 response.content_type = 'application/x-msgpack'
261 return value
261 return value
262 return _render
262 return _render
263
263
264 def hg_proxy(self):
264 def hg_proxy(self):
265 @wsgiapp
265 @wsgiapp
266 def _hg_proxy(environ, start_response):
266 def _hg_proxy(environ, start_response):
267 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
267 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
268 return app(environ, start_response)
268 return app(environ, start_response)
269 return _hg_proxy
269 return _hg_proxy
270
270
271 def git_proxy(self):
271 def git_proxy(self):
272 @wsgiapp
272 @wsgiapp
273 def _git_proxy(environ, start_response):
273 def _git_proxy(environ, start_response):
274 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
274 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
275 return app(environ, start_response)
275 return app(environ, start_response)
276 return _git_proxy
276 return _git_proxy
277
277
278 def hg_stream(self):
278 def hg_stream(self):
279 if self._use_echo_app:
279 if self._use_echo_app:
280 @wsgiapp
280 @wsgiapp
281 def _hg_stream(environ, start_response):
281 def _hg_stream(environ, start_response):
282 app = EchoApp('fake_path', 'fake_name', None)
282 app = EchoApp('fake_path', 'fake_name', None)
283 return app(environ, start_response)
283 return app(environ, start_response)
284 return _hg_stream
284 return _hg_stream
285 else:
285 else:
286 @wsgiapp
286 @wsgiapp
287 def _hg_stream(environ, start_response):
287 def _hg_stream(environ, start_response):
288 repo_path = environ['HTTP_X_RC_REPO_PATH']
288 repo_path = environ['HTTP_X_RC_REPO_PATH']
289 repo_name = environ['HTTP_X_RC_REPO_NAME']
289 repo_name = environ['HTTP_X_RC_REPO_NAME']
290 packed_config = base64.b64decode(
290 packed_config = base64.b64decode(
291 environ['HTTP_X_RC_REPO_CONFIG'])
291 environ['HTTP_X_RC_REPO_CONFIG'])
292 config = msgpack.unpackb(packed_config)
292 config = msgpack.unpackb(packed_config)
293 app = scm_app.create_hg_wsgi_app(
293 app = scm_app.create_hg_wsgi_app(
294 repo_path, repo_name, config)
294 repo_path, repo_name, config)
295
295
296 # Consitent path information for hgweb
296 # Consitent path information for hgweb
297 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
297 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
298 environ['REPO_NAME'] = repo_name
298 environ['REPO_NAME'] = repo_name
299 return app(environ, ResponseFilter(start_response))
299 return app(environ, ResponseFilter(start_response))
300 return _hg_stream
300 return _hg_stream
301
301
302 def git_stream(self):
302 def git_stream(self):
303 if self._use_echo_app:
303 if self._use_echo_app:
304 @wsgiapp
304 @wsgiapp
305 def _git_stream(environ, start_response):
305 def _git_stream(environ, start_response):
306 app = EchoApp('fake_path', 'fake_name', None)
306 app = EchoApp('fake_path', 'fake_name', None)
307 return app(environ, start_response)
307 return app(environ, start_response)
308 return _git_stream
308 return _git_stream
309 else:
309 else:
310 @wsgiapp
310 @wsgiapp
311 def _git_stream(environ, start_response):
311 def _git_stream(environ, start_response):
312 repo_path = environ['HTTP_X_RC_REPO_PATH']
312 repo_path = environ['HTTP_X_RC_REPO_PATH']
313 repo_name = environ['HTTP_X_RC_REPO_NAME']
313 repo_name = environ['HTTP_X_RC_REPO_NAME']
314 packed_config = base64.b64decode(
314 packed_config = base64.b64decode(
315 environ['HTTP_X_RC_REPO_CONFIG'])
315 environ['HTTP_X_RC_REPO_CONFIG'])
316 config = msgpack.unpackb(packed_config)
316 config = msgpack.unpackb(packed_config)
317
317
318 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
318 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
319 app = scm_app.create_git_wsgi_app(
319 app = scm_app.create_git_wsgi_app(
320 repo_path, repo_name, config)
320 repo_path, repo_name, config)
321 return app(environ, start_response)
321 return app(environ, start_response)
322 return _git_stream
322 return _git_stream
323
323
324 def is_vcs_exception(self, context, request):
324 def is_vcs_exception(self, context, request):
325 """
325 """
326 View predicate that returns true if the context object is a VCS
326 View predicate that returns true if the context object is a VCS
327 exception.
327 exception.
328 """
328 """
329 return hasattr(context, '_vcs_kind')
329 return hasattr(context, '_vcs_kind')
330
330
331 def handle_vcs_exception(self, exception, request):
331 def handle_vcs_exception(self, exception, request):
332 if exception._vcs_kind == 'repo_locked':
332 if exception._vcs_kind == 'repo_locked':
333 # Get custom repo-locked status code if present.
333 # Get custom repo-locked status code if present.
334 status_code = request.headers.get('X-RC-Locked-Status-Code')
334 status_code = request.headers.get('X-RC-Locked-Status-Code')
335 return HTTPRepoLocked(
335 return HTTPRepoLocked(
336 title=exception.message, status_code=status_code)
336 title=exception.message, status_code=status_code)
337
337
338 # Re-raise exception if we can not handle it.
338 # Re-raise exception if we can not handle it.
339 raise exception
339 raise exception
340
340
341
341
342 class ResponseFilter(object):
342 class ResponseFilter(object):
343
343
344 def __init__(self, start_response):
344 def __init__(self, start_response):
345 self._start_response = start_response
345 self._start_response = start_response
346
346
347 def __call__(self, status, response_headers, exc_info=None):
347 def __call__(self, status, response_headers, exc_info=None):
348 headers = tuple(
348 headers = tuple(
349 (h, v) for h, v in response_headers
349 (h, v) for h, v in response_headers
350 if not wsgiref.util.is_hop_by_hop(h))
350 if not wsgiref.util.is_hop_by_hop(h))
351 return self._start_response(status, headers, exc_info)
351 return self._start_response(status, headers, exc_info)
352
352
353
353
354 def main(global_config, **settings):
354 def main(global_config, **settings):
355 if MercurialFactory:
355 if MercurialFactory:
356 hgpatches.patch_largefiles_capabilities()
356 hgpatches.patch_largefiles_capabilities()
357 hgpatches.patch_subrepo_type_mapping()
357 app = HTTPApplication(settings=settings)
358 app = HTTPApplication(settings=settings)
358 return app.wsgi_app()
359 return app.wsgi_app()
@@ -1,507 +1,508 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2016 RodeCode GmbH
2 # Copyright (C) 2014-2016 RodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import atexit
18 import atexit
19 import locale
19 import locale
20 import logging
20 import logging
21 import optparse
21 import optparse
22 import os
22 import os
23 import textwrap
23 import textwrap
24 import threading
24 import threading
25 import sys
25 import sys
26
26
27 import configobj
27 import configobj
28 import Pyro4
28 import Pyro4
29 from beaker.cache import CacheManager
29 from beaker.cache import CacheManager
30 from beaker.util import parse_cache_config_options
30 from beaker.util import parse_cache_config_options
31
31
32 try:
32 try:
33 from vcsserver.git import GitFactory, GitRemote
33 from vcsserver.git import GitFactory, GitRemote
34 except ImportError:
34 except ImportError:
35 GitFactory = None
35 GitFactory = None
36 GitRemote = None
36 GitRemote = None
37 try:
37 try:
38 from vcsserver.hg import MercurialFactory, HgRemote
38 from vcsserver.hg import MercurialFactory, HgRemote
39 except ImportError:
39 except ImportError:
40 MercurialFactory = None
40 MercurialFactory = None
41 HgRemote = None
41 HgRemote = None
42 try:
42 try:
43 from vcsserver.svn import SubversionFactory, SvnRemote
43 from vcsserver.svn import SubversionFactory, SvnRemote
44 except ImportError:
44 except ImportError:
45 SubversionFactory = None
45 SubversionFactory = None
46 SvnRemote = None
46 SvnRemote = None
47
47
48 from server import VcsServer
48 from server import VcsServer
49 from vcsserver import hgpatches, remote_wsgi, settings
49 from vcsserver import hgpatches, remote_wsgi, settings
50 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
50 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54 HERE = os.path.dirname(os.path.abspath(__file__))
54 HERE = os.path.dirname(os.path.abspath(__file__))
55 SERVER_RUNNING_FILE = None
55 SERVER_RUNNING_FILE = None
56
56
57
57
58 # HOOKS - inspired by gunicorn #
58 # HOOKS - inspired by gunicorn #
59
59
60 def when_ready(server):
60 def when_ready(server):
61 """
61 """
62 Called just after the server is started.
62 Called just after the server is started.
63 """
63 """
64
64
65 def _remove_server_running_file():
65 def _remove_server_running_file():
66 if os.path.isfile(SERVER_RUNNING_FILE):
66 if os.path.isfile(SERVER_RUNNING_FILE):
67 os.remove(SERVER_RUNNING_FILE)
67 os.remove(SERVER_RUNNING_FILE)
68
68
69 # top up to match to level location
69 # top up to match to level location
70 if SERVER_RUNNING_FILE:
70 if SERVER_RUNNING_FILE:
71 with open(SERVER_RUNNING_FILE, 'wb') as f:
71 with open(SERVER_RUNNING_FILE, 'wb') as f:
72 f.write(str(os.getpid()))
72 f.write(str(os.getpid()))
73 # register cleanup of that file when server exits
73 # register cleanup of that file when server exits
74 atexit.register(_remove_server_running_file)
74 atexit.register(_remove_server_running_file)
75
75
76
76
77 class LazyWriter(object):
77 class LazyWriter(object):
78 """
78 """
79 File-like object that opens a file lazily when it is first written
79 File-like object that opens a file lazily when it is first written
80 to.
80 to.
81 """
81 """
82
82
83 def __init__(self, filename, mode='w'):
83 def __init__(self, filename, mode='w'):
84 self.filename = filename
84 self.filename = filename
85 self.fileobj = None
85 self.fileobj = None
86 self.lock = threading.Lock()
86 self.lock = threading.Lock()
87 self.mode = mode
87 self.mode = mode
88
88
89 def open(self):
89 def open(self):
90 if self.fileobj is None:
90 if self.fileobj is None:
91 with self.lock:
91 with self.lock:
92 self.fileobj = open(self.filename, self.mode)
92 self.fileobj = open(self.filename, self.mode)
93 return self.fileobj
93 return self.fileobj
94
94
95 def close(self):
95 def close(self):
96 fileobj = self.fileobj
96 fileobj = self.fileobj
97 if fileobj is not None:
97 if fileobj is not None:
98 fileobj.close()
98 fileobj.close()
99
99
100 def __del__(self):
100 def __del__(self):
101 self.close()
101 self.close()
102
102
103 def write(self, text):
103 def write(self, text):
104 fileobj = self.open()
104 fileobj = self.open()
105 fileobj.write(text)
105 fileobj.write(text)
106 fileobj.flush()
106 fileobj.flush()
107
107
108 def writelines(self, text):
108 def writelines(self, text):
109 fileobj = self.open()
109 fileobj = self.open()
110 fileobj.writelines(text)
110 fileobj.writelines(text)
111 fileobj.flush()
111 fileobj.flush()
112
112
113 def flush(self):
113 def flush(self):
114 self.open().flush()
114 self.open().flush()
115
115
116
116
117 class Application(object):
117 class Application(object):
118 """
118 """
119 Represents the vcs server application.
119 Represents the vcs server application.
120
120
121 This object is responsible to initialize the application and all needed
121 This object is responsible to initialize the application and all needed
122 libraries. After that it hooks together the different objects and provides
122 libraries. After that it hooks together the different objects and provides
123 them a way to access things like configuration.
123 them a way to access things like configuration.
124 """
124 """
125
125
126 def __init__(
126 def __init__(
127 self, host, port=None, locale='', threadpool_size=None,
127 self, host, port=None, locale='', threadpool_size=None,
128 timeout=None, cache_config=None, remote_wsgi_=None):
128 timeout=None, cache_config=None, remote_wsgi_=None):
129
129
130 self.host = host
130 self.host = host
131 self.port = int(port) or settings.PYRO_PORT
131 self.port = int(port) or settings.PYRO_PORT
132 self.threadpool_size = (
132 self.threadpool_size = (
133 int(threadpool_size) if threadpool_size else None)
133 int(threadpool_size) if threadpool_size else None)
134 self.locale = locale
134 self.locale = locale
135 self.timeout = timeout
135 self.timeout = timeout
136 self.cache_config = cache_config
136 self.cache_config = cache_config
137 self.remote_wsgi = remote_wsgi_ or remote_wsgi
137 self.remote_wsgi = remote_wsgi_ or remote_wsgi
138
138
139 def init(self):
139 def init(self):
140 """
140 """
141 Configure and hook together all relevant objects.
141 Configure and hook together all relevant objects.
142 """
142 """
143 self._configure_locale()
143 self._configure_locale()
144 self._configure_pyro()
144 self._configure_pyro()
145 self._initialize_cache()
145 self._initialize_cache()
146 self._create_daemon_and_remote_objects(host=self.host, port=self.port)
146 self._create_daemon_and_remote_objects(host=self.host, port=self.port)
147
147
148 def run(self):
148 def run(self):
149 """
149 """
150 Start the main loop of the application.
150 Start the main loop of the application.
151 """
151 """
152
152
153 if hasattr(os, 'getpid'):
153 if hasattr(os, 'getpid'):
154 log.info('Starting %s in PID %i.', __name__, os.getpid())
154 log.info('Starting %s in PID %i.', __name__, os.getpid())
155 else:
155 else:
156 log.info('Starting %s.', __name__)
156 log.info('Starting %s.', __name__)
157 if SERVER_RUNNING_FILE:
157 if SERVER_RUNNING_FILE:
158 log.info('PID file written as %s', SERVER_RUNNING_FILE)
158 log.info('PID file written as %s', SERVER_RUNNING_FILE)
159 else:
159 else:
160 log.info('No PID file written by default.')
160 log.info('No PID file written by default.')
161 when_ready(self)
161 when_ready(self)
162 try:
162 try:
163 self._pyrodaemon.requestLoop(
163 self._pyrodaemon.requestLoop(
164 loopCondition=lambda: not self._vcsserver._shutdown)
164 loopCondition=lambda: not self._vcsserver._shutdown)
165 finally:
165 finally:
166 self._pyrodaemon.shutdown()
166 self._pyrodaemon.shutdown()
167
167
168 def _configure_locale(self):
168 def _configure_locale(self):
169 if self.locale:
169 if self.locale:
170 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
170 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
171 else:
171 else:
172 log.info(
172 log.info(
173 'Configuring locale subsystem based on environment variables')
173 'Configuring locale subsystem based on environment variables')
174
174
175 try:
175 try:
176 # If self.locale is the empty string, then the locale
176 # If self.locale is the empty string, then the locale
177 # module will use the environment variables. See the
177 # module will use the environment variables. See the
178 # documentation of the package `locale`.
178 # documentation of the package `locale`.
179 locale.setlocale(locale.LC_ALL, self.locale)
179 locale.setlocale(locale.LC_ALL, self.locale)
180
180
181 language_code, encoding = locale.getlocale()
181 language_code, encoding = locale.getlocale()
182 log.info(
182 log.info(
183 'Locale set to language code "%s" with encoding "%s".',
183 'Locale set to language code "%s" with encoding "%s".',
184 language_code, encoding)
184 language_code, encoding)
185 except locale.Error:
185 except locale.Error:
186 log.exception(
186 log.exception(
187 'Cannot set locale, not configuring the locale system')
187 'Cannot set locale, not configuring the locale system')
188
188
189 def _configure_pyro(self):
189 def _configure_pyro(self):
190 if self.threadpool_size is not None:
190 if self.threadpool_size is not None:
191 log.info("Threadpool size set to %s", self.threadpool_size)
191 log.info("Threadpool size set to %s", self.threadpool_size)
192 Pyro4.config.THREADPOOL_SIZE = self.threadpool_size
192 Pyro4.config.THREADPOOL_SIZE = self.threadpool_size
193 if self.timeout not in (None, 0, 0.0, '0'):
193 if self.timeout not in (None, 0, 0.0, '0'):
194 log.info("Timeout for RPC calls set to %s seconds", self.timeout)
194 log.info("Timeout for RPC calls set to %s seconds", self.timeout)
195 Pyro4.config.COMMTIMEOUT = float(self.timeout)
195 Pyro4.config.COMMTIMEOUT = float(self.timeout)
196 Pyro4.config.SERIALIZER = 'pickle'
196 Pyro4.config.SERIALIZER = 'pickle'
197 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
197 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
198 Pyro4.config.SOCK_REUSE = True
198 Pyro4.config.SOCK_REUSE = True
199 # Uncomment the next line when you need to debug remote errors
199 # Uncomment the next line when you need to debug remote errors
200 # Pyro4.config.DETAILED_TRACEBACK = True
200 # Pyro4.config.DETAILED_TRACEBACK = True
201
201
202 def _initialize_cache(self):
202 def _initialize_cache(self):
203 cache_config = parse_cache_config_options(self.cache_config)
203 cache_config = parse_cache_config_options(self.cache_config)
204 log.info('Initializing beaker cache: %s' % cache_config)
204 log.info('Initializing beaker cache: %s' % cache_config)
205 self.cache = CacheManager(**cache_config)
205 self.cache = CacheManager(**cache_config)
206
206
207 def _create_daemon_and_remote_objects(self, host='localhost',
207 def _create_daemon_and_remote_objects(self, host='localhost',
208 port=settings.PYRO_PORT):
208 port=settings.PYRO_PORT):
209 daemon = Pyro4.Daemon(host=host, port=port)
209 daemon = Pyro4.Daemon(host=host, port=port)
210
210
211 self._vcsserver = VcsServer()
211 self._vcsserver = VcsServer()
212 uri = daemon.register(
212 uri = daemon.register(
213 self._vcsserver, objectId=settings.PYRO_VCSSERVER)
213 self._vcsserver, objectId=settings.PYRO_VCSSERVER)
214 log.info("Object registered = %s", uri)
214 log.info("Object registered = %s", uri)
215
215
216 if GitFactory and GitRemote:
216 if GitFactory and GitRemote:
217 git_repo_cache = self.cache.get_cache_region('git', region='repo_object')
217 git_repo_cache = self.cache.get_cache_region('git', region='repo_object')
218 git_factory = GitFactory(git_repo_cache)
218 git_factory = GitFactory(git_repo_cache)
219 self._git_remote = GitRemote(git_factory)
219 self._git_remote = GitRemote(git_factory)
220 uri = daemon.register(self._git_remote, objectId=settings.PYRO_GIT)
220 uri = daemon.register(self._git_remote, objectId=settings.PYRO_GIT)
221 log.info("Object registered = %s", uri)
221 log.info("Object registered = %s", uri)
222 else:
222 else:
223 log.info("Git client import failed")
223 log.info("Git client import failed")
224
224
225 if MercurialFactory and HgRemote:
225 if MercurialFactory and HgRemote:
226 hg_repo_cache = self.cache.get_cache_region('hg', region='repo_object')
226 hg_repo_cache = self.cache.get_cache_region('hg', region='repo_object')
227 hg_factory = MercurialFactory(hg_repo_cache)
227 hg_factory = MercurialFactory(hg_repo_cache)
228 self._hg_remote = HgRemote(hg_factory)
228 self._hg_remote = HgRemote(hg_factory)
229 uri = daemon.register(self._hg_remote, objectId=settings.PYRO_HG)
229 uri = daemon.register(self._hg_remote, objectId=settings.PYRO_HG)
230 log.info("Object registered = %s", uri)
230 log.info("Object registered = %s", uri)
231 else:
231 else:
232 log.info("Mercurial client import failed")
232 log.info("Mercurial client import failed")
233
233
234 if SubversionFactory and SvnRemote:
234 if SubversionFactory and SvnRemote:
235 svn_repo_cache = self.cache.get_cache_region('svn', region='repo_object')
235 svn_repo_cache = self.cache.get_cache_region('svn', region='repo_object')
236 svn_factory = SubversionFactory(svn_repo_cache)
236 svn_factory = SubversionFactory(svn_repo_cache)
237 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
237 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
238 uri = daemon.register(self._svn_remote, objectId=settings.PYRO_SVN)
238 uri = daemon.register(self._svn_remote, objectId=settings.PYRO_SVN)
239 log.info("Object registered = %s", uri)
239 log.info("Object registered = %s", uri)
240 else:
240 else:
241 log.info("Subversion client import failed")
241 log.info("Subversion client import failed")
242
242
243 self._git_remote_wsgi = self.remote_wsgi.GitRemoteWsgi()
243 self._git_remote_wsgi = self.remote_wsgi.GitRemoteWsgi()
244 uri = daemon.register(self._git_remote_wsgi,
244 uri = daemon.register(self._git_remote_wsgi,
245 objectId=settings.PYRO_GIT_REMOTE_WSGI)
245 objectId=settings.PYRO_GIT_REMOTE_WSGI)
246 log.info("Object registered = %s", uri)
246 log.info("Object registered = %s", uri)
247
247
248 self._hg_remote_wsgi = self.remote_wsgi.HgRemoteWsgi()
248 self._hg_remote_wsgi = self.remote_wsgi.HgRemoteWsgi()
249 uri = daemon.register(self._hg_remote_wsgi,
249 uri = daemon.register(self._hg_remote_wsgi,
250 objectId=settings.PYRO_HG_REMOTE_WSGI)
250 objectId=settings.PYRO_HG_REMOTE_WSGI)
251 log.info("Object registered = %s", uri)
251 log.info("Object registered = %s", uri)
252
252
253 self._pyrodaemon = daemon
253 self._pyrodaemon = daemon
254
254
255
255
256 class VcsServerCommand(object):
256 class VcsServerCommand(object):
257
257
258 usage = '%prog'
258 usage = '%prog'
259 description = """
259 description = """
260 Runs the VCS server
260 Runs the VCS server
261 """
261 """
262 default_verbosity = 1
262 default_verbosity = 1
263
263
264 parser = optparse.OptionParser(
264 parser = optparse.OptionParser(
265 usage,
265 usage,
266 description=textwrap.dedent(description)
266 description=textwrap.dedent(description)
267 )
267 )
268 parser.add_option(
268 parser.add_option(
269 '--host',
269 '--host',
270 type="str",
270 type="str",
271 dest="host",
271 dest="host",
272 )
272 )
273 parser.add_option(
273 parser.add_option(
274 '--port',
274 '--port',
275 type="int",
275 type="int",
276 dest="port"
276 dest="port"
277 )
277 )
278 parser.add_option(
278 parser.add_option(
279 '--running-file',
279 '--running-file',
280 dest='running_file',
280 dest='running_file',
281 metavar='RUNNING_FILE',
281 metavar='RUNNING_FILE',
282 help="Create a running file after the server is initalized with "
282 help="Create a running file after the server is initalized with "
283 "stored PID of process"
283 "stored PID of process"
284 )
284 )
285 parser.add_option(
285 parser.add_option(
286 '--locale',
286 '--locale',
287 dest='locale',
287 dest='locale',
288 help="Allows to set the locale, e.g. en_US.UTF-8",
288 help="Allows to set the locale, e.g. en_US.UTF-8",
289 default=""
289 default=""
290 )
290 )
291 parser.add_option(
291 parser.add_option(
292 '--log-file',
292 '--log-file',
293 dest='log_file',
293 dest='log_file',
294 metavar='LOG_FILE',
294 metavar='LOG_FILE',
295 help="Save output to the given log file (redirects stdout)"
295 help="Save output to the given log file (redirects stdout)"
296 )
296 )
297 parser.add_option(
297 parser.add_option(
298 '--log-level',
298 '--log-level',
299 dest="log_level",
299 dest="log_level",
300 metavar="LOG_LEVEL",
300 metavar="LOG_LEVEL",
301 help="use LOG_LEVEL to set log level "
301 help="use LOG_LEVEL to set log level "
302 "(debug,info,warning,error,critical)"
302 "(debug,info,warning,error,critical)"
303 )
303 )
304 parser.add_option(
304 parser.add_option(
305 '--threadpool',
305 '--threadpool',
306 dest='threadpool_size',
306 dest='threadpool_size',
307 type='int',
307 type='int',
308 help="Set the size of the threadpool used to communicate with the "
308 help="Set the size of the threadpool used to communicate with the "
309 "WSGI workers. This should be at least 6 times the number of "
309 "WSGI workers. This should be at least 6 times the number of "
310 "WSGI worker processes."
310 "WSGI worker processes."
311 )
311 )
312 parser.add_option(
312 parser.add_option(
313 '--timeout',
313 '--timeout',
314 dest='timeout',
314 dest='timeout',
315 type='float',
315 type='float',
316 help="Set the timeout for RPC communication in seconds."
316 help="Set the timeout for RPC communication in seconds."
317 )
317 )
318 parser.add_option(
318 parser.add_option(
319 '--config',
319 '--config',
320 dest='config_file',
320 dest='config_file',
321 type='string',
321 type='string',
322 help="Configuration file for vcsserver."
322 help="Configuration file for vcsserver."
323 )
323 )
324
324
325 def __init__(self, argv, quiet=False):
325 def __init__(self, argv, quiet=False):
326 self.options, self.args = self.parser.parse_args(argv[1:])
326 self.options, self.args = self.parser.parse_args(argv[1:])
327 if quiet:
327 if quiet:
328 self.options.verbose = 0
328 self.options.verbose = 0
329
329
330 def _get_file_config(self):
330 def _get_file_config(self):
331 ini_conf = {}
331 ini_conf = {}
332 conf = configobj.ConfigObj(self.options.config_file)
332 conf = configobj.ConfigObj(self.options.config_file)
333 if 'DEFAULT' in conf:
333 if 'DEFAULT' in conf:
334 ini_conf = conf['DEFAULT']
334 ini_conf = conf['DEFAULT']
335
335
336 return ini_conf
336 return ini_conf
337
337
338 def _show_config(self, vcsserver_config):
338 def _show_config(self, vcsserver_config):
339 order = [
339 order = [
340 'config_file',
340 'config_file',
341 'host',
341 'host',
342 'port',
342 'port',
343 'log_file',
343 'log_file',
344 'log_level',
344 'log_level',
345 'locale',
345 'locale',
346 'threadpool_size',
346 'threadpool_size',
347 'timeout',
347 'timeout',
348 'cache_config',
348 'cache_config',
349 ]
349 ]
350
350
351 def sorter(k):
351 def sorter(k):
352 return dict([(y, x) for x, y in enumerate(order)]).get(k)
352 return dict([(y, x) for x, y in enumerate(order)]).get(k)
353
353
354 _config = []
354 _config = []
355 for k in sorted(vcsserver_config.keys(), key=sorter):
355 for k in sorted(vcsserver_config.keys(), key=sorter):
356 v = vcsserver_config[k]
356 v = vcsserver_config[k]
357 # construct padded key for display eg %-20s % = key: val
357 # construct padded key for display eg %-20s % = key: val
358 k_formatted = ('%-'+str(len(max(order, key=len))+1)+'s') % (k+':')
358 k_formatted = ('%-'+str(len(max(order, key=len))+1)+'s') % (k+':')
359 _config.append(' * %s %s' % (k_formatted, v))
359 _config.append(' * %s %s' % (k_formatted, v))
360 log.info('\n[vcsserver configuration]:\n'+'\n'.join(_config))
360 log.info('\n[vcsserver configuration]:\n'+'\n'.join(_config))
361
361
362 def _get_vcsserver_configuration(self):
362 def _get_vcsserver_configuration(self):
363 _defaults = {
363 _defaults = {
364 'config_file': None,
364 'config_file': None,
365 'git_path': 'git',
365 'git_path': 'git',
366 'host': 'localhost',
366 'host': 'localhost',
367 'port': settings.PYRO_PORT,
367 'port': settings.PYRO_PORT,
368 'log_file': None,
368 'log_file': None,
369 'log_level': 'debug',
369 'log_level': 'debug',
370 'locale': None,
370 'locale': None,
371 'threadpool_size': 16,
371 'threadpool_size': 16,
372 'timeout': None,
372 'timeout': None,
373
373
374 # Development support
374 # Development support
375 'dev.use_echo_app': False,
375 'dev.use_echo_app': False,
376
376
377 # caches, baker style config
377 # caches, baker style config
378 'beaker.cache.regions': 'repo_object',
378 'beaker.cache.regions': 'repo_object',
379 'beaker.cache.repo_object.expire': '10',
379 'beaker.cache.repo_object.expire': '10',
380 'beaker.cache.repo_object.type': 'memory',
380 'beaker.cache.repo_object.type': 'memory',
381 }
381 }
382 config = {}
382 config = {}
383 config.update(_defaults)
383 config.update(_defaults)
384 # overwrite defaults with one loaded from file
384 # overwrite defaults with one loaded from file
385 config.update(self._get_file_config())
385 config.update(self._get_file_config())
386
386
387 # overwrite with self.option which has the top priority
387 # overwrite with self.option which has the top priority
388 for k, v in self.options.__dict__.items():
388 for k, v in self.options.__dict__.items():
389 if v or v == 0:
389 if v or v == 0:
390 config[k] = v
390 config[k] = v
391
391
392 # clear all "extra" keys if they are somehow passed,
392 # clear all "extra" keys if they are somehow passed,
393 # we only want defaults, so any extra stuff from self.options is cleared
393 # we only want defaults, so any extra stuff from self.options is cleared
394 # except beaker stuff which needs to be dynamic
394 # except beaker stuff which needs to be dynamic
395 for k in [k for k in config.copy().keys() if not k.startswith('beaker.cache.')]:
395 for k in [k for k in config.copy().keys() if not k.startswith('beaker.cache.')]:
396 if k not in _defaults:
396 if k not in _defaults:
397 del config[k]
397 del config[k]
398
398
399 # group together the cache into one key.
399 # group together the cache into one key.
400 # Needed further for beaker lib configuration
400 # Needed further for beaker lib configuration
401 _k = {}
401 _k = {}
402 for k in [k for k in config.copy() if k.startswith('beaker.cache.')]:
402 for k in [k for k in config.copy() if k.startswith('beaker.cache.')]:
403 _k[k] = config.pop(k)
403 _k[k] = config.pop(k)
404 config['cache_config'] = _k
404 config['cache_config'] = _k
405
405
406 return config
406 return config
407
407
408 def out(self, msg): # pragma: no cover
408 def out(self, msg): # pragma: no cover
409 if self.options.verbose > 0:
409 if self.options.verbose > 0:
410 print(msg)
410 print(msg)
411
411
412 def run(self): # pragma: no cover
412 def run(self): # pragma: no cover
413 vcsserver_config = self._get_vcsserver_configuration()
413 vcsserver_config = self._get_vcsserver_configuration()
414
414
415 # Ensure the log file is writeable
415 # Ensure the log file is writeable
416 if vcsserver_config['log_file']:
416 if vcsserver_config['log_file']:
417 stdout_log = self._configure_logfile()
417 stdout_log = self._configure_logfile()
418 else:
418 else:
419 stdout_log = None
419 stdout_log = None
420
420
421 # set PID file with running lock
421 # set PID file with running lock
422 if self.options.running_file:
422 if self.options.running_file:
423 global SERVER_RUNNING_FILE
423 global SERVER_RUNNING_FILE
424 SERVER_RUNNING_FILE = self.options.running_file
424 SERVER_RUNNING_FILE = self.options.running_file
425
425
426 # configure logging, and logging based on configuration file
426 # configure logging, and logging based on configuration file
427 self._configure_logging(level=vcsserver_config['log_level'],
427 self._configure_logging(level=vcsserver_config['log_level'],
428 stream=stdout_log)
428 stream=stdout_log)
429 if self.options.config_file:
429 if self.options.config_file:
430 if not os.path.isfile(self.options.config_file):
430 if not os.path.isfile(self.options.config_file):
431 raise OSError('File %s does not exist' %
431 raise OSError('File %s does not exist' %
432 self.options.config_file)
432 self.options.config_file)
433
433
434 self._configure_file_logging(self.options.config_file)
434 self._configure_file_logging(self.options.config_file)
435
435
436 self._configure_settings(vcsserver_config)
436 self._configure_settings(vcsserver_config)
437
437
438 # display current configuration of vcsserver
438 # display current configuration of vcsserver
439 self._show_config(vcsserver_config)
439 self._show_config(vcsserver_config)
440
440
441 if not vcsserver_config['dev.use_echo_app']:
441 if not vcsserver_config['dev.use_echo_app']:
442 remote_wsgi_mod = remote_wsgi
442 remote_wsgi_mod = remote_wsgi
443 else:
443 else:
444 log.warning("Using EchoApp for VCS endpoints.")
444 log.warning("Using EchoApp for VCS endpoints.")
445 remote_wsgi_mod = remote_wsgi_stub
445 remote_wsgi_mod = remote_wsgi_stub
446
446
447 app = Application(
447 app = Application(
448 host=vcsserver_config['host'],
448 host=vcsserver_config['host'],
449 port=vcsserver_config['port'],
449 port=vcsserver_config['port'],
450 locale=vcsserver_config['locale'],
450 locale=vcsserver_config['locale'],
451 threadpool_size=vcsserver_config['threadpool_size'],
451 threadpool_size=vcsserver_config['threadpool_size'],
452 timeout=vcsserver_config['timeout'],
452 timeout=vcsserver_config['timeout'],
453 cache_config=vcsserver_config['cache_config'],
453 cache_config=vcsserver_config['cache_config'],
454 remote_wsgi_=remote_wsgi_mod)
454 remote_wsgi_=remote_wsgi_mod)
455 app.init()
455 app.init()
456 app.run()
456 app.run()
457
457
458 def _configure_logging(self, level, stream=None):
458 def _configure_logging(self, level, stream=None):
459 _format = (
459 _format = (
460 '%(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s')
460 '%(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s')
461 levels = {
461 levels = {
462 'debug': logging.DEBUG,
462 'debug': logging.DEBUG,
463 'info': logging.INFO,
463 'info': logging.INFO,
464 'warning': logging.WARNING,
464 'warning': logging.WARNING,
465 'error': logging.ERROR,
465 'error': logging.ERROR,
466 'critical': logging.CRITICAL,
466 'critical': logging.CRITICAL,
467 }
467 }
468 try:
468 try:
469 level = levels[level]
469 level = levels[level]
470 except KeyError:
470 except KeyError:
471 raise AttributeError(
471 raise AttributeError(
472 'Invalid log level please use one of %s' % (levels.keys(),))
472 'Invalid log level please use one of %s' % (levels.keys(),))
473 logging.basicConfig(format=_format, stream=stream, level=level)
473 logging.basicConfig(format=_format, stream=stream, level=level)
474 logging.getLogger('Pyro4').setLevel(level)
474 logging.getLogger('Pyro4').setLevel(level)
475
475
476 def _configure_file_logging(self, config):
476 def _configure_file_logging(self, config):
477 import logging.config
477 import logging.config
478 try:
478 try:
479 logging.config.fileConfig(config)
479 logging.config.fileConfig(config)
480 except Exception as e:
480 except Exception as e:
481 log.warning('Failed to configure logging based on given '
481 log.warning('Failed to configure logging based on given '
482 'config file. Error: %s' % e)
482 'config file. Error: %s' % e)
483
483
484 def _configure_logfile(self):
484 def _configure_logfile(self):
485 try:
485 try:
486 writeable_log_file = open(self.options.log_file, 'a')
486 writeable_log_file = open(self.options.log_file, 'a')
487 except IOError as ioe:
487 except IOError as ioe:
488 msg = 'Error: Unable to write to log file: %s' % ioe
488 msg = 'Error: Unable to write to log file: %s' % ioe
489 raise ValueError(msg)
489 raise ValueError(msg)
490 writeable_log_file.close()
490 writeable_log_file.close()
491 stdout_log = LazyWriter(self.options.log_file, 'a')
491 stdout_log = LazyWriter(self.options.log_file, 'a')
492 sys.stdout = stdout_log
492 sys.stdout = stdout_log
493 sys.stderr = stdout_log
493 sys.stderr = stdout_log
494 return stdout_log
494 return stdout_log
495
495
496 def _configure_settings(self, config):
496 def _configure_settings(self, config):
497 """
497 """
498 Configure the settings module based on the given `config`.
498 Configure the settings module based on the given `config`.
499 """
499 """
500 settings.GIT_EXECUTABLE = config['git_path']
500 settings.GIT_EXECUTABLE = config['git_path']
501
501
502
502
503 def main(argv=sys.argv, quiet=False):
503 def main(argv=sys.argv, quiet=False):
504 if MercurialFactory:
504 if MercurialFactory:
505 hgpatches.patch_largefiles_capabilities()
505 hgpatches.patch_largefiles_capabilities()
506 hgpatches.patch_subrepo_type_mapping()
506 command = VcsServerCommand(argv, quiet=quiet)
507 command = VcsServerCommand(argv, quiet=quiet)
507 return command.run()
508 return command.run()