##// END OF EJS Templates
hg: fixed code after version upgrade to 4.6.0 release
marcink -
r432:92b33b73 default
parent child Browse files
Show More
@@ -1,776 +1,789 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import urllib
21 import urllib
22 import urllib2
22 import urllib2
23
23
24 from hgext import largefiles, rebase
24 from hgext import largefiles, rebase
25 from hgext.strip import strip as hgext_strip
25 from hgext.strip import strip as hgext_strip
26 from mercurial import commands
26 from mercurial import commands
27 from mercurial import unionrepo
27 from mercurial import unionrepo
28 from mercurial import verify
28 from mercurial import verify
29
29
30 from vcsserver import exceptions
30 from vcsserver import exceptions
31 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
31 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
32 from vcsserver.hgcompat import (
32 from vcsserver.hgcompat import (
33 archival, bin, clone, config as hgconfig, diffopts, hex,
33 archival, bin, clone, config as hgconfig, diffopts, hex,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
34 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
35 httppeer, localrepository, match, memctx, exchange, memfilectx, nullrev,
36 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
36 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
37 RepoLookupError, InterventionRequired, RequirementError)
37 RepoLookupError, InterventionRequired, RequirementError)
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 def make_ui_from_config(repo_config):
42 def make_ui_from_config(repo_config):
43 baseui = ui.ui()
43 baseui = ui.ui()
44
44
45 # clean the baseui object
45 # clean the baseui object
46 baseui._ocfg = hgconfig.config()
46 baseui._ocfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
47 baseui._ucfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
48 baseui._tcfg = hgconfig.config()
49
49
50 for section, option, value in repo_config:
50 for section, option, value in repo_config:
51 baseui.setconfig(section, option, value)
51 baseui.setconfig(section, option, value)
52
52
53 # make our hgweb quiet so it doesn't print output
53 # make our hgweb quiet so it doesn't print output
54 baseui.setconfig('ui', 'quiet', 'true')
54 baseui.setconfig('ui', 'quiet', 'true')
55
55
56 baseui.setconfig('ui', 'paginate', 'never')
56 baseui.setconfig('ui', 'paginate', 'never')
57 # force mercurial to only use 1 thread, otherwise it may try to set a
57 # force mercurial to only use 1 thread, otherwise it may try to set a
58 # signal in a non-main thread, thus generating a ValueError.
58 # signal in a non-main thread, thus generating a ValueError.
59 baseui.setconfig('worker', 'numcpus', 1)
59 baseui.setconfig('worker', 'numcpus', 1)
60
60
61 # If there is no config for the largefiles extension, we explicitly disable
61 # If there is no config for the largefiles extension, we explicitly disable
62 # it here. This overrides settings from repositories hgrc file. Recent
62 # it here. This overrides settings from repositories hgrc file. Recent
63 # mercurial versions enable largefiles in hgrc on clone from largefile
63 # mercurial versions enable largefiles in hgrc on clone from largefile
64 # repo.
64 # repo.
65 if not baseui.hasconfig('extensions', 'largefiles'):
65 if not baseui.hasconfig('extensions', 'largefiles'):
66 log.debug('Explicitly disable largefiles extension for repo.')
66 log.debug('Explicitly disable largefiles extension for repo.')
67 baseui.setconfig('extensions', 'largefiles', '!')
67 baseui.setconfig('extensions', 'largefiles', '!')
68
68
69 return baseui
69 return baseui
70
70
71
71
72 def reraise_safe_exceptions(func):
72 def reraise_safe_exceptions(func):
73 """Decorator for converting mercurial exceptions to something neutral."""
73 """Decorator for converting mercurial exceptions to something neutral."""
74 def wrapper(*args, **kwargs):
74 def wrapper(*args, **kwargs):
75 try:
75 try:
76 return func(*args, **kwargs)
76 return func(*args, **kwargs)
77 except (Abort, InterventionRequired):
77 except (Abort, InterventionRequired):
78 raise_from_original(exceptions.AbortException)
78 raise_from_original(exceptions.AbortException)
79 except RepoLookupError:
79 except RepoLookupError:
80 raise_from_original(exceptions.LookupException)
80 raise_from_original(exceptions.LookupException)
81 except RequirementError:
81 except RequirementError:
82 raise_from_original(exceptions.RequirementException)
82 raise_from_original(exceptions.RequirementException)
83 except RepoError:
83 except RepoError:
84 raise_from_original(exceptions.VcsException)
84 raise_from_original(exceptions.VcsException)
85 except LookupError:
85 except LookupError:
86 raise_from_original(exceptions.LookupException)
86 raise_from_original(exceptions.LookupException)
87 except Exception as e:
87 except Exception as e:
88 if not hasattr(e, '_vcs_kind'):
88 if not hasattr(e, '_vcs_kind'):
89 log.exception("Unhandled exception in hg remote call")
89 log.exception("Unhandled exception in hg remote call")
90 raise_from_original(exceptions.UnhandledException)
90 raise_from_original(exceptions.UnhandledException)
91 raise
91 raise
92 return wrapper
92 return wrapper
93
93
94
94
95 class MercurialFactory(RepoFactory):
95 class MercurialFactory(RepoFactory):
96
96
97 def _create_config(self, config, hooks=True):
97 def _create_config(self, config, hooks=True):
98 if not hooks:
98 if not hooks:
99 hooks_to_clean = frozenset((
99 hooks_to_clean = frozenset((
100 'changegroup.repo_size', 'preoutgoing.pre_pull',
100 'changegroup.repo_size', 'preoutgoing.pre_pull',
101 'outgoing.pull_logger', 'prechangegroup.pre_push'))
101 'outgoing.pull_logger', 'prechangegroup.pre_push'))
102 new_config = []
102 new_config = []
103 for section, option, value in config:
103 for section, option, value in config:
104 if section == 'hooks' and option in hooks_to_clean:
104 if section == 'hooks' and option in hooks_to_clean:
105 continue
105 continue
106 new_config.append((section, option, value))
106 new_config.append((section, option, value))
107 config = new_config
107 config = new_config
108
108
109 baseui = make_ui_from_config(config)
109 baseui = make_ui_from_config(config)
110 return baseui
110 return baseui
111
111
112 def _create_repo(self, wire, create):
112 def _create_repo(self, wire, create):
113 baseui = self._create_config(wire["config"])
113 baseui = self._create_config(wire["config"])
114 return localrepository(baseui, wire["path"], create)
114 return localrepository(baseui, wire["path"], create)
115
115
116
116
117 class HgRemote(object):
117 class HgRemote(object):
118
118
119 def __init__(self, factory):
119 def __init__(self, factory):
120 self._factory = factory
120 self._factory = factory
121
121
122 self._bulk_methods = {
122 self._bulk_methods = {
123 "affected_files": self.ctx_files,
123 "affected_files": self.ctx_files,
124 "author": self.ctx_user,
124 "author": self.ctx_user,
125 "branch": self.ctx_branch,
125 "branch": self.ctx_branch,
126 "children": self.ctx_children,
126 "children": self.ctx_children,
127 "date": self.ctx_date,
127 "date": self.ctx_date,
128 "message": self.ctx_description,
128 "message": self.ctx_description,
129 "parents": self.ctx_parents,
129 "parents": self.ctx_parents,
130 "status": self.ctx_status,
130 "status": self.ctx_status,
131 "obsolete": self.ctx_obsolete,
131 "obsolete": self.ctx_obsolete,
132 "phase": self.ctx_phase,
132 "phase": self.ctx_phase,
133 "hidden": self.ctx_hidden,
133 "hidden": self.ctx_hidden,
134 "_file_paths": self.ctx_list,
134 "_file_paths": self.ctx_list,
135 }
135 }
136
136
137 @reraise_safe_exceptions
137 @reraise_safe_exceptions
138 def discover_hg_version(self):
138 def discover_hg_version(self):
139 from mercurial import util
139 from mercurial import util
140 return util.version()
140 return util.version()
141
141
142 @reraise_safe_exceptions
142 @reraise_safe_exceptions
143 def archive_repo(self, archive_path, mtime, file_info, kind):
143 def archive_repo(self, archive_path, mtime, file_info, kind):
144 if kind == "tgz":
144 if kind == "tgz":
145 archiver = archival.tarit(archive_path, mtime, "gz")
145 archiver = archival.tarit(archive_path, mtime, "gz")
146 elif kind == "tbz2":
146 elif kind == "tbz2":
147 archiver = archival.tarit(archive_path, mtime, "bz2")
147 archiver = archival.tarit(archive_path, mtime, "bz2")
148 elif kind == 'zip':
148 elif kind == 'zip':
149 archiver = archival.zipit(archive_path, mtime)
149 archiver = archival.zipit(archive_path, mtime)
150 else:
150 else:
151 raise exceptions.ArchiveException(
151 raise exceptions.ArchiveException(
152 'Remote does not support: "%s".' % kind)
152 'Remote does not support: "%s".' % kind)
153
153
154 for f_path, f_mode, f_is_link, f_content in file_info:
154 for f_path, f_mode, f_is_link, f_content in file_info:
155 archiver.addfile(f_path, f_mode, f_is_link, f_content)
155 archiver.addfile(f_path, f_mode, f_is_link, f_content)
156 archiver.done()
156 archiver.done()
157
157
158 @reraise_safe_exceptions
158 @reraise_safe_exceptions
159 def bookmarks(self, wire):
159 def bookmarks(self, wire):
160 repo = self._factory.repo(wire)
160 repo = self._factory.repo(wire)
161 return dict(repo._bookmarks)
161 return dict(repo._bookmarks)
162
162
163 @reraise_safe_exceptions
163 @reraise_safe_exceptions
164 def branches(self, wire, normal, closed):
164 def branches(self, wire, normal, closed):
165 repo = self._factory.repo(wire)
165 repo = self._factory.repo(wire)
166 iter_branches = repo.branchmap().iterbranches()
166 iter_branches = repo.branchmap().iterbranches()
167 bt = {}
167 bt = {}
168 for branch_name, _heads, tip, is_closed in iter_branches:
168 for branch_name, _heads, tip, is_closed in iter_branches:
169 if normal and not is_closed:
169 if normal and not is_closed:
170 bt[branch_name] = tip
170 bt[branch_name] = tip
171 if closed and is_closed:
171 if closed and is_closed:
172 bt[branch_name] = tip
172 bt[branch_name] = tip
173
173
174 return bt
174 return bt
175
175
176 @reraise_safe_exceptions
176 @reraise_safe_exceptions
177 def bulk_request(self, wire, rev, pre_load):
177 def bulk_request(self, wire, rev, pre_load):
178 result = {}
178 result = {}
179 for attr in pre_load:
179 for attr in pre_load:
180 try:
180 try:
181 method = self._bulk_methods[attr]
181 method = self._bulk_methods[attr]
182 result[attr] = method(wire, rev)
182 result[attr] = method(wire, rev)
183 except KeyError:
183 except KeyError:
184 raise exceptions.VcsException(
184 raise exceptions.VcsException(
185 'Unknown bulk attribute: "%s"' % attr)
185 'Unknown bulk attribute: "%s"' % attr)
186 return result
186 return result
187
187
188 @reraise_safe_exceptions
188 @reraise_safe_exceptions
189 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
189 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
190 baseui = self._factory._create_config(wire["config"], hooks=hooks)
190 baseui = self._factory._create_config(wire["config"], hooks=hooks)
191 clone(baseui, source, dest, noupdate=not update_after_clone)
191 clone(baseui, source, dest, noupdate=not update_after_clone)
192
192
193 @reraise_safe_exceptions
193 @reraise_safe_exceptions
194 def commitctx(
194 def commitctx(
195 self, wire, message, parents, commit_time, commit_timezone,
195 self, wire, message, parents, commit_time, commit_timezone,
196 user, files, extra, removed, updated):
196 user, files, extra, removed, updated):
197
197
198 def _filectxfn(_repo, memctx, path):
198 def _filectxfn(_repo, memctx, path):
199 """
199 """
200 Marks given path as added/changed/removed in a given _repo. This is
200 Marks given path as added/changed/removed in a given _repo. This is
201 for internal mercurial commit function.
201 for internal mercurial commit function.
202 """
202 """
203
203
204 # check if this path is removed
204 # check if this path is removed
205 if path in removed:
205 if path in removed:
206 # returning None is a way to mark node for removal
206 # returning None is a way to mark node for removal
207 return None
207 return None
208
208
209 # check if this path is added
209 # check if this path is added
210 for node in updated:
210 for node in updated:
211 if node['path'] == path:
211 if node['path'] == path:
212 return memfilectx(
212 return memfilectx(
213 _repo,
213 _repo,
214 changectx=memctx,
214 path=node['path'],
215 path=node['path'],
215 data=node['content'],
216 data=node['content'],
216 islink=False,
217 islink=False,
217 isexec=bool(node['mode'] & stat.S_IXUSR),
218 isexec=bool(node['mode'] & stat.S_IXUSR),
218 copied=False,
219 copied=False)
219 memctx=memctx)
220
220
221 raise exceptions.AbortException(
221 raise exceptions.AbortException(
222 "Given path haven't been marked as added, "
222 "Given path haven't been marked as added, "
223 "changed or removed (%s)" % path)
223 "changed or removed (%s)" % path)
224
224
225 repo = self._factory.repo(wire)
225 repo = self._factory.repo(wire)
226
226
227 commit_ctx = memctx(
227 commit_ctx = memctx(
228 repo=repo,
228 repo=repo,
229 parents=parents,
229 parents=parents,
230 text=message,
230 text=message,
231 files=files,
231 files=files,
232 filectxfn=_filectxfn,
232 filectxfn=_filectxfn,
233 user=user,
233 user=user,
234 date=(commit_time, commit_timezone),
234 date=(commit_time, commit_timezone),
235 extra=extra)
235 extra=extra)
236
236
237 n = repo.commitctx(commit_ctx)
237 n = repo.commitctx(commit_ctx)
238 new_id = hex(n)
238 new_id = hex(n)
239
239
240 return new_id
240 return new_id
241
241
242 @reraise_safe_exceptions
242 @reraise_safe_exceptions
243 def ctx_branch(self, wire, revision):
243 def ctx_branch(self, wire, revision):
244 repo = self._factory.repo(wire)
244 repo = self._factory.repo(wire)
245 ctx = repo[revision]
245 ctx = repo[revision]
246 return ctx.branch()
246 return ctx.branch()
247
247
248 @reraise_safe_exceptions
248 @reraise_safe_exceptions
249 def ctx_children(self, wire, revision):
249 def ctx_children(self, wire, revision):
250 repo = self._factory.repo(wire)
250 repo = self._factory.repo(wire)
251 ctx = repo[revision]
251 ctx = repo[revision]
252 return [child.rev() for child in ctx.children()]
252 return [child.rev() for child in ctx.children()]
253
253
254 @reraise_safe_exceptions
254 @reraise_safe_exceptions
255 def ctx_date(self, wire, revision):
255 def ctx_date(self, wire, revision):
256 repo = self._factory.repo(wire)
256 repo = self._factory.repo(wire)
257 ctx = repo[revision]
257 ctx = repo[revision]
258 return ctx.date()
258 return ctx.date()
259
259
260 @reraise_safe_exceptions
260 @reraise_safe_exceptions
261 def ctx_description(self, wire, revision):
261 def ctx_description(self, wire, revision):
262 repo = self._factory.repo(wire)
262 repo = self._factory.repo(wire)
263 ctx = repo[revision]
263 ctx = repo[revision]
264 return ctx.description()
264 return ctx.description()
265
265
266 @reraise_safe_exceptions
266 @reraise_safe_exceptions
267 def ctx_diff(
267 def ctx_diff(
268 self, wire, revision, git=True, ignore_whitespace=True, context=3):
268 self, wire, revision, git=True, ignore_whitespace=True, context=3):
269 repo = self._factory.repo(wire)
269 repo = self._factory.repo(wire)
270 ctx = repo[revision]
270 ctx = repo[revision]
271 result = ctx.diff(
271 result = ctx.diff(
272 git=git, ignore_whitespace=ignore_whitespace, context=context)
272 git=git, ignore_whitespace=ignore_whitespace, context=context)
273 return list(result)
273 return list(result)
274
274
275 @reraise_safe_exceptions
275 @reraise_safe_exceptions
276 def ctx_files(self, wire, revision):
276 def ctx_files(self, wire, revision):
277 repo = self._factory.repo(wire)
277 repo = self._factory.repo(wire)
278 ctx = repo[revision]
278 ctx = repo[revision]
279 return ctx.files()
279 return ctx.files()
280
280
281 @reraise_safe_exceptions
281 @reraise_safe_exceptions
282 def ctx_list(self, path, revision):
282 def ctx_list(self, path, revision):
283 repo = self._factory.repo(path)
283 repo = self._factory.repo(path)
284 ctx = repo[revision]
284 ctx = repo[revision]
285 return list(ctx)
285 return list(ctx)
286
286
287 @reraise_safe_exceptions
287 @reraise_safe_exceptions
288 def ctx_parents(self, wire, revision):
288 def ctx_parents(self, wire, revision):
289 repo = self._factory.repo(wire)
289 repo = self._factory.repo(wire)
290 ctx = repo[revision]
290 ctx = repo[revision]
291 return [parent.rev() for parent in ctx.parents()]
291 return [parent.rev() for parent in ctx.parents()]
292
292
293 @reraise_safe_exceptions
293 @reraise_safe_exceptions
294 def ctx_phase(self, wire, revision):
294 def ctx_phase(self, wire, revision):
295 repo = self._factory.repo(wire)
295 repo = self._factory.repo(wire)
296 ctx = repo[revision]
296 ctx = repo[revision]
297 # public=0, draft=1, secret=3
297 # public=0, draft=1, secret=3
298 return ctx.phase()
298 return ctx.phase()
299
299
300 @reraise_safe_exceptions
300 @reraise_safe_exceptions
301 def ctx_obsolete(self, wire, revision):
301 def ctx_obsolete(self, wire, revision):
302 repo = self._factory.repo(wire)
302 repo = self._factory.repo(wire)
303 ctx = repo[revision]
303 ctx = repo[revision]
304 return ctx.obsolete()
304 return ctx.obsolete()
305
305
306 @reraise_safe_exceptions
306 @reraise_safe_exceptions
307 def ctx_hidden(self, wire, revision):
307 def ctx_hidden(self, wire, revision):
308 repo = self._factory.repo(wire)
308 repo = self._factory.repo(wire)
309 ctx = repo[revision]
309 ctx = repo[revision]
310 return ctx.hidden()
310 return ctx.hidden()
311
311
312 @reraise_safe_exceptions
312 @reraise_safe_exceptions
313 def ctx_substate(self, wire, revision):
313 def ctx_substate(self, wire, revision):
314 repo = self._factory.repo(wire)
314 repo = self._factory.repo(wire)
315 ctx = repo[revision]
315 ctx = repo[revision]
316 return ctx.substate
316 return ctx.substate
317
317
318 @reraise_safe_exceptions
318 @reraise_safe_exceptions
319 def ctx_status(self, wire, revision):
319 def ctx_status(self, wire, revision):
320 repo = self._factory.repo(wire)
320 repo = self._factory.repo(wire)
321 ctx = repo[revision]
321 ctx = repo[revision]
322 status = repo[ctx.p1().node()].status(other=ctx.node())
322 status = repo[ctx.p1().node()].status(other=ctx.node())
323 # object of status (odd, custom named tuple in mercurial) is not
323 # object of status (odd, custom named tuple in mercurial) is not
324 # correctly serializable, we make it a list, as the underling
324 # correctly serializable, we make it a list, as the underling
325 # API expects this to be a list
325 # API expects this to be a list
326 return list(status)
326 return list(status)
327
327
328 @reraise_safe_exceptions
328 @reraise_safe_exceptions
329 def ctx_user(self, wire, revision):
329 def ctx_user(self, wire, revision):
330 repo = self._factory.repo(wire)
330 repo = self._factory.repo(wire)
331 ctx = repo[revision]
331 ctx = repo[revision]
332 return ctx.user()
332 return ctx.user()
333
333
334 @reraise_safe_exceptions
334 @reraise_safe_exceptions
335 def check_url(self, url, config):
335 def check_url(self, url, config):
336 _proto = None
336 _proto = None
337 if '+' in url[:url.find('://')]:
337 if '+' in url[:url.find('://')]:
338 _proto = url[0:url.find('+')]
338 _proto = url[0:url.find('+')]
339 url = url[url.find('+') + 1:]
339 url = url[url.find('+') + 1:]
340 handlers = []
340 handlers = []
341 url_obj = url_parser(url)
341 url_obj = url_parser(url)
342 test_uri, authinfo = url_obj.authinfo()
342 test_uri, authinfo = url_obj.authinfo()
343 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
343 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
344 url_obj.query = obfuscate_qs(url_obj.query)
344 url_obj.query = obfuscate_qs(url_obj.query)
345
345
346 cleaned_uri = str(url_obj)
346 cleaned_uri = str(url_obj)
347 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
347 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
348
348
349 if authinfo:
349 if authinfo:
350 # create a password manager
350 # create a password manager
351 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
351 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
352 passmgr.add_password(*authinfo)
352 passmgr.add_password(*authinfo)
353
353
354 handlers.extend((httpbasicauthhandler(passmgr),
354 handlers.extend((httpbasicauthhandler(passmgr),
355 httpdigestauthhandler(passmgr)))
355 httpdigestauthhandler(passmgr)))
356
356
357 o = urllib2.build_opener(*handlers)
357 o = urllib2.build_opener(*handlers)
358 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
358 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
359 ('Accept', 'application/mercurial-0.1')]
359 ('Accept', 'application/mercurial-0.1')]
360
360
361 q = {"cmd": 'between'}
361 q = {"cmd": 'between'}
362 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
362 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
363 qs = '?%s' % urllib.urlencode(q)
363 qs = '?%s' % urllib.urlencode(q)
364 cu = "%s%s" % (test_uri, qs)
364 cu = "%s%s" % (test_uri, qs)
365 req = urllib2.Request(cu, None, {})
365 req = urllib2.Request(cu, None, {})
366
366
367 try:
367 try:
368 log.debug("Trying to open URL %s", cleaned_uri)
368 log.debug("Trying to open URL %s", cleaned_uri)
369 resp = o.open(req)
369 resp = o.open(req)
370 if resp.code != 200:
370 if resp.code != 200:
371 raise exceptions.URLError('Return Code is not 200')
371 raise exceptions.URLError('Return Code is not 200')
372 except Exception as e:
372 except Exception as e:
373 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
373 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
374 # means it cannot be cloned
374 # means it cannot be cloned
375 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
375 raise exceptions.URLError("[%s] org_exc: %s" % (cleaned_uri, e))
376
376
377 # now check if it's a proper hg repo, but don't do it for svn
377 # now check if it's a proper hg repo, but don't do it for svn
378 try:
378 try:
379 if _proto == 'svn':
379 if _proto == 'svn':
380 pass
380 pass
381 else:
381 else:
382 # check for pure hg repos
382 # check for pure hg repos
383 log.debug(
383 log.debug(
384 "Verifying if URL is a Mercurial repository: %s",
384 "Verifying if URL is a Mercurial repository: %s",
385 cleaned_uri)
385 cleaned_uri)
386 httppeer(make_ui_from_config(config), url).lookup('tip')
386 httppeer(make_ui_from_config(config), url).lookup('tip')
387 except Exception as e:
387 except Exception as e:
388 log.warning("URL is not a valid Mercurial repository: %s",
388 log.warning("URL is not a valid Mercurial repository: %s",
389 cleaned_uri)
389 cleaned_uri)
390 raise exceptions.URLError(
390 raise exceptions.URLError(
391 "url [%s] does not look like an hg repo org_exc: %s"
391 "url [%s] does not look like an hg repo org_exc: %s"
392 % (cleaned_uri, e))
392 % (cleaned_uri, e))
393
393
394 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
394 log.info("URL is a valid Mercurial repository: %s", cleaned_uri)
395 return True
395 return True
396
396
397 @reraise_safe_exceptions
397 @reraise_safe_exceptions
398 def diff(
398 def diff(
399 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
399 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
400 context):
400 context):
401 repo = self._factory.repo(wire)
401 repo = self._factory.repo(wire)
402
402
403 if file_filter:
403 if file_filter:
404 match_filter = match(file_filter[0], '', [file_filter[1]])
404 match_filter = match(file_filter[0], '', [file_filter[1]])
405 else:
405 else:
406 match_filter = file_filter
406 match_filter = file_filter
407 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
407 opts = diffopts(git=opt_git, ignorews=opt_ignorews, context=context)
408
408
409 try:
409 try:
410 return "".join(patch.diff(
410 return "".join(patch.diff(
411 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
411 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
412 except RepoLookupError:
412 except RepoLookupError:
413 raise exceptions.LookupException()
413 raise exceptions.LookupException()
414
414
415 @reraise_safe_exceptions
415 @reraise_safe_exceptions
416 def file_history(self, wire, revision, path, limit):
416 def file_history(self, wire, revision, path, limit):
417 repo = self._factory.repo(wire)
417 repo = self._factory.repo(wire)
418
418
419 ctx = repo[revision]
419 ctx = repo[revision]
420 fctx = ctx.filectx(path)
420 fctx = ctx.filectx(path)
421
421
422 def history_iter():
422 def history_iter():
423 limit_rev = fctx.rev()
423 limit_rev = fctx.rev()
424 for obj in reversed(list(fctx.filelog())):
424 for obj in reversed(list(fctx.filelog())):
425 obj = fctx.filectx(obj)
425 obj = fctx.filectx(obj)
426 if limit_rev >= obj.rev():
426 if limit_rev >= obj.rev():
427 yield obj
427 yield obj
428
428
429 history = []
429 history = []
430 for cnt, obj in enumerate(history_iter()):
430 for cnt, obj in enumerate(history_iter()):
431 if limit and cnt >= limit:
431 if limit and cnt >= limit:
432 break
432 break
433 history.append(hex(obj.node()))
433 history.append(hex(obj.node()))
434
434
435 return [x for x in history]
435 return [x for x in history]
436
436
437 @reraise_safe_exceptions
437 @reraise_safe_exceptions
438 def file_history_untill(self, wire, revision, path, limit):
438 def file_history_untill(self, wire, revision, path, limit):
439 repo = self._factory.repo(wire)
439 repo = self._factory.repo(wire)
440 ctx = repo[revision]
440 ctx = repo[revision]
441 fctx = ctx.filectx(path)
441 fctx = ctx.filectx(path)
442
442
443 file_log = list(fctx.filelog())
443 file_log = list(fctx.filelog())
444 if limit:
444 if limit:
445 # Limit to the last n items
445 # Limit to the last n items
446 file_log = file_log[-limit:]
446 file_log = file_log[-limit:]
447
447
448 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
448 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
449
449
450 @reraise_safe_exceptions
450 @reraise_safe_exceptions
451 def fctx_annotate(self, wire, revision, path):
451 def fctx_annotate(self, wire, revision, path):
452 repo = self._factory.repo(wire)
452 repo = self._factory.repo(wire)
453 ctx = repo[revision]
453 ctx = repo[revision]
454 fctx = ctx.filectx(path)
454 fctx = ctx.filectx(path)
455
455
456 result = []
456 result = []
457 for i, (a_line, content) in enumerate(fctx.annotate()):
457 for i, annotate_obj in enumerate(fctx.annotate(), 1):
458 ln_no = i + 1
458 ln_no = i
459 sha = hex(a_line.fctx.node())
459 sha = hex(annotate_obj.fctx.node())
460 content = annotate_obj.text
460 result.append((ln_no, sha, content))
461 result.append((ln_no, sha, content))
461 return result
462 return result
462
463
463 @reraise_safe_exceptions
464 @reraise_safe_exceptions
464 def fctx_data(self, wire, revision, path):
465 def fctx_data(self, wire, revision, path):
465 repo = self._factory.repo(wire)
466 repo = self._factory.repo(wire)
466 ctx = repo[revision]
467 ctx = repo[revision]
467 fctx = ctx.filectx(path)
468 fctx = ctx.filectx(path)
468 return fctx.data()
469 return fctx.data()
469
470
470 @reraise_safe_exceptions
471 @reraise_safe_exceptions
471 def fctx_flags(self, wire, revision, path):
472 def fctx_flags(self, wire, revision, path):
472 repo = self._factory.repo(wire)
473 repo = self._factory.repo(wire)
473 ctx = repo[revision]
474 ctx = repo[revision]
474 fctx = ctx.filectx(path)
475 fctx = ctx.filectx(path)
475 return fctx.flags()
476 return fctx.flags()
476
477
477 @reraise_safe_exceptions
478 @reraise_safe_exceptions
478 def fctx_size(self, wire, revision, path):
479 def fctx_size(self, wire, revision, path):
479 repo = self._factory.repo(wire)
480 repo = self._factory.repo(wire)
480 ctx = repo[revision]
481 ctx = repo[revision]
481 fctx = ctx.filectx(path)
482 fctx = ctx.filectx(path)
482 return fctx.size()
483 return fctx.size()
483
484
484 @reraise_safe_exceptions
485 @reraise_safe_exceptions
485 def get_all_commit_ids(self, wire, name):
486 def get_all_commit_ids(self, wire, name):
486 repo = self._factory.repo(wire)
487 repo = self._factory.repo(wire)
487 revs = repo.filtered(name).changelog.index
488 revs = repo.filtered(name).changelog.index
488 return map(lambda x: hex(x[7]), revs)[:-1]
489 return map(lambda x: hex(x[7]), revs)[:-1]
489
490
490 @reraise_safe_exceptions
491 @reraise_safe_exceptions
491 def get_config_value(self, wire, section, name, untrusted=False):
492 def get_config_value(self, wire, section, name, untrusted=False):
492 repo = self._factory.repo(wire)
493 repo = self._factory.repo(wire)
493 return repo.ui.config(section, name, untrusted=untrusted)
494 return repo.ui.config(section, name, untrusted=untrusted)
494
495
495 @reraise_safe_exceptions
496 @reraise_safe_exceptions
496 def get_config_bool(self, wire, section, name, untrusted=False):
497 def get_config_bool(self, wire, section, name, untrusted=False):
497 repo = self._factory.repo(wire)
498 repo = self._factory.repo(wire)
498 return repo.ui.configbool(section, name, untrusted=untrusted)
499 return repo.ui.configbool(section, name, untrusted=untrusted)
499
500
500 @reraise_safe_exceptions
501 @reraise_safe_exceptions
501 def get_config_list(self, wire, section, name, untrusted=False):
502 def get_config_list(self, wire, section, name, untrusted=False):
502 repo = self._factory.repo(wire)
503 repo = self._factory.repo(wire)
503 return repo.ui.configlist(section, name, untrusted=untrusted)
504 return repo.ui.configlist(section, name, untrusted=untrusted)
504
505
505 @reraise_safe_exceptions
506 @reraise_safe_exceptions
506 def is_large_file(self, wire, path):
507 def is_large_file(self, wire, path):
507 return largefiles.lfutil.isstandin(path)
508 return largefiles.lfutil.isstandin(path)
508
509
509 @reraise_safe_exceptions
510 @reraise_safe_exceptions
510 def in_largefiles_store(self, wire, sha):
511 def in_largefiles_store(self, wire, sha):
511 repo = self._factory.repo(wire)
512 repo = self._factory.repo(wire)
512 return largefiles.lfutil.instore(repo, sha)
513 return largefiles.lfutil.instore(repo, sha)
513
514
514 @reraise_safe_exceptions
515 @reraise_safe_exceptions
515 def in_user_cache(self, wire, sha):
516 def in_user_cache(self, wire, sha):
516 repo = self._factory.repo(wire)
517 repo = self._factory.repo(wire)
517 return largefiles.lfutil.inusercache(repo.ui, sha)
518 return largefiles.lfutil.inusercache(repo.ui, sha)
518
519
519 @reraise_safe_exceptions
520 @reraise_safe_exceptions
520 def store_path(self, wire, sha):
521 def store_path(self, wire, sha):
521 repo = self._factory.repo(wire)
522 repo = self._factory.repo(wire)
522 return largefiles.lfutil.storepath(repo, sha)
523 return largefiles.lfutil.storepath(repo, sha)
523
524
524 @reraise_safe_exceptions
525 @reraise_safe_exceptions
525 def link(self, wire, sha, path):
526 def link(self, wire, sha, path):
526 repo = self._factory.repo(wire)
527 repo = self._factory.repo(wire)
527 largefiles.lfutil.link(
528 largefiles.lfutil.link(
528 largefiles.lfutil.usercachepath(repo.ui, sha), path)
529 largefiles.lfutil.usercachepath(repo.ui, sha), path)
529
530
530 @reraise_safe_exceptions
531 @reraise_safe_exceptions
531 def localrepository(self, wire, create=False):
532 def localrepository(self, wire, create=False):
532 self._factory.repo(wire, create=create)
533 self._factory.repo(wire, create=create)
533
534
534 @reraise_safe_exceptions
535 @reraise_safe_exceptions
535 def lookup(self, wire, revision, both):
536 def lookup(self, wire, revision, both):
536 # TODO Paris: Ugly hack to "deserialize" long for msgpack
537
537 if isinstance(revision, float):
538 revision = long(revision)
539 repo = self._factory.repo(wire)
538 repo = self._factory.repo(wire)
539
540 if isinstance(revision, int):
541 # NOTE(marcink):
542 # since Mercurial doesn't support indexes properly
543 # we need to shift accordingly by one to get proper index, e.g
544 # repo[-1] => repo[-2]
545 # repo[0] => repo[-1]
546 # repo[1] => repo[2] we also never call repo[0] because
547 # it's actually second commit
548 if revision <= 0:
549 revision = revision + -1
550 else:
551 revision = revision + 1
552
540 try:
553 try:
541 ctx = repo[revision]
554 ctx = repo[revision]
542 except RepoLookupError:
555 except RepoLookupError:
543 raise exceptions.LookupException(revision)
556 raise exceptions.LookupException(revision)
544 except LookupError as e:
557 except LookupError as e:
545 raise exceptions.LookupException(e.name)
558 raise exceptions.LookupException(e.name)
546
559
547 if not both:
560 if not both:
548 return ctx.hex()
561 return ctx.hex()
549
562
550 ctx = repo[ctx.hex()]
563 ctx = repo[ctx.hex()]
551 return ctx.hex(), ctx.rev()
564 return ctx.hex(), ctx.rev()
552
565
553 @reraise_safe_exceptions
566 @reraise_safe_exceptions
554 def pull(self, wire, url, commit_ids=None):
567 def pull(self, wire, url, commit_ids=None):
555 repo = self._factory.repo(wire)
568 repo = self._factory.repo(wire)
556 # Disable any prompts for this repo
569 # Disable any prompts for this repo
557 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
570 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
558
571
559 remote = peer(repo, {}, url)
572 remote = peer(repo, {}, url)
560 # Disable any prompts for this remote
573 # Disable any prompts for this remote
561 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
574 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
562
575
563 if commit_ids:
576 if commit_ids:
564 commit_ids = [bin(commit_id) for commit_id in commit_ids]
577 commit_ids = [bin(commit_id) for commit_id in commit_ids]
565
578
566 return exchange.pull(
579 return exchange.pull(
567 repo, remote, heads=commit_ids, force=None).cgresult
580 repo, remote, heads=commit_ids, force=None).cgresult
568
581
569 @reraise_safe_exceptions
582 @reraise_safe_exceptions
570 def sync_push(self, wire, url):
583 def sync_push(self, wire, url):
571 if self.check_url(url, wire['config']):
584 if self.check_url(url, wire['config']):
572 repo = self._factory.repo(wire)
585 repo = self._factory.repo(wire)
573
586
574 # Disable any prompts for this repo
587 # Disable any prompts for this repo
575 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
588 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
576
589
577 bookmarks = dict(repo._bookmarks).keys()
590 bookmarks = dict(repo._bookmarks).keys()
578 remote = peer(repo, {}, url)
591 remote = peer(repo, {}, url)
579 # Disable any prompts for this remote
592 # Disable any prompts for this remote
580 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
593 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
581
594
582 return exchange.push(
595 return exchange.push(
583 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
596 repo, remote, newbranch=True, bookmarks=bookmarks).cgresult
584
597
585 @reraise_safe_exceptions
598 @reraise_safe_exceptions
586 def revision(self, wire, rev):
599 def revision(self, wire, rev):
587 repo = self._factory.repo(wire)
600 repo = self._factory.repo(wire)
588 ctx = repo[rev]
601 ctx = repo[rev]
589 return ctx.rev()
602 return ctx.rev()
590
603
591 @reraise_safe_exceptions
604 @reraise_safe_exceptions
592 def rev_range(self, wire, filter):
605 def rev_range(self, wire, filter):
593 repo = self._factory.repo(wire)
606 repo = self._factory.repo(wire)
594 revisions = [rev for rev in revrange(repo, filter)]
607 revisions = [rev for rev in revrange(repo, filter)]
595 return revisions
608 return revisions
596
609
597 @reraise_safe_exceptions
610 @reraise_safe_exceptions
598 def rev_range_hash(self, wire, node):
611 def rev_range_hash(self, wire, node):
599 repo = self._factory.repo(wire)
612 repo = self._factory.repo(wire)
600
613
601 def get_revs(repo, rev_opt):
614 def get_revs(repo, rev_opt):
602 if rev_opt:
615 if rev_opt:
603 revs = revrange(repo, rev_opt)
616 revs = revrange(repo, rev_opt)
604 if len(revs) == 0:
617 if len(revs) == 0:
605 return (nullrev, nullrev)
618 return (nullrev, nullrev)
606 return max(revs), min(revs)
619 return max(revs), min(revs)
607 else:
620 else:
608 return len(repo) - 1, 0
621 return len(repo) - 1, 0
609
622
610 stop, start = get_revs(repo, [node + ':'])
623 stop, start = get_revs(repo, [node + ':'])
611 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
624 revs = [hex(repo[r].node()) for r in xrange(start, stop + 1)]
612 return revs
625 return revs
613
626
614 @reraise_safe_exceptions
627 @reraise_safe_exceptions
615 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
628 def revs_from_revspec(self, wire, rev_spec, *args, **kwargs):
616 other_path = kwargs.pop('other_path', None)
629 other_path = kwargs.pop('other_path', None)
617
630
618 # case when we want to compare two independent repositories
631 # case when we want to compare two independent repositories
619 if other_path and other_path != wire["path"]:
632 if other_path and other_path != wire["path"]:
620 baseui = self._factory._create_config(wire["config"])
633 baseui = self._factory._create_config(wire["config"])
621 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
634 repo = unionrepo.unionrepository(baseui, other_path, wire["path"])
622 else:
635 else:
623 repo = self._factory.repo(wire)
636 repo = self._factory.repo(wire)
624 return list(repo.revs(rev_spec, *args))
637 return list(repo.revs(rev_spec, *args))
625
638
626 @reraise_safe_exceptions
639 @reraise_safe_exceptions
627 def strip(self, wire, revision, update, backup):
640 def strip(self, wire, revision, update, backup):
628 repo = self._factory.repo(wire)
641 repo = self._factory.repo(wire)
629 ctx = repo[revision]
642 ctx = repo[revision]
630 hgext_strip(
643 hgext_strip(
631 repo.baseui, repo, ctx.node(), update=update, backup=backup)
644 repo.baseui, repo, ctx.node(), update=update, backup=backup)
632
645
633 @reraise_safe_exceptions
646 @reraise_safe_exceptions
634 def verify(self, wire,):
647 def verify(self, wire,):
635 repo = self._factory.repo(wire)
648 repo = self._factory.repo(wire)
636 baseui = self._factory._create_config(wire['config'])
649 baseui = self._factory._create_config(wire['config'])
637 baseui.setconfig('ui', 'quiet', 'false')
650 baseui.setconfig('ui', 'quiet', 'false')
638 output = io.BytesIO()
651 output = io.BytesIO()
639
652
640 def write(data, **unused_kwargs):
653 def write(data, **unused_kwargs):
641 output.write(data)
654 output.write(data)
642 baseui.write = write
655 baseui.write = write
643
656
644 repo.ui = baseui
657 repo.ui = baseui
645 verify.verify(repo)
658 verify.verify(repo)
646 return output.getvalue()
659 return output.getvalue()
647
660
648 @reraise_safe_exceptions
661 @reraise_safe_exceptions
649 def tag(self, wire, name, revision, message, local, user,
662 def tag(self, wire, name, revision, message, local, user,
650 tag_time, tag_timezone):
663 tag_time, tag_timezone):
651 repo = self._factory.repo(wire)
664 repo = self._factory.repo(wire)
652 ctx = repo[revision]
665 ctx = repo[revision]
653 node = ctx.node()
666 node = ctx.node()
654
667
655 date = (tag_time, tag_timezone)
668 date = (tag_time, tag_timezone)
656 try:
669 try:
657 hg_tag.tag(repo, name, node, message, local, user, date)
670 hg_tag.tag(repo, name, node, message, local, user, date)
658 except Abort as e:
671 except Abort as e:
659 log.exception("Tag operation aborted")
672 log.exception("Tag operation aborted")
660 # Exception can contain unicode which we convert
673 # Exception can contain unicode which we convert
661 raise exceptions.AbortException(repr(e))
674 raise exceptions.AbortException(repr(e))
662
675
663 @reraise_safe_exceptions
676 @reraise_safe_exceptions
664 def tags(self, wire):
677 def tags(self, wire):
665 repo = self._factory.repo(wire)
678 repo = self._factory.repo(wire)
666 return repo.tags()
679 return repo.tags()
667
680
668 @reraise_safe_exceptions
681 @reraise_safe_exceptions
669 def update(self, wire, node=None, clean=False):
682 def update(self, wire, node=None, clean=False):
670 repo = self._factory.repo(wire)
683 repo = self._factory.repo(wire)
671 baseui = self._factory._create_config(wire['config'])
684 baseui = self._factory._create_config(wire['config'])
672 commands.update(baseui, repo, node=node, clean=clean)
685 commands.update(baseui, repo, node=node, clean=clean)
673
686
674 @reraise_safe_exceptions
687 @reraise_safe_exceptions
675 def identify(self, wire):
688 def identify(self, wire):
676 repo = self._factory.repo(wire)
689 repo = self._factory.repo(wire)
677 baseui = self._factory._create_config(wire['config'])
690 baseui = self._factory._create_config(wire['config'])
678 output = io.BytesIO()
691 output = io.BytesIO()
679 baseui.write = output.write
692 baseui.write = output.write
680 # This is required to get a full node id
693 # This is required to get a full node id
681 baseui.debugflag = True
694 baseui.debugflag = True
682 commands.identify(baseui, repo, id=True)
695 commands.identify(baseui, repo, id=True)
683
696
684 return output.getvalue()
697 return output.getvalue()
685
698
686 @reraise_safe_exceptions
699 @reraise_safe_exceptions
687 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
700 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
688 hooks=True):
701 hooks=True):
689 repo = self._factory.repo(wire)
702 repo = self._factory.repo(wire)
690 baseui = self._factory._create_config(wire['config'], hooks=hooks)
703 baseui = self._factory._create_config(wire['config'], hooks=hooks)
691
704
692 # Mercurial internally has a lot of logic that checks ONLY if
705 # Mercurial internally has a lot of logic that checks ONLY if
693 # option is defined, we just pass those if they are defined then
706 # option is defined, we just pass those if they are defined then
694 opts = {}
707 opts = {}
695 if bookmark:
708 if bookmark:
696 opts['bookmark'] = bookmark
709 opts['bookmark'] = bookmark
697 if branch:
710 if branch:
698 opts['branch'] = branch
711 opts['branch'] = branch
699 if revision:
712 if revision:
700 opts['rev'] = revision
713 opts['rev'] = revision
701
714
702 commands.pull(baseui, repo, source, **opts)
715 commands.pull(baseui, repo, source, **opts)
703
716
704 @reraise_safe_exceptions
717 @reraise_safe_exceptions
705 def heads(self, wire, branch=None):
718 def heads(self, wire, branch=None):
706 repo = self._factory.repo(wire)
719 repo = self._factory.repo(wire)
707 baseui = self._factory._create_config(wire['config'])
720 baseui = self._factory._create_config(wire['config'])
708 output = io.BytesIO()
721 output = io.BytesIO()
709
722
710 def write(data, **unused_kwargs):
723 def write(data, **unused_kwargs):
711 output.write(data)
724 output.write(data)
712
725
713 baseui.write = write
726 baseui.write = write
714 if branch:
727 if branch:
715 args = [branch]
728 args = [branch]
716 else:
729 else:
717 args = []
730 args = []
718 commands.heads(baseui, repo, template='{node} ', *args)
731 commands.heads(baseui, repo, template='{node} ', *args)
719
732
720 return output.getvalue()
733 return output.getvalue()
721
734
722 @reraise_safe_exceptions
735 @reraise_safe_exceptions
723 def ancestor(self, wire, revision1, revision2):
736 def ancestor(self, wire, revision1, revision2):
724 repo = self._factory.repo(wire)
737 repo = self._factory.repo(wire)
725 changelog = repo.changelog
738 changelog = repo.changelog
726 lookup = repo.lookup
739 lookup = repo.lookup
727 a = changelog.ancestor(lookup(revision1), lookup(revision2))
740 a = changelog.ancestor(lookup(revision1), lookup(revision2))
728 return hex(a)
741 return hex(a)
729
742
730 @reraise_safe_exceptions
743 @reraise_safe_exceptions
731 def push(self, wire, revisions, dest_path, hooks=True,
744 def push(self, wire, revisions, dest_path, hooks=True,
732 push_branches=False):
745 push_branches=False):
733 repo = self._factory.repo(wire)
746 repo = self._factory.repo(wire)
734 baseui = self._factory._create_config(wire['config'], hooks=hooks)
747 baseui = self._factory._create_config(wire['config'], hooks=hooks)
735 commands.push(baseui, repo, dest=dest_path, rev=revisions,
748 commands.push(baseui, repo, dest=dest_path, rev=revisions,
736 new_branch=push_branches)
749 new_branch=push_branches)
737
750
738 @reraise_safe_exceptions
751 @reraise_safe_exceptions
739 def merge(self, wire, revision):
752 def merge(self, wire, revision):
740 repo = self._factory.repo(wire)
753 repo = self._factory.repo(wire)
741 baseui = self._factory._create_config(wire['config'])
754 baseui = self._factory._create_config(wire['config'])
742 repo.ui.setconfig('ui', 'merge', 'internal:dump')
755 repo.ui.setconfig('ui', 'merge', 'internal:dump')
743
756
744 # In case of sub repositories are used mercurial prompts the user in
757 # In case of sub repositories are used mercurial prompts the user in
745 # case of merge conflicts or different sub repository sources. By
758 # case of merge conflicts or different sub repository sources. By
746 # setting the interactive flag to `False` mercurial doesn't prompt the
759 # setting the interactive flag to `False` mercurial doesn't prompt the
747 # used but instead uses a default value.
760 # used but instead uses a default value.
748 repo.ui.setconfig('ui', 'interactive', False)
761 repo.ui.setconfig('ui', 'interactive', False)
749
762
750 commands.merge(baseui, repo, rev=revision)
763 commands.merge(baseui, repo, rev=revision)
751
764
752 @reraise_safe_exceptions
765 @reraise_safe_exceptions
753 def commit(self, wire, message, username, close_branch=False):
766 def commit(self, wire, message, username, close_branch=False):
754 repo = self._factory.repo(wire)
767 repo = self._factory.repo(wire)
755 baseui = self._factory._create_config(wire['config'])
768 baseui = self._factory._create_config(wire['config'])
756 repo.ui.setconfig('ui', 'username', username)
769 repo.ui.setconfig('ui', 'username', username)
757 commands.commit(baseui, repo, message=message, close_branch=close_branch)
770 commands.commit(baseui, repo, message=message, close_branch=close_branch)
758
771
759 @reraise_safe_exceptions
772 @reraise_safe_exceptions
760 def rebase(self, wire, source=None, dest=None, abort=False):
773 def rebase(self, wire, source=None, dest=None, abort=False):
761 repo = self._factory.repo(wire)
774 repo = self._factory.repo(wire)
762 baseui = self._factory._create_config(wire['config'])
775 baseui = self._factory._create_config(wire['config'])
763 repo.ui.setconfig('ui', 'merge', 'internal:dump')
776 repo.ui.setconfig('ui', 'merge', 'internal:dump')
764 rebase.rebase(
777 rebase.rebase(
765 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
778 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
766
779
767 @reraise_safe_exceptions
780 @reraise_safe_exceptions
768 def bookmark(self, wire, bookmark, revision=None):
781 def bookmark(self, wire, bookmark, revision=None):
769 repo = self._factory.repo(wire)
782 repo = self._factory.repo(wire)
770 baseui = self._factory._create_config(wire['config'])
783 baseui = self._factory._create_config(wire['config'])
771 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
784 commands.bookmark(baseui, repo, bookmark, rev=revision, force=True)
772
785
773 @reraise_safe_exceptions
786 @reraise_safe_exceptions
774 def install_hooks(self, wire, force=False):
787 def install_hooks(self, wire, force=False):
775 # we don't need any special hooks for Mercurial
788 # we don't need any special hooks for Mercurial
776 pass
789 pass
@@ -1,134 +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-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 """
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(orig, 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 return calc_capabilities(orig, repo, proto)
55 else:
56 else:
56 logger.debug('Extension largefiles disabled')
57 logger.debug('Extension largefiles disabled')
57 calc_capabilities = lfproto.capabilitiesorig
58 return orig(repo, proto)
58 return calc_capabilities(repo, proto)
59
59
60 return _dynamic_capabilities
60 return _dynamic_capabilities
61
61
62
62
63 def patch_subrepo_type_mapping():
63 def patch_subrepo_type_mapping():
64 from collections import defaultdict
64 from collections import defaultdict
65 from hgcompat import subrepo
65 from hgcompat import subrepo
66 from exceptions import SubrepoMergeException
66 from exceptions import SubrepoMergeException
67
67
68 class NoOpSubrepo(subrepo.abstractsubrepo):
68 class NoOpSubrepo(subrepo.abstractsubrepo):
69
69
70 def __init__(self, ctx, path, *args, **kwargs):
70 def __init__(self, ctx, path, *args, **kwargs):
71 """Initialize abstractsubrepo part
71 """Initialize abstractsubrepo part
72
72
73 ``ctx`` is the context referring this subrepository in the
73 ``ctx`` is the context referring this subrepository in the
74 parent repository.
74 parent repository.
75
75
76 ``path`` is the path to this subrepository as seen from
76 ``path`` is the path to this subrepository as seen from
77 innermost repository.
77 innermost repository.
78 """
78 """
79 self.ui = ctx.repo().ui
79 self.ui = ctx.repo().ui
80 self._ctx = ctx
80 self._ctx = ctx
81 self._path = path
81 self._path = path
82
82
83 def storeclean(self, path):
83 def storeclean(self, path):
84 """
84 """
85 returns true if the repository has not changed since it was last
85 returns true if the repository has not changed since it was last
86 cloned from or pushed to a given repository.
86 cloned from or pushed to a given repository.
87 """
87 """
88 return True
88 return True
89
89
90 def dirty(self, ignoreupdate=False, missing=False):
90 def dirty(self, ignoreupdate=False, missing=False):
91 """returns true if the dirstate of the subrepo is dirty or does not
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
92 match current stored state. If ignoreupdate is true, only check
93 whether the subrepo has uncommitted changes in its dirstate.
93 whether the subrepo has uncommitted changes in its dirstate.
94 """
94 """
95 return False
95 return False
96
96
97 def basestate(self):
97 def basestate(self):
98 """current working directory base state, disregarding .hgsubstate
98 """current working directory base state, disregarding .hgsubstate
99 state and working directory modifications"""
99 state and working directory modifications"""
100 substate = subrepo.state(self._ctx, self.ui)
100 substate = subrepo.state(self._ctx, self.ui)
101 file_system_path, rev, repotype = substate.get(self._path)
101 file_system_path, rev, repotype = substate.get(self._path)
102 return rev
102 return rev
103
103
104 def remove(self):
104 def remove(self):
105 """remove the subrepo
105 """remove the subrepo
106
106
107 (should verify the dirstate is not dirty first)
107 (should verify the dirstate is not dirty first)
108 """
108 """
109 pass
109 pass
110
110
111 def get(self, state, overwrite=False):
111 def get(self, state, overwrite=False):
112 """run whatever commands are needed to put the subrepo into
112 """run whatever commands are needed to put the subrepo into
113 this state
113 this state
114 """
114 """
115 pass
115 pass
116
116
117 def merge(self, state):
117 def merge(self, state):
118 """merge currently-saved state with the new state."""
118 """merge currently-saved state with the new state."""
119 raise SubrepoMergeException()
119 raise SubrepoMergeException()
120
120
121 def push(self, opts):
121 def push(self, opts):
122 """perform whatever action is analogous to 'hg push'
122 """perform whatever action is analogous to 'hg push'
123
123
124 This may be a no-op on some systems.
124 This may be a no-op on some systems.
125 """
125 """
126 pass
126 pass
127
127
128 # Patch subrepo type mapping to always return our NoOpSubrepo class
128 # Patch subrepo type mapping to always return our NoOpSubrepo class
129 # whenever a subrepo class is looked up.
129 # whenever a subrepo class is looked up.
130 subrepo.types = {
130 subrepo.types = {
131 'hg': NoOpSubrepo,
131 'hg': NoOpSubrepo,
132 'git': NoOpSubrepo,
132 'git': NoOpSubrepo,
133 'svn': NoOpSubrepo
133 'svn': NoOpSubrepo
134 }
134 }
@@ -1,541 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2018 RhodeCode GmbH
4 # Copyright (C) 2014-2018 RhodeCode GmbH
5 #
5 #
6 # This program is free software; you can redistribute it and/or modify
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
19
20 import io
20 import io
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import importlib
25 import importlib
26 import base64
26 import base64
27
27
28 from httplib import HTTPConnection
28 from httplib import HTTPConnection
29
29
30
30
31 import mercurial.scmutil
31 import mercurial.scmutil
32 import mercurial.node
32 import mercurial.node
33 import simplejson as json
33 import simplejson as json
34
34
35 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver import exceptions, subprocessio, settings
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class HooksHttpClient(object):
40 class HooksHttpClient(object):
41 connection = None
41 connection = None
42
42
43 def __init__(self, hooks_uri):
43 def __init__(self, hooks_uri):
44 self.hooks_uri = hooks_uri
44 self.hooks_uri = hooks_uri
45
45
46 def __call__(self, method, extras):
46 def __call__(self, method, extras):
47 connection = HTTPConnection(self.hooks_uri)
47 connection = HTTPConnection(self.hooks_uri)
48 body = self._serialize(method, extras)
48 body = self._serialize(method, extras)
49 try:
49 try:
50 connection.request('POST', '/', body)
50 connection.request('POST', '/', body)
51 except Exception:
51 except Exception:
52 log.error('Connection failed on %s', connection)
52 log.error('Connection failed on %s', connection)
53 raise
53 raise
54 response = connection.getresponse()
54 response = connection.getresponse()
55 return json.loads(response.read())
55 return json.loads(response.read())
56
56
57 def _serialize(self, hook_name, extras):
57 def _serialize(self, hook_name, extras):
58 data = {
58 data = {
59 'method': hook_name,
59 'method': hook_name,
60 'extras': extras
60 'extras': extras
61 }
61 }
62 return json.dumps(data)
62 return json.dumps(data)
63
63
64
64
65 class HooksDummyClient(object):
65 class HooksDummyClient(object):
66 def __init__(self, hooks_module):
66 def __init__(self, hooks_module):
67 self._hooks_module = importlib.import_module(hooks_module)
67 self._hooks_module = importlib.import_module(hooks_module)
68
68
69 def __call__(self, hook_name, extras):
69 def __call__(self, hook_name, extras):
70 with self._hooks_module.Hooks() as hooks:
70 with self._hooks_module.Hooks() as hooks:
71 return getattr(hooks, hook_name)(extras)
71 return getattr(hooks, hook_name)(extras)
72
72
73
73
74 class RemoteMessageWriter(object):
74 class RemoteMessageWriter(object):
75 """Writer base class."""
75 """Writer base class."""
76 def write(self, message):
76 def write(self, message):
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79
79
80 class HgMessageWriter(RemoteMessageWriter):
80 class HgMessageWriter(RemoteMessageWriter):
81 """Writer that knows how to send messages to mercurial clients."""
81 """Writer that knows how to send messages to mercurial clients."""
82
82
83 def __init__(self, ui):
83 def __init__(self, ui):
84 self.ui = ui
84 self.ui = ui
85
85
86 def write(self, message):
86 def write(self, message):
87 # TODO: Check why the quiet flag is set by default.
87 # TODO: Check why the quiet flag is set by default.
88 old = self.ui.quiet
88 old = self.ui.quiet
89 self.ui.quiet = False
89 self.ui.quiet = False
90 self.ui.status(message.encode('utf-8'))
90 self.ui.status(message.encode('utf-8'))
91 self.ui.quiet = old
91 self.ui.quiet = old
92
92
93
93
94 class GitMessageWriter(RemoteMessageWriter):
94 class GitMessageWriter(RemoteMessageWriter):
95 """Writer that knows how to send messages to git clients."""
95 """Writer that knows how to send messages to git clients."""
96
96
97 def __init__(self, stdout=None):
97 def __init__(self, stdout=None):
98 self.stdout = stdout or sys.stdout
98 self.stdout = stdout or sys.stdout
99
99
100 def write(self, message):
100 def write(self, message):
101 self.stdout.write(message.encode('utf-8'))
101 self.stdout.write(message.encode('utf-8'))
102
102
103
103
104 class SvnMessageWriter(RemoteMessageWriter):
104 class SvnMessageWriter(RemoteMessageWriter):
105 """Writer that knows how to send messages to svn clients."""
105 """Writer that knows how to send messages to svn clients."""
106
106
107 def __init__(self, stderr=None):
107 def __init__(self, stderr=None):
108 # SVN needs data sent to stderr for back-to-client messaging
108 # SVN needs data sent to stderr for back-to-client messaging
109 self.stderr = stderr or sys.stderr
109 self.stderr = stderr or sys.stderr
110
110
111 def write(self, message):
111 def write(self, message):
112 self.stderr.write(message.encode('utf-8'))
112 self.stderr.write(message.encode('utf-8'))
113
113
114
114
115 def _handle_exception(result):
115 def _handle_exception(result):
116 exception_class = result.get('exception')
116 exception_class = result.get('exception')
117 exception_traceback = result.get('exception_traceback')
117 exception_traceback = result.get('exception_traceback')
118
118
119 if exception_traceback:
119 if exception_traceback:
120 log.error('Got traceback from remote call:%s', exception_traceback)
120 log.error('Got traceback from remote call:%s', exception_traceback)
121
121
122 if exception_class == 'HTTPLockedRC':
122 if exception_class == 'HTTPLockedRC':
123 raise exceptions.RepositoryLockedException(*result['exception_args'])
123 raise exceptions.RepositoryLockedException(*result['exception_args'])
124 elif exception_class == 'RepositoryError':
124 elif exception_class == 'RepositoryError':
125 raise exceptions.VcsException(*result['exception_args'])
125 raise exceptions.VcsException(*result['exception_args'])
126 elif exception_class:
126 elif exception_class:
127 raise Exception('Got remote exception "%s" with args "%s"' %
127 raise Exception('Got remote exception "%s" with args "%s"' %
128 (exception_class, result['exception_args']))
128 (exception_class, result['exception_args']))
129
129
130
130
131 def _get_hooks_client(extras):
131 def _get_hooks_client(extras):
132 if 'hooks_uri' in extras:
132 if 'hooks_uri' in extras:
133 protocol = extras.get('hooks_protocol')
133 protocol = extras.get('hooks_protocol')
134 return HooksHttpClient(extras['hooks_uri'])
134 return HooksHttpClient(extras['hooks_uri'])
135 else:
135 else:
136 return HooksDummyClient(extras['hooks_module'])
136 return HooksDummyClient(extras['hooks_module'])
137
137
138
138
139 def _call_hook(hook_name, extras, writer):
139 def _call_hook(hook_name, extras, writer):
140 hooks_client = _get_hooks_client(extras)
140 hooks_client = _get_hooks_client(extras)
141 log.debug('Hooks, using client:%s', hooks_client)
141 log.debug('Hooks, using client:%s', hooks_client)
142 result = hooks_client(hook_name, extras)
142 result = hooks_client(hook_name, extras)
143 log.debug('Hooks got result: %s', result)
143 log.debug('Hooks got result: %s', result)
144 writer.write(result['output'])
144 writer.write(result['output'])
145 _handle_exception(result)
145 _handle_exception(result)
146
146
147 return result['status']
147 return result['status']
148
148
149
149
150 def _extras_from_ui(ui):
150 def _extras_from_ui(ui):
151 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
151 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
152 if not hook_data:
152 if not hook_data:
153 # maybe it's inside environ ?
153 # maybe it's inside environ ?
154 env_hook_data = os.environ.get('RC_SCM_DATA')
154 env_hook_data = os.environ.get('RC_SCM_DATA')
155 if env_hook_data:
155 if env_hook_data:
156 hook_data = env_hook_data
156 hook_data = env_hook_data
157
157
158 extras = {}
158 extras = {}
159 if hook_data:
159 if hook_data:
160 extras = json.loads(hook_data)
160 extras = json.loads(hook_data)
161 return extras
161 return extras
162
162
163
163
164 def _rev_range_hash(repo, node):
164 def _rev_range_hash(repo, node):
165
165
166 commits = []
166 commits = []
167 for rev in xrange(repo[node], len(repo)):
167 start = repo[node].rev()
168 for rev in xrange(start, len(repo)):
168 ctx = repo[rev]
169 ctx = repo[rev]
169 commit_id = mercurial.node.hex(ctx.node())
170 commit_id = mercurial.node.hex(ctx.node())
170 branch = ctx.branch()
171 branch = ctx.branch()
171 commits.append((commit_id, branch))
172 commits.append((commit_id, branch))
172
173
173 return commits
174 return commits
174
175
175
176
176 def repo_size(ui, repo, **kwargs):
177 def repo_size(ui, repo, **kwargs):
177 extras = _extras_from_ui(ui)
178 extras = _extras_from_ui(ui)
178 return _call_hook('repo_size', extras, HgMessageWriter(ui))
179 return _call_hook('repo_size', extras, HgMessageWriter(ui))
179
180
180
181
181 def pre_pull(ui, repo, **kwargs):
182 def pre_pull(ui, repo, **kwargs):
182 extras = _extras_from_ui(ui)
183 extras = _extras_from_ui(ui)
183 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
184 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
184
185
185
186
186 def pre_pull_ssh(ui, repo, **kwargs):
187 def pre_pull_ssh(ui, repo, **kwargs):
187 extras = _extras_from_ui(ui)
188 extras = _extras_from_ui(ui)
188 if extras and extras.get('SSH'):
189 if extras and extras.get('SSH'):
189 return pre_pull(ui, repo, **kwargs)
190 return pre_pull(ui, repo, **kwargs)
190 return 0
191 return 0
191
192
192
193
193 def post_pull(ui, repo, **kwargs):
194 def post_pull(ui, repo, **kwargs):
194 extras = _extras_from_ui(ui)
195 extras = _extras_from_ui(ui)
195 return _call_hook('post_pull', extras, HgMessageWriter(ui))
196 return _call_hook('post_pull', extras, HgMessageWriter(ui))
196
197
197
198
198 def post_pull_ssh(ui, repo, **kwargs):
199 def post_pull_ssh(ui, repo, **kwargs):
199 extras = _extras_from_ui(ui)
200 extras = _extras_from_ui(ui)
200 if extras and extras.get('SSH'):
201 if extras and extras.get('SSH'):
201 return post_pull(ui, repo, **kwargs)
202 return post_pull(ui, repo, **kwargs)
202 return 0
203 return 0
203
204
204
205
205 def pre_push(ui, repo, node=None, **kwargs):
206 def pre_push(ui, repo, node=None, **kwargs):
206 extras = _extras_from_ui(ui)
207 extras = _extras_from_ui(ui)
207
208
208 rev_data = []
209 rev_data = []
209 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
210 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
210 branches = collections.defaultdict(list)
211 branches = collections.defaultdict(list)
211 for commit_id, branch in _rev_range_hash(repo, node):
212 for commit_id, branch in _rev_range_hash(repo, node):
212 branches[branch].append(commit_id)
213 branches[branch].append(commit_id)
213
214
214 for branch, commits in branches.iteritems():
215 for branch, commits in branches.iteritems():
215 old_rev = kwargs.get('node_last') or commits[0]
216 old_rev = kwargs.get('node_last') or commits[0]
216 rev_data.append({
217 rev_data.append({
217 'old_rev': old_rev,
218 'old_rev': old_rev,
218 'new_rev': commits[-1],
219 'new_rev': commits[-1],
219 'ref': '',
220 'ref': '',
220 'type': 'branch',
221 'type': 'branch',
221 'name': branch,
222 'name': branch,
222 })
223 })
223
224
224 extras['commit_ids'] = rev_data
225 extras['commit_ids'] = rev_data
225 return _call_hook('pre_push', extras, HgMessageWriter(ui))
226 return _call_hook('pre_push', extras, HgMessageWriter(ui))
226
227
227
228
228 def pre_push_ssh(ui, repo, node=None, **kwargs):
229 def pre_push_ssh(ui, repo, node=None, **kwargs):
229 if _extras_from_ui(ui).get('SSH'):
230 if _extras_from_ui(ui).get('SSH'):
230 return pre_push(ui, repo, node, **kwargs)
231 return pre_push(ui, repo, node, **kwargs)
231
232
232 return 0
233 return 0
233
234
234
235
235 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
236 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
236 extras = _extras_from_ui(ui)
237 extras = _extras_from_ui(ui)
237 if extras.get('SSH'):
238 if extras.get('SSH'):
238 permission = extras['SSH_PERMISSIONS']
239 permission = extras['SSH_PERMISSIONS']
239
240
240 if 'repository.write' == permission or 'repository.admin' == permission:
241 if 'repository.write' == permission or 'repository.admin' == permission:
241 return 0
242 return 0
242
243
243 # non-zero ret code
244 # non-zero ret code
244 return 1
245 return 1
245
246
246 return 0
247 return 0
247
248
248
249
249 def post_push(ui, repo, node, **kwargs):
250 def post_push(ui, repo, node, **kwargs):
250 extras = _extras_from_ui(ui)
251 extras = _extras_from_ui(ui)
251
252
252 commit_ids = []
253 commit_ids = []
253 branches = []
254 branches = []
254 bookmarks = []
255 bookmarks = []
255 tags = []
256 tags = []
256
257
257 for commit_id, branch in _rev_range_hash(repo, node):
258 for commit_id, branch in _rev_range_hash(repo, node):
258 commit_ids.append(commit_id)
259 commit_ids.append(commit_id)
259 if branch not in branches:
260 if branch not in branches:
260 branches.append(branch)
261 branches.append(branch)
261
262
262 if hasattr(ui, '_rc_pushkey_branches'):
263 if hasattr(ui, '_rc_pushkey_branches'):
263 bookmarks = ui._rc_pushkey_branches
264 bookmarks = ui._rc_pushkey_branches
264
265
265 extras['commit_ids'] = commit_ids
266 extras['commit_ids'] = commit_ids
266 extras['new_refs'] = {
267 extras['new_refs'] = {
267 'branches': branches,
268 'branches': branches,
268 'bookmarks': bookmarks,
269 'bookmarks': bookmarks,
269 'tags': tags
270 'tags': tags
270 }
271 }
271
272
272 return _call_hook('post_push', extras, HgMessageWriter(ui))
273 return _call_hook('post_push', extras, HgMessageWriter(ui))
273
274
274
275
275 def post_push_ssh(ui, repo, node, **kwargs):
276 def post_push_ssh(ui, repo, node, **kwargs):
276 if _extras_from_ui(ui).get('SSH'):
277 if _extras_from_ui(ui).get('SSH'):
277 return post_push(ui, repo, node, **kwargs)
278 return post_push(ui, repo, node, **kwargs)
278 return 0
279 return 0
279
280
280
281
281 def key_push(ui, repo, **kwargs):
282 def key_push(ui, repo, **kwargs):
282 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
283 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
283 # store new bookmarks in our UI object propagated later to post_push
284 # store new bookmarks in our UI object propagated later to post_push
284 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
285 ui._rc_pushkey_branches = repo[kwargs['key']].bookmarks()
285 return
286 return
286
287
287
288
288 # backward compat
289 # backward compat
289 log_pull_action = post_pull
290 log_pull_action = post_pull
290
291
291 # backward compat
292 # backward compat
292 log_push_action = post_push
293 log_push_action = post_push
293
294
294
295
295 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
296 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
296 """
297 """
297 Old hook name: keep here for backward compatibility.
298 Old hook name: keep here for backward compatibility.
298
299
299 This is only required when the installed git hooks are not upgraded.
300 This is only required when the installed git hooks are not upgraded.
300 """
301 """
301 pass
302 pass
302
303
303
304
304 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
305 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
305 """
306 """
306 Old hook name: keep here for backward compatibility.
307 Old hook name: keep here for backward compatibility.
307
308
308 This is only required when the installed git hooks are not upgraded.
309 This is only required when the installed git hooks are not upgraded.
309 """
310 """
310 pass
311 pass
311
312
312
313
313 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
314 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
314
315
315
316
316 def git_pre_pull(extras):
317 def git_pre_pull(extras):
317 """
318 """
318 Pre pull hook.
319 Pre pull hook.
319
320
320 :param extras: dictionary containing the keys defined in simplevcs
321 :param extras: dictionary containing the keys defined in simplevcs
321 :type extras: dict
322 :type extras: dict
322
323
323 :return: status code of the hook. 0 for success.
324 :return: status code of the hook. 0 for success.
324 :rtype: int
325 :rtype: int
325 """
326 """
326 if 'pull' not in extras['hooks']:
327 if 'pull' not in extras['hooks']:
327 return HookResponse(0, '')
328 return HookResponse(0, '')
328
329
329 stdout = io.BytesIO()
330 stdout = io.BytesIO()
330 try:
331 try:
331 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
332 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
332 except Exception as error:
333 except Exception as error:
333 status = 128
334 status = 128
334 stdout.write('ERROR: %s\n' % str(error))
335 stdout.write('ERROR: %s\n' % str(error))
335
336
336 return HookResponse(status, stdout.getvalue())
337 return HookResponse(status, stdout.getvalue())
337
338
338
339
339 def git_post_pull(extras):
340 def git_post_pull(extras):
340 """
341 """
341 Post pull hook.
342 Post pull hook.
342
343
343 :param extras: dictionary containing the keys defined in simplevcs
344 :param extras: dictionary containing the keys defined in simplevcs
344 :type extras: dict
345 :type extras: dict
345
346
346 :return: status code of the hook. 0 for success.
347 :return: status code of the hook. 0 for success.
347 :rtype: int
348 :rtype: int
348 """
349 """
349 if 'pull' not in extras['hooks']:
350 if 'pull' not in extras['hooks']:
350 return HookResponse(0, '')
351 return HookResponse(0, '')
351
352
352 stdout = io.BytesIO()
353 stdout = io.BytesIO()
353 try:
354 try:
354 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
355 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
355 except Exception as error:
356 except Exception as error:
356 status = 128
357 status = 128
357 stdout.write('ERROR: %s\n' % error)
358 stdout.write('ERROR: %s\n' % error)
358
359
359 return HookResponse(status, stdout.getvalue())
360 return HookResponse(status, stdout.getvalue())
360
361
361
362
362 def _parse_git_ref_lines(revision_lines):
363 def _parse_git_ref_lines(revision_lines):
363 rev_data = []
364 rev_data = []
364 for revision_line in revision_lines or []:
365 for revision_line in revision_lines or []:
365 old_rev, new_rev, ref = revision_line.strip().split(' ')
366 old_rev, new_rev, ref = revision_line.strip().split(' ')
366 ref_data = ref.split('/', 2)
367 ref_data = ref.split('/', 2)
367 if ref_data[1] in ('tags', 'heads'):
368 if ref_data[1] in ('tags', 'heads'):
368 rev_data.append({
369 rev_data.append({
369 'old_rev': old_rev,
370 'old_rev': old_rev,
370 'new_rev': new_rev,
371 'new_rev': new_rev,
371 'ref': ref,
372 'ref': ref,
372 'type': ref_data[1],
373 'type': ref_data[1],
373 'name': ref_data[2],
374 'name': ref_data[2],
374 })
375 })
375 return rev_data
376 return rev_data
376
377
377
378
378 def git_pre_receive(unused_repo_path, revision_lines, env):
379 def git_pre_receive(unused_repo_path, revision_lines, env):
379 """
380 """
380 Pre push hook.
381 Pre push hook.
381
382
382 :param extras: dictionary containing the keys defined in simplevcs
383 :param extras: dictionary containing the keys defined in simplevcs
383 :type extras: dict
384 :type extras: dict
384
385
385 :return: status code of the hook. 0 for success.
386 :return: status code of the hook. 0 for success.
386 :rtype: int
387 :rtype: int
387 """
388 """
388 extras = json.loads(env['RC_SCM_DATA'])
389 extras = json.loads(env['RC_SCM_DATA'])
389 rev_data = _parse_git_ref_lines(revision_lines)
390 rev_data = _parse_git_ref_lines(revision_lines)
390 if 'push' not in extras['hooks']:
391 if 'push' not in extras['hooks']:
391 return 0
392 return 0
392 extras['commit_ids'] = rev_data
393 extras['commit_ids'] = rev_data
393 return _call_hook('pre_push', extras, GitMessageWriter())
394 return _call_hook('pre_push', extras, GitMessageWriter())
394
395
395
396
396 def git_post_receive(unused_repo_path, revision_lines, env):
397 def git_post_receive(unused_repo_path, revision_lines, env):
397 """
398 """
398 Post push hook.
399 Post push hook.
399
400
400 :param extras: dictionary containing the keys defined in simplevcs
401 :param extras: dictionary containing the keys defined in simplevcs
401 :type extras: dict
402 :type extras: dict
402
403
403 :return: status code of the hook. 0 for success.
404 :return: status code of the hook. 0 for success.
404 :rtype: int
405 :rtype: int
405 """
406 """
406 extras = json.loads(env['RC_SCM_DATA'])
407 extras = json.loads(env['RC_SCM_DATA'])
407 if 'push' not in extras['hooks']:
408 if 'push' not in extras['hooks']:
408 return 0
409 return 0
409
410
410 rev_data = _parse_git_ref_lines(revision_lines)
411 rev_data = _parse_git_ref_lines(revision_lines)
411
412
412 git_revs = []
413 git_revs = []
413
414
414 # N.B.(skreft): it is ok to just call git, as git before calling a
415 # N.B.(skreft): it is ok to just call git, as git before calling a
415 # subcommand sets the PATH environment variable so that it point to the
416 # subcommand sets the PATH environment variable so that it point to the
416 # correct version of the git executable.
417 # correct version of the git executable.
417 empty_commit_id = '0' * 40
418 empty_commit_id = '0' * 40
418 branches = []
419 branches = []
419 tags = []
420 tags = []
420 for push_ref in rev_data:
421 for push_ref in rev_data:
421 type_ = push_ref['type']
422 type_ = push_ref['type']
422
423
423 if type_ == 'heads':
424 if type_ == 'heads':
424 if push_ref['old_rev'] == empty_commit_id:
425 if push_ref['old_rev'] == empty_commit_id:
425 # starting new branch case
426 # starting new branch case
426 if push_ref['name'] not in branches:
427 if push_ref['name'] not in branches:
427 branches.append(push_ref['name'])
428 branches.append(push_ref['name'])
428
429
429 # Fix up head revision if needed
430 # Fix up head revision if needed
430 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
431 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
431 try:
432 try:
432 subprocessio.run_command(cmd, env=os.environ.copy())
433 subprocessio.run_command(cmd, env=os.environ.copy())
433 except Exception:
434 except Exception:
434 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
435 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
435 'refs/heads/%s' % push_ref['name']]
436 'refs/heads/%s' % push_ref['name']]
436 print("Setting default branch to %s" % push_ref['name'])
437 print("Setting default branch to %s" % push_ref['name'])
437 subprocessio.run_command(cmd, env=os.environ.copy())
438 subprocessio.run_command(cmd, env=os.environ.copy())
438
439
439 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
440 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
440 '--format=%(refname)', 'refs/heads/*']
441 '--format=%(refname)', 'refs/heads/*']
441 stdout, stderr = subprocessio.run_command(
442 stdout, stderr = subprocessio.run_command(
442 cmd, env=os.environ.copy())
443 cmd, env=os.environ.copy())
443 heads = stdout
444 heads = stdout
444 heads = heads.replace(push_ref['ref'], '')
445 heads = heads.replace(push_ref['ref'], '')
445 heads = ' '.join(head for head in heads.splitlines() if head)
446 heads = ' '.join(head for head in heads.splitlines() if head)
446 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
447 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
447 '--pretty=format:%H', '--', push_ref['new_rev'],
448 '--pretty=format:%H', '--', push_ref['new_rev'],
448 '--not', heads]
449 '--not', heads]
449 stdout, stderr = subprocessio.run_command(
450 stdout, stderr = subprocessio.run_command(
450 cmd, env=os.environ.copy())
451 cmd, env=os.environ.copy())
451 git_revs.extend(stdout.splitlines())
452 git_revs.extend(stdout.splitlines())
452 elif push_ref['new_rev'] == empty_commit_id:
453 elif push_ref['new_rev'] == empty_commit_id:
453 # delete branch case
454 # delete branch case
454 git_revs.append('delete_branch=>%s' % push_ref['name'])
455 git_revs.append('delete_branch=>%s' % push_ref['name'])
455 else:
456 else:
456 if push_ref['name'] not in branches:
457 if push_ref['name'] not in branches:
457 branches.append(push_ref['name'])
458 branches.append(push_ref['name'])
458
459
459 cmd = [settings.GIT_EXECUTABLE, 'log',
460 cmd = [settings.GIT_EXECUTABLE, 'log',
460 '{old_rev}..{new_rev}'.format(**push_ref),
461 '{old_rev}..{new_rev}'.format(**push_ref),
461 '--reverse', '--pretty=format:%H']
462 '--reverse', '--pretty=format:%H']
462 stdout, stderr = subprocessio.run_command(
463 stdout, stderr = subprocessio.run_command(
463 cmd, env=os.environ.copy())
464 cmd, env=os.environ.copy())
464 git_revs.extend(stdout.splitlines())
465 git_revs.extend(stdout.splitlines())
465 elif type_ == 'tags':
466 elif type_ == 'tags':
466 if push_ref['name'] not in tags:
467 if push_ref['name'] not in tags:
467 tags.append(push_ref['name'])
468 tags.append(push_ref['name'])
468 git_revs.append('tag=>%s' % push_ref['name'])
469 git_revs.append('tag=>%s' % push_ref['name'])
469
470
470 extras['commit_ids'] = git_revs
471 extras['commit_ids'] = git_revs
471 extras['new_refs'] = {
472 extras['new_refs'] = {
472 'branches': branches,
473 'branches': branches,
473 'bookmarks': [],
474 'bookmarks': [],
474 'tags': tags,
475 'tags': tags,
475 }
476 }
476
477
477 if 'repo_size' in extras['hooks']:
478 if 'repo_size' in extras['hooks']:
478 try:
479 try:
479 _call_hook('repo_size', extras, GitMessageWriter())
480 _call_hook('repo_size', extras, GitMessageWriter())
480 except:
481 except:
481 pass
482 pass
482
483
483 return _call_hook('post_push', extras, GitMessageWriter())
484 return _call_hook('post_push', extras, GitMessageWriter())
484
485
485
486
486 def svn_pre_commit(repo_path, commit_data, env):
487 def svn_pre_commit(repo_path, commit_data, env):
487 path, txn_id = commit_data
488 path, txn_id = commit_data
488 branches = []
489 branches = []
489 tags = []
490 tags = []
490
491
491 cmd = ['svnlook', 'pget',
492 cmd = ['svnlook', 'pget',
492 '-t', txn_id,
493 '-t', txn_id,
493 '--revprop', path, 'rc-scm-extras']
494 '--revprop', path, 'rc-scm-extras']
494 stdout, stderr = subprocessio.run_command(
495 stdout, stderr = subprocessio.run_command(
495 cmd, env=os.environ.copy())
496 cmd, env=os.environ.copy())
496 extras = json.loads(base64.urlsafe_b64decode(stdout))
497 extras = json.loads(base64.urlsafe_b64decode(stdout))
497
498
498 extras['commit_ids'] = []
499 extras['commit_ids'] = []
499 extras['txn_id'] = txn_id
500 extras['txn_id'] = txn_id
500 extras['new_refs'] = {
501 extras['new_refs'] = {
501 'branches': branches,
502 'branches': branches,
502 'bookmarks': [],
503 'bookmarks': [],
503 'tags': tags,
504 'tags': tags,
504 }
505 }
505 sys.stderr.write(str(extras))
506 sys.stderr.write(str(extras))
506 return _call_hook('pre_push', extras, SvnMessageWriter())
507 return _call_hook('pre_push', extras, SvnMessageWriter())
507
508
508
509
509 def svn_post_commit(repo_path, commit_data, env):
510 def svn_post_commit(repo_path, commit_data, env):
510 """
511 """
511 commit_data is path, rev, txn_id
512 commit_data is path, rev, txn_id
512 """
513 """
513 path, commit_id, txn_id = commit_data
514 path, commit_id, txn_id = commit_data
514 branches = []
515 branches = []
515 tags = []
516 tags = []
516
517
517 cmd = ['svnlook', 'pget',
518 cmd = ['svnlook', 'pget',
518 '-r', commit_id,
519 '-r', commit_id,
519 '--revprop', path, 'rc-scm-extras']
520 '--revprop', path, 'rc-scm-extras']
520 stdout, stderr = subprocessio.run_command(
521 stdout, stderr = subprocessio.run_command(
521 cmd, env=os.environ.copy())
522 cmd, env=os.environ.copy())
522
523
523 extras = json.loads(base64.urlsafe_b64decode(stdout))
524 extras = json.loads(base64.urlsafe_b64decode(stdout))
524
525
525 extras['commit_ids'] = [commit_id]
526 extras['commit_ids'] = [commit_id]
526 extras['txn_id'] = txn_id
527 extras['txn_id'] = txn_id
527 extras['new_refs'] = {
528 extras['new_refs'] = {
528 'branches': branches,
529 'branches': branches,
529 'bookmarks': [],
530 'bookmarks': [],
530 'tags': tags,
531 'tags': tags,
531 }
532 }
532
533
533 if 'repo_size' in extras['hooks']:
534 if 'repo_size' in extras['hooks']:
534 try:
535 try:
535 _call_hook('repo_size', extras, SvnMessageWriter())
536 _call_hook('repo_size', extras, SvnMessageWriter())
536 except:
537 except:
537 pass
538 pass
538
539
539 return _call_hook('post_push', extras, SvnMessageWriter())
540 return _call_hook('post_push', extras, SvnMessageWriter())
540
541
541
542
@@ -1,229 +1,234 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import logging
19 import logging
20 import itertools
20 import itertools
21
21
22 import mercurial
22 import mercurial
23 import mercurial.error
23 import mercurial.error
24 import mercurial.wireprotoserver
24 import mercurial.hgweb.common
25 import mercurial.hgweb.common
25 import mercurial.hgweb.hgweb_mod
26 import mercurial.hgweb.hgweb_mod
26 import mercurial.hgweb.protocol
27 import webob.exc
27 import webob.exc
28
28
29 from vcsserver import pygrack, exceptions, settings, git_lfs
29 from vcsserver import pygrack, exceptions, settings, git_lfs
30
30
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 # propagated from mercurial documentation
35 # propagated from mercurial documentation
36 HG_UI_SECTIONS = [
36 HG_UI_SECTIONS = [
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
40 ]
40 ]
41
41
42
42
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
44 """Extension of hgweb that simplifies some functions."""
44 """Extension of hgweb that simplifies some functions."""
45
45
46 def _get_view(self, repo):
46 def _get_view(self, repo):
47 """Views are not supported."""
47 """Views are not supported."""
48 return repo
48 return repo
49
49
50 def loadsubweb(self):
50 def loadsubweb(self):
51 """The result is only used in the templater method which is not used."""
51 """The result is only used in the templater method which is not used."""
52 return None
52 return None
53
53
54 def run(self):
54 def run(self):
55 """Unused function so raise an exception if accidentally called."""
55 """Unused function so raise an exception if accidentally called."""
56 raise NotImplementedError
56 raise NotImplementedError
57
57
58 def templater(self, req):
58 def templater(self, req):
59 """Function used in an unreachable code path.
59 """Function used in an unreachable code path.
60
60
61 This code is unreachable because we guarantee that the HTTP request,
61 This code is unreachable because we guarantee that the HTTP request,
62 corresponds to a Mercurial command. See the is_hg method. So, we are
62 corresponds to a Mercurial command. See the is_hg method. So, we are
63 never going to get a user-visible url.
63 never going to get a user-visible url.
64 """
64 """
65 raise NotImplementedError
65 raise NotImplementedError
66
66
67 def archivelist(self, nodeid):
67 def archivelist(self, nodeid):
68 """Unused function so raise an exception if accidentally called."""
68 """Unused function so raise an exception if accidentally called."""
69 raise NotImplementedError
69 raise NotImplementedError
70
70
71 def __call__(self, environ, start_response):
71 def __call__(self, environ, start_response):
72 """Run the WSGI application.
72 """Run the WSGI application.
73
73
74 This may be called by multiple threads.
74 This may be called by multiple threads.
75 """
75 """
76 req = mercurial.hgweb.request.wsgirequest(environ, start_response)
76 from mercurial.hgweb import request as requestmod
77 gen = self.run_wsgi(req)
77 req = requestmod.parserequestfromenv(environ)
78 res = requestmod.wsgiresponse(req, start_response)
79 gen = self.run_wsgi(req, res)
78
80
79 first_chunk = None
81 first_chunk = None
80
82
81 try:
83 try:
82 data = gen.next()
84 data = gen.next()
83 def first_chunk(): yield data
85
86 def first_chunk():
87 yield data
84 except StopIteration:
88 except StopIteration:
85 pass
89 pass
86
90
87 if first_chunk:
91 if first_chunk:
88 return itertools.chain(first_chunk(), gen)
92 return itertools.chain(first_chunk(), gen)
89 return gen
93 return gen
90
94
91 def _runwsgi(self, req, repo):
95 def _runwsgi(self, req, res, repo):
92 cmd = req.form.get('cmd', [''])[0]
93 if not mercurial.hgweb.protocol.iscmd(cmd):
94 req.respond(
95 mercurial.hgweb.common.ErrorResponse(
96 mercurial.hgweb.common.HTTP_BAD_REQUEST),
97 mercurial.hgweb.protocol.HGTYPE
98 )
99 return ['']
100
96
101 return super(HgWeb, self)._runwsgi(req, repo)
97 cmd = req.qsparams.get('cmd', '')
98 if not mercurial.wireprotoserver.iscmd(cmd):
99 # NOTE(marcink): for unsupported commands, we return bad request
100 # internally from HG
101 from mercurial.hgweb.common import statusmessage
102 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
103 res.setbodybytes('')
104 return res.sendresponse()
105
106 return super(HgWeb, self)._runwsgi(req, res, repo)
102
107
103
108
104 def make_hg_ui_from_config(repo_config):
109 def make_hg_ui_from_config(repo_config):
105 baseui = mercurial.ui.ui()
110 baseui = mercurial.ui.ui()
106
111
107 # clean the baseui object
112 # clean the baseui object
108 baseui._ocfg = mercurial.config.config()
113 baseui._ocfg = mercurial.config.config()
109 baseui._ucfg = mercurial.config.config()
114 baseui._ucfg = mercurial.config.config()
110 baseui._tcfg = mercurial.config.config()
115 baseui._tcfg = mercurial.config.config()
111
116
112 for section, option, value in repo_config:
117 for section, option, value in repo_config:
113 baseui.setconfig(section, option, value)
118 baseui.setconfig(section, option, value)
114
119
115 # make our hgweb quiet so it doesn't print output
120 # make our hgweb quiet so it doesn't print output
116 baseui.setconfig('ui', 'quiet', 'true')
121 baseui.setconfig('ui', 'quiet', 'true')
117
122
118 return baseui
123 return baseui
119
124
120
125
121 def update_hg_ui_from_hgrc(baseui, repo_path):
126 def update_hg_ui_from_hgrc(baseui, repo_path):
122 path = os.path.join(repo_path, '.hg', 'hgrc')
127 path = os.path.join(repo_path, '.hg', 'hgrc')
123
128
124 if not os.path.isfile(path):
129 if not os.path.isfile(path):
125 log.debug('hgrc file is not present at %s, skipping...', path)
130 log.debug('hgrc file is not present at %s, skipping...', path)
126 return
131 return
127 log.debug('reading hgrc from %s', path)
132 log.debug('reading hgrc from %s', path)
128 cfg = mercurial.config.config()
133 cfg = mercurial.config.config()
129 cfg.read(path)
134 cfg.read(path)
130 for section in HG_UI_SECTIONS:
135 for section in HG_UI_SECTIONS:
131 for k, v in cfg.items(section):
136 for k, v in cfg.items(section):
132 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
137 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
133 baseui.setconfig(section, k, v)
138 baseui.setconfig(section, k, v)
134
139
135
140
136 def create_hg_wsgi_app(repo_path, repo_name, config):
141 def create_hg_wsgi_app(repo_path, repo_name, config):
137 """
142 """
138 Prepares a WSGI application to handle Mercurial requests.
143 Prepares a WSGI application to handle Mercurial requests.
139
144
140 :param config: is a list of 3-item tuples representing a ConfigObject
145 :param config: is a list of 3-item tuples representing a ConfigObject
141 (it is the serialized version of the config object).
146 (it is the serialized version of the config object).
142 """
147 """
143 log.debug("Creating Mercurial WSGI application")
148 log.debug("Creating Mercurial WSGI application")
144
149
145 baseui = make_hg_ui_from_config(config)
150 baseui = make_hg_ui_from_config(config)
146 update_hg_ui_from_hgrc(baseui, repo_path)
151 update_hg_ui_from_hgrc(baseui, repo_path)
147
152
148 try:
153 try:
149 return HgWeb(repo_path, name=repo_name, baseui=baseui)
154 return HgWeb(repo_path, name=repo_name, baseui=baseui)
150 except mercurial.error.RequirementError as exc:
155 except mercurial.error.RequirementError as exc:
151 raise exceptions.RequirementException(exc)
156 raise exceptions.RequirementException(exc)
152
157
153
158
154 class GitHandler(object):
159 class GitHandler(object):
155 """
160 """
156 Handler for Git operations like push/pull etc
161 Handler for Git operations like push/pull etc
157 """
162 """
158 def __init__(self, repo_location, repo_name, git_path, update_server_info,
163 def __init__(self, repo_location, repo_name, git_path, update_server_info,
159 extras):
164 extras):
160 if not os.path.isdir(repo_location):
165 if not os.path.isdir(repo_location):
161 raise OSError(repo_location)
166 raise OSError(repo_location)
162 self.content_path = repo_location
167 self.content_path = repo_location
163 self.repo_name = repo_name
168 self.repo_name = repo_name
164 self.repo_location = repo_location
169 self.repo_location = repo_location
165 self.extras = extras
170 self.extras = extras
166 self.git_path = git_path
171 self.git_path = git_path
167 self.update_server_info = update_server_info
172 self.update_server_info = update_server_info
168
173
169 def __call__(self, environ, start_response):
174 def __call__(self, environ, start_response):
170 app = webob.exc.HTTPNotFound()
175 app = webob.exc.HTTPNotFound()
171 candidate_paths = (
176 candidate_paths = (
172 self.content_path, os.path.join(self.content_path, '.git'))
177 self.content_path, os.path.join(self.content_path, '.git'))
173
178
174 for content_path in candidate_paths:
179 for content_path in candidate_paths:
175 try:
180 try:
176 app = pygrack.GitRepository(
181 app = pygrack.GitRepository(
177 self.repo_name, content_path, self.git_path,
182 self.repo_name, content_path, self.git_path,
178 self.update_server_info, self.extras)
183 self.update_server_info, self.extras)
179 break
184 break
180 except OSError:
185 except OSError:
181 continue
186 continue
182
187
183 return app(environ, start_response)
188 return app(environ, start_response)
184
189
185
190
186 def create_git_wsgi_app(repo_path, repo_name, config):
191 def create_git_wsgi_app(repo_path, repo_name, config):
187 """
192 """
188 Creates a WSGI application to handle Git requests.
193 Creates a WSGI application to handle Git requests.
189
194
190 :param config: is a dictionary holding the extras.
195 :param config: is a dictionary holding the extras.
191 """
196 """
192 git_path = settings.GIT_EXECUTABLE
197 git_path = settings.GIT_EXECUTABLE
193 update_server_info = config.pop('git_update_server_info')
198 update_server_info = config.pop('git_update_server_info')
194 app = GitHandler(
199 app = GitHandler(
195 repo_path, repo_name, git_path, update_server_info, config)
200 repo_path, repo_name, git_path, update_server_info, config)
196
201
197 return app
202 return app
198
203
199
204
200 class GitLFSHandler(object):
205 class GitLFSHandler(object):
201 """
206 """
202 Handler for Git LFS operations
207 Handler for Git LFS operations
203 """
208 """
204
209
205 def __init__(self, repo_location, repo_name, git_path, update_server_info,
210 def __init__(self, repo_location, repo_name, git_path, update_server_info,
206 extras):
211 extras):
207 if not os.path.isdir(repo_location):
212 if not os.path.isdir(repo_location):
208 raise OSError(repo_location)
213 raise OSError(repo_location)
209 self.content_path = repo_location
214 self.content_path = repo_location
210 self.repo_name = repo_name
215 self.repo_name = repo_name
211 self.repo_location = repo_location
216 self.repo_location = repo_location
212 self.extras = extras
217 self.extras = extras
213 self.git_path = git_path
218 self.git_path = git_path
214 self.update_server_info = update_server_info
219 self.update_server_info = update_server_info
215
220
216 def get_app(self, git_lfs_enabled, git_lfs_store_path):
221 def get_app(self, git_lfs_enabled, git_lfs_store_path):
217 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path)
222 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path)
218 return app
223 return app
219
224
220
225
221 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
226 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
222 git_path = settings.GIT_EXECUTABLE
227 git_path = settings.GIT_EXECUTABLE
223 update_server_info = config.pop('git_update_server_info')
228 update_server_info = config.pop('git_update_server_info')
224 git_lfs_enabled = config.pop('git_lfs_enabled')
229 git_lfs_enabled = config.pop('git_lfs_enabled')
225 git_lfs_store_path = config.pop('git_lfs_store_path')
230 git_lfs_store_path = config.pop('git_lfs_store_path')
226 app = GitLFSHandler(
231 app = GitLFSHandler(
227 repo_path, repo_name, git_path, update_server_info, config)
232 repo_path, repo_name, git_path, update_server_info, config)
228
233
229 return app.get_app(git_lfs_enabled, git_lfs_store_path)
234 return app.get_app(git_lfs_enabled, git_lfs_store_path)
@@ -1,130 +1,124 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2018 RhodeCode GmbH
2 # Copyright (C) 2014-2018 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import mock
18 import mock
19 import pytest
19 import pytest
20
20
21 from vcsserver import hgcompat, hgpatches
21 from vcsserver import hgcompat, hgpatches
22
22
23
23
24 LARGEFILES_CAPABILITY = 'largefiles=serve'
24 LARGEFILES_CAPABILITY = 'largefiles=serve'
25
25
26
26
27 def test_patch_largefiles_capabilities_applies_patch(
27 def test_patch_largefiles_capabilities_applies_patch(
28 patched_capabilities):
28 patched_capabilities):
29 lfproto = hgcompat.largefiles.proto
29 lfproto = hgcompat.largefiles.proto
30 hgpatches.patch_largefiles_capabilities()
30 hgpatches.patch_largefiles_capabilities()
31 assert lfproto.capabilities.func_name == '_dynamic_capabilities'
31 assert lfproto._capabilities.func_name == '_dynamic_capabilities'
32
32
33
33
34 def test_dynamic_capabilities_uses_original_function_if_not_enabled(
34 def test_dynamic_capabilities_uses_original_function_if_not_enabled(
35 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities):
35 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities,
36 orig_capabilities):
36 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
37 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
37 hgcompat.largefiles.proto, stub_extensions)
38 hgcompat.largefiles.proto, stub_extensions)
38
39
39 caps = dynamic_capabilities(stub_repo, stub_proto)
40 caps = dynamic_capabilities(orig_capabilities, stub_repo, stub_proto)
40
41
41 stub_extensions.assert_called_once_with(stub_ui)
42 stub_extensions.assert_called_once_with(stub_ui)
42 assert LARGEFILES_CAPABILITY not in caps
43 assert LARGEFILES_CAPABILITY not in caps
43
44
44
45
45 def test_dynamic_capabilities_uses_updated_capabilitiesorig(
46 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities):
47 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
48 hgcompat.largefiles.proto, stub_extensions)
49
50 # This happens when the extension is loaded for the first time, important
51 # to ensure that an updated function is correctly picked up.
52 hgcompat.largefiles.proto.capabilitiesorig = mock.Mock(
53 return_value='REPLACED')
54
55 caps = dynamic_capabilities(stub_repo, stub_proto)
56 assert 'REPLACED' == caps
57
58
59 def test_dynamic_capabilities_ignores_updated_capabilities(
46 def test_dynamic_capabilities_ignores_updated_capabilities(
60 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities):
47 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities,
48 orig_capabilities):
61 stub_extensions.return_value = [('largefiles', mock.Mock())]
49 stub_extensions.return_value = [('largefiles', mock.Mock())]
62 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
50 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
63 hgcompat.largefiles.proto, stub_extensions)
51 hgcompat.largefiles.proto, stub_extensions)
64
52
65 # This happens when the extension is loaded for the first time, important
53 # This happens when the extension is loaded for the first time, important
66 # to ensure that an updated function is correctly picked up.
54 # to ensure that an updated function is correctly picked up.
67 hgcompat.largefiles.proto.capabilities = mock.Mock(
55 hgcompat.largefiles.proto._capabilities = mock.Mock(
68 side_effect=Exception('Must not be called'))
56 side_effect=Exception('Must not be called'))
69
57
70 dynamic_capabilities(stub_repo, stub_proto)
58 dynamic_capabilities(orig_capabilities, stub_repo, stub_proto)
71
59
72
60
73 def test_dynamic_capabilities_uses_largefiles_if_enabled(
61 def test_dynamic_capabilities_uses_largefiles_if_enabled(
74 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities):
62 stub_repo, stub_proto, stub_ui, stub_extensions, patched_capabilities,
63 orig_capabilities):
75 stub_extensions.return_value = [('largefiles', mock.Mock())]
64 stub_extensions.return_value = [('largefiles', mock.Mock())]
76
65
77 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
66 dynamic_capabilities = hgpatches._dynamic_capabilities_wrapper(
78 hgcompat.largefiles.proto, stub_extensions)
67 hgcompat.largefiles.proto, stub_extensions)
79
68
80 caps = dynamic_capabilities(stub_repo, stub_proto)
69 caps = dynamic_capabilities(orig_capabilities, stub_repo, stub_proto)
81
70
82 stub_extensions.assert_called_once_with(stub_ui)
71 stub_extensions.assert_called_once_with(stub_ui)
83 assert LARGEFILES_CAPABILITY in caps
72 assert LARGEFILES_CAPABILITY in caps
84
73
85
74
86 def test_hgsubversion_import():
75 def test_hgsubversion_import():
87 from hgsubversion import svnrepo
76 from hgsubversion import svnrepo
88 assert svnrepo
77 assert svnrepo
89
78
90
79
91 @pytest.fixture
80 @pytest.fixture
92 def patched_capabilities(request):
81 def patched_capabilities(request):
93 """
82 """
94 Patch in `capabilitiesorig` and restore both capability functions.
83 Patch in `capabilitiesorig` and restore both capability functions.
95 """
84 """
96 lfproto = hgcompat.largefiles.proto
85 lfproto = hgcompat.largefiles.proto
97 orig_capabilities = lfproto.capabilities
86 orig_capabilities = lfproto._capabilities
98 orig_capabilitiesorig = lfproto.capabilitiesorig
99
100 lfproto.capabilitiesorig = mock.Mock(return_value='ORIG')
101
87
102 @request.addfinalizer
88 @request.addfinalizer
103 def restore():
89 def restore():
104 lfproto.capabilities = orig_capabilities
90 lfproto._capabilities = orig_capabilities
105 lfproto.capabilitiesorig = orig_capabilitiesorig
106
91
107
92
108 @pytest.fixture
93 @pytest.fixture
109 def stub_repo(stub_ui):
94 def stub_repo(stub_ui):
110 repo = mock.Mock()
95 repo = mock.Mock()
111 repo.ui = stub_ui
96 repo.ui = stub_ui
112 return repo
97 return repo
113
98
114
99
115 @pytest.fixture
100 @pytest.fixture
116 def stub_proto(stub_ui):
101 def stub_proto(stub_ui):
117 proto = mock.Mock()
102 proto = mock.Mock()
118 proto.ui = stub_ui
103 proto.ui = stub_ui
119 return proto
104 return proto
120
105
121
106
122 @pytest.fixture
107 @pytest.fixture
108 def orig_capabilities():
109 from mercurial.wireprotov1server import wireprotocaps
110
111 def _capabilities(repo, proto):
112 return wireprotocaps
113 return _capabilities
114
115
116 @pytest.fixture
123 def stub_ui():
117 def stub_ui():
124 return hgcompat.ui.ui()
118 return hgcompat.ui.ui()
125
119
126
120
127 @pytest.fixture
121 @pytest.fixture
128 def stub_extensions():
122 def stub_extensions():
129 extensions = mock.Mock(return_value=tuple())
123 extensions = mock.Mock(return_value=tuple())
130 return extensions
124 return extensions
General Comments 0
You need to be logged in to leave comments. Login now