##// END OF EJS Templates
hidden: add support for --remote-hidden to HTTP peer...
Manuel Jacob -
r51313:315f5376 default
parent child Browse files
Show More
@@ -1,659 +1,663 b''
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9
9
10 import errno
10 import errno
11 import io
11 import io
12 import os
12 import os
13 import socket
13 import socket
14 import struct
14 import struct
15
15
16 from concurrent import futures
16 from concurrent import futures
17 from .i18n import _
17 from .i18n import _
18 from .pycompat import getattr
18 from .pycompat import getattr
19 from . import (
19 from . import (
20 bundle2,
20 bundle2,
21 error,
21 error,
22 httpconnection,
22 httpconnection,
23 pycompat,
23 pycompat,
24 statichttprepo,
24 statichttprepo,
25 url as urlmod,
25 url as urlmod,
26 util,
26 util,
27 wireprotov1peer,
27 wireprotov1peer,
28 )
28 )
29 from .utils import urlutil
29 from .utils import urlutil
30
30
31 httplib = util.httplib
31 httplib = util.httplib
32 urlerr = util.urlerr
32 urlerr = util.urlerr
33 urlreq = util.urlreq
33 urlreq = util.urlreq
34
34
35
35
36 def encodevalueinheaders(value, header, limit):
36 def encodevalueinheaders(value, header, limit):
37 """Encode a string value into multiple HTTP headers.
37 """Encode a string value into multiple HTTP headers.
38
38
39 ``value`` will be encoded into 1 or more HTTP headers with the names
39 ``value`` will be encoded into 1 or more HTTP headers with the names
40 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
40 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
41 name + value will be at most ``limit`` bytes long.
41 name + value will be at most ``limit`` bytes long.
42
42
43 Returns an iterable of 2-tuples consisting of header names and
43 Returns an iterable of 2-tuples consisting of header names and
44 values as native strings.
44 values as native strings.
45 """
45 """
46 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
46 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
47 # not bytes. This function always takes bytes in as arguments.
47 # not bytes. This function always takes bytes in as arguments.
48 fmt = pycompat.strurl(header) + r'-%s'
48 fmt = pycompat.strurl(header) + r'-%s'
49 # Note: it is *NOT* a bug that the last bit here is a bytestring
49 # Note: it is *NOT* a bug that the last bit here is a bytestring
50 # and not a unicode: we're just getting the encoded length anyway,
50 # and not a unicode: we're just getting the encoded length anyway,
51 # and using an r-string to make it portable between Python 2 and 3
51 # and using an r-string to make it portable between Python 2 and 3
52 # doesn't work because then the \r is a literal backslash-r
52 # doesn't work because then the \r is a literal backslash-r
53 # instead of a carriage return.
53 # instead of a carriage return.
54 valuelen = limit - len(fmt % '000') - len(b': \r\n')
54 valuelen = limit - len(fmt % '000') - len(b': \r\n')
55 result = []
55 result = []
56
56
57 n = 0
57 n = 0
58 for i in range(0, len(value), valuelen):
58 for i in range(0, len(value), valuelen):
59 n += 1
59 n += 1
60 result.append((fmt % str(n), pycompat.strurl(value[i : i + valuelen])))
60 result.append((fmt % str(n), pycompat.strurl(value[i : i + valuelen])))
61
61
62 return result
62 return result
63
63
64
64
65 class _multifile:
65 class _multifile:
66 def __init__(self, *fileobjs):
66 def __init__(self, *fileobjs):
67 for f in fileobjs:
67 for f in fileobjs:
68 if not util.safehasattr(f, b'length'):
68 if not util.safehasattr(f, b'length'):
69 raise ValueError(
69 raise ValueError(
70 b'_multifile only supports file objects that '
70 b'_multifile only supports file objects that '
71 b'have a length but this one does not:',
71 b'have a length but this one does not:',
72 type(f),
72 type(f),
73 f,
73 f,
74 )
74 )
75 self._fileobjs = fileobjs
75 self._fileobjs = fileobjs
76 self._index = 0
76 self._index = 0
77
77
78 @property
78 @property
79 def length(self):
79 def length(self):
80 return sum(f.length for f in self._fileobjs)
80 return sum(f.length for f in self._fileobjs)
81
81
82 def read(self, amt=None):
82 def read(self, amt=None):
83 if amt <= 0:
83 if amt <= 0:
84 return b''.join(f.read() for f in self._fileobjs)
84 return b''.join(f.read() for f in self._fileobjs)
85 parts = []
85 parts = []
86 while amt and self._index < len(self._fileobjs):
86 while amt and self._index < len(self._fileobjs):
87 parts.append(self._fileobjs[self._index].read(amt))
87 parts.append(self._fileobjs[self._index].read(amt))
88 got = len(parts[-1])
88 got = len(parts[-1])
89 if got < amt:
89 if got < amt:
90 self._index += 1
90 self._index += 1
91 amt -= got
91 amt -= got
92 return b''.join(parts)
92 return b''.join(parts)
93
93
94 def seek(self, offset, whence=os.SEEK_SET):
94 def seek(self, offset, whence=os.SEEK_SET):
95 if whence != os.SEEK_SET:
95 if whence != os.SEEK_SET:
96 raise NotImplementedError(
96 raise NotImplementedError(
97 b'_multifile does not support anything other'
97 b'_multifile does not support anything other'
98 b' than os.SEEK_SET for whence on seek()'
98 b' than os.SEEK_SET for whence on seek()'
99 )
99 )
100 if offset != 0:
100 if offset != 0:
101 raise NotImplementedError(
101 raise NotImplementedError(
102 b'_multifile only supports seeking to start, but that '
102 b'_multifile only supports seeking to start, but that '
103 b'could be fixed if you need it'
103 b'could be fixed if you need it'
104 )
104 )
105 for f in self._fileobjs:
105 for f in self._fileobjs:
106 f.seek(0)
106 f.seek(0)
107 self._index = 0
107 self._index = 0
108
108
109
109
110 def makev1commandrequest(
110 def makev1commandrequest(
111 ui, requestbuilder, caps, capablefn, repobaseurl, cmd, args
111 ui,
112 requestbuilder,
113 caps,
114 capablefn,
115 repobaseurl,
116 cmd,
117 args,
118 remotehidden=False,
112 ):
119 ):
113 """Make an HTTP request to run a command for a version 1 client.
120 """Make an HTTP request to run a command for a version 1 client.
114
121
115 ``caps`` is a set of known server capabilities. The value may be
122 ``caps`` is a set of known server capabilities. The value may be
116 None if capabilities are not yet known.
123 None if capabilities are not yet known.
117
124
118 ``capablefn`` is a function to evaluate a capability.
125 ``capablefn`` is a function to evaluate a capability.
119
126
120 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
127 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
121 raw data to pass to it.
128 raw data to pass to it.
122 """
129 """
123 if cmd == b'pushkey':
130 if cmd == b'pushkey':
124 args[b'data'] = b''
131 args[b'data'] = b''
125 data = args.pop(b'data', None)
132 data = args.pop(b'data', None)
126 headers = args.pop(b'headers', {})
133 headers = args.pop(b'headers', {})
127
134
128 ui.debug(b"sending %s command\n" % cmd)
135 ui.debug(b"sending %s command\n" % cmd)
129 q = [(b'cmd', cmd)]
136 q = [(b'cmd', cmd)]
137 if remotehidden:
138 q.append(('access-hidden', '1'))
130 headersize = 0
139 headersize = 0
131 # Important: don't use self.capable() here or else you end up
140 # Important: don't use self.capable() here or else you end up
132 # with infinite recursion when trying to look up capabilities
141 # with infinite recursion when trying to look up capabilities
133 # for the first time.
142 # for the first time.
134 postargsok = caps is not None and b'httppostargs' in caps
143 postargsok = caps is not None and b'httppostargs' in caps
135
144
136 # Send arguments via POST.
145 # Send arguments via POST.
137 if postargsok and args:
146 if postargsok and args:
138 strargs = urlreq.urlencode(sorted(args.items()))
147 strargs = urlreq.urlencode(sorted(args.items()))
139 if not data:
148 if not data:
140 data = strargs
149 data = strargs
141 else:
150 else:
142 if isinstance(data, bytes):
151 if isinstance(data, bytes):
143 i = io.BytesIO(data)
152 i = io.BytesIO(data)
144 i.length = len(data)
153 i.length = len(data)
145 data = i
154 data = i
146 argsio = io.BytesIO(strargs)
155 argsio = io.BytesIO(strargs)
147 argsio.length = len(strargs)
156 argsio.length = len(strargs)
148 data = _multifile(argsio, data)
157 data = _multifile(argsio, data)
149 headers['X-HgArgs-Post'] = len(strargs)
158 headers['X-HgArgs-Post'] = len(strargs)
150 elif args:
159 elif args:
151 # Calling self.capable() can infinite loop if we are calling
160 # Calling self.capable() can infinite loop if we are calling
152 # "capabilities". But that command should never accept wire
161 # "capabilities". But that command should never accept wire
153 # protocol arguments. So this should never happen.
162 # protocol arguments. So this should never happen.
154 assert cmd != b'capabilities'
163 assert cmd != b'capabilities'
155 httpheader = capablefn(b'httpheader')
164 httpheader = capablefn(b'httpheader')
156 if httpheader:
165 if httpheader:
157 headersize = int(httpheader.split(b',', 1)[0])
166 headersize = int(httpheader.split(b',', 1)[0])
158
167
159 # Send arguments via HTTP headers.
168 # Send arguments via HTTP headers.
160 if headersize > 0:
169 if headersize > 0:
161 # The headers can typically carry more data than the URL.
170 # The headers can typically carry more data than the URL.
162 encoded_args = urlreq.urlencode(sorted(args.items()))
171 encoded_args = urlreq.urlencode(sorted(args.items()))
163 for header, value in encodevalueinheaders(
172 for header, value in encodevalueinheaders(
164 encoded_args, b'X-HgArg', headersize
173 encoded_args, b'X-HgArg', headersize
165 ):
174 ):
166 headers[header] = value
175 headers[header] = value
167 # Send arguments via query string (Mercurial <1.9).
176 # Send arguments via query string (Mercurial <1.9).
168 else:
177 else:
169 q += sorted(args.items())
178 q += sorted(args.items())
170
179
171 qs = b'?%s' % urlreq.urlencode(q)
180 qs = b'?%s' % urlreq.urlencode(q)
172 cu = b"%s%s" % (repobaseurl, qs)
181 cu = b"%s%s" % (repobaseurl, qs)
173 size = 0
182 size = 0
174 if util.safehasattr(data, b'length'):
183 if util.safehasattr(data, b'length'):
175 size = data.length
184 size = data.length
176 elif data is not None:
185 elif data is not None:
177 size = len(data)
186 size = len(data)
178 if data is not None and 'Content-Type' not in headers:
187 if data is not None and 'Content-Type' not in headers:
179 headers['Content-Type'] = 'application/mercurial-0.1'
188 headers['Content-Type'] = 'application/mercurial-0.1'
180
189
181 # Tell the server we accept application/mercurial-0.2 and multiple
190 # Tell the server we accept application/mercurial-0.2 and multiple
182 # compression formats if the server is capable of emitting those
191 # compression formats if the server is capable of emitting those
183 # payloads.
192 # payloads.
184 # Note: Keep this set empty by default, as client advertisement of
193 # Note: Keep this set empty by default, as client advertisement of
185 # protocol parameters should only occur after the handshake.
194 # protocol parameters should only occur after the handshake.
186 protoparams = set()
195 protoparams = set()
187
196
188 mediatypes = set()
197 mediatypes = set()
189 if caps is not None:
198 if caps is not None:
190 mt = capablefn(b'httpmediatype')
199 mt = capablefn(b'httpmediatype')
191 if mt:
200 if mt:
192 protoparams.add(b'0.1')
201 protoparams.add(b'0.1')
193 mediatypes = set(mt.split(b','))
202 mediatypes = set(mt.split(b','))
194
203
195 protoparams.add(b'partial-pull')
204 protoparams.add(b'partial-pull')
196
205
197 if b'0.2tx' in mediatypes:
206 if b'0.2tx' in mediatypes:
198 protoparams.add(b'0.2')
207 protoparams.add(b'0.2')
199
208
200 if b'0.2tx' in mediatypes and capablefn(b'compression'):
209 if b'0.2tx' in mediatypes and capablefn(b'compression'):
201 # We /could/ compare supported compression formats and prune
210 # We /could/ compare supported compression formats and prune
202 # non-mutually supported or error if nothing is mutually supported.
211 # non-mutually supported or error if nothing is mutually supported.
203 # For now, send the full list to the server and have it error.
212 # For now, send the full list to the server and have it error.
204 comps = [
213 comps = [
205 e.wireprotosupport().name
214 e.wireprotosupport().name
206 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
215 for e in util.compengines.supportedwireengines(util.CLIENTROLE)
207 ]
216 ]
208 protoparams.add(b'comp=%s' % b','.join(comps))
217 protoparams.add(b'comp=%s' % b','.join(comps))
209
218
210 if protoparams:
219 if protoparams:
211 protoheaders = encodevalueinheaders(
220 protoheaders = encodevalueinheaders(
212 b' '.join(sorted(protoparams)), b'X-HgProto', headersize or 1024
221 b' '.join(sorted(protoparams)), b'X-HgProto', headersize or 1024
213 )
222 )
214 for header, value in protoheaders:
223 for header, value in protoheaders:
215 headers[header] = value
224 headers[header] = value
216
225
217 varyheaders = []
226 varyheaders = []
218 for header in headers:
227 for header in headers:
219 if header.lower().startswith('x-hg'):
228 if header.lower().startswith('x-hg'):
220 varyheaders.append(header)
229 varyheaders.append(header)
221
230
222 if varyheaders:
231 if varyheaders:
223 headers['Vary'] = ','.join(sorted(varyheaders))
232 headers['Vary'] = ','.join(sorted(varyheaders))
224
233
225 req = requestbuilder(pycompat.strurl(cu), data, headers)
234 req = requestbuilder(pycompat.strurl(cu), data, headers)
226
235
227 if data is not None:
236 if data is not None:
228 ui.debug(b"sending %d bytes\n" % size)
237 ui.debug(b"sending %d bytes\n" % size)
229 req.add_unredirected_header('Content-Length', '%d' % size)
238 req.add_unredirected_header('Content-Length', '%d' % size)
230
239
231 return req, cu, qs
240 return req, cu, qs
232
241
233
242
234 def sendrequest(ui, opener, req):
243 def sendrequest(ui, opener, req):
235 """Send a prepared HTTP request.
244 """Send a prepared HTTP request.
236
245
237 Returns the response object.
246 Returns the response object.
238 """
247 """
239 dbg = ui.debug
248 dbg = ui.debug
240 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
249 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
241 line = b'devel-peer-request: %s\n'
250 line = b'devel-peer-request: %s\n'
242 dbg(
251 dbg(
243 line
252 line
244 % b'%s %s'
253 % b'%s %s'
245 % (
254 % (
246 pycompat.bytesurl(req.get_method()),
255 pycompat.bytesurl(req.get_method()),
247 pycompat.bytesurl(req.get_full_url()),
256 pycompat.bytesurl(req.get_full_url()),
248 )
257 )
249 )
258 )
250 hgargssize = None
259 hgargssize = None
251
260
252 for header, value in sorted(req.header_items()):
261 for header, value in sorted(req.header_items()):
253 header = pycompat.bytesurl(header)
262 header = pycompat.bytesurl(header)
254 value = pycompat.bytesurl(value)
263 value = pycompat.bytesurl(value)
255 if header.startswith(b'X-hgarg-'):
264 if header.startswith(b'X-hgarg-'):
256 if hgargssize is None:
265 if hgargssize is None:
257 hgargssize = 0
266 hgargssize = 0
258 hgargssize += len(value)
267 hgargssize += len(value)
259 else:
268 else:
260 dbg(line % b' %s %s' % (header, value))
269 dbg(line % b' %s %s' % (header, value))
261
270
262 if hgargssize is not None:
271 if hgargssize is not None:
263 dbg(
272 dbg(
264 line
273 line
265 % b' %d bytes of commands arguments in headers'
274 % b' %d bytes of commands arguments in headers'
266 % hgargssize
275 % hgargssize
267 )
276 )
268 data = req.data
277 data = req.data
269 if data is not None:
278 if data is not None:
270 length = getattr(data, 'length', None)
279 length = getattr(data, 'length', None)
271 if length is None:
280 if length is None:
272 length = len(data)
281 length = len(data)
273 dbg(line % b' %d bytes of data' % length)
282 dbg(line % b' %d bytes of data' % length)
274
283
275 start = util.timer()
284 start = util.timer()
276
285
277 res = None
286 res = None
278 try:
287 try:
279 res = opener.open(req)
288 res = opener.open(req)
280 except urlerr.httperror as inst:
289 except urlerr.httperror as inst:
281 if inst.code == 401:
290 if inst.code == 401:
282 raise error.Abort(_(b'authorization failed'))
291 raise error.Abort(_(b'authorization failed'))
283 raise
292 raise
284 except httplib.HTTPException as inst:
293 except httplib.HTTPException as inst:
285 ui.debug(
294 ui.debug(
286 b'http error requesting %s\n'
295 b'http error requesting %s\n'
287 % urlutil.hidepassword(req.get_full_url())
296 % urlutil.hidepassword(req.get_full_url())
288 )
297 )
289 ui.traceback()
298 ui.traceback()
290 raise IOError(None, inst)
299 raise IOError(None, inst)
291 finally:
300 finally:
292 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
301 if ui.debugflag and ui.configbool(b'devel', b'debug.peer-request'):
293 code = res.code if res else -1
302 code = res.code if res else -1
294 dbg(
303 dbg(
295 line
304 line
296 % b' finished in %.4f seconds (%d)'
305 % b' finished in %.4f seconds (%d)'
297 % (util.timer() - start, code)
306 % (util.timer() - start, code)
298 )
307 )
299
308
300 # Insert error handlers for common I/O failures.
309 # Insert error handlers for common I/O failures.
301 urlmod.wrapresponse(res)
310 urlmod.wrapresponse(res)
302
311
303 return res
312 return res
304
313
305
314
306 class RedirectedRepoError(error.RepoError):
315 class RedirectedRepoError(error.RepoError):
307 def __init__(self, msg, respurl):
316 def __init__(self, msg, respurl):
308 super(RedirectedRepoError, self).__init__(msg)
317 super(RedirectedRepoError, self).__init__(msg)
309 self.respurl = respurl
318 self.respurl = respurl
310
319
311
320
312 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible):
321 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible):
313 # record the url we got redirected to
322 # record the url we got redirected to
314 redirected = False
323 redirected = False
315 respurl = pycompat.bytesurl(resp.geturl())
324 respurl = pycompat.bytesurl(resp.geturl())
316 if respurl.endswith(qs):
325 if respurl.endswith(qs):
317 respurl = respurl[: -len(qs)]
326 respurl = respurl[: -len(qs)]
318 qsdropped = False
327 qsdropped = False
319 else:
328 else:
320 qsdropped = True
329 qsdropped = True
321
330
322 if baseurl.rstrip(b'/') != respurl.rstrip(b'/'):
331 if baseurl.rstrip(b'/') != respurl.rstrip(b'/'):
323 redirected = True
332 redirected = True
324 if not ui.quiet:
333 if not ui.quiet:
325 ui.warn(_(b'real URL is %s\n') % respurl)
334 ui.warn(_(b'real URL is %s\n') % respurl)
326
335
327 try:
336 try:
328 proto = pycompat.bytesurl(resp.getheader('content-type', ''))
337 proto = pycompat.bytesurl(resp.getheader('content-type', ''))
329 except AttributeError:
338 except AttributeError:
330 proto = pycompat.bytesurl(resp.headers.get('content-type', ''))
339 proto = pycompat.bytesurl(resp.headers.get('content-type', ''))
331
340
332 safeurl = urlutil.hidepassword(baseurl)
341 safeurl = urlutil.hidepassword(baseurl)
333 if proto.startswith(b'application/hg-error'):
342 if proto.startswith(b'application/hg-error'):
334 raise error.OutOfBandError(resp.read())
343 raise error.OutOfBandError(resp.read())
335
344
336 # Pre 1.0 versions of Mercurial used text/plain and
345 # Pre 1.0 versions of Mercurial used text/plain and
337 # application/hg-changegroup. We don't support such old servers.
346 # application/hg-changegroup. We don't support such old servers.
338 if not proto.startswith(b'application/mercurial-'):
347 if not proto.startswith(b'application/mercurial-'):
339 ui.debug(b"requested URL: '%s'\n" % urlutil.hidepassword(requrl))
348 ui.debug(b"requested URL: '%s'\n" % urlutil.hidepassword(requrl))
340 msg = _(
349 msg = _(
341 b"'%s' does not appear to be an hg repository:\n"
350 b"'%s' does not appear to be an hg repository:\n"
342 b"---%%<--- (%s)\n%s\n---%%<---\n"
351 b"---%%<--- (%s)\n%s\n---%%<---\n"
343 ) % (safeurl, proto or b'no content-type', resp.read(1024))
352 ) % (safeurl, proto or b'no content-type', resp.read(1024))
344
353
345 # Some servers may strip the query string from the redirect. We
354 # Some servers may strip the query string from the redirect. We
346 # raise a special error type so callers can react to this specially.
355 # raise a special error type so callers can react to this specially.
347 if redirected and qsdropped:
356 if redirected and qsdropped:
348 raise RedirectedRepoError(msg, respurl)
357 raise RedirectedRepoError(msg, respurl)
349 else:
358 else:
350 raise error.RepoError(msg)
359 raise error.RepoError(msg)
351
360
352 try:
361 try:
353 subtype = proto.split(b'-', 1)[1]
362 subtype = proto.split(b'-', 1)[1]
354
363
355 version_info = tuple([int(n) for n in subtype.split(b'.')])
364 version_info = tuple([int(n) for n in subtype.split(b'.')])
356 except ValueError:
365 except ValueError:
357 raise error.RepoError(
366 raise error.RepoError(
358 _(b"'%s' sent a broken Content-Type header (%s)") % (safeurl, proto)
367 _(b"'%s' sent a broken Content-Type header (%s)") % (safeurl, proto)
359 )
368 )
360
369
361 # TODO consider switching to a decompression reader that uses
370 # TODO consider switching to a decompression reader that uses
362 # generators.
371 # generators.
363 if version_info == (0, 1):
372 if version_info == (0, 1):
364 if compressible:
373 if compressible:
365 resp = util.compengines[b'zlib'].decompressorreader(resp)
374 resp = util.compengines[b'zlib'].decompressorreader(resp)
366
375
367 elif version_info == (0, 2):
376 elif version_info == (0, 2):
368 # application/mercurial-0.2 always identifies the compression
377 # application/mercurial-0.2 always identifies the compression
369 # engine in the payload header.
378 # engine in the payload header.
370 elen = struct.unpack(b'B', util.readexactly(resp, 1))[0]
379 elen = struct.unpack(b'B', util.readexactly(resp, 1))[0]
371 ename = util.readexactly(resp, elen)
380 ename = util.readexactly(resp, elen)
372 engine = util.compengines.forwiretype(ename)
381 engine = util.compengines.forwiretype(ename)
373
382
374 resp = engine.decompressorreader(resp)
383 resp = engine.decompressorreader(resp)
375 else:
384 else:
376 raise error.RepoError(
385 raise error.RepoError(
377 _(b"'%s' uses newer protocol %s") % (safeurl, subtype)
386 _(b"'%s' uses newer protocol %s") % (safeurl, subtype)
378 )
387 )
379
388
380 return respurl, proto, resp
389 return respurl, proto, resp
381
390
382
391
383 class httppeer(wireprotov1peer.wirepeer):
392 class httppeer(wireprotov1peer.wirepeer):
384 def __init__(
393 def __init__(
385 self, ui, path, url, opener, requestbuilder, caps, remotehidden=False
394 self, ui, path, url, opener, requestbuilder, caps, remotehidden=False
386 ):
395 ):
387 super().__init__(ui, path=path, remotehidden=remotehidden)
396 super().__init__(ui, path=path, remotehidden=remotehidden)
388 if remotehidden:
389 msg = _(
390 b"ignoring `--remote-hidden` request\n"
391 b"(access to hidden changeset for http peers not "
392 b"supported yet)\n"
393 )
394 ui.warn(msg)
395 self._url = url
397 self._url = url
396 self._caps = caps
398 self._caps = caps
397 self.limitedarguments = caps is not None and b'httppostargs' not in caps
399 self.limitedarguments = caps is not None and b'httppostargs' not in caps
398 self._urlopener = opener
400 self._urlopener = opener
399 self._requestbuilder = requestbuilder
401 self._requestbuilder = requestbuilder
402 self._remotehidden = remotehidden
400
403
401 def __del__(self):
404 def __del__(self):
402 for h in self._urlopener.handlers:
405 for h in self._urlopener.handlers:
403 h.close()
406 h.close()
404 getattr(h, "close_all", lambda: None)()
407 getattr(h, "close_all", lambda: None)()
405
408
406 # Begin of ipeerconnection interface.
409 # Begin of ipeerconnection interface.
407
410
408 def url(self):
411 def url(self):
409 return self.path.loc
412 return self.path.loc
410
413
411 def local(self):
414 def local(self):
412 return None
415 return None
413
416
414 def canpush(self):
417 def canpush(self):
415 return True
418 return True
416
419
417 def close(self):
420 def close(self):
418 try:
421 try:
419 reqs, sent, recv = (
422 reqs, sent, recv = (
420 self._urlopener.requestscount,
423 self._urlopener.requestscount,
421 self._urlopener.sentbytescount,
424 self._urlopener.sentbytescount,
422 self._urlopener.receivedbytescount,
425 self._urlopener.receivedbytescount,
423 )
426 )
424 except AttributeError:
427 except AttributeError:
425 return
428 return
426 self.ui.note(
429 self.ui.note(
427 _(
430 _(
428 b'(sent %d HTTP requests and %d bytes; '
431 b'(sent %d HTTP requests and %d bytes; '
429 b'received %d bytes in responses)\n'
432 b'received %d bytes in responses)\n'
430 )
433 )
431 % (reqs, sent, recv)
434 % (reqs, sent, recv)
432 )
435 )
433
436
434 # End of ipeerconnection interface.
437 # End of ipeerconnection interface.
435
438
436 # Begin of ipeercommands interface.
439 # Begin of ipeercommands interface.
437
440
438 def capabilities(self):
441 def capabilities(self):
439 return self._caps
442 return self._caps
440
443
441 # End of ipeercommands interface.
444 # End of ipeercommands interface.
442
445
443 def _callstream(self, cmd, _compressible=False, **args):
446 def _callstream(self, cmd, _compressible=False, **args):
444 args = pycompat.byteskwargs(args)
447 args = pycompat.byteskwargs(args)
445
448
446 req, cu, qs = makev1commandrequest(
449 req, cu, qs = makev1commandrequest(
447 self.ui,
450 self.ui,
448 self._requestbuilder,
451 self._requestbuilder,
449 self._caps,
452 self._caps,
450 self.capable,
453 self.capable,
451 self._url,
454 self._url,
452 cmd,
455 cmd,
453 args,
456 args,
457 self._remotehidden,
454 )
458 )
455
459
456 resp = sendrequest(self.ui, self._urlopener, req)
460 resp = sendrequest(self.ui, self._urlopener, req)
457
461
458 self._url, ct, resp = parsev1commandresponse(
462 self._url, ct, resp = parsev1commandresponse(
459 self.ui, self._url, cu, qs, resp, _compressible
463 self.ui, self._url, cu, qs, resp, _compressible
460 )
464 )
461
465
462 return resp
466 return resp
463
467
464 def _call(self, cmd, **args):
468 def _call(self, cmd, **args):
465 fp = self._callstream(cmd, **args)
469 fp = self._callstream(cmd, **args)
466 try:
470 try:
467 return fp.read()
471 return fp.read()
468 finally:
472 finally:
469 # if using keepalive, allow connection to be reused
473 # if using keepalive, allow connection to be reused
470 fp.close()
474 fp.close()
471
475
472 def _callpush(self, cmd, cg, **args):
476 def _callpush(self, cmd, cg, **args):
473 # have to stream bundle to a temp file because we do not have
477 # have to stream bundle to a temp file because we do not have
474 # http 1.1 chunked transfer.
478 # http 1.1 chunked transfer.
475
479
476 types = self.capable(b'unbundle')
480 types = self.capable(b'unbundle')
477 try:
481 try:
478 types = types.split(b',')
482 types = types.split(b',')
479 except AttributeError:
483 except AttributeError:
480 # servers older than d1b16a746db6 will send 'unbundle' as a
484 # servers older than d1b16a746db6 will send 'unbundle' as a
481 # boolean capability. They only support headerless/uncompressed
485 # boolean capability. They only support headerless/uncompressed
482 # bundles.
486 # bundles.
483 types = [b""]
487 types = [b""]
484 for x in types:
488 for x in types:
485 if x in bundle2.bundletypes:
489 if x in bundle2.bundletypes:
486 type = x
490 type = x
487 break
491 break
488
492
489 tempname = bundle2.writebundle(self.ui, cg, None, type)
493 tempname = bundle2.writebundle(self.ui, cg, None, type)
490 fp = httpconnection.httpsendfile(self.ui, tempname, b"rb")
494 fp = httpconnection.httpsendfile(self.ui, tempname, b"rb")
491 headers = {'Content-Type': 'application/mercurial-0.1'}
495 headers = {'Content-Type': 'application/mercurial-0.1'}
492
496
493 try:
497 try:
494 r = self._call(cmd, data=fp, headers=headers, **args)
498 r = self._call(cmd, data=fp, headers=headers, **args)
495 vals = r.split(b'\n', 1)
499 vals = r.split(b'\n', 1)
496 if len(vals) < 2:
500 if len(vals) < 2:
497 raise error.ResponseError(_(b"unexpected response:"), r)
501 raise error.ResponseError(_(b"unexpected response:"), r)
498 return vals
502 return vals
499 except urlerr.httperror:
503 except urlerr.httperror:
500 # Catch and re-raise these so we don't try and treat them
504 # Catch and re-raise these so we don't try and treat them
501 # like generic socket errors. They lack any values in
505 # like generic socket errors. They lack any values in
502 # .args on Python 3 which breaks our socket.error block.
506 # .args on Python 3 which breaks our socket.error block.
503 raise
507 raise
504 except socket.error as err:
508 except socket.error as err:
505 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
509 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
506 raise error.Abort(_(b'push failed: %s') % err.args[1])
510 raise error.Abort(_(b'push failed: %s') % err.args[1])
507 raise error.Abort(err.args[1])
511 raise error.Abort(err.args[1])
508 finally:
512 finally:
509 fp.close()
513 fp.close()
510 os.unlink(tempname)
514 os.unlink(tempname)
511
515
512 def _calltwowaystream(self, cmd, fp, **args):
516 def _calltwowaystream(self, cmd, fp, **args):
513 filename = None
517 filename = None
514 try:
518 try:
515 # dump bundle to disk
519 # dump bundle to disk
516 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
520 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
517 with os.fdopen(fd, "wb") as fh:
521 with os.fdopen(fd, "wb") as fh:
518 d = fp.read(4096)
522 d = fp.read(4096)
519 while d:
523 while d:
520 fh.write(d)
524 fh.write(d)
521 d = fp.read(4096)
525 d = fp.read(4096)
522 # start http push
526 # start http push
523 with httpconnection.httpsendfile(self.ui, filename, b"rb") as fp_:
527 with httpconnection.httpsendfile(self.ui, filename, b"rb") as fp_:
524 headers = {'Content-Type': 'application/mercurial-0.1'}
528 headers = {'Content-Type': 'application/mercurial-0.1'}
525 return self._callstream(cmd, data=fp_, headers=headers, **args)
529 return self._callstream(cmd, data=fp_, headers=headers, **args)
526 finally:
530 finally:
527 if filename is not None:
531 if filename is not None:
528 os.unlink(filename)
532 os.unlink(filename)
529
533
530 def _callcompressable(self, cmd, **args):
534 def _callcompressable(self, cmd, **args):
531 return self._callstream(cmd, _compressible=True, **args)
535 return self._callstream(cmd, _compressible=True, **args)
532
536
533 def _abort(self, exception):
537 def _abort(self, exception):
534 raise exception
538 raise exception
535
539
536
540
537 class queuedcommandfuture(futures.Future):
541 class queuedcommandfuture(futures.Future):
538 """Wraps result() on command futures to trigger submission on call."""
542 """Wraps result() on command futures to trigger submission on call."""
539
543
540 def result(self, timeout=None):
544 def result(self, timeout=None):
541 if self.done():
545 if self.done():
542 return futures.Future.result(self, timeout)
546 return futures.Future.result(self, timeout)
543
547
544 self._peerexecutor.sendcommands()
548 self._peerexecutor.sendcommands()
545
549
546 # sendcommands() will restore the original __class__ and self.result
550 # sendcommands() will restore the original __class__ and self.result
547 # will resolve to Future.result.
551 # will resolve to Future.result.
548 return self.result(timeout)
552 return self.result(timeout)
549
553
550
554
551 def performhandshake(ui, url, opener, requestbuilder):
555 def performhandshake(ui, url, opener, requestbuilder):
552 # The handshake is a request to the capabilities command.
556 # The handshake is a request to the capabilities command.
553
557
554 caps = None
558 caps = None
555
559
556 def capable(x):
560 def capable(x):
557 raise error.ProgrammingError(b'should not be called')
561 raise error.ProgrammingError(b'should not be called')
558
562
559 args = {}
563 args = {}
560
564
561 req, requrl, qs = makev1commandrequest(
565 req, requrl, qs = makev1commandrequest(
562 ui, requestbuilder, caps, capable, url, b'capabilities', args
566 ui, requestbuilder, caps, capable, url, b'capabilities', args
563 )
567 )
564 resp = sendrequest(ui, opener, req)
568 resp = sendrequest(ui, opener, req)
565
569
566 # The server may redirect us to the repo root, stripping the
570 # The server may redirect us to the repo root, stripping the
567 # ?cmd=capabilities query string from the URL. The server would likely
571 # ?cmd=capabilities query string from the URL. The server would likely
568 # return HTML in this case and ``parsev1commandresponse()`` would raise.
572 # return HTML in this case and ``parsev1commandresponse()`` would raise.
569 # We catch this special case and re-issue the capabilities request against
573 # We catch this special case and re-issue the capabilities request against
570 # the new URL.
574 # the new URL.
571 #
575 #
572 # We should ideally not do this, as a redirect that drops the query
576 # We should ideally not do this, as a redirect that drops the query
573 # string from the URL is arguably a server bug. (Garbage in, garbage out).
577 # string from the URL is arguably a server bug. (Garbage in, garbage out).
574 # However, Mercurial clients for several years appeared to handle this
578 # However, Mercurial clients for several years appeared to handle this
575 # issue without behavior degradation. And according to issue 5860, it may
579 # issue without behavior degradation. And according to issue 5860, it may
576 # be a longstanding bug in some server implementations. So we allow a
580 # be a longstanding bug in some server implementations. So we allow a
577 # redirect that drops the query string to "just work."
581 # redirect that drops the query string to "just work."
578 try:
582 try:
579 respurl, ct, resp = parsev1commandresponse(
583 respurl, ct, resp = parsev1commandresponse(
580 ui, url, requrl, qs, resp, compressible=False
584 ui, url, requrl, qs, resp, compressible=False
581 )
585 )
582 except RedirectedRepoError as e:
586 except RedirectedRepoError as e:
583 req, requrl, qs = makev1commandrequest(
587 req, requrl, qs = makev1commandrequest(
584 ui, requestbuilder, caps, capable, e.respurl, b'capabilities', args
588 ui, requestbuilder, caps, capable, e.respurl, b'capabilities', args
585 )
589 )
586 resp = sendrequest(ui, opener, req)
590 resp = sendrequest(ui, opener, req)
587 respurl, ct, resp = parsev1commandresponse(
591 respurl, ct, resp = parsev1commandresponse(
588 ui, url, requrl, qs, resp, compressible=False
592 ui, url, requrl, qs, resp, compressible=False
589 )
593 )
590
594
591 try:
595 try:
592 rawdata = resp.read()
596 rawdata = resp.read()
593 finally:
597 finally:
594 resp.close()
598 resp.close()
595
599
596 if not ct.startswith(b'application/mercurial-'):
600 if not ct.startswith(b'application/mercurial-'):
597 raise error.ProgrammingError(b'unexpected content-type: %s' % ct)
601 raise error.ProgrammingError(b'unexpected content-type: %s' % ct)
598
602
599 info = {b'v1capabilities': set(rawdata.split())}
603 info = {b'v1capabilities': set(rawdata.split())}
600
604
601 return respurl, info
605 return respurl, info
602
606
603
607
604 def _make_peer(
608 def _make_peer(
605 ui, path, opener=None, requestbuilder=urlreq.request, remotehidden=False
609 ui, path, opener=None, requestbuilder=urlreq.request, remotehidden=False
606 ):
610 ):
607 """Construct an appropriate HTTP peer instance.
611 """Construct an appropriate HTTP peer instance.
608
612
609 ``opener`` is an ``url.opener`` that should be used to establish
613 ``opener`` is an ``url.opener`` that should be used to establish
610 connections, perform HTTP requests.
614 connections, perform HTTP requests.
611
615
612 ``requestbuilder`` is the type used for constructing HTTP requests.
616 ``requestbuilder`` is the type used for constructing HTTP requests.
613 It exists as an argument so extensions can override the default.
617 It exists as an argument so extensions can override the default.
614 """
618 """
615 if path.url.query or path.url.fragment:
619 if path.url.query or path.url.fragment:
616 msg = _(b'unsupported URL component: "%s"')
620 msg = _(b'unsupported URL component: "%s"')
617 msg %= path.url.query or path.url.fragment
621 msg %= path.url.query or path.url.fragment
618 raise error.Abort(msg)
622 raise error.Abort(msg)
619
623
620 # urllib cannot handle URLs with embedded user or passwd.
624 # urllib cannot handle URLs with embedded user or passwd.
621 url, authinfo = path.url.authinfo()
625 url, authinfo = path.url.authinfo()
622 ui.debug(b'using %s\n' % url)
626 ui.debug(b'using %s\n' % url)
623
627
624 opener = opener or urlmod.opener(ui, authinfo)
628 opener = opener or urlmod.opener(ui, authinfo)
625
629
626 respurl, info = performhandshake(ui, url, opener, requestbuilder)
630 respurl, info = performhandshake(ui, url, opener, requestbuilder)
627
631
628 return httppeer(
632 return httppeer(
629 ui,
633 ui,
630 path,
634 path,
631 respurl,
635 respurl,
632 opener,
636 opener,
633 requestbuilder,
637 requestbuilder,
634 info[b'v1capabilities'],
638 info[b'v1capabilities'],
635 remotehidden=remotehidden,
639 remotehidden=remotehidden,
636 )
640 )
637
641
638
642
639 def make_peer(
643 def make_peer(
640 ui, path, create, intents=None, createopts=None, remotehidden=False
644 ui, path, create, intents=None, createopts=None, remotehidden=False
641 ):
645 ):
642 if create:
646 if create:
643 raise error.Abort(_(b'cannot create new http repository'))
647 raise error.Abort(_(b'cannot create new http repository'))
644 try:
648 try:
645 if path.url.scheme == b'https' and not urlmod.has_https:
649 if path.url.scheme == b'https' and not urlmod.has_https:
646 raise error.Abort(
650 raise error.Abort(
647 _(b'Python support for SSL and HTTPS is not installed')
651 _(b'Python support for SSL and HTTPS is not installed')
648 )
652 )
649
653
650 inst = _make_peer(ui, path, remotehidden=remotehidden)
654 inst = _make_peer(ui, path, remotehidden=remotehidden)
651
655
652 return inst
656 return inst
653 except error.RepoError as httpexception:
657 except error.RepoError as httpexception:
654 try:
658 try:
655 r = statichttprepo.make_peer(ui, b"static-" + path.loc, create)
659 r = statichttprepo.make_peer(ui, b"static-" + path.loc, create)
656 ui.note(_(b'(falling back to static-http)\n'))
660 ui.note(_(b'(falling back to static-http)\n'))
657 return r
661 return r
658 except error.RepoError:
662 except error.RepoError:
659 raise httpexception # use the original http RepoError instead
663 raise httpexception # use the original http RepoError instead
@@ -1,218 +1,312 b''
1 ========================================================
1 ========================================================
2 Test the ability to access a hidden revision on a server
2 Test the ability to access a hidden revision on a server
3 ========================================================
3 ========================================================
4
4
5 #require serve
5 #require serve
6
6
7 $ . $TESTDIR/testlib/obsmarker-common.sh
7 $ . $TESTDIR/testlib/obsmarker-common.sh
8 $ cat >> $HGRCPATH << EOF
8 $ cat >> $HGRCPATH << EOF
9 > [phases]
9 > [phases]
10 > # public changeset are not obsolete
10 > # public changeset are not obsolete
11 > publish=false
11 > publish=false
12 > [experimental]
12 > [experimental]
13 > evolution=all
13 > evolution=all
14 > [ui]
14 > [ui]
15 > logtemplate='{rev}:{node|short} {desc} [{phase}]\n'
15 > logtemplate='{rev}:{node|short} {desc} [{phase}]\n'
16 > EOF
16 > EOF
17
17
18 Setup a simple repository with some hidden revisions
18 Setup a simple repository with some hidden revisions
19 ----------------------------------------------------
19 ----------------------------------------------------
20
20
21 Testing the `served.hidden` view
21 Testing the `served.hidden` view
22
22
23 $ hg init repo-with-hidden
23 $ hg init repo-with-hidden
24 $ cd repo-with-hidden
24 $ cd repo-with-hidden
25
25
26 $ echo 0 > a
26 $ echo 0 > a
27 $ hg ci -qAm "c_Public"
27 $ hg ci -qAm "c_Public"
28 $ hg phase --public
28 $ hg phase --public
29 $ echo 1 > a
29 $ echo 1 > a
30 $ hg ci -m "c_Amend_Old"
30 $ hg ci -m "c_Amend_Old"
31 $ echo 2 > a
31 $ echo 2 > a
32 $ hg ci -m "c_Amend_New" --amend
32 $ hg ci -m "c_Amend_New" --amend
33 $ hg up ".^"
33 $ hg up ".^"
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 $ echo 3 > a
35 $ echo 3 > a
36 $ hg ci -m "c_Pruned"
36 $ hg ci -m "c_Pruned"
37 created new head
37 created new head
38 $ hg debugobsolete --record-parents `getid 'desc("c_Pruned")'` -d '0 0'
38 $ hg debugobsolete --record-parents `getid 'desc("c_Pruned")'` -d '0 0'
39 1 new obsolescence markers
39 1 new obsolescence markers
40 obsoleted 1 changesets
40 obsoleted 1 changesets
41 $ hg up ".^"
41 $ hg up ".^"
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ echo 4 > a
43 $ echo 4 > a
44 $ hg ci -m "c_Secret" --secret
44 $ hg ci -m "c_Secret" --secret
45 created new head
45 created new head
46 $ echo 5 > a
46 $ echo 5 > a
47 $ hg ci -m "c_Secret_Pruned" --secret
47 $ hg ci -m "c_Secret_Pruned" --secret
48 $ hg debugobsolete --record-parents `getid 'desc("c_Secret_Pruned")'` -d '0 0'
48 $ hg debugobsolete --record-parents `getid 'desc("c_Secret_Pruned")'` -d '0 0'
49 1 new obsolescence markers
49 1 new obsolescence markers
50 obsoleted 1 changesets
50 obsoleted 1 changesets
51 $ hg up null
51 $ hg up null
52 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
53
53
54 $ hg log -G -T '{rev}:{node|short} {desc} [{phase}]\n' --hidden
54 $ hg log -G -T '{rev}:{node|short} {desc} [{phase}]\n' --hidden
55 x 5:8d28cbe335f3 c_Secret_Pruned [secret]
55 x 5:8d28cbe335f3 c_Secret_Pruned [secret]
56 |
56 |
57 o 4:1c6afd79eb66 c_Secret [secret]
57 o 4:1c6afd79eb66 c_Secret [secret]
58 |
58 |
59 | x 3:5d1575e42c25 c_Pruned [draft]
59 | x 3:5d1575e42c25 c_Pruned [draft]
60 |/
60 |/
61 | o 2:c33affeb3f6b c_Amend_New [draft]
61 | o 2:c33affeb3f6b c_Amend_New [draft]
62 |/
62 |/
63 | x 1:be215fbb8c50 c_Amend_Old [draft]
63 | x 1:be215fbb8c50 c_Amend_Old [draft]
64 |/
64 |/
65 o 0:5f354f46e585 c_Public [public]
65 o 0:5f354f46e585 c_Public [public]
66
66
67 $ hg debugobsolete
67 $ hg debugobsolete
68 be215fbb8c5090028b00154c1fe877ad1b376c61 c33affeb3f6b4e9621d1839d6175ddc07708807c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'amend', 'user': 'test'}
68 be215fbb8c5090028b00154c1fe877ad1b376c61 c33affeb3f6b4e9621d1839d6175ddc07708807c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'amend', 'user': 'test'}
69 5d1575e42c25b7f2db75cd4e0b881b1c35158fae 0 {5f354f46e5853535841ec7a128423e991ca4d59b} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
69 5d1575e42c25b7f2db75cd4e0b881b1c35158fae 0 {5f354f46e5853535841ec7a128423e991ca4d59b} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
70 8d28cbe335f311bc89332d7bbe8a07889b6914a0 0 {1c6afd79eb6663275bbe30097e162b1c24ced0f0} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
70 8d28cbe335f311bc89332d7bbe8a07889b6914a0 0 {1c6afd79eb6663275bbe30097e162b1c24ced0f0} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
71
71
72 $ cd ..
72 $ cd ..
73
73
74 Test the feature
74 Test the feature
75 ================
75 ================
76
76
77 Check cache pre-warm
77 Check cache pre-warm
78 --------------------
78 --------------------
79
79
80 $ ls -1 repo-with-hidden/.hg/cache
80 $ ls -1 repo-with-hidden/.hg/cache
81 branch2
81 branch2
82 branch2-base
82 branch2-base
83 branch2-served
83 branch2-served
84 branch2-served.hidden
84 branch2-served.hidden
85 branch2-visible
85 branch2-visible
86 rbc-names-v1
86 rbc-names-v1
87 rbc-revs-v1
87 rbc-revs-v1
88 tags2
88 tags2
89 tags2-visible
89 tags2-visible
90
90
91 Check that the `served.hidden` repoview
91 Check that the `served.hidden` repoview
92 ---------------------------------------
92 ---------------------------------------
93
93
94 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config web.view=served.hidden
94 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config web.view=served.hidden
95 $ cat hg.pid >> $DAEMON_PIDS
95 $ cat hg.pid >> $DAEMON_PIDS
96
96
97 changesets in secret and higher phases are not visible through hgweb
97 changesets in secret and higher phases are not visible through hgweb
98
98
99 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())"
99 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())"
100 revision: 2
100 revision: 2
101 revision: 0
101 revision: 0
102 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())" --hidden
102 $ hg -R repo-with-hidden log --template "revision: {rev}\\n" --rev "reverse(not secret())" --hidden
103 revision: 3
103 revision: 3
104 revision: 2
104 revision: 2
105 revision: 1
105 revision: 1
106 revision: 0
106 revision: 0
107 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
107 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
108 revision: 3
108 revision: 3
109 revision: 2
109 revision: 2
110 revision: 1
110 revision: 1
111 revision: 0
111 revision: 0
112
112
113 $ killdaemons.py
113 $ killdaemons.py
114
114
115 Test --remote-hidden for local peer
115 Test --remote-hidden for local peer
116 -----------------------------------
116 -----------------------------------
117
117
118 $ hg clone --pull repo-with-hidden client
118 $ hg clone --pull repo-with-hidden client
119 requesting all changes
119 requesting all changes
120 adding changesets
120 adding changesets
121 adding manifests
121 adding manifests
122 adding file changes
122 adding file changes
123 added 2 changesets with 2 changes to 1 files
123 added 2 changesets with 2 changes to 1 files
124 2 new obsolescence markers
124 2 new obsolescence markers
125 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
125 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
126 updating to branch default
126 updating to branch default
127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 $ hg -R client log -G --hidden -v
128 $ hg -R client log -G --hidden -v
129 @ 1:c33affeb3f6b c_Amend_New [draft]
129 @ 1:c33affeb3f6b c_Amend_New [draft]
130 |
130 |
131 o 0:5f354f46e585 c_Public [public]
131 o 0:5f354f46e585 c_Public [public]
132
132
133
133
134 pulling an hidden changeset should fail:
134 pulling an hidden changeset should fail:
135
135
136 $ hg -R client pull -r be215fbb8c50
136 $ hg -R client pull -r be215fbb8c50
137 pulling from $TESTTMP/repo-with-hidden
137 pulling from $TESTTMP/repo-with-hidden
138 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
138 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
139 [10]
139 [10]
140
140
141 pulling an hidden changeset with --remote-hidden should succeed:
141 pulling an hidden changeset with --remote-hidden should succeed:
142
142
143 $ hg -R client pull --remote-hidden --traceback -r be215fbb8c50
143 $ hg -R client pull --remote-hidden --traceback -r be215fbb8c50
144 pulling from $TESTTMP/repo-with-hidden
144 pulling from $TESTTMP/repo-with-hidden
145 searching for changes
145 searching for changes
146 adding changesets
146 adding changesets
147 adding manifests
147 adding manifests
148 adding file changes
148 adding file changes
149 added 1 changesets with 1 changes to 1 files (+1 heads)
149 added 1 changesets with 1 changes to 1 files (+1 heads)
150 (1 other changesets obsolete on arrival)
150 (1 other changesets obsolete on arrival)
151 (run 'hg heads' to see heads)
151 (run 'hg heads' to see heads)
152 $ hg -R client log -G --hidden -v
152 $ hg -R client log -G --hidden -v
153 x 2:be215fbb8c50 c_Amend_Old [draft]
153 x 2:be215fbb8c50 c_Amend_Old [draft]
154 |
154 |
155 | @ 1:c33affeb3f6b c_Amend_New [draft]
155 | @ 1:c33affeb3f6b c_Amend_New [draft]
156 |/
156 |/
157 o 0:5f354f46e585 c_Public [public]
157 o 0:5f354f46e585 c_Public [public]
158
158
159
159
160 Pulling a secret changeset is still forbidden:
160 Pulling a secret changeset is still forbidden:
161
161
162 secret visible:
162 secret visible:
163
163
164 $ hg -R client pull --remote-hidden -r 8d28cbe335f3
164 $ hg -R client pull --remote-hidden -r 8d28cbe335f3
165 pulling from $TESTTMP/repo-with-hidden
165 pulling from $TESTTMP/repo-with-hidden
166 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
166 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
167 [10]
167 [10]
168
168
169 secret hidden:
169 secret hidden:
170
170
171 $ hg -R client pull --remote-hidden -r 1c6afd79eb66
171 $ hg -R client pull --remote-hidden -r 1c6afd79eb66
172 pulling from $TESTTMP/repo-with-hidden
172 pulling from $TESTTMP/repo-with-hidden
173 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
173 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
174 [10]
174 [10]
175
175
176 Test accessing hidden changeset through hgweb
176 Test accessing hidden changeset through hgweb
177 ---------------------------------------------
177 ---------------------------------------------
178
178
179 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log
179 $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log
180 $ cat hg.pid >> $DAEMON_PIDS
180 $ cat hg.pid >> $DAEMON_PIDS
181
181
182 Hidden changeset are hidden by default:
182 Hidden changeset are hidden by default:
183
183
184 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
184 $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
185 revision: 2
185 revision: 2
186 revision: 0
186 revision: 0
187
187
188 Hidden changeset are visible when requested:
188 Hidden changeset are visible when requested:
189
189
190 $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision:
190 $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision:
191 revision: 3
191 revision: 3
192 revision: 2
192 revision: 2
193 revision: 1
193 revision: 1
194 revision: 0
194 revision: 0
195
195
196 Same check on a server that do not allow hidden access:
196 Same check on a server that do not allow hidden access:
197 ```````````````````````````````````````````````````````
197 ```````````````````````````````````````````````````````
198
198
199 $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log
199 $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log
200 $ cat hg2.pid >> $DAEMON_PIDS
200 $ cat hg2.pid >> $DAEMON_PIDS
201
201
202 Hidden changeset are hidden by default:
202 Hidden changeset are hidden by default:
203
203
204 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision:
204 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision:
205 revision: 2
205 revision: 2
206 revision: 0
206 revision: 0
207
207
208 Hidden changeset are still hidden despite being the hidden access request:
208 Hidden changeset are still hidden despite being the hidden access request:
209
209
210 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision:
210 $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision:
211 revision: 2
211 revision: 2
212 revision: 0
212 revision: 0
213
213
214 Test --remote-hidden for http peer
215 ----------------------------------
216
217 $ hg clone --pull http://localhost:$HGPORT client-http
218 requesting all changes
219 adding changesets
220 adding manifests
221 adding file changes
222 added 2 changesets with 2 changes to 1 files
223 2 new obsolescence markers
224 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
225 updating to branch default
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 $ hg -R client-http log -G --hidden -v
228 @ 1:c33affeb3f6b c_Amend_New [draft]
229 |
230 o 0:5f354f46e585 c_Public [public]
231
232
233 pulling an hidden changeset should fail:
234
235 $ hg -R client-http pull -r be215fbb8c50
236 pulling from http://localhost:$HGPORT/
237 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
238 [255]
239
240 pulling an hidden changeset with --remote-hidden should succeed:
241
242 $ hg -R client-http pull --remote-hidden -r be215fbb8c50
243 pulling from http://localhost:$HGPORT/
244 searching for changes
245 adding changesets
246 adding manifests
247 adding file changes
248 added 1 changesets with 1 changes to 1 files (+1 heads)
249 (1 other changesets obsolete on arrival)
250 (run 'hg heads' to see heads)
251 $ hg -R client-http log -G --hidden -v
252 x 2:be215fbb8c50 c_Amend_Old [draft]
253 |
254 | @ 1:c33affeb3f6b c_Amend_New [draft]
255 |/
256 o 0:5f354f46e585 c_Public [public]
257
258
259 Pulling a secret changeset is still forbidden:
260
261 secret visible:
262
263 $ hg -R client-http pull --remote-hidden -r 8d28cbe335f3
264 pulling from http://localhost:$HGPORT/
265 abort: filtered revision '8d28cbe335f3' (not in 'served.hidden' subset)
266 [255]
267
268 secret hidden:
269
270 $ hg -R client-http pull --remote-hidden -r 1c6afd79eb66
271 pulling from http://localhost:$HGPORT/
272 abort: filtered revision '1c6afd79eb66' (not in 'served.hidden' subset)
273 [255]
274
275 Same check on a server that do not allow hidden access:
276 ```````````````````````````````````````````````````````
277
278 $ hg clone --pull http://localhost:$HGPORT1 client-http2
279 requesting all changes
280 adding changesets
281 adding manifests
282 adding file changes
283 added 2 changesets with 2 changes to 1 files
284 2 new obsolescence markers
285 new changesets 5f354f46e585:c33affeb3f6b (1 drafts)
286 updating to branch default
287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 $ hg -R client-http2 log -G --hidden -v
289 @ 1:c33affeb3f6b c_Amend_New [draft]
290 |
291 o 0:5f354f46e585 c_Public [public]
292
293
294 pulling an hidden changeset should fail:
295
296 $ hg -R client-http2 pull -r be215fbb8c50
297 pulling from http://localhost:$HGPORT1/
298 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
299 [255]
300
301 pulling an hidden changeset with --remote-hidden should fail too:
302
303 $ hg -R client-http2 pull --remote-hidden -r be215fbb8c50
304 pulling from http://localhost:$HGPORT1/
305 abort: filtered revision 'be215fbb8c50' (not in 'served' subset)
306 [255]
307
214 =============
308 =============
215 Final cleanup
309 Final cleanup
216 =============
310 =============
217
311
218 $ killdaemons.py
312 $ killdaemons.py
General Comments 0
You need to be logged in to leave comments. Login now