##// END OF EJS Templates
hgweb: transition permissions hooks to modern request type (API)...
Gregory Szorc -
r36893:02bea04b default
parent child Browse files
Show More
@@ -1,253 +1,252
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import base64
11 import base64
12 import errno
12 import errno
13 import mimetypes
13 import mimetypes
14 import os
14 import os
15 import stat
15 import stat
16
16
17 from .. import (
17 from .. import (
18 encoding,
18 encoding,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 )
21 )
22
22
23 httpserver = util.httpserver
23 httpserver = util.httpserver
24
24
25 HTTP_OK = 200
25 HTTP_OK = 200
26 HTTP_NOT_MODIFIED = 304
26 HTTP_NOT_MODIFIED = 304
27 HTTP_BAD_REQUEST = 400
27 HTTP_BAD_REQUEST = 400
28 HTTP_UNAUTHORIZED = 401
28 HTTP_UNAUTHORIZED = 401
29 HTTP_FORBIDDEN = 403
29 HTTP_FORBIDDEN = 403
30 HTTP_NOT_FOUND = 404
30 HTTP_NOT_FOUND = 404
31 HTTP_METHOD_NOT_ALLOWED = 405
31 HTTP_METHOD_NOT_ALLOWED = 405
32 HTTP_SERVER_ERROR = 500
32 HTTP_SERVER_ERROR = 500
33
33
34
34
35 def ismember(ui, username, userlist):
35 def ismember(ui, username, userlist):
36 """Check if username is a member of userlist.
36 """Check if username is a member of userlist.
37
37
38 If userlist has a single '*' member, all users are considered members.
38 If userlist has a single '*' member, all users are considered members.
39 Can be overridden by extensions to provide more complex authorization
39 Can be overridden by extensions to provide more complex authorization
40 schemes.
40 schemes.
41 """
41 """
42 return userlist == ['*'] or username in userlist
42 return userlist == ['*'] or username in userlist
43
43
44 def checkauthz(hgweb, req, op):
44 def checkauthz(hgweb, req, op):
45 '''Check permission for operation based on request data (including
45 '''Check permission for operation based on request data (including
46 authentication info). Return if op allowed, else raise an ErrorResponse
46 authentication info). Return if op allowed, else raise an ErrorResponse
47 exception.'''
47 exception.'''
48
48
49 user = req.env.get(r'REMOTE_USER')
49 user = req.remoteuser
50
50
51 deny_read = hgweb.configlist('web', 'deny_read')
51 deny_read = hgweb.configlist('web', 'deny_read')
52 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
52 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
53 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
53 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
54
54
55 allow_read = hgweb.configlist('web', 'allow_read')
55 allow_read = hgweb.configlist('web', 'allow_read')
56 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
56 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
57 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
57 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
58
58
59 if op == 'pull' and not hgweb.allowpull:
59 if op == 'pull' and not hgweb.allowpull:
60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
61 elif op == 'pull' or op is None: # op is None for interface requests
61 elif op == 'pull' or op is None: # op is None for interface requests
62 return
62 return
63
63
64 # enforce that you can only push using POST requests
64 # enforce that you can only push using POST requests
65 if req.env[r'REQUEST_METHOD'] != r'POST':
65 if req.method != 'POST':
66 msg = 'push requires POST request'
66 msg = 'push requires POST request'
67 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
67 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
68
68
69 # require ssl by default for pushing, auth info cannot be sniffed
69 # require ssl by default for pushing, auth info cannot be sniffed
70 # and replayed
70 # and replayed
71 scheme = req.env.get('wsgi.url_scheme')
71 if hgweb.configbool('web', 'push_ssl') and req.urlscheme != 'https':
72 if hgweb.configbool('web', 'push_ssl') and scheme != 'https':
73 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
72 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
74
73
75 deny = hgweb.configlist('web', 'deny_push')
74 deny = hgweb.configlist('web', 'deny_push')
76 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
75 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
77 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
76 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
78
77
79 allow = hgweb.configlist('web', 'allow-push')
78 allow = hgweb.configlist('web', 'allow-push')
80 if not (allow and ismember(hgweb.repo.ui, user, allow)):
79 if not (allow and ismember(hgweb.repo.ui, user, allow)):
81 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
80 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
82
81
83 # Hooks for hgweb permission checks; extensions can add hooks here.
82 # Hooks for hgweb permission checks; extensions can add hooks here.
84 # Each hook is invoked like this: hook(hgweb, request, operation),
83 # Each hook is invoked like this: hook(hgweb, request, operation),
85 # where operation is either read, pull or push. Hooks should either
84 # where operation is either read, pull or push. Hooks should either
86 # raise an ErrorResponse exception, or just return.
85 # raise an ErrorResponse exception, or just return.
87 #
86 #
88 # It is possible to do both authentication and authorization through
87 # It is possible to do both authentication and authorization through
89 # this.
88 # this.
90 permhooks = [checkauthz]
89 permhooks = [checkauthz]
91
90
92
91
93 class ErrorResponse(Exception):
92 class ErrorResponse(Exception):
94 def __init__(self, code, message=None, headers=None):
93 def __init__(self, code, message=None, headers=None):
95 if message is None:
94 if message is None:
96 message = _statusmessage(code)
95 message = _statusmessage(code)
97 Exception.__init__(self, pycompat.sysstr(message))
96 Exception.__init__(self, pycompat.sysstr(message))
98 self.code = code
97 self.code = code
99 if headers is None:
98 if headers is None:
100 headers = []
99 headers = []
101 self.headers = headers
100 self.headers = headers
102
101
103 class continuereader(object):
102 class continuereader(object):
104 """File object wrapper to handle HTTP 100-continue.
103 """File object wrapper to handle HTTP 100-continue.
105
104
106 This is used by servers so they automatically handle Expect: 100-continue
105 This is used by servers so they automatically handle Expect: 100-continue
107 request headers. On first read of the request body, the 100 Continue
106 request headers. On first read of the request body, the 100 Continue
108 response is sent. This should trigger the client into actually sending
107 response is sent. This should trigger the client into actually sending
109 the request body.
108 the request body.
110 """
109 """
111 def __init__(self, f, write):
110 def __init__(self, f, write):
112 self.f = f
111 self.f = f
113 self._write = write
112 self._write = write
114 self.continued = False
113 self.continued = False
115
114
116 def read(self, amt=-1):
115 def read(self, amt=-1):
117 if not self.continued:
116 if not self.continued:
118 self.continued = True
117 self.continued = True
119 self._write('HTTP/1.1 100 Continue\r\n\r\n')
118 self._write('HTTP/1.1 100 Continue\r\n\r\n')
120 return self.f.read(amt)
119 return self.f.read(amt)
121
120
122 def __getattr__(self, attr):
121 def __getattr__(self, attr):
123 if attr in ('close', 'readline', 'readlines', '__iter__'):
122 if attr in ('close', 'readline', 'readlines', '__iter__'):
124 return getattr(self.f, attr)
123 return getattr(self.f, attr)
125 raise AttributeError
124 raise AttributeError
126
125
127 def _statusmessage(code):
126 def _statusmessage(code):
128 responses = httpserver.basehttprequesthandler.responses
127 responses = httpserver.basehttprequesthandler.responses
129 return responses.get(code, ('Error', 'Unknown error'))[0]
128 return responses.get(code, ('Error', 'Unknown error'))[0]
130
129
131 def statusmessage(code, message=None):
130 def statusmessage(code, message=None):
132 return '%d %s' % (code, message or _statusmessage(code))
131 return '%d %s' % (code, message or _statusmessage(code))
133
132
134 def get_stat(spath, fn):
133 def get_stat(spath, fn):
135 """stat fn if it exists, spath otherwise"""
134 """stat fn if it exists, spath otherwise"""
136 cl_path = os.path.join(spath, fn)
135 cl_path = os.path.join(spath, fn)
137 if os.path.exists(cl_path):
136 if os.path.exists(cl_path):
138 return os.stat(cl_path)
137 return os.stat(cl_path)
139 else:
138 else:
140 return os.stat(spath)
139 return os.stat(spath)
141
140
142 def get_mtime(spath):
141 def get_mtime(spath):
143 return get_stat(spath, "00changelog.i")[stat.ST_MTIME]
142 return get_stat(spath, "00changelog.i")[stat.ST_MTIME]
144
143
145 def ispathsafe(path):
144 def ispathsafe(path):
146 """Determine if a path is safe to use for filesystem access."""
145 """Determine if a path is safe to use for filesystem access."""
147 parts = path.split('/')
146 parts = path.split('/')
148 for part in parts:
147 for part in parts:
149 if (part in ('', pycompat.oscurdir, pycompat.ospardir) or
148 if (part in ('', pycompat.oscurdir, pycompat.ospardir) or
150 pycompat.ossep in part or
149 pycompat.ossep in part or
151 pycompat.osaltsep is not None and pycompat.osaltsep in part):
150 pycompat.osaltsep is not None and pycompat.osaltsep in part):
152 return False
151 return False
153
152
154 return True
153 return True
155
154
156 def staticfile(directory, fname, res):
155 def staticfile(directory, fname, res):
157 """return a file inside directory with guessed Content-Type header
156 """return a file inside directory with guessed Content-Type header
158
157
159 fname always uses '/' as directory separator and isn't allowed to
158 fname always uses '/' as directory separator and isn't allowed to
160 contain unusual path components.
159 contain unusual path components.
161 Content-Type is guessed using the mimetypes module.
160 Content-Type is guessed using the mimetypes module.
162 Return an empty string if fname is illegal or file not found.
161 Return an empty string if fname is illegal or file not found.
163
162
164 """
163 """
165 if not ispathsafe(fname):
164 if not ispathsafe(fname):
166 return
165 return
167
166
168 fpath = os.path.join(*fname.split('/'))
167 fpath = os.path.join(*fname.split('/'))
169 if isinstance(directory, str):
168 if isinstance(directory, str):
170 directory = [directory]
169 directory = [directory]
171 for d in directory:
170 for d in directory:
172 path = os.path.join(d, fpath)
171 path = os.path.join(d, fpath)
173 if os.path.exists(path):
172 if os.path.exists(path):
174 break
173 break
175 try:
174 try:
176 os.stat(path)
175 os.stat(path)
177 ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
176 ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
178 with open(path, 'rb') as fh:
177 with open(path, 'rb') as fh:
179 data = fh.read()
178 data = fh.read()
180
179
181 res.headers['Content-Type'] = ct
180 res.headers['Content-Type'] = ct
182 res.setbodybytes(data)
181 res.setbodybytes(data)
183 return res
182 return res
184 except TypeError:
183 except TypeError:
185 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
184 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
186 except OSError as err:
185 except OSError as err:
187 if err.errno == errno.ENOENT:
186 if err.errno == errno.ENOENT:
188 raise ErrorResponse(HTTP_NOT_FOUND)
187 raise ErrorResponse(HTTP_NOT_FOUND)
189 else:
188 else:
190 raise ErrorResponse(HTTP_SERVER_ERROR,
189 raise ErrorResponse(HTTP_SERVER_ERROR,
191 encoding.strtolocal(err.strerror))
190 encoding.strtolocal(err.strerror))
192
191
193 def paritygen(stripecount, offset=0):
192 def paritygen(stripecount, offset=0):
194 """count parity of horizontal stripes for easier reading"""
193 """count parity of horizontal stripes for easier reading"""
195 if stripecount and offset:
194 if stripecount and offset:
196 # account for offset, e.g. due to building the list in reverse
195 # account for offset, e.g. due to building the list in reverse
197 count = (stripecount + offset) % stripecount
196 count = (stripecount + offset) % stripecount
198 parity = (stripecount + offset) // stripecount & 1
197 parity = (stripecount + offset) // stripecount & 1
199 else:
198 else:
200 count = 0
199 count = 0
201 parity = 0
200 parity = 0
202 while True:
201 while True:
203 yield parity
202 yield parity
204 count += 1
203 count += 1
205 if stripecount and count >= stripecount:
204 if stripecount and count >= stripecount:
206 parity = 1 - parity
205 parity = 1 - parity
207 count = 0
206 count = 0
208
207
209 def get_contact(config):
208 def get_contact(config):
210 """Return repo contact information or empty string.
209 """Return repo contact information or empty string.
211
210
212 web.contact is the primary source, but if that is not set, try
211 web.contact is the primary source, but if that is not set, try
213 ui.username or $EMAIL as a fallback to display something useful.
212 ui.username or $EMAIL as a fallback to display something useful.
214 """
213 """
215 return (config("web", "contact") or
214 return (config("web", "contact") or
216 config("ui", "username") or
215 config("ui", "username") or
217 encoding.environ.get("EMAIL") or "")
216 encoding.environ.get("EMAIL") or "")
218
217
219 def cspvalues(ui):
218 def cspvalues(ui):
220 """Obtain the Content-Security-Policy header and nonce value.
219 """Obtain the Content-Security-Policy header and nonce value.
221
220
222 Returns a 2-tuple of the CSP header value and the nonce value.
221 Returns a 2-tuple of the CSP header value and the nonce value.
223
222
224 First value is ``None`` if CSP isn't enabled. Second value is ``None``
223 First value is ``None`` if CSP isn't enabled. Second value is ``None``
225 if CSP isn't enabled or if the CSP header doesn't need a nonce.
224 if CSP isn't enabled or if the CSP header doesn't need a nonce.
226 """
225 """
227 # Without demandimport, "import uuid" could have an immediate side-effect
226 # Without demandimport, "import uuid" could have an immediate side-effect
228 # running "ldconfig" on Linux trying to find libuuid.
227 # running "ldconfig" on Linux trying to find libuuid.
229 # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
228 # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
230 # may pollute the terminal with:
229 # may pollute the terminal with:
231 #
230 #
232 # shell-init: error retrieving current directory: getcwd: cannot access
231 # shell-init: error retrieving current directory: getcwd: cannot access
233 # parent directories: No such file or directory
232 # parent directories: No such file or directory
234 #
233 #
235 # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
234 # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
236 # shell (hg changeset a09ae70f3489).
235 # shell (hg changeset a09ae70f3489).
237 #
236 #
238 # Moved "import uuid" from here so it's executed after we know we have
237 # Moved "import uuid" from here so it's executed after we know we have
239 # a sane cwd (i.e. after dispatch.py cwd check).
238 # a sane cwd (i.e. after dispatch.py cwd check).
240 #
239 #
241 # We can move it back once we no longer need Python <= 2.7.12 support.
240 # We can move it back once we no longer need Python <= 2.7.12 support.
242 import uuid
241 import uuid
243
242
244 # Don't allow untrusted CSP setting since it be disable protections
243 # Don't allow untrusted CSP setting since it be disable protections
245 # from a trusted/global source.
244 # from a trusted/global source.
246 csp = ui.config('web', 'csp', untrusted=False)
245 csp = ui.config('web', 'csp', untrusted=False)
247 nonce = None
246 nonce = None
248
247
249 if csp and '%nonce%' in csp:
248 if csp and '%nonce%' in csp:
250 nonce = base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip('=')
249 nonce = base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip('=')
251 csp = csp.replace('%nonce%', nonce)
250 csp = csp.replace('%nonce%', nonce)
252
251
253 return csp, nonce
252 return csp, nonce
@@ -1,456 +1,456
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import contextlib
11 import contextlib
12 import os
12 import os
13
13
14 from .common import (
14 from .common import (
15 ErrorResponse,
15 ErrorResponse,
16 HTTP_BAD_REQUEST,
16 HTTP_BAD_REQUEST,
17 HTTP_NOT_FOUND,
17 HTTP_NOT_FOUND,
18 HTTP_NOT_MODIFIED,
18 HTTP_NOT_MODIFIED,
19 HTTP_OK,
19 HTTP_OK,
20 HTTP_SERVER_ERROR,
20 HTTP_SERVER_ERROR,
21 cspvalues,
21 cspvalues,
22 permhooks,
22 permhooks,
23 )
23 )
24
24
25 from .. import (
25 from .. import (
26 encoding,
26 encoding,
27 error,
27 error,
28 formatter,
28 formatter,
29 hg,
29 hg,
30 hook,
30 hook,
31 profiling,
31 profiling,
32 pycompat,
32 pycompat,
33 repoview,
33 repoview,
34 templatefilters,
34 templatefilters,
35 templater,
35 templater,
36 ui as uimod,
36 ui as uimod,
37 util,
37 util,
38 wireprotoserver,
38 wireprotoserver,
39 )
39 )
40
40
41 from . import (
41 from . import (
42 request as requestmod,
42 request as requestmod,
43 webcommands,
43 webcommands,
44 webutil,
44 webutil,
45 wsgicgi,
45 wsgicgi,
46 )
46 )
47
47
48 archivespecs = util.sortdict((
48 archivespecs = util.sortdict((
49 ('zip', ('application/zip', 'zip', '.zip', None)),
49 ('zip', ('application/zip', 'zip', '.zip', None)),
50 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
50 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
51 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
51 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
52 ))
52 ))
53
53
54 def getstyle(req, configfn, templatepath):
54 def getstyle(req, configfn, templatepath):
55 styles = (
55 styles = (
56 req.qsparams.get('style', None),
56 req.qsparams.get('style', None),
57 configfn('web', 'style'),
57 configfn('web', 'style'),
58 'paper',
58 'paper',
59 )
59 )
60 return styles, templater.stylemap(styles, templatepath)
60 return styles, templater.stylemap(styles, templatepath)
61
61
62 def makebreadcrumb(url, prefix=''):
62 def makebreadcrumb(url, prefix=''):
63 '''Return a 'URL breadcrumb' list
63 '''Return a 'URL breadcrumb' list
64
64
65 A 'URL breadcrumb' is a list of URL-name pairs,
65 A 'URL breadcrumb' is a list of URL-name pairs,
66 corresponding to each of the path items on a URL.
66 corresponding to each of the path items on a URL.
67 This can be used to create path navigation entries.
67 This can be used to create path navigation entries.
68 '''
68 '''
69 if url.endswith('/'):
69 if url.endswith('/'):
70 url = url[:-1]
70 url = url[:-1]
71 if prefix:
71 if prefix:
72 url = '/' + prefix + url
72 url = '/' + prefix + url
73 relpath = url
73 relpath = url
74 if relpath.startswith('/'):
74 if relpath.startswith('/'):
75 relpath = relpath[1:]
75 relpath = relpath[1:]
76
76
77 breadcrumb = []
77 breadcrumb = []
78 urlel = url
78 urlel = url
79 pathitems = [''] + relpath.split('/')
79 pathitems = [''] + relpath.split('/')
80 for pathel in reversed(pathitems):
80 for pathel in reversed(pathitems):
81 if not pathel or not urlel:
81 if not pathel or not urlel:
82 break
82 break
83 breadcrumb.append({'url': urlel, 'name': pathel})
83 breadcrumb.append({'url': urlel, 'name': pathel})
84 urlel = os.path.dirname(urlel)
84 urlel = os.path.dirname(urlel)
85 return reversed(breadcrumb)
85 return reversed(breadcrumb)
86
86
87 class requestcontext(object):
87 class requestcontext(object):
88 """Holds state/context for an individual request.
88 """Holds state/context for an individual request.
89
89
90 Servers can be multi-threaded. Holding state on the WSGI application
90 Servers can be multi-threaded. Holding state on the WSGI application
91 is prone to race conditions. Instances of this class exist to hold
91 is prone to race conditions. Instances of this class exist to hold
92 mutable and race-free state for requests.
92 mutable and race-free state for requests.
93 """
93 """
94 def __init__(self, app, repo, req, res):
94 def __init__(self, app, repo, req, res):
95 self.repo = repo
95 self.repo = repo
96 self.reponame = app.reponame
96 self.reponame = app.reponame
97 self.req = req
97 self.req = req
98 self.res = res
98 self.res = res
99
99
100 self.archivespecs = archivespecs
100 self.archivespecs = archivespecs
101
101
102 self.maxchanges = self.configint('web', 'maxchanges')
102 self.maxchanges = self.configint('web', 'maxchanges')
103 self.stripecount = self.configint('web', 'stripes')
103 self.stripecount = self.configint('web', 'stripes')
104 self.maxshortchanges = self.configint('web', 'maxshortchanges')
104 self.maxshortchanges = self.configint('web', 'maxshortchanges')
105 self.maxfiles = self.configint('web', 'maxfiles')
105 self.maxfiles = self.configint('web', 'maxfiles')
106 self.allowpull = self.configbool('web', 'allow-pull')
106 self.allowpull = self.configbool('web', 'allow-pull')
107
107
108 # we use untrusted=False to prevent a repo owner from using
108 # we use untrusted=False to prevent a repo owner from using
109 # web.templates in .hg/hgrc to get access to any file readable
109 # web.templates in .hg/hgrc to get access to any file readable
110 # by the user running the CGI script
110 # by the user running the CGI script
111 self.templatepath = self.config('web', 'templates', untrusted=False)
111 self.templatepath = self.config('web', 'templates', untrusted=False)
112
112
113 # This object is more expensive to build than simple config values.
113 # This object is more expensive to build than simple config values.
114 # It is shared across requests. The app will replace the object
114 # It is shared across requests. The app will replace the object
115 # if it is updated. Since this is a reference and nothing should
115 # if it is updated. Since this is a reference and nothing should
116 # modify the underlying object, it should be constant for the lifetime
116 # modify the underlying object, it should be constant for the lifetime
117 # of the request.
117 # of the request.
118 self.websubtable = app.websubtable
118 self.websubtable = app.websubtable
119
119
120 self.csp, self.nonce = cspvalues(self.repo.ui)
120 self.csp, self.nonce = cspvalues(self.repo.ui)
121
121
122 # Trust the settings from the .hg/hgrc files by default.
122 # Trust the settings from the .hg/hgrc files by default.
123 def config(self, section, name, default=uimod._unset, untrusted=True):
123 def config(self, section, name, default=uimod._unset, untrusted=True):
124 return self.repo.ui.config(section, name, default,
124 return self.repo.ui.config(section, name, default,
125 untrusted=untrusted)
125 untrusted=untrusted)
126
126
127 def configbool(self, section, name, default=uimod._unset, untrusted=True):
127 def configbool(self, section, name, default=uimod._unset, untrusted=True):
128 return self.repo.ui.configbool(section, name, default,
128 return self.repo.ui.configbool(section, name, default,
129 untrusted=untrusted)
129 untrusted=untrusted)
130
130
131 def configint(self, section, name, default=uimod._unset, untrusted=True):
131 def configint(self, section, name, default=uimod._unset, untrusted=True):
132 return self.repo.ui.configint(section, name, default,
132 return self.repo.ui.configint(section, name, default,
133 untrusted=untrusted)
133 untrusted=untrusted)
134
134
135 def configlist(self, section, name, default=uimod._unset, untrusted=True):
135 def configlist(self, section, name, default=uimod._unset, untrusted=True):
136 return self.repo.ui.configlist(section, name, default,
136 return self.repo.ui.configlist(section, name, default,
137 untrusted=untrusted)
137 untrusted=untrusted)
138
138
139 def archivelist(self, nodeid):
139 def archivelist(self, nodeid):
140 allowed = self.configlist('web', 'allow_archive')
140 allowed = self.configlist('web', 'allow_archive')
141 for typ, spec in self.archivespecs.iteritems():
141 for typ, spec in self.archivespecs.iteritems():
142 if typ in allowed or self.configbool('web', 'allow%s' % typ):
142 if typ in allowed or self.configbool('web', 'allow%s' % typ):
143 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
143 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
144
144
145 def templater(self, req):
145 def templater(self, req):
146 # determine scheme, port and server name
146 # determine scheme, port and server name
147 # this is needed to create absolute urls
147 # this is needed to create absolute urls
148 logourl = self.config('web', 'logourl')
148 logourl = self.config('web', 'logourl')
149 logoimg = self.config('web', 'logoimg')
149 logoimg = self.config('web', 'logoimg')
150 staticurl = (self.config('web', 'staticurl')
150 staticurl = (self.config('web', 'staticurl')
151 or req.apppath + '/static/')
151 or req.apppath + '/static/')
152 if not staticurl.endswith('/'):
152 if not staticurl.endswith('/'):
153 staticurl += '/'
153 staticurl += '/'
154
154
155 # some functions for the templater
155 # some functions for the templater
156
156
157 def motd(**map):
157 def motd(**map):
158 yield self.config('web', 'motd')
158 yield self.config('web', 'motd')
159
159
160 # figure out which style to use
160 # figure out which style to use
161
161
162 vars = {}
162 vars = {}
163 styles, (style, mapfile) = getstyle(req, self.config,
163 styles, (style, mapfile) = getstyle(req, self.config,
164 self.templatepath)
164 self.templatepath)
165 if style == styles[0]:
165 if style == styles[0]:
166 vars['style'] = style
166 vars['style'] = style
167
167
168 sessionvars = webutil.sessionvars(vars, '?')
168 sessionvars = webutil.sessionvars(vars, '?')
169
169
170 if not self.reponame:
170 if not self.reponame:
171 self.reponame = (self.config('web', 'name', '')
171 self.reponame = (self.config('web', 'name', '')
172 or req.reponame
172 or req.reponame
173 or req.apppath
173 or req.apppath
174 or self.repo.root)
174 or self.repo.root)
175
175
176 def websubfilter(text):
176 def websubfilter(text):
177 return templatefilters.websub(text, self.websubtable)
177 return templatefilters.websub(text, self.websubtable)
178
178
179 # create the templater
179 # create the templater
180 # TODO: export all keywords: defaults = templatekw.keywords.copy()
180 # TODO: export all keywords: defaults = templatekw.keywords.copy()
181 defaults = {
181 defaults = {
182 'url': req.apppath + '/',
182 'url': req.apppath + '/',
183 'logourl': logourl,
183 'logourl': logourl,
184 'logoimg': logoimg,
184 'logoimg': logoimg,
185 'staticurl': staticurl,
185 'staticurl': staticurl,
186 'urlbase': req.advertisedbaseurl,
186 'urlbase': req.advertisedbaseurl,
187 'repo': self.reponame,
187 'repo': self.reponame,
188 'encoding': encoding.encoding,
188 'encoding': encoding.encoding,
189 'motd': motd,
189 'motd': motd,
190 'sessionvars': sessionvars,
190 'sessionvars': sessionvars,
191 'pathdef': makebreadcrumb(req.apppath),
191 'pathdef': makebreadcrumb(req.apppath),
192 'style': style,
192 'style': style,
193 'nonce': self.nonce,
193 'nonce': self.nonce,
194 }
194 }
195 tres = formatter.templateresources(self.repo.ui, self.repo)
195 tres = formatter.templateresources(self.repo.ui, self.repo)
196 tmpl = templater.templater.frommapfile(mapfile,
196 tmpl = templater.templater.frommapfile(mapfile,
197 filters={'websub': websubfilter},
197 filters={'websub': websubfilter},
198 defaults=defaults,
198 defaults=defaults,
199 resources=tres)
199 resources=tres)
200 return tmpl
200 return tmpl
201
201
202
202
203 class hgweb(object):
203 class hgweb(object):
204 """HTTP server for individual repositories.
204 """HTTP server for individual repositories.
205
205
206 Instances of this class serve HTTP responses for a particular
206 Instances of this class serve HTTP responses for a particular
207 repository.
207 repository.
208
208
209 Instances are typically used as WSGI applications.
209 Instances are typically used as WSGI applications.
210
210
211 Some servers are multi-threaded. On these servers, there may
211 Some servers are multi-threaded. On these servers, there may
212 be multiple active threads inside __call__.
212 be multiple active threads inside __call__.
213 """
213 """
214 def __init__(self, repo, name=None, baseui=None):
214 def __init__(self, repo, name=None, baseui=None):
215 if isinstance(repo, str):
215 if isinstance(repo, str):
216 if baseui:
216 if baseui:
217 u = baseui.copy()
217 u = baseui.copy()
218 else:
218 else:
219 u = uimod.ui.load()
219 u = uimod.ui.load()
220 r = hg.repository(u, repo)
220 r = hg.repository(u, repo)
221 else:
221 else:
222 # we trust caller to give us a private copy
222 # we trust caller to give us a private copy
223 r = repo
223 r = repo
224
224
225 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
225 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
226 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
226 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
227 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
227 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
228 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
228 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
229 # resolve file patterns relative to repo root
229 # resolve file patterns relative to repo root
230 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
230 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
231 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
231 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
232 # displaying bundling progress bar while serving feel wrong and may
232 # displaying bundling progress bar while serving feel wrong and may
233 # break some wsgi implementation.
233 # break some wsgi implementation.
234 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
234 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
235 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
235 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
236 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
236 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
237 self._lastrepo = self._repos[0]
237 self._lastrepo = self._repos[0]
238 hook.redirect(True)
238 hook.redirect(True)
239 self.reponame = name
239 self.reponame = name
240
240
241 def _webifyrepo(self, repo):
241 def _webifyrepo(self, repo):
242 repo = getwebview(repo)
242 repo = getwebview(repo)
243 self.websubtable = webutil.getwebsubs(repo)
243 self.websubtable = webutil.getwebsubs(repo)
244 return repo
244 return repo
245
245
246 @contextlib.contextmanager
246 @contextlib.contextmanager
247 def _obtainrepo(self):
247 def _obtainrepo(self):
248 """Obtain a repo unique to the caller.
248 """Obtain a repo unique to the caller.
249
249
250 Internally we maintain a stack of cachedlocalrepo instances
250 Internally we maintain a stack of cachedlocalrepo instances
251 to be handed out. If one is available, we pop it and return it,
251 to be handed out. If one is available, we pop it and return it,
252 ensuring it is up to date in the process. If one is not available,
252 ensuring it is up to date in the process. If one is not available,
253 we clone the most recently used repo instance and return it.
253 we clone the most recently used repo instance and return it.
254
254
255 It is currently possible for the stack to grow without bounds
255 It is currently possible for the stack to grow without bounds
256 if the server allows infinite threads. However, servers should
256 if the server allows infinite threads. However, servers should
257 have a thread limit, thus establishing our limit.
257 have a thread limit, thus establishing our limit.
258 """
258 """
259 if self._repos:
259 if self._repos:
260 cached = self._repos.pop()
260 cached = self._repos.pop()
261 r, created = cached.fetch()
261 r, created = cached.fetch()
262 else:
262 else:
263 cached = self._lastrepo.copy()
263 cached = self._lastrepo.copy()
264 r, created = cached.fetch()
264 r, created = cached.fetch()
265 if created:
265 if created:
266 r = self._webifyrepo(r)
266 r = self._webifyrepo(r)
267
267
268 self._lastrepo = cached
268 self._lastrepo = cached
269 self.mtime = cached.mtime
269 self.mtime = cached.mtime
270 try:
270 try:
271 yield r
271 yield r
272 finally:
272 finally:
273 self._repos.append(cached)
273 self._repos.append(cached)
274
274
275 def run(self):
275 def run(self):
276 """Start a server from CGI environment.
276 """Start a server from CGI environment.
277
277
278 Modern servers should be using WSGI and should avoid this
278 Modern servers should be using WSGI and should avoid this
279 method, if possible.
279 method, if possible.
280 """
280 """
281 if not encoding.environ.get('GATEWAY_INTERFACE',
281 if not encoding.environ.get('GATEWAY_INTERFACE',
282 '').startswith("CGI/1."):
282 '').startswith("CGI/1."):
283 raise RuntimeError("This function is only intended to be "
283 raise RuntimeError("This function is only intended to be "
284 "called while running as a CGI script.")
284 "called while running as a CGI script.")
285 wsgicgi.launch(self)
285 wsgicgi.launch(self)
286
286
287 def __call__(self, env, respond):
287 def __call__(self, env, respond):
288 """Run the WSGI application.
288 """Run the WSGI application.
289
289
290 This may be called by multiple threads.
290 This may be called by multiple threads.
291 """
291 """
292 req = requestmod.wsgirequest(env, respond)
292 req = requestmod.wsgirequest(env, respond)
293 return self.run_wsgi(req)
293 return self.run_wsgi(req)
294
294
295 def run_wsgi(self, wsgireq):
295 def run_wsgi(self, wsgireq):
296 """Internal method to run the WSGI application.
296 """Internal method to run the WSGI application.
297
297
298 This is typically only called by Mercurial. External consumers
298 This is typically only called by Mercurial. External consumers
299 should be using instances of this class as the WSGI application.
299 should be using instances of this class as the WSGI application.
300 """
300 """
301 with self._obtainrepo() as repo:
301 with self._obtainrepo() as repo:
302 profile = repo.ui.configbool('profiling', 'enabled')
302 profile = repo.ui.configbool('profiling', 'enabled')
303 with profiling.profile(repo.ui, enabled=profile):
303 with profiling.profile(repo.ui, enabled=profile):
304 for r in self._runwsgi(wsgireq, repo):
304 for r in self._runwsgi(wsgireq, repo):
305 yield r
305 yield r
306
306
307 def _runwsgi(self, wsgireq, repo):
307 def _runwsgi(self, wsgireq, repo):
308 req = wsgireq.req
308 req = wsgireq.req
309 res = wsgireq.res
309 res = wsgireq.res
310 rctx = requestcontext(self, repo, req, res)
310 rctx = requestcontext(self, repo, req, res)
311
311
312 # This state is global across all threads.
312 # This state is global across all threads.
313 encoding.encoding = rctx.config('web', 'encoding')
313 encoding.encoding = rctx.config('web', 'encoding')
314 rctx.repo.ui.environ = wsgireq.env
314 rctx.repo.ui.environ = wsgireq.env
315
315
316 if rctx.csp:
316 if rctx.csp:
317 # hgwebdir may have added CSP header. Since we generate our own,
317 # hgwebdir may have added CSP header. Since we generate our own,
318 # replace it.
318 # replace it.
319 wsgireq.headers = [h for h in wsgireq.headers
319 wsgireq.headers = [h for h in wsgireq.headers
320 if h[0] != 'Content-Security-Policy']
320 if h[0] != 'Content-Security-Policy']
321 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
321 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
322 res.headers['Content-Security-Policy'] = rctx.csp
322 res.headers['Content-Security-Policy'] = rctx.csp
323
323
324 handled = wireprotoserver.handlewsgirequest(
324 handled = wireprotoserver.handlewsgirequest(
325 rctx, wsgireq, req, res, self.check_perm)
325 rctx, req, res, self.check_perm)
326 if handled:
326 if handled:
327 return res.sendresponse()
327 return res.sendresponse()
328
328
329 if req.havepathinfo:
329 if req.havepathinfo:
330 query = req.dispatchpath
330 query = req.dispatchpath
331 else:
331 else:
332 query = req.querystring.partition('&')[0].partition(';')[0]
332 query = req.querystring.partition('&')[0].partition(';')[0]
333
333
334 # translate user-visible url structure to internal structure
334 # translate user-visible url structure to internal structure
335
335
336 args = query.split('/', 2)
336 args = query.split('/', 2)
337 if 'cmd' not in req.qsparams and args and args[0]:
337 if 'cmd' not in req.qsparams and args and args[0]:
338 cmd = args.pop(0)
338 cmd = args.pop(0)
339 style = cmd.rfind('-')
339 style = cmd.rfind('-')
340 if style != -1:
340 if style != -1:
341 req.qsparams['style'] = cmd[:style]
341 req.qsparams['style'] = cmd[:style]
342 cmd = cmd[style + 1:]
342 cmd = cmd[style + 1:]
343
343
344 # avoid accepting e.g. style parameter as command
344 # avoid accepting e.g. style parameter as command
345 if util.safehasattr(webcommands, cmd):
345 if util.safehasattr(webcommands, cmd):
346 req.qsparams['cmd'] = cmd
346 req.qsparams['cmd'] = cmd
347
347
348 if cmd == 'static':
348 if cmd == 'static':
349 req.qsparams['file'] = '/'.join(args)
349 req.qsparams['file'] = '/'.join(args)
350 else:
350 else:
351 if args and args[0]:
351 if args and args[0]:
352 node = args.pop(0).replace('%2F', '/')
352 node = args.pop(0).replace('%2F', '/')
353 req.qsparams['node'] = node
353 req.qsparams['node'] = node
354 if args:
354 if args:
355 if 'file' in req.qsparams:
355 if 'file' in req.qsparams:
356 del req.qsparams['file']
356 del req.qsparams['file']
357 for a in args:
357 for a in args:
358 req.qsparams.add('file', a)
358 req.qsparams.add('file', a)
359
359
360 ua = req.headers.get('User-Agent', '')
360 ua = req.headers.get('User-Agent', '')
361 if cmd == 'rev' and 'mercurial' in ua:
361 if cmd == 'rev' and 'mercurial' in ua:
362 req.qsparams['style'] = 'raw'
362 req.qsparams['style'] = 'raw'
363
363
364 if cmd == 'archive':
364 if cmd == 'archive':
365 fn = req.qsparams['node']
365 fn = req.qsparams['node']
366 for type_, spec in rctx.archivespecs.iteritems():
366 for type_, spec in rctx.archivespecs.iteritems():
367 ext = spec[2]
367 ext = spec[2]
368 if fn.endswith(ext):
368 if fn.endswith(ext):
369 req.qsparams['node'] = fn[:-len(ext)]
369 req.qsparams['node'] = fn[:-len(ext)]
370 req.qsparams['type'] = type_
370 req.qsparams['type'] = type_
371 else:
371 else:
372 cmd = req.qsparams.get('cmd', '')
372 cmd = req.qsparams.get('cmd', '')
373
373
374 # process the web interface request
374 # process the web interface request
375
375
376 try:
376 try:
377 tmpl = rctx.templater(req)
377 tmpl = rctx.templater(req)
378 ctype = tmpl('mimetype', encoding=encoding.encoding)
378 ctype = tmpl('mimetype', encoding=encoding.encoding)
379 ctype = templater.stringify(ctype)
379 ctype = templater.stringify(ctype)
380
380
381 # check read permissions non-static content
381 # check read permissions non-static content
382 if cmd != 'static':
382 if cmd != 'static':
383 self.check_perm(rctx, wsgireq, None)
383 self.check_perm(rctx, req, None)
384
384
385 if cmd == '':
385 if cmd == '':
386 req.qsparams['cmd'] = tmpl.cache['default']
386 req.qsparams['cmd'] = tmpl.cache['default']
387 cmd = req.qsparams['cmd']
387 cmd = req.qsparams['cmd']
388
388
389 # Don't enable caching if using a CSP nonce because then it wouldn't
389 # Don't enable caching if using a CSP nonce because then it wouldn't
390 # be a nonce.
390 # be a nonce.
391 if rctx.configbool('web', 'cache') and not rctx.nonce:
391 if rctx.configbool('web', 'cache') and not rctx.nonce:
392 tag = 'W/"%d"' % self.mtime
392 tag = 'W/"%d"' % self.mtime
393 if req.headers.get('If-None-Match') == tag:
393 if req.headers.get('If-None-Match') == tag:
394 raise ErrorResponse(HTTP_NOT_MODIFIED)
394 raise ErrorResponse(HTTP_NOT_MODIFIED)
395
395
396 wsgireq.headers.append((r'ETag', pycompat.sysstr(tag)))
396 wsgireq.headers.append((r'ETag', pycompat.sysstr(tag)))
397 res.headers['ETag'] = tag
397 res.headers['ETag'] = tag
398
398
399 if cmd not in webcommands.__all__:
399 if cmd not in webcommands.__all__:
400 msg = 'no such method: %s' % cmd
400 msg = 'no such method: %s' % cmd
401 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
401 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
402 else:
402 else:
403 # Set some globals appropriate for web handlers. Commands can
403 # Set some globals appropriate for web handlers. Commands can
404 # override easily enough.
404 # override easily enough.
405 res.status = '200 Script output follows'
405 res.status = '200 Script output follows'
406 res.headers['Content-Type'] = ctype
406 res.headers['Content-Type'] = ctype
407 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
407 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
408
408
409 if content is res:
409 if content is res:
410 return res.sendresponse()
410 return res.sendresponse()
411 elif content is True:
411 elif content is True:
412 return []
412 return []
413 else:
413 else:
414 wsgireq.respond(HTTP_OK, ctype)
414 wsgireq.respond(HTTP_OK, ctype)
415 return content
415 return content
416
416
417 except (error.LookupError, error.RepoLookupError) as err:
417 except (error.LookupError, error.RepoLookupError) as err:
418 wsgireq.respond(HTTP_NOT_FOUND, ctype)
418 wsgireq.respond(HTTP_NOT_FOUND, ctype)
419 msg = pycompat.bytestr(err)
419 msg = pycompat.bytestr(err)
420 if (util.safehasattr(err, 'name') and
420 if (util.safehasattr(err, 'name') and
421 not isinstance(err, error.ManifestLookupError)):
421 not isinstance(err, error.ManifestLookupError)):
422 msg = 'revision not found: %s' % err.name
422 msg = 'revision not found: %s' % err.name
423 return tmpl('error', error=msg)
423 return tmpl('error', error=msg)
424 except (error.RepoError, error.RevlogError) as inst:
424 except (error.RepoError, error.RevlogError) as inst:
425 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
425 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
426 return tmpl('error', error=pycompat.bytestr(inst))
426 return tmpl('error', error=pycompat.bytestr(inst))
427 except ErrorResponse as inst:
427 except ErrorResponse as inst:
428 wsgireq.respond(inst, ctype)
428 wsgireq.respond(inst, ctype)
429 if inst.code == HTTP_NOT_MODIFIED:
429 if inst.code == HTTP_NOT_MODIFIED:
430 # Not allowed to return a body on a 304
430 # Not allowed to return a body on a 304
431 return ['']
431 return ['']
432 return tmpl('error', error=pycompat.bytestr(inst))
432 return tmpl('error', error=pycompat.bytestr(inst))
433
433
434 def check_perm(self, rctx, req, op):
434 def check_perm(self, rctx, req, op):
435 for permhook in permhooks:
435 for permhook in permhooks:
436 permhook(rctx, req, op)
436 permhook(rctx, req, op)
437
437
438 def getwebview(repo):
438 def getwebview(repo):
439 """The 'web.view' config controls changeset filter to hgweb. Possible
439 """The 'web.view' config controls changeset filter to hgweb. Possible
440 values are ``served``, ``visible`` and ``all``. Default is ``served``.
440 values are ``served``, ``visible`` and ``all``. Default is ``served``.
441 The ``served`` filter only shows changesets that can be pulled from the
441 The ``served`` filter only shows changesets that can be pulled from the
442 hgweb instance. The``visible`` filter includes secret changesets but
442 hgweb instance. The``visible`` filter includes secret changesets but
443 still excludes "hidden" one.
443 still excludes "hidden" one.
444
444
445 See the repoview module for details.
445 See the repoview module for details.
446
446
447 The option has been around undocumented since Mercurial 2.5, but no
447 The option has been around undocumented since Mercurial 2.5, but no
448 user ever asked about it. So we better keep it undocumented for now."""
448 user ever asked about it. So we better keep it undocumented for now."""
449 # experimental config: web.view
449 # experimental config: web.view
450 viewconfig = repo.ui.config('web', 'view', untrusted=True)
450 viewconfig = repo.ui.config('web', 'view', untrusted=True)
451 if viewconfig == 'all':
451 if viewconfig == 'all':
452 return repo.unfiltered()
452 return repo.unfiltered()
453 elif viewconfig in repoview.filtertable:
453 elif viewconfig in repoview.filtertable:
454 return repo.filtered(viewconfig)
454 return repo.filtered(viewconfig)
455 else:
455 else:
456 return repo.filtered('served')
456 return repo.filtered('served')
@@ -1,654 +1,653
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import struct
10 import struct
11 import sys
11 import sys
12 import threading
12 import threading
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 hook,
18 hook,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 wireprototypes,
22 wireprototypes,
23 )
23 )
24
24
25 stringio = util.stringio
25 stringio = util.stringio
26
26
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 HTTP_OK = 200
30 HTTP_OK = 200
31
31
32 HGTYPE = 'application/mercurial-0.1'
32 HGTYPE = 'application/mercurial-0.1'
33 HGTYPE2 = 'application/mercurial-0.2'
33 HGTYPE2 = 'application/mercurial-0.2'
34 HGERRTYPE = 'application/hg-error'
34 HGERRTYPE = 'application/hg-error'
35
35
36 SSHV1 = wireprototypes.SSHV1
36 SSHV1 = wireprototypes.SSHV1
37 SSHV2 = wireprototypes.SSHV2
37 SSHV2 = wireprototypes.SSHV2
38
38
39 def decodevaluefromheaders(req, headerprefix):
39 def decodevaluefromheaders(req, headerprefix):
40 """Decode a long value from multiple HTTP request headers.
40 """Decode a long value from multiple HTTP request headers.
41
41
42 Returns the value as a bytes, not a str.
42 Returns the value as a bytes, not a str.
43 """
43 """
44 chunks = []
44 chunks = []
45 i = 1
45 i = 1
46 while True:
46 while True:
47 v = req.headers.get(b'%s-%d' % (headerprefix, i))
47 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 if v is None:
48 if v is None:
49 break
49 break
50 chunks.append(pycompat.bytesurl(v))
50 chunks.append(pycompat.bytesurl(v))
51 i += 1
51 i += 1
52
52
53 return ''.join(chunks)
53 return ''.join(chunks)
54
54
55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 def __init__(self, req, ui, checkperm):
56 def __init__(self, req, ui, checkperm):
57 self._req = req
57 self._req = req
58 self._ui = ui
58 self._ui = ui
59 self._checkperm = checkperm
59 self._checkperm = checkperm
60
60
61 @property
61 @property
62 def name(self):
62 def name(self):
63 return 'http-v1'
63 return 'http-v1'
64
64
65 def getargs(self, args):
65 def getargs(self, args):
66 knownargs = self._args()
66 knownargs = self._args()
67 data = {}
67 data = {}
68 keys = args.split()
68 keys = args.split()
69 for k in keys:
69 for k in keys:
70 if k == '*':
70 if k == '*':
71 star = {}
71 star = {}
72 for key in knownargs.keys():
72 for key in knownargs.keys():
73 if key != 'cmd' and key not in keys:
73 if key != 'cmd' and key not in keys:
74 star[key] = knownargs[key][0]
74 star[key] = knownargs[key][0]
75 data['*'] = star
75 data['*'] = star
76 else:
76 else:
77 data[k] = knownargs[k][0]
77 data[k] = knownargs[k][0]
78 return [data[k] for k in keys]
78 return [data[k] for k in keys]
79
79
80 def _args(self):
80 def _args(self):
81 args = self._req.qsparams.asdictoflists()
81 args = self._req.qsparams.asdictoflists()
82 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
82 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 if postlen:
83 if postlen:
84 args.update(urlreq.parseqs(
84 args.update(urlreq.parseqs(
85 self._req.bodyfh.read(postlen), keep_blank_values=True))
85 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 return args
86 return args
87
87
88 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
88 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
89 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 return args
90 return args
91
91
92 def forwardpayload(self, fp):
92 def forwardpayload(self, fp):
93 # Existing clients *always* send Content-Length.
93 # Existing clients *always* send Content-Length.
94 length = int(self._req.headers[b'Content-Length'])
94 length = int(self._req.headers[b'Content-Length'])
95
95
96 # If httppostargs is used, we need to read Content-Length
96 # If httppostargs is used, we need to read Content-Length
97 # minus the amount that was consumed by args.
97 # minus the amount that was consumed by args.
98 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
98 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 for s in util.filechunkiter(self._req.bodyfh, limit=length):
99 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 fp.write(s)
100 fp.write(s)
101
101
102 @contextlib.contextmanager
102 @contextlib.contextmanager
103 def mayberedirectstdio(self):
103 def mayberedirectstdio(self):
104 oldout = self._ui.fout
104 oldout = self._ui.fout
105 olderr = self._ui.ferr
105 olderr = self._ui.ferr
106
106
107 out = util.stringio()
107 out = util.stringio()
108
108
109 try:
109 try:
110 self._ui.fout = out
110 self._ui.fout = out
111 self._ui.ferr = out
111 self._ui.ferr = out
112 yield out
112 yield out
113 finally:
113 finally:
114 self._ui.fout = oldout
114 self._ui.fout = oldout
115 self._ui.ferr = olderr
115 self._ui.ferr = olderr
116
116
117 def client(self):
117 def client(self):
118 return 'remote:%s:%s:%s' % (
118 return 'remote:%s:%s:%s' % (
119 self._req.urlscheme,
119 self._req.urlscheme,
120 urlreq.quote(self._req.remotehost or ''),
120 urlreq.quote(self._req.remotehost or ''),
121 urlreq.quote(self._req.remoteuser or ''))
121 urlreq.quote(self._req.remoteuser or ''))
122
122
123 def addcapabilities(self, repo, caps):
123 def addcapabilities(self, repo, caps):
124 caps.append('httpheader=%d' %
124 caps.append('httpheader=%d' %
125 repo.ui.configint('server', 'maxhttpheaderlen'))
125 repo.ui.configint('server', 'maxhttpheaderlen'))
126 if repo.ui.configbool('experimental', 'httppostargs'):
126 if repo.ui.configbool('experimental', 'httppostargs'):
127 caps.append('httppostargs')
127 caps.append('httppostargs')
128
128
129 # FUTURE advertise 0.2rx once support is implemented
129 # FUTURE advertise 0.2rx once support is implemented
130 # FUTURE advertise minrx and mintx after consulting config option
130 # FUTURE advertise minrx and mintx after consulting config option
131 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
131 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132
132
133 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
133 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 if compengines:
134 if compengines:
135 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
135 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 for e in compengines)
136 for e in compengines)
137 caps.append('compression=%s' % comptypes)
137 caps.append('compression=%s' % comptypes)
138
138
139 return caps
139 return caps
140
140
141 def checkperm(self, perm):
141 def checkperm(self, perm):
142 return self._checkperm(perm)
142 return self._checkperm(perm)
143
143
144 # This method exists mostly so that extensions like remotefilelog can
144 # This method exists mostly so that extensions like remotefilelog can
145 # disable a kludgey legacy method only over http. As of early 2018,
145 # disable a kludgey legacy method only over http. As of early 2018,
146 # there are no other known users, so with any luck we can discard this
146 # there are no other known users, so with any luck we can discard this
147 # hook if remotefilelog becomes a first-party extension.
147 # hook if remotefilelog becomes a first-party extension.
148 def iscmd(cmd):
148 def iscmd(cmd):
149 return cmd in wireproto.commands
149 return cmd in wireproto.commands
150
150
151 def handlewsgirequest(rctx, wsgireq, req, res, checkperm):
151 def handlewsgirequest(rctx, req, res, checkperm):
152 """Possibly process a wire protocol request.
152 """Possibly process a wire protocol request.
153
153
154 If the current request is a wire protocol request, the request is
154 If the current request is a wire protocol request, the request is
155 processed by this function.
155 processed by this function.
156
156
157 ``wsgireq`` is a ``wsgirequest`` instance.
158 ``req`` is a ``parsedrequest`` instance.
157 ``req`` is a ``parsedrequest`` instance.
159 ``res`` is a ``wsgiresponse`` instance.
158 ``res`` is a ``wsgiresponse`` instance.
160
159
161 Returns a bool indicating if the request was serviced. If set, the caller
160 Returns a bool indicating if the request was serviced. If set, the caller
162 should stop processing the request, as a response has already been issued.
161 should stop processing the request, as a response has already been issued.
163 """
162 """
164 # Avoid cycle involving hg module.
163 # Avoid cycle involving hg module.
165 from .hgweb import common as hgwebcommon
164 from .hgweb import common as hgwebcommon
166
165
167 repo = rctx.repo
166 repo = rctx.repo
168
167
169 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
168 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
170 # string parameter. If it isn't present, this isn't a wire protocol
169 # string parameter. If it isn't present, this isn't a wire protocol
171 # request.
170 # request.
172 if 'cmd' not in req.qsparams:
171 if 'cmd' not in req.qsparams:
173 return False
172 return False
174
173
175 cmd = req.qsparams['cmd']
174 cmd = req.qsparams['cmd']
176
175
177 # The "cmd" request parameter is used by both the wire protocol and hgweb.
176 # The "cmd" request parameter is used by both the wire protocol and hgweb.
178 # While not all wire protocol commands are available for all transports,
177 # While not all wire protocol commands are available for all transports,
179 # if we see a "cmd" value that resembles a known wire protocol command, we
178 # if we see a "cmd" value that resembles a known wire protocol command, we
180 # route it to a protocol handler. This is better than routing possible
179 # route it to a protocol handler. This is better than routing possible
181 # wire protocol requests to hgweb because it prevents hgweb from using
180 # wire protocol requests to hgweb because it prevents hgweb from using
182 # known wire protocol commands and it is less confusing for machine
181 # known wire protocol commands and it is less confusing for machine
183 # clients.
182 # clients.
184 if not iscmd(cmd):
183 if not iscmd(cmd):
185 return False
184 return False
186
185
187 # The "cmd" query string argument is only valid on the root path of the
186 # The "cmd" query string argument is only valid on the root path of the
188 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
187 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
189 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
188 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
190 # in this case. We send an HTTP 404 for backwards compatibility reasons.
189 # in this case. We send an HTTP 404 for backwards compatibility reasons.
191 if req.dispatchpath:
190 if req.dispatchpath:
192 res.status = hgwebcommon.statusmessage(404)
191 res.status = hgwebcommon.statusmessage(404)
193 res.headers['Content-Type'] = HGTYPE
192 res.headers['Content-Type'] = HGTYPE
194 # TODO This is not a good response to issue for this request. This
193 # TODO This is not a good response to issue for this request. This
195 # is mostly for BC for now.
194 # is mostly for BC for now.
196 res.setbodybytes('0\n%s\n' % b'Not Found')
195 res.setbodybytes('0\n%s\n' % b'Not Found')
197 return True
196 return True
198
197
199 proto = httpv1protocolhandler(req, repo.ui,
198 proto = httpv1protocolhandler(req, repo.ui,
200 lambda perm: checkperm(rctx, wsgireq, perm))
199 lambda perm: checkperm(rctx, req, perm))
201
200
202 # The permissions checker should be the only thing that can raise an
201 # The permissions checker should be the only thing that can raise an
203 # ErrorResponse. It is kind of a layer violation to catch an hgweb
202 # ErrorResponse. It is kind of a layer violation to catch an hgweb
204 # exception here. So consider refactoring into a exception type that
203 # exception here. So consider refactoring into a exception type that
205 # is associated with the wire protocol.
204 # is associated with the wire protocol.
206 try:
205 try:
207 _callhttp(repo, req, res, proto, cmd)
206 _callhttp(repo, req, res, proto, cmd)
208 except hgwebcommon.ErrorResponse as e:
207 except hgwebcommon.ErrorResponse as e:
209 for k, v in e.headers:
208 for k, v in e.headers:
210 res.headers[k] = v
209 res.headers[k] = v
211 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
210 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
212 # TODO This response body assumes the failed command was
211 # TODO This response body assumes the failed command was
213 # "unbundle." That assumption is not always valid.
212 # "unbundle." That assumption is not always valid.
214 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
213 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
215
214
216 return True
215 return True
217
216
218 def _httpresponsetype(ui, req, prefer_uncompressed):
217 def _httpresponsetype(ui, req, prefer_uncompressed):
219 """Determine the appropriate response type and compression settings.
218 """Determine the appropriate response type and compression settings.
220
219
221 Returns a tuple of (mediatype, compengine, engineopts).
220 Returns a tuple of (mediatype, compengine, engineopts).
222 """
221 """
223 # Determine the response media type and compression engine based
222 # Determine the response media type and compression engine based
224 # on the request parameters.
223 # on the request parameters.
225 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
224 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
226
225
227 if '0.2' in protocaps:
226 if '0.2' in protocaps:
228 # All clients are expected to support uncompressed data.
227 # All clients are expected to support uncompressed data.
229 if prefer_uncompressed:
228 if prefer_uncompressed:
230 return HGTYPE2, util._noopengine(), {}
229 return HGTYPE2, util._noopengine(), {}
231
230
232 # Default as defined by wire protocol spec.
231 # Default as defined by wire protocol spec.
233 compformats = ['zlib', 'none']
232 compformats = ['zlib', 'none']
234 for cap in protocaps:
233 for cap in protocaps:
235 if cap.startswith('comp='):
234 if cap.startswith('comp='):
236 compformats = cap[5:].split(',')
235 compformats = cap[5:].split(',')
237 break
236 break
238
237
239 # Now find an agreed upon compression format.
238 # Now find an agreed upon compression format.
240 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
239 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
241 if engine.wireprotosupport().name in compformats:
240 if engine.wireprotosupport().name in compformats:
242 opts = {}
241 opts = {}
243 level = ui.configint('server', '%slevel' % engine.name())
242 level = ui.configint('server', '%slevel' % engine.name())
244 if level is not None:
243 if level is not None:
245 opts['level'] = level
244 opts['level'] = level
246
245
247 return HGTYPE2, engine, opts
246 return HGTYPE2, engine, opts
248
247
249 # No mutually supported compression format. Fall back to the
248 # No mutually supported compression format. Fall back to the
250 # legacy protocol.
249 # legacy protocol.
251
250
252 # Don't allow untrusted settings because disabling compression or
251 # Don't allow untrusted settings because disabling compression or
253 # setting a very high compression level could lead to flooding
252 # setting a very high compression level could lead to flooding
254 # the server's network or CPU.
253 # the server's network or CPU.
255 opts = {'level': ui.configint('server', 'zliblevel')}
254 opts = {'level': ui.configint('server', 'zliblevel')}
256 return HGTYPE, util.compengines['zlib'], opts
255 return HGTYPE, util.compengines['zlib'], opts
257
256
258 def _callhttp(repo, req, res, proto, cmd):
257 def _callhttp(repo, req, res, proto, cmd):
259 # Avoid cycle involving hg module.
258 # Avoid cycle involving hg module.
260 from .hgweb import common as hgwebcommon
259 from .hgweb import common as hgwebcommon
261
260
262 def genversion2(gen, engine, engineopts):
261 def genversion2(gen, engine, engineopts):
263 # application/mercurial-0.2 always sends a payload header
262 # application/mercurial-0.2 always sends a payload header
264 # identifying the compression engine.
263 # identifying the compression engine.
265 name = engine.wireprotosupport().name
264 name = engine.wireprotosupport().name
266 assert 0 < len(name) < 256
265 assert 0 < len(name) < 256
267 yield struct.pack('B', len(name))
266 yield struct.pack('B', len(name))
268 yield name
267 yield name
269
268
270 for chunk in gen:
269 for chunk in gen:
271 yield chunk
270 yield chunk
272
271
273 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
272 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
274 if code == HTTP_OK:
273 if code == HTTP_OK:
275 res.status = '200 Script output follows'
274 res.status = '200 Script output follows'
276 else:
275 else:
277 res.status = hgwebcommon.statusmessage(code)
276 res.status = hgwebcommon.statusmessage(code)
278
277
279 res.headers['Content-Type'] = contenttype
278 res.headers['Content-Type'] = contenttype
280
279
281 if bodybytes is not None:
280 if bodybytes is not None:
282 res.setbodybytes(bodybytes)
281 res.setbodybytes(bodybytes)
283 if bodygen is not None:
282 if bodygen is not None:
284 res.setbodygen(bodygen)
283 res.setbodygen(bodygen)
285
284
286 if not wireproto.commands.commandavailable(cmd, proto):
285 if not wireproto.commands.commandavailable(cmd, proto):
287 setresponse(HTTP_OK, HGERRTYPE,
286 setresponse(HTTP_OK, HGERRTYPE,
288 _('requested wire protocol command is not available over '
287 _('requested wire protocol command is not available over '
289 'HTTP'))
288 'HTTP'))
290 return
289 return
291
290
292 proto.checkperm(wireproto.commands[cmd].permission)
291 proto.checkperm(wireproto.commands[cmd].permission)
293
292
294 rsp = wireproto.dispatch(repo, proto, cmd)
293 rsp = wireproto.dispatch(repo, proto, cmd)
295
294
296 if isinstance(rsp, bytes):
295 if isinstance(rsp, bytes):
297 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
296 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
298 elif isinstance(rsp, wireprototypes.bytesresponse):
297 elif isinstance(rsp, wireprototypes.bytesresponse):
299 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
298 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
300 elif isinstance(rsp, wireprototypes.streamreslegacy):
299 elif isinstance(rsp, wireprototypes.streamreslegacy):
301 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
300 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
302 elif isinstance(rsp, wireprototypes.streamres):
301 elif isinstance(rsp, wireprototypes.streamres):
303 gen = rsp.gen
302 gen = rsp.gen
304
303
305 # This code for compression should not be streamres specific. It
304 # This code for compression should not be streamres specific. It
306 # is here because we only compress streamres at the moment.
305 # is here because we only compress streamres at the moment.
307 mediatype, engine, engineopts = _httpresponsetype(
306 mediatype, engine, engineopts = _httpresponsetype(
308 repo.ui, req, rsp.prefer_uncompressed)
307 repo.ui, req, rsp.prefer_uncompressed)
309 gen = engine.compressstream(gen, engineopts)
308 gen = engine.compressstream(gen, engineopts)
310
309
311 if mediatype == HGTYPE2:
310 if mediatype == HGTYPE2:
312 gen = genversion2(gen, engine, engineopts)
311 gen = genversion2(gen, engine, engineopts)
313
312
314 setresponse(HTTP_OK, mediatype, bodygen=gen)
313 setresponse(HTTP_OK, mediatype, bodygen=gen)
315 elif isinstance(rsp, wireprototypes.pushres):
314 elif isinstance(rsp, wireprototypes.pushres):
316 rsp = '%d\n%s' % (rsp.res, rsp.output)
315 rsp = '%d\n%s' % (rsp.res, rsp.output)
317 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
316 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
318 elif isinstance(rsp, wireprototypes.pusherr):
317 elif isinstance(rsp, wireprototypes.pusherr):
319 rsp = '0\n%s\n' % rsp.res
318 rsp = '0\n%s\n' % rsp.res
320 res.drain = True
319 res.drain = True
321 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
320 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
322 elif isinstance(rsp, wireprototypes.ooberror):
321 elif isinstance(rsp, wireprototypes.ooberror):
323 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
322 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
324 else:
323 else:
325 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
324 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
326
325
327 def _sshv1respondbytes(fout, value):
326 def _sshv1respondbytes(fout, value):
328 """Send a bytes response for protocol version 1."""
327 """Send a bytes response for protocol version 1."""
329 fout.write('%d\n' % len(value))
328 fout.write('%d\n' % len(value))
330 fout.write(value)
329 fout.write(value)
331 fout.flush()
330 fout.flush()
332
331
333 def _sshv1respondstream(fout, source):
332 def _sshv1respondstream(fout, source):
334 write = fout.write
333 write = fout.write
335 for chunk in source.gen:
334 for chunk in source.gen:
336 write(chunk)
335 write(chunk)
337 fout.flush()
336 fout.flush()
338
337
339 def _sshv1respondooberror(fout, ferr, rsp):
338 def _sshv1respondooberror(fout, ferr, rsp):
340 ferr.write(b'%s\n-\n' % rsp)
339 ferr.write(b'%s\n-\n' % rsp)
341 ferr.flush()
340 ferr.flush()
342 fout.write(b'\n')
341 fout.write(b'\n')
343 fout.flush()
342 fout.flush()
344
343
345 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
344 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
346 """Handler for requests services via version 1 of SSH protocol."""
345 """Handler for requests services via version 1 of SSH protocol."""
347 def __init__(self, ui, fin, fout):
346 def __init__(self, ui, fin, fout):
348 self._ui = ui
347 self._ui = ui
349 self._fin = fin
348 self._fin = fin
350 self._fout = fout
349 self._fout = fout
351
350
352 @property
351 @property
353 def name(self):
352 def name(self):
354 return wireprototypes.SSHV1
353 return wireprototypes.SSHV1
355
354
356 def getargs(self, args):
355 def getargs(self, args):
357 data = {}
356 data = {}
358 keys = args.split()
357 keys = args.split()
359 for n in xrange(len(keys)):
358 for n in xrange(len(keys)):
360 argline = self._fin.readline()[:-1]
359 argline = self._fin.readline()[:-1]
361 arg, l = argline.split()
360 arg, l = argline.split()
362 if arg not in keys:
361 if arg not in keys:
363 raise error.Abort(_("unexpected parameter %r") % arg)
362 raise error.Abort(_("unexpected parameter %r") % arg)
364 if arg == '*':
363 if arg == '*':
365 star = {}
364 star = {}
366 for k in xrange(int(l)):
365 for k in xrange(int(l)):
367 argline = self._fin.readline()[:-1]
366 argline = self._fin.readline()[:-1]
368 arg, l = argline.split()
367 arg, l = argline.split()
369 val = self._fin.read(int(l))
368 val = self._fin.read(int(l))
370 star[arg] = val
369 star[arg] = val
371 data['*'] = star
370 data['*'] = star
372 else:
371 else:
373 val = self._fin.read(int(l))
372 val = self._fin.read(int(l))
374 data[arg] = val
373 data[arg] = val
375 return [data[k] for k in keys]
374 return [data[k] for k in keys]
376
375
377 def forwardpayload(self, fpout):
376 def forwardpayload(self, fpout):
378 # We initially send an empty response. This tells the client it is
377 # We initially send an empty response. This tells the client it is
379 # OK to start sending data. If a client sees any other response, it
378 # OK to start sending data. If a client sees any other response, it
380 # interprets it as an error.
379 # interprets it as an error.
381 _sshv1respondbytes(self._fout, b'')
380 _sshv1respondbytes(self._fout, b'')
382
381
383 # The file is in the form:
382 # The file is in the form:
384 #
383 #
385 # <chunk size>\n<chunk>
384 # <chunk size>\n<chunk>
386 # ...
385 # ...
387 # 0\n
386 # 0\n
388 count = int(self._fin.readline())
387 count = int(self._fin.readline())
389 while count:
388 while count:
390 fpout.write(self._fin.read(count))
389 fpout.write(self._fin.read(count))
391 count = int(self._fin.readline())
390 count = int(self._fin.readline())
392
391
393 @contextlib.contextmanager
392 @contextlib.contextmanager
394 def mayberedirectstdio(self):
393 def mayberedirectstdio(self):
395 yield None
394 yield None
396
395
397 def client(self):
396 def client(self):
398 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
397 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
399 return 'remote:ssh:' + client
398 return 'remote:ssh:' + client
400
399
401 def addcapabilities(self, repo, caps):
400 def addcapabilities(self, repo, caps):
402 return caps
401 return caps
403
402
404 def checkperm(self, perm):
403 def checkperm(self, perm):
405 pass
404 pass
406
405
407 class sshv2protocolhandler(sshv1protocolhandler):
406 class sshv2protocolhandler(sshv1protocolhandler):
408 """Protocol handler for version 2 of the SSH protocol."""
407 """Protocol handler for version 2 of the SSH protocol."""
409
408
410 @property
409 @property
411 def name(self):
410 def name(self):
412 return wireprototypes.SSHV2
411 return wireprototypes.SSHV2
413
412
414 def _runsshserver(ui, repo, fin, fout, ev):
413 def _runsshserver(ui, repo, fin, fout, ev):
415 # This function operates like a state machine of sorts. The following
414 # This function operates like a state machine of sorts. The following
416 # states are defined:
415 # states are defined:
417 #
416 #
418 # protov1-serving
417 # protov1-serving
419 # Server is in protocol version 1 serving mode. Commands arrive on
418 # Server is in protocol version 1 serving mode. Commands arrive on
420 # new lines. These commands are processed in this state, one command
419 # new lines. These commands are processed in this state, one command
421 # after the other.
420 # after the other.
422 #
421 #
423 # protov2-serving
422 # protov2-serving
424 # Server is in protocol version 2 serving mode.
423 # Server is in protocol version 2 serving mode.
425 #
424 #
426 # upgrade-initial
425 # upgrade-initial
427 # The server is going to process an upgrade request.
426 # The server is going to process an upgrade request.
428 #
427 #
429 # upgrade-v2-filter-legacy-handshake
428 # upgrade-v2-filter-legacy-handshake
430 # The protocol is being upgraded to version 2. The server is expecting
429 # The protocol is being upgraded to version 2. The server is expecting
431 # the legacy handshake from version 1.
430 # the legacy handshake from version 1.
432 #
431 #
433 # upgrade-v2-finish
432 # upgrade-v2-finish
434 # The upgrade to version 2 of the protocol is imminent.
433 # The upgrade to version 2 of the protocol is imminent.
435 #
434 #
436 # shutdown
435 # shutdown
437 # The server is shutting down, possibly in reaction to a client event.
436 # The server is shutting down, possibly in reaction to a client event.
438 #
437 #
439 # And here are their transitions:
438 # And here are their transitions:
440 #
439 #
441 # protov1-serving -> shutdown
440 # protov1-serving -> shutdown
442 # When server receives an empty request or encounters another
441 # When server receives an empty request or encounters another
443 # error.
442 # error.
444 #
443 #
445 # protov1-serving -> upgrade-initial
444 # protov1-serving -> upgrade-initial
446 # An upgrade request line was seen.
445 # An upgrade request line was seen.
447 #
446 #
448 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
447 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
449 # Upgrade to version 2 in progress. Server is expecting to
448 # Upgrade to version 2 in progress. Server is expecting to
450 # process a legacy handshake.
449 # process a legacy handshake.
451 #
450 #
452 # upgrade-v2-filter-legacy-handshake -> shutdown
451 # upgrade-v2-filter-legacy-handshake -> shutdown
453 # Client did not fulfill upgrade handshake requirements.
452 # Client did not fulfill upgrade handshake requirements.
454 #
453 #
455 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
454 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
456 # Client fulfilled version 2 upgrade requirements. Finishing that
455 # Client fulfilled version 2 upgrade requirements. Finishing that
457 # upgrade.
456 # upgrade.
458 #
457 #
459 # upgrade-v2-finish -> protov2-serving
458 # upgrade-v2-finish -> protov2-serving
460 # Protocol upgrade to version 2 complete. Server can now speak protocol
459 # Protocol upgrade to version 2 complete. Server can now speak protocol
461 # version 2.
460 # version 2.
462 #
461 #
463 # protov2-serving -> protov1-serving
462 # protov2-serving -> protov1-serving
464 # Ths happens by default since protocol version 2 is the same as
463 # Ths happens by default since protocol version 2 is the same as
465 # version 1 except for the handshake.
464 # version 1 except for the handshake.
466
465
467 state = 'protov1-serving'
466 state = 'protov1-serving'
468 proto = sshv1protocolhandler(ui, fin, fout)
467 proto = sshv1protocolhandler(ui, fin, fout)
469 protoswitched = False
468 protoswitched = False
470
469
471 while not ev.is_set():
470 while not ev.is_set():
472 if state == 'protov1-serving':
471 if state == 'protov1-serving':
473 # Commands are issued on new lines.
472 # Commands are issued on new lines.
474 request = fin.readline()[:-1]
473 request = fin.readline()[:-1]
475
474
476 # Empty lines signal to terminate the connection.
475 # Empty lines signal to terminate the connection.
477 if not request:
476 if not request:
478 state = 'shutdown'
477 state = 'shutdown'
479 continue
478 continue
480
479
481 # It looks like a protocol upgrade request. Transition state to
480 # It looks like a protocol upgrade request. Transition state to
482 # handle it.
481 # handle it.
483 if request.startswith(b'upgrade '):
482 if request.startswith(b'upgrade '):
484 if protoswitched:
483 if protoswitched:
485 _sshv1respondooberror(fout, ui.ferr,
484 _sshv1respondooberror(fout, ui.ferr,
486 b'cannot upgrade protocols multiple '
485 b'cannot upgrade protocols multiple '
487 b'times')
486 b'times')
488 state = 'shutdown'
487 state = 'shutdown'
489 continue
488 continue
490
489
491 state = 'upgrade-initial'
490 state = 'upgrade-initial'
492 continue
491 continue
493
492
494 available = wireproto.commands.commandavailable(request, proto)
493 available = wireproto.commands.commandavailable(request, proto)
495
494
496 # This command isn't available. Send an empty response and go
495 # This command isn't available. Send an empty response and go
497 # back to waiting for a new command.
496 # back to waiting for a new command.
498 if not available:
497 if not available:
499 _sshv1respondbytes(fout, b'')
498 _sshv1respondbytes(fout, b'')
500 continue
499 continue
501
500
502 rsp = wireproto.dispatch(repo, proto, request)
501 rsp = wireproto.dispatch(repo, proto, request)
503
502
504 if isinstance(rsp, bytes):
503 if isinstance(rsp, bytes):
505 _sshv1respondbytes(fout, rsp)
504 _sshv1respondbytes(fout, rsp)
506 elif isinstance(rsp, wireprototypes.bytesresponse):
505 elif isinstance(rsp, wireprototypes.bytesresponse):
507 _sshv1respondbytes(fout, rsp.data)
506 _sshv1respondbytes(fout, rsp.data)
508 elif isinstance(rsp, wireprototypes.streamres):
507 elif isinstance(rsp, wireprototypes.streamres):
509 _sshv1respondstream(fout, rsp)
508 _sshv1respondstream(fout, rsp)
510 elif isinstance(rsp, wireprototypes.streamreslegacy):
509 elif isinstance(rsp, wireprototypes.streamreslegacy):
511 _sshv1respondstream(fout, rsp)
510 _sshv1respondstream(fout, rsp)
512 elif isinstance(rsp, wireprototypes.pushres):
511 elif isinstance(rsp, wireprototypes.pushres):
513 _sshv1respondbytes(fout, b'')
512 _sshv1respondbytes(fout, b'')
514 _sshv1respondbytes(fout, b'%d' % rsp.res)
513 _sshv1respondbytes(fout, b'%d' % rsp.res)
515 elif isinstance(rsp, wireprototypes.pusherr):
514 elif isinstance(rsp, wireprototypes.pusherr):
516 _sshv1respondbytes(fout, rsp.res)
515 _sshv1respondbytes(fout, rsp.res)
517 elif isinstance(rsp, wireprototypes.ooberror):
516 elif isinstance(rsp, wireprototypes.ooberror):
518 _sshv1respondooberror(fout, ui.ferr, rsp.message)
517 _sshv1respondooberror(fout, ui.ferr, rsp.message)
519 else:
518 else:
520 raise error.ProgrammingError('unhandled response type from '
519 raise error.ProgrammingError('unhandled response type from '
521 'wire protocol command: %s' % rsp)
520 'wire protocol command: %s' % rsp)
522
521
523 # For now, protocol version 2 serving just goes back to version 1.
522 # For now, protocol version 2 serving just goes back to version 1.
524 elif state == 'protov2-serving':
523 elif state == 'protov2-serving':
525 state = 'protov1-serving'
524 state = 'protov1-serving'
526 continue
525 continue
527
526
528 elif state == 'upgrade-initial':
527 elif state == 'upgrade-initial':
529 # We should never transition into this state if we've switched
528 # We should never transition into this state if we've switched
530 # protocols.
529 # protocols.
531 assert not protoswitched
530 assert not protoswitched
532 assert proto.name == wireprototypes.SSHV1
531 assert proto.name == wireprototypes.SSHV1
533
532
534 # Expected: upgrade <token> <capabilities>
533 # Expected: upgrade <token> <capabilities>
535 # If we get something else, the request is malformed. It could be
534 # If we get something else, the request is malformed. It could be
536 # from a future client that has altered the upgrade line content.
535 # from a future client that has altered the upgrade line content.
537 # We treat this as an unknown command.
536 # We treat this as an unknown command.
538 try:
537 try:
539 token, caps = request.split(b' ')[1:]
538 token, caps = request.split(b' ')[1:]
540 except ValueError:
539 except ValueError:
541 _sshv1respondbytes(fout, b'')
540 _sshv1respondbytes(fout, b'')
542 state = 'protov1-serving'
541 state = 'protov1-serving'
543 continue
542 continue
544
543
545 # Send empty response if we don't support upgrading protocols.
544 # Send empty response if we don't support upgrading protocols.
546 if not ui.configbool('experimental', 'sshserver.support-v2'):
545 if not ui.configbool('experimental', 'sshserver.support-v2'):
547 _sshv1respondbytes(fout, b'')
546 _sshv1respondbytes(fout, b'')
548 state = 'protov1-serving'
547 state = 'protov1-serving'
549 continue
548 continue
550
549
551 try:
550 try:
552 caps = urlreq.parseqs(caps)
551 caps = urlreq.parseqs(caps)
553 except ValueError:
552 except ValueError:
554 _sshv1respondbytes(fout, b'')
553 _sshv1respondbytes(fout, b'')
555 state = 'protov1-serving'
554 state = 'protov1-serving'
556 continue
555 continue
557
556
558 # We don't see an upgrade request to protocol version 2. Ignore
557 # We don't see an upgrade request to protocol version 2. Ignore
559 # the upgrade request.
558 # the upgrade request.
560 wantedprotos = caps.get(b'proto', [b''])[0]
559 wantedprotos = caps.get(b'proto', [b''])[0]
561 if SSHV2 not in wantedprotos:
560 if SSHV2 not in wantedprotos:
562 _sshv1respondbytes(fout, b'')
561 _sshv1respondbytes(fout, b'')
563 state = 'protov1-serving'
562 state = 'protov1-serving'
564 continue
563 continue
565
564
566 # It looks like we can honor this upgrade request to protocol 2.
565 # It looks like we can honor this upgrade request to protocol 2.
567 # Filter the rest of the handshake protocol request lines.
566 # Filter the rest of the handshake protocol request lines.
568 state = 'upgrade-v2-filter-legacy-handshake'
567 state = 'upgrade-v2-filter-legacy-handshake'
569 continue
568 continue
570
569
571 elif state == 'upgrade-v2-filter-legacy-handshake':
570 elif state == 'upgrade-v2-filter-legacy-handshake':
572 # Client should have sent legacy handshake after an ``upgrade``
571 # Client should have sent legacy handshake after an ``upgrade``
573 # request. Expected lines:
572 # request. Expected lines:
574 #
573 #
575 # hello
574 # hello
576 # between
575 # between
577 # pairs 81
576 # pairs 81
578 # 0000...-0000...
577 # 0000...-0000...
579
578
580 ok = True
579 ok = True
581 for line in (b'hello', b'between', b'pairs 81'):
580 for line in (b'hello', b'between', b'pairs 81'):
582 request = fin.readline()[:-1]
581 request = fin.readline()[:-1]
583
582
584 if request != line:
583 if request != line:
585 _sshv1respondooberror(fout, ui.ferr,
584 _sshv1respondooberror(fout, ui.ferr,
586 b'malformed handshake protocol: '
585 b'malformed handshake protocol: '
587 b'missing %s' % line)
586 b'missing %s' % line)
588 ok = False
587 ok = False
589 state = 'shutdown'
588 state = 'shutdown'
590 break
589 break
591
590
592 if not ok:
591 if not ok:
593 continue
592 continue
594
593
595 request = fin.read(81)
594 request = fin.read(81)
596 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
595 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
597 _sshv1respondooberror(fout, ui.ferr,
596 _sshv1respondooberror(fout, ui.ferr,
598 b'malformed handshake protocol: '
597 b'malformed handshake protocol: '
599 b'missing between argument value')
598 b'missing between argument value')
600 state = 'shutdown'
599 state = 'shutdown'
601 continue
600 continue
602
601
603 state = 'upgrade-v2-finish'
602 state = 'upgrade-v2-finish'
604 continue
603 continue
605
604
606 elif state == 'upgrade-v2-finish':
605 elif state == 'upgrade-v2-finish':
607 # Send the upgrade response.
606 # Send the upgrade response.
608 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
607 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
609 servercaps = wireproto.capabilities(repo, proto)
608 servercaps = wireproto.capabilities(repo, proto)
610 rsp = b'capabilities: %s' % servercaps.data
609 rsp = b'capabilities: %s' % servercaps.data
611 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
610 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
612 fout.flush()
611 fout.flush()
613
612
614 proto = sshv2protocolhandler(ui, fin, fout)
613 proto = sshv2protocolhandler(ui, fin, fout)
615 protoswitched = True
614 protoswitched = True
616
615
617 state = 'protov2-serving'
616 state = 'protov2-serving'
618 continue
617 continue
619
618
620 elif state == 'shutdown':
619 elif state == 'shutdown':
621 break
620 break
622
621
623 else:
622 else:
624 raise error.ProgrammingError('unhandled ssh server state: %s' %
623 raise error.ProgrammingError('unhandled ssh server state: %s' %
625 state)
624 state)
626
625
627 class sshserver(object):
626 class sshserver(object):
628 def __init__(self, ui, repo, logfh=None):
627 def __init__(self, ui, repo, logfh=None):
629 self._ui = ui
628 self._ui = ui
630 self._repo = repo
629 self._repo = repo
631 self._fin = ui.fin
630 self._fin = ui.fin
632 self._fout = ui.fout
631 self._fout = ui.fout
633
632
634 # Log write I/O to stdout and stderr if configured.
633 # Log write I/O to stdout and stderr if configured.
635 if logfh:
634 if logfh:
636 self._fout = util.makeloggingfileobject(
635 self._fout = util.makeloggingfileobject(
637 logfh, self._fout, 'o', logdata=True)
636 logfh, self._fout, 'o', logdata=True)
638 ui.ferr = util.makeloggingfileobject(
637 ui.ferr = util.makeloggingfileobject(
639 logfh, ui.ferr, 'e', logdata=True)
638 logfh, ui.ferr, 'e', logdata=True)
640
639
641 hook.redirect(True)
640 hook.redirect(True)
642 ui.fout = repo.ui.fout = ui.ferr
641 ui.fout = repo.ui.fout = ui.ferr
643
642
644 # Prevent insertion/deletion of CRs
643 # Prevent insertion/deletion of CRs
645 util.setbinary(self._fin)
644 util.setbinary(self._fin)
646 util.setbinary(self._fout)
645 util.setbinary(self._fout)
647
646
648 def serve_forever(self):
647 def serve_forever(self):
649 self.serveuntil(threading.Event())
648 self.serveuntil(threading.Event())
650 sys.exit(0)
649 sys.exit(0)
651
650
652 def serveuntil(self, ev):
651 def serveuntil(self, ev):
653 """Serve until a threading.Event is set."""
652 """Serve until a threading.Event is set."""
654 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
653 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,404 +1,404
1 #require serve
1 #require serve
2
2
3 This test is a duplicate of 'test-http.t', feel free to factor out
3 This test is a duplicate of 'test-http.t', feel free to factor out
4 parts that are not bundle1/bundle2 specific.
4 parts that are not bundle1/bundle2 specific.
5
5
6 $ cat << EOF >> $HGRCPATH
6 $ cat << EOF >> $HGRCPATH
7 > [devel]
7 > [devel]
8 > # This test is dedicated to interaction through old bundle
8 > # This test is dedicated to interaction through old bundle
9 > legacy.exchange = bundle1
9 > legacy.exchange = bundle1
10 > EOF
10 > EOF
11
11
12 $ hg init test
12 $ hg init test
13 $ cd test
13 $ cd test
14 $ echo foo>foo
14 $ echo foo>foo
15 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
15 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
16 $ echo foo>foo.d/foo
16 $ echo foo>foo.d/foo
17 $ echo bar>foo.d/bAr.hg.d/BaR
17 $ echo bar>foo.d/bAr.hg.d/BaR
18 $ echo bar>foo.d/baR.d.hg/bAR
18 $ echo bar>foo.d/baR.d.hg/bAR
19 $ hg commit -A -m 1
19 $ hg commit -A -m 1
20 adding foo
20 adding foo
21 adding foo.d/bAr.hg.d/BaR
21 adding foo.d/bAr.hg.d/BaR
22 adding foo.d/baR.d.hg/bAR
22 adding foo.d/baR.d.hg/bAR
23 adding foo.d/foo
23 adding foo.d/foo
24 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
24 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
25 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
25 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
26
26
27 Test server address cannot be reused
27 Test server address cannot be reused
28
28
29 $ hg serve -p $HGPORT1 2>&1
29 $ hg serve -p $HGPORT1 2>&1
30 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
30 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
31 [255]
31 [255]
32
32
33 $ cd ..
33 $ cd ..
34 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
34 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
35
35
36 clone via stream
36 clone via stream
37
37
38 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
38 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
39 streaming all changes
39 streaming all changes
40 6 files to transfer, 606 bytes of data
40 6 files to transfer, 606 bytes of data
41 transferred * bytes in * seconds (*/sec) (glob)
41 transferred * bytes in * seconds (*/sec) (glob)
42 searching for changes
42 searching for changes
43 no changes found
43 no changes found
44 updating to branch default
44 updating to branch default
45 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 $ hg verify -R copy
46 $ hg verify -R copy
47 checking changesets
47 checking changesets
48 checking manifests
48 checking manifests
49 crosschecking files in changesets and manifests
49 crosschecking files in changesets and manifests
50 checking files
50 checking files
51 4 files, 1 changesets, 4 total revisions
51 4 files, 1 changesets, 4 total revisions
52
52
53 try to clone via stream, should use pull instead
53 try to clone via stream, should use pull instead
54
54
55 $ hg clone --stream http://localhost:$HGPORT1/ copy2
55 $ hg clone --stream http://localhost:$HGPORT1/ copy2
56 warning: stream clone requested but server has them disabled
56 warning: stream clone requested but server has them disabled
57 requesting all changes
57 requesting all changes
58 adding changesets
58 adding changesets
59 adding manifests
59 adding manifests
60 adding file changes
60 adding file changes
61 added 1 changesets with 4 changes to 4 files
61 added 1 changesets with 4 changes to 4 files
62 new changesets 8b6053c928fe
62 new changesets 8b6053c928fe
63 updating to branch default
63 updating to branch default
64 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
65
65
66 try to clone via stream but missing requirements, so should use pull instead
66 try to clone via stream but missing requirements, so should use pull instead
67
67
68 $ cat > $TESTTMP/removesupportedformat.py << EOF
68 $ cat > $TESTTMP/removesupportedformat.py << EOF
69 > from mercurial import localrepo
69 > from mercurial import localrepo
70 > def extsetup(ui):
70 > def extsetup(ui):
71 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
71 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
72 > EOF
72 > EOF
73
73
74 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
74 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
75 warning: stream clone requested but client is missing requirements: generaldelta
75 warning: stream clone requested but client is missing requirements: generaldelta
76 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
76 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
77 requesting all changes
77 requesting all changes
78 adding changesets
78 adding changesets
79 adding manifests
79 adding manifests
80 adding file changes
80 adding file changes
81 added 1 changesets with 4 changes to 4 files
81 added 1 changesets with 4 changes to 4 files
82 new changesets 8b6053c928fe
82 new changesets 8b6053c928fe
83 updating to branch default
83 updating to branch default
84 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
85
85
86 clone via pull
86 clone via pull
87
87
88 $ hg clone http://localhost:$HGPORT1/ copy-pull
88 $ hg clone http://localhost:$HGPORT1/ copy-pull
89 requesting all changes
89 requesting all changes
90 adding changesets
90 adding changesets
91 adding manifests
91 adding manifests
92 adding file changes
92 adding file changes
93 added 1 changesets with 4 changes to 4 files
93 added 1 changesets with 4 changes to 4 files
94 new changesets 8b6053c928fe
94 new changesets 8b6053c928fe
95 updating to branch default
95 updating to branch default
96 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 $ hg verify -R copy-pull
97 $ hg verify -R copy-pull
98 checking changesets
98 checking changesets
99 checking manifests
99 checking manifests
100 crosschecking files in changesets and manifests
100 crosschecking files in changesets and manifests
101 checking files
101 checking files
102 4 files, 1 changesets, 4 total revisions
102 4 files, 1 changesets, 4 total revisions
103 $ cd test
103 $ cd test
104 $ echo bar > bar
104 $ echo bar > bar
105 $ hg commit -A -d '1 0' -m 2
105 $ hg commit -A -d '1 0' -m 2
106 adding bar
106 adding bar
107 $ cd ..
107 $ cd ..
108
108
109 clone over http with --update
109 clone over http with --update
110
110
111 $ hg clone http://localhost:$HGPORT1/ updated --update 0
111 $ hg clone http://localhost:$HGPORT1/ updated --update 0
112 requesting all changes
112 requesting all changes
113 adding changesets
113 adding changesets
114 adding manifests
114 adding manifests
115 adding file changes
115 adding file changes
116 added 2 changesets with 5 changes to 5 files
116 added 2 changesets with 5 changes to 5 files
117 new changesets 8b6053c928fe:5fed3813f7f5
117 new changesets 8b6053c928fe:5fed3813f7f5
118 updating to branch default
118 updating to branch default
119 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 $ hg log -r . -R updated
120 $ hg log -r . -R updated
121 changeset: 0:8b6053c928fe
121 changeset: 0:8b6053c928fe
122 user: test
122 user: test
123 date: Thu Jan 01 00:00:00 1970 +0000
123 date: Thu Jan 01 00:00:00 1970 +0000
124 summary: 1
124 summary: 1
125
125
126 $ rm -rf updated
126 $ rm -rf updated
127
127
128 incoming via HTTP
128 incoming via HTTP
129
129
130 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
130 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
131 adding changesets
131 adding changesets
132 adding manifests
132 adding manifests
133 adding file changes
133 adding file changes
134 added 1 changesets with 4 changes to 4 files
134 added 1 changesets with 4 changes to 4 files
135 new changesets 8b6053c928fe
135 new changesets 8b6053c928fe
136 updating to branch default
136 updating to branch default
137 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 $ cd partial
138 $ cd partial
139 $ touch LOCAL
139 $ touch LOCAL
140 $ hg ci -qAm LOCAL
140 $ hg ci -qAm LOCAL
141 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
141 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
142 comparing with http://localhost:$HGPORT1/
142 comparing with http://localhost:$HGPORT1/
143 searching for changes
143 searching for changes
144 2
144 2
145 $ cd ..
145 $ cd ..
146
146
147 pull
147 pull
148
148
149 $ cd copy-pull
149 $ cd copy-pull
150 $ cat >> .hg/hgrc <<EOF
150 $ cat >> .hg/hgrc <<EOF
151 > [hooks]
151 > [hooks]
152 > changegroup = sh -c "printenv.py changegroup"
152 > changegroup = sh -c "printenv.py changegroup"
153 > EOF
153 > EOF
154 $ hg pull
154 $ hg pull
155 pulling from http://localhost:$HGPORT1/
155 pulling from http://localhost:$HGPORT1/
156 searching for changes
156 searching for changes
157 adding changesets
157 adding changesets
158 adding manifests
158 adding manifests
159 adding file changes
159 adding file changes
160 added 1 changesets with 1 changes to 1 files
160 added 1 changesets with 1 changes to 1 files
161 new changesets 5fed3813f7f5
161 new changesets 5fed3813f7f5
162 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
162 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
163 (run 'hg update' to get a working copy)
163 (run 'hg update' to get a working copy)
164 $ cd ..
164 $ cd ..
165
165
166 clone from invalid URL
166 clone from invalid URL
167
167
168 $ hg clone http://localhost:$HGPORT/bad
168 $ hg clone http://localhost:$HGPORT/bad
169 abort: HTTP Error 404: Not Found
169 abort: HTTP Error 404: Not Found
170 [255]
170 [255]
171
171
172 test http authentication
172 test http authentication
173 + use the same server to test server side streaming preference
173 + use the same server to test server side streaming preference
174
174
175 $ cd test
175 $ cd test
176 $ cat << EOT > userpass.py
176 $ cat << EOT > userpass.py
177 > import base64
177 > import base64
178 > from mercurial.hgweb import common
178 > from mercurial.hgweb import common
179 > def perform_authentication(hgweb, req, op):
179 > def perform_authentication(hgweb, req, op):
180 > auth = req.env.get('HTTP_AUTHORIZATION')
180 > auth = req.headers.get('Authorization')
181 > if not auth:
181 > if not auth:
182 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
182 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
183 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
183 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
184 > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user',
184 > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user',
185 > b'pass']:
185 > b'pass']:
186 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
186 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
187 > def extsetup():
187 > def extsetup():
188 > common.permhooks.insert(0, perform_authentication)
188 > common.permhooks.insert(0, perform_authentication)
189 > EOT
189 > EOT
190 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
190 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
191 > --config server.preferuncompressed=True \
191 > --config server.preferuncompressed=True \
192 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
192 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
193 $ cat pid >> $DAEMON_PIDS
193 $ cat pid >> $DAEMON_PIDS
194
194
195 $ cat << EOF > get_pass.py
195 $ cat << EOF > get_pass.py
196 > import getpass
196 > import getpass
197 > def newgetpass(arg):
197 > def newgetpass(arg):
198 > return "pass"
198 > return "pass"
199 > getpass.getpass = newgetpass
199 > getpass.getpass = newgetpass
200 > EOF
200 > EOF
201
201
202 $ hg id http://localhost:$HGPORT2/
202 $ hg id http://localhost:$HGPORT2/
203 abort: http authorization required for http://localhost:$HGPORT2/
203 abort: http authorization required for http://localhost:$HGPORT2/
204 [255]
204 [255]
205 $ hg id http://localhost:$HGPORT2/
205 $ hg id http://localhost:$HGPORT2/
206 abort: http authorization required for http://localhost:$HGPORT2/
206 abort: http authorization required for http://localhost:$HGPORT2/
207 [255]
207 [255]
208 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
208 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
209 http authorization required for http://localhost:$HGPORT2/
209 http authorization required for http://localhost:$HGPORT2/
210 realm: mercurial
210 realm: mercurial
211 user: user
211 user: user
212 password: 5fed3813f7f5
212 password: 5fed3813f7f5
213 $ hg id http://user:pass@localhost:$HGPORT2/
213 $ hg id http://user:pass@localhost:$HGPORT2/
214 5fed3813f7f5
214 5fed3813f7f5
215 $ echo '[auth]' >> .hg/hgrc
215 $ echo '[auth]' >> .hg/hgrc
216 $ echo 'l.schemes=http' >> .hg/hgrc
216 $ echo 'l.schemes=http' >> .hg/hgrc
217 $ echo 'l.prefix=lo' >> .hg/hgrc
217 $ echo 'l.prefix=lo' >> .hg/hgrc
218 $ echo 'l.username=user' >> .hg/hgrc
218 $ echo 'l.username=user' >> .hg/hgrc
219 $ echo 'l.password=pass' >> .hg/hgrc
219 $ echo 'l.password=pass' >> .hg/hgrc
220 $ hg id http://localhost:$HGPORT2/
220 $ hg id http://localhost:$HGPORT2/
221 5fed3813f7f5
221 5fed3813f7f5
222 $ hg id http://localhost:$HGPORT2/
222 $ hg id http://localhost:$HGPORT2/
223 5fed3813f7f5
223 5fed3813f7f5
224 $ hg id http://user@localhost:$HGPORT2/
224 $ hg id http://user@localhost:$HGPORT2/
225 5fed3813f7f5
225 5fed3813f7f5
226 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
226 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
227 streaming all changes
227 streaming all changes
228 7 files to transfer, 916 bytes of data
228 7 files to transfer, 916 bytes of data
229 transferred * bytes in * seconds (*/sec) (glob)
229 transferred * bytes in * seconds (*/sec) (glob)
230 searching for changes
230 searching for changes
231 no changes found
231 no changes found
232 updating to branch default
232 updating to branch default
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 --pull should override server's preferuncompressed
234 --pull should override server's preferuncompressed
235 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
235 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
236 requesting all changes
236 requesting all changes
237 adding changesets
237 adding changesets
238 adding manifests
238 adding manifests
239 adding file changes
239 adding file changes
240 added 2 changesets with 5 changes to 5 files
240 added 2 changesets with 5 changes to 5 files
241 new changesets 8b6053c928fe:5fed3813f7f5
241 new changesets 8b6053c928fe:5fed3813f7f5
242 updating to branch default
242 updating to branch default
243 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
244
244
245 $ hg id http://user2@localhost:$HGPORT2/
245 $ hg id http://user2@localhost:$HGPORT2/
246 abort: http authorization required for http://localhost:$HGPORT2/
246 abort: http authorization required for http://localhost:$HGPORT2/
247 [255]
247 [255]
248 $ hg id http://user:pass2@localhost:$HGPORT2/
248 $ hg id http://user:pass2@localhost:$HGPORT2/
249 abort: HTTP Error 403: no
249 abort: HTTP Error 403: no
250 [255]
250 [255]
251
251
252 $ hg -R dest tag -r tip top
252 $ hg -R dest tag -r tip top
253 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
253 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
254 pushing to http://user:***@localhost:$HGPORT2/
254 pushing to http://user:***@localhost:$HGPORT2/
255 searching for changes
255 searching for changes
256 remote: adding changesets
256 remote: adding changesets
257 remote: adding manifests
257 remote: adding manifests
258 remote: adding file changes
258 remote: adding file changes
259 remote: added 1 changesets with 1 changes to 1 files
259 remote: added 1 changesets with 1 changes to 1 files
260 $ hg rollback -q
260 $ hg rollback -q
261
261
262 $ sed 's/.*] "/"/' < ../access.log
262 $ sed 's/.*] "/"/' < ../access.log
263 "GET /?cmd=capabilities HTTP/1.1" 401 -
263 "GET /?cmd=capabilities HTTP/1.1" 401 -
264 "GET /?cmd=capabilities HTTP/1.1" 401 -
264 "GET /?cmd=capabilities HTTP/1.1" 401 -
265 "GET /?cmd=capabilities HTTP/1.1" 401 -
265 "GET /?cmd=capabilities HTTP/1.1" 401 -
266 "GET /?cmd=capabilities HTTP/1.1" 200 -
266 "GET /?cmd=capabilities HTTP/1.1" 200 -
267 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
267 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
268 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
268 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
269 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
269 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
270 "GET /?cmd=capabilities HTTP/1.1" 401 -
270 "GET /?cmd=capabilities HTTP/1.1" 401 -
271 "GET /?cmd=capabilities HTTP/1.1" 200 -
271 "GET /?cmd=capabilities HTTP/1.1" 200 -
272 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
272 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
274 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
274 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
275 "GET /?cmd=capabilities HTTP/1.1" 401 -
275 "GET /?cmd=capabilities HTTP/1.1" 401 -
276 "GET /?cmd=capabilities HTTP/1.1" 200 -
276 "GET /?cmd=capabilities HTTP/1.1" 200 -
277 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
277 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
278 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
278 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
279 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
279 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
280 "GET /?cmd=capabilities HTTP/1.1" 401 -
280 "GET /?cmd=capabilities HTTP/1.1" 401 -
281 "GET /?cmd=capabilities HTTP/1.1" 200 -
281 "GET /?cmd=capabilities HTTP/1.1" 200 -
282 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
282 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
283 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
283 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
284 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
284 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
285 "GET /?cmd=capabilities HTTP/1.1" 401 -
285 "GET /?cmd=capabilities HTTP/1.1" 401 -
286 "GET /?cmd=capabilities HTTP/1.1" 200 -
286 "GET /?cmd=capabilities HTTP/1.1" 200 -
287 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
287 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
288 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
288 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
290 "GET /?cmd=capabilities HTTP/1.1" 401 -
290 "GET /?cmd=capabilities HTTP/1.1" 401 -
291 "GET /?cmd=capabilities HTTP/1.1" 200 -
291 "GET /?cmd=capabilities HTTP/1.1" 200 -
292 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
292 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
293 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
293 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
295 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
295 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
296 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
296 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
297 "GET /?cmd=capabilities HTTP/1.1" 401 -
297 "GET /?cmd=capabilities HTTP/1.1" 401 -
298 "GET /?cmd=capabilities HTTP/1.1" 200 -
298 "GET /?cmd=capabilities HTTP/1.1" 200 -
299 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
299 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
301 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
301 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
302 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
302 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
303 "GET /?cmd=capabilities HTTP/1.1" 401 -
303 "GET /?cmd=capabilities HTTP/1.1" 401 -
304 "GET /?cmd=capabilities HTTP/1.1" 401 -
304 "GET /?cmd=capabilities HTTP/1.1" 401 -
305 "GET /?cmd=capabilities HTTP/1.1" 403 -
305 "GET /?cmd=capabilities HTTP/1.1" 403 -
306 "GET /?cmd=capabilities HTTP/1.1" 401 -
306 "GET /?cmd=capabilities HTTP/1.1" 401 -
307 "GET /?cmd=capabilities HTTP/1.1" 200 -
307 "GET /?cmd=capabilities HTTP/1.1" 200 -
308 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
308 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
309 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
309 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
310 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
310 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
311 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
311 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
312 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
312 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
313 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
313 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
314 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524* (glob)
314 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524* (glob)
315 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
315 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
316
316
317 $ cd ..
317 $ cd ..
318
318
319 clone of serve with repo in root and unserved subrepo (issue2970)
319 clone of serve with repo in root and unserved subrepo (issue2970)
320
320
321 $ hg --cwd test init sub
321 $ hg --cwd test init sub
322 $ echo empty > test/sub/empty
322 $ echo empty > test/sub/empty
323 $ hg --cwd test/sub add empty
323 $ hg --cwd test/sub add empty
324 $ hg --cwd test/sub commit -qm 'add empty'
324 $ hg --cwd test/sub commit -qm 'add empty'
325 $ hg --cwd test/sub tag -r 0 something
325 $ hg --cwd test/sub tag -r 0 something
326 $ echo sub = sub > test/.hgsub
326 $ echo sub = sub > test/.hgsub
327 $ hg --cwd test add .hgsub
327 $ hg --cwd test add .hgsub
328 $ hg --cwd test commit -qm 'add subrepo'
328 $ hg --cwd test commit -qm 'add subrepo'
329 $ hg clone http://localhost:$HGPORT noslash-clone
329 $ hg clone http://localhost:$HGPORT noslash-clone
330 requesting all changes
330 requesting all changes
331 adding changesets
331 adding changesets
332 adding manifests
332 adding manifests
333 adding file changes
333 adding file changes
334 added 3 changesets with 7 changes to 7 files
334 added 3 changesets with 7 changes to 7 files
335 new changesets 8b6053c928fe:56f9bc90cce6
335 new changesets 8b6053c928fe:56f9bc90cce6
336 updating to branch default
336 updating to branch default
337 abort: HTTP Error 404: Not Found
337 abort: HTTP Error 404: Not Found
338 [255]
338 [255]
339 $ hg clone http://localhost:$HGPORT/ slash-clone
339 $ hg clone http://localhost:$HGPORT/ slash-clone
340 requesting all changes
340 requesting all changes
341 adding changesets
341 adding changesets
342 adding manifests
342 adding manifests
343 adding file changes
343 adding file changes
344 added 3 changesets with 7 changes to 7 files
344 added 3 changesets with 7 changes to 7 files
345 new changesets 8b6053c928fe:56f9bc90cce6
345 new changesets 8b6053c928fe:56f9bc90cce6
346 updating to branch default
346 updating to branch default
347 abort: HTTP Error 404: Not Found
347 abort: HTTP Error 404: Not Found
348 [255]
348 [255]
349
349
350 check error log
350 check error log
351
351
352 $ cat error.log
352 $ cat error.log
353
353
354 Check error reporting while pulling/cloning
354 Check error reporting while pulling/cloning
355
355
356 $ $RUNTESTDIR/killdaemons.py
356 $ $RUNTESTDIR/killdaemons.py
357 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
357 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
358 $ cat hg3.pid >> $DAEMON_PIDS
358 $ cat hg3.pid >> $DAEMON_PIDS
359 $ hg clone http://localhost:$HGPORT/ abort-clone
359 $ hg clone http://localhost:$HGPORT/ abort-clone
360 requesting all changes
360 requesting all changes
361 abort: remote error:
361 abort: remote error:
362 this is an exercise
362 this is an exercise
363 [255]
363 [255]
364 $ cat error.log
364 $ cat error.log
365
365
366 disable pull-based clones
366 disable pull-based clones
367
367
368 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
368 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
369 $ cat hg4.pid >> $DAEMON_PIDS
369 $ cat hg4.pid >> $DAEMON_PIDS
370 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
370 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
371 requesting all changes
371 requesting all changes
372 abort: remote error:
372 abort: remote error:
373 server has pull-based clones disabled
373 server has pull-based clones disabled
374 [255]
374 [255]
375
375
376 ... but keep stream clones working
376 ... but keep stream clones working
377
377
378 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
378 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
379 streaming all changes
379 streaming all changes
380 * files to transfer, * of data (glob)
380 * files to transfer, * of data (glob)
381 transferred * in * seconds (* KB/sec) (glob)
381 transferred * in * seconds (* KB/sec) (glob)
382 searching for changes
382 searching for changes
383 no changes found
383 no changes found
384
384
385 ... and also keep partial clones and pulls working
385 ... and also keep partial clones and pulls working
386 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
386 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
387 adding changesets
387 adding changesets
388 adding manifests
388 adding manifests
389 adding file changes
389 adding file changes
390 added 1 changesets with 4 changes to 4 files
390 added 1 changesets with 4 changes to 4 files
391 new changesets 8b6053c928fe
391 new changesets 8b6053c928fe
392 updating to branch default
392 updating to branch default
393 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 $ hg pull -R test-partial-clone
394 $ hg pull -R test-partial-clone
395 pulling from http://localhost:$HGPORT1/
395 pulling from http://localhost:$HGPORT1/
396 searching for changes
396 searching for changes
397 adding changesets
397 adding changesets
398 adding manifests
398 adding manifests
399 adding file changes
399 adding file changes
400 added 2 changesets with 3 changes to 3 files
400 added 2 changesets with 3 changes to 3 files
401 new changesets 5fed3813f7f5:56f9bc90cce6
401 new changesets 5fed3813f7f5:56f9bc90cce6
402 (run 'hg update' to get a working copy)
402 (run 'hg update' to get a working copy)
403
403
404 $ cat error.log
404 $ cat error.log
@@ -1,552 +1,552
1 #require killdaemons serve
1 #require killdaemons serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo foo>foo
5 $ echo foo>foo
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 $ echo foo>foo.d/foo
7 $ echo foo>foo.d/foo
8 $ echo bar>foo.d/bAr.hg.d/BaR
8 $ echo bar>foo.d/bAr.hg.d/BaR
9 $ echo bar>foo.d/baR.d.hg/bAR
9 $ echo bar>foo.d/baR.d.hg/bAR
10 $ hg commit -A -m 1
10 $ hg commit -A -m 1
11 adding foo
11 adding foo
12 adding foo.d/bAr.hg.d/BaR
12 adding foo.d/bAr.hg.d/BaR
13 adding foo.d/baR.d.hg/bAR
13 adding foo.d/baR.d.hg/bAR
14 adding foo.d/foo
14 adding foo.d/foo
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17
17
18 Test server address cannot be reused
18 Test server address cannot be reused
19
19
20 $ hg serve -p $HGPORT1 2>&1
20 $ hg serve -p $HGPORT1 2>&1
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 [255]
22 [255]
23
23
24 $ cd ..
24 $ cd ..
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26
26
27 clone via stream
27 clone via stream
28
28
29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
30 streaming all changes
30 streaming all changes
31 6 files to transfer, 606 bytes of data
31 6 files to transfer, 606 bytes of data
32 transferred * bytes in * seconds (*/sec) (glob)
32 transferred * bytes in * seconds (*/sec) (glob)
33 searching for changes
33 searching for changes
34 no changes found
34 no changes found
35 updating to branch default
35 updating to branch default
36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 $ hg verify -R copy
37 $ hg verify -R copy
38 checking changesets
38 checking changesets
39 checking manifests
39 checking manifests
40 crosschecking files in changesets and manifests
40 crosschecking files in changesets and manifests
41 checking files
41 checking files
42 4 files, 1 changesets, 4 total revisions
42 4 files, 1 changesets, 4 total revisions
43
43
44 try to clone via stream, should use pull instead
44 try to clone via stream, should use pull instead
45
45
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 warning: stream clone requested but server has them disabled
47 warning: stream clone requested but server has them disabled
48 requesting all changes
48 requesting all changes
49 adding changesets
49 adding changesets
50 adding manifests
50 adding manifests
51 adding file changes
51 adding file changes
52 added 1 changesets with 4 changes to 4 files
52 added 1 changesets with 4 changes to 4 files
53 new changesets 8b6053c928fe
53 new changesets 8b6053c928fe
54 updating to branch default
54 updating to branch default
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56
56
57 try to clone via stream but missing requirements, so should use pull instead
57 try to clone via stream but missing requirements, so should use pull instead
58
58
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 > from mercurial import localrepo
60 > from mercurial import localrepo
61 > def extsetup(ui):
61 > def extsetup(ui):
62 > localrepo.localrepository.supportedformats.remove('generaldelta')
62 > localrepo.localrepository.supportedformats.remove('generaldelta')
63 > EOF
63 > EOF
64
64
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 warning: stream clone requested but client is missing requirements: generaldelta
66 warning: stream clone requested but client is missing requirements: generaldelta
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 requesting all changes
68 requesting all changes
69 adding changesets
69 adding changesets
70 adding manifests
70 adding manifests
71 adding file changes
71 adding file changes
72 added 1 changesets with 4 changes to 4 files
72 added 1 changesets with 4 changes to 4 files
73 new changesets 8b6053c928fe
73 new changesets 8b6053c928fe
74 updating to branch default
74 updating to branch default
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76
76
77 clone via pull
77 clone via pull
78
78
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 requesting all changes
80 requesting all changes
81 adding changesets
81 adding changesets
82 adding manifests
82 adding manifests
83 adding file changes
83 adding file changes
84 added 1 changesets with 4 changes to 4 files
84 added 1 changesets with 4 changes to 4 files
85 new changesets 8b6053c928fe
85 new changesets 8b6053c928fe
86 updating to branch default
86 updating to branch default
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 $ hg verify -R copy-pull
88 $ hg verify -R copy-pull
89 checking changesets
89 checking changesets
90 checking manifests
90 checking manifests
91 crosschecking files in changesets and manifests
91 crosschecking files in changesets and manifests
92 checking files
92 checking files
93 4 files, 1 changesets, 4 total revisions
93 4 files, 1 changesets, 4 total revisions
94 $ cd test
94 $ cd test
95 $ echo bar > bar
95 $ echo bar > bar
96 $ hg commit -A -d '1 0' -m 2
96 $ hg commit -A -d '1 0' -m 2
97 adding bar
97 adding bar
98 $ cd ..
98 $ cd ..
99
99
100 clone over http with --update
100 clone over http with --update
101
101
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 requesting all changes
103 requesting all changes
104 adding changesets
104 adding changesets
105 adding manifests
105 adding manifests
106 adding file changes
106 adding file changes
107 added 2 changesets with 5 changes to 5 files
107 added 2 changesets with 5 changes to 5 files
108 new changesets 8b6053c928fe:5fed3813f7f5
108 new changesets 8b6053c928fe:5fed3813f7f5
109 updating to branch default
109 updating to branch default
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 $ hg log -r . -R updated
111 $ hg log -r . -R updated
112 changeset: 0:8b6053c928fe
112 changeset: 0:8b6053c928fe
113 user: test
113 user: test
114 date: Thu Jan 01 00:00:00 1970 +0000
114 date: Thu Jan 01 00:00:00 1970 +0000
115 summary: 1
115 summary: 1
116
116
117 $ rm -rf updated
117 $ rm -rf updated
118
118
119 incoming via HTTP
119 incoming via HTTP
120
120
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 adding changesets
122 adding changesets
123 adding manifests
123 adding manifests
124 adding file changes
124 adding file changes
125 added 1 changesets with 4 changes to 4 files
125 added 1 changesets with 4 changes to 4 files
126 new changesets 8b6053c928fe
126 new changesets 8b6053c928fe
127 updating to branch default
127 updating to branch default
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 $ cd partial
129 $ cd partial
130 $ touch LOCAL
130 $ touch LOCAL
131 $ hg ci -qAm LOCAL
131 $ hg ci -qAm LOCAL
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 comparing with http://localhost:$HGPORT1/
133 comparing with http://localhost:$HGPORT1/
134 searching for changes
134 searching for changes
135 2
135 2
136 $ cd ..
136 $ cd ..
137
137
138 pull
138 pull
139
139
140 $ cd copy-pull
140 $ cd copy-pull
141 $ cat >> .hg/hgrc <<EOF
141 $ cat >> .hg/hgrc <<EOF
142 > [hooks]
142 > [hooks]
143 > changegroup = sh -c "printenv.py changegroup"
143 > changegroup = sh -c "printenv.py changegroup"
144 > EOF
144 > EOF
145 $ hg pull
145 $ hg pull
146 pulling from http://localhost:$HGPORT1/
146 pulling from http://localhost:$HGPORT1/
147 searching for changes
147 searching for changes
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 1 changes to 1 files
151 added 1 changesets with 1 changes to 1 files
152 new changesets 5fed3813f7f5
152 new changesets 5fed3813f7f5
153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
154 (run 'hg update' to get a working copy)
154 (run 'hg update' to get a working copy)
155 $ cd ..
155 $ cd ..
156
156
157 clone from invalid URL
157 clone from invalid URL
158
158
159 $ hg clone http://localhost:$HGPORT/bad
159 $ hg clone http://localhost:$HGPORT/bad
160 abort: HTTP Error 404: Not Found
160 abort: HTTP Error 404: Not Found
161 [255]
161 [255]
162
162
163 test http authentication
163 test http authentication
164 + use the same server to test server side streaming preference
164 + use the same server to test server side streaming preference
165
165
166 $ cd test
166 $ cd test
167 $ cat << EOT > userpass.py
167 $ cat << EOT > userpass.py
168 > import base64
168 > import base64
169 > from mercurial.hgweb import common
169 > from mercurial.hgweb import common
170 > def perform_authentication(hgweb, req, op):
170 > def perform_authentication(hgweb, req, op):
171 > auth = req.env.get('HTTP_AUTHORIZATION')
171 > auth = req.headers.get('Authorization')
172 > if not auth:
172 > if not auth:
173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
177 > def extsetup():
177 > def extsetup():
178 > common.permhooks.insert(0, perform_authentication)
178 > common.permhooks.insert(0, perform_authentication)
179 > EOT
179 > EOT
180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
181 > --config server.preferuncompressed=True \
181 > --config server.preferuncompressed=True \
182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
183 $ cat pid >> $DAEMON_PIDS
183 $ cat pid >> $DAEMON_PIDS
184
184
185 $ cat << EOF > get_pass.py
185 $ cat << EOF > get_pass.py
186 > import getpass
186 > import getpass
187 > def newgetpass(arg):
187 > def newgetpass(arg):
188 > return "pass"
188 > return "pass"
189 > getpass.getpass = newgetpass
189 > getpass.getpass = newgetpass
190 > EOF
190 > EOF
191
191
192 $ hg id http://localhost:$HGPORT2/
192 $ hg id http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
193 abort: http authorization required for http://localhost:$HGPORT2/
194 [255]
194 [255]
195 $ hg id http://localhost:$HGPORT2/
195 $ hg id http://localhost:$HGPORT2/
196 abort: http authorization required for http://localhost:$HGPORT2/
196 abort: http authorization required for http://localhost:$HGPORT2/
197 [255]
197 [255]
198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
199 http authorization required for http://localhost:$HGPORT2/
199 http authorization required for http://localhost:$HGPORT2/
200 realm: mercurial
200 realm: mercurial
201 user: user
201 user: user
202 password: 5fed3813f7f5
202 password: 5fed3813f7f5
203 $ hg id http://user:pass@localhost:$HGPORT2/
203 $ hg id http://user:pass@localhost:$HGPORT2/
204 5fed3813f7f5
204 5fed3813f7f5
205 $ echo '[auth]' >> .hg/hgrc
205 $ echo '[auth]' >> .hg/hgrc
206 $ echo 'l.schemes=http' >> .hg/hgrc
206 $ echo 'l.schemes=http' >> .hg/hgrc
207 $ echo 'l.prefix=lo' >> .hg/hgrc
207 $ echo 'l.prefix=lo' >> .hg/hgrc
208 $ echo 'l.username=user' >> .hg/hgrc
208 $ echo 'l.username=user' >> .hg/hgrc
209 $ echo 'l.password=pass' >> .hg/hgrc
209 $ echo 'l.password=pass' >> .hg/hgrc
210 $ hg id http://localhost:$HGPORT2/
210 $ hg id http://localhost:$HGPORT2/
211 5fed3813f7f5
211 5fed3813f7f5
212 $ hg id http://localhost:$HGPORT2/
212 $ hg id http://localhost:$HGPORT2/
213 5fed3813f7f5
213 5fed3813f7f5
214 $ hg id http://user@localhost:$HGPORT2/
214 $ hg id http://user@localhost:$HGPORT2/
215 5fed3813f7f5
215 5fed3813f7f5
216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
217 streaming all changes
217 streaming all changes
218 7 files to transfer, 916 bytes of data
218 7 files to transfer, 916 bytes of data
219 transferred * bytes in * seconds (*/sec) (glob)
219 transferred * bytes in * seconds (*/sec) (glob)
220 searching for changes
220 searching for changes
221 no changes found
221 no changes found
222 updating to branch default
222 updating to branch default
223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 --pull should override server's preferuncompressed
224 --pull should override server's preferuncompressed
225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
226 requesting all changes
226 requesting all changes
227 adding changesets
227 adding changesets
228 adding manifests
228 adding manifests
229 adding file changes
229 adding file changes
230 added 2 changesets with 5 changes to 5 files
230 added 2 changesets with 5 changes to 5 files
231 new changesets 8b6053c928fe:5fed3813f7f5
231 new changesets 8b6053c928fe:5fed3813f7f5
232 updating to branch default
232 updating to branch default
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234
234
235 $ hg id http://user2@localhost:$HGPORT2/
235 $ hg id http://user2@localhost:$HGPORT2/
236 abort: http authorization required for http://localhost:$HGPORT2/
236 abort: http authorization required for http://localhost:$HGPORT2/
237 [255]
237 [255]
238 $ hg id http://user:pass2@localhost:$HGPORT2/
238 $ hg id http://user:pass2@localhost:$HGPORT2/
239 abort: HTTP Error 403: no
239 abort: HTTP Error 403: no
240 [255]
240 [255]
241
241
242 $ hg -R dest tag -r tip top
242 $ hg -R dest tag -r tip top
243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
244 pushing to http://user:***@localhost:$HGPORT2/
244 pushing to http://user:***@localhost:$HGPORT2/
245 searching for changes
245 searching for changes
246 remote: adding changesets
246 remote: adding changesets
247 remote: adding manifests
247 remote: adding manifests
248 remote: adding file changes
248 remote: adding file changes
249 remote: added 1 changesets with 1 changes to 1 files
249 remote: added 1 changesets with 1 changes to 1 files
250 $ hg rollback -q
250 $ hg rollback -q
251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
252 pushing to http://user:***@localhost:$HGPORT2/
252 pushing to http://user:***@localhost:$HGPORT2/
253 using http://localhost:$HGPORT2/
253 using http://localhost:$HGPORT2/
254 http auth: user user, password ****
254 http auth: user user, password ****
255 sending capabilities command
255 sending capabilities command
256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
257 http auth: user user, password ****
257 http auth: user user, password ****
258 devel-peer-request: finished in *.???? seconds (200) (glob)
258 devel-peer-request: finished in *.???? seconds (200) (glob)
259 query 1; heads
259 query 1; heads
260 sending batch command
260 sending batch command
261 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
261 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
262 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
262 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
263 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
263 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
264 devel-peer-request: 68 bytes of commands arguments in headers
264 devel-peer-request: 68 bytes of commands arguments in headers
265 devel-peer-request: finished in *.???? seconds (200) (glob)
265 devel-peer-request: finished in *.???? seconds (200) (glob)
266 searching for changes
266 searching for changes
267 all remote heads known locally
267 all remote heads known locally
268 preparing listkeys for "phases"
268 preparing listkeys for "phases"
269 sending listkeys command
269 sending listkeys command
270 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
270 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
271 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
271 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
272 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
272 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 devel-peer-request: 16 bytes of commands arguments in headers
273 devel-peer-request: 16 bytes of commands arguments in headers
274 devel-peer-request: finished in *.???? seconds (200) (glob)
274 devel-peer-request: finished in *.???? seconds (200) (glob)
275 received listkey for "phases": 58 bytes
275 received listkey for "phases": 58 bytes
276 checking for updated bookmarks
276 checking for updated bookmarks
277 preparing listkeys for "bookmarks"
277 preparing listkeys for "bookmarks"
278 sending listkeys command
278 sending listkeys command
279 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
279 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
280 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
280 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
281 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
281 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
282 devel-peer-request: 19 bytes of commands arguments in headers
282 devel-peer-request: 19 bytes of commands arguments in headers
283 devel-peer-request: finished in *.???? seconds (200) (glob)
283 devel-peer-request: finished in *.???? seconds (200) (glob)
284 received listkey for "bookmarks": 0 bytes
284 received listkey for "bookmarks": 0 bytes
285 sending branchmap command
285 sending branchmap command
286 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
286 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
287 devel-peer-request: Vary X-HgProto-1
287 devel-peer-request: Vary X-HgProto-1
288 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
288 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 devel-peer-request: finished in *.???? seconds (200) (glob)
289 devel-peer-request: finished in *.???? seconds (200) (glob)
290 sending branchmap command
290 sending branchmap command
291 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
291 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
292 devel-peer-request: Vary X-HgProto-1
292 devel-peer-request: Vary X-HgProto-1
293 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
293 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 devel-peer-request: finished in *.???? seconds (200) (glob)
294 devel-peer-request: finished in *.???? seconds (200) (glob)
295 preparing listkeys for "bookmarks"
295 preparing listkeys for "bookmarks"
296 sending listkeys command
296 sending listkeys command
297 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
297 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
298 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
298 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
299 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
299 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 devel-peer-request: 19 bytes of commands arguments in headers
300 devel-peer-request: 19 bytes of commands arguments in headers
301 devel-peer-request: finished in *.???? seconds (200) (glob)
301 devel-peer-request: finished in *.???? seconds (200) (glob)
302 received listkey for "bookmarks": 0 bytes
302 received listkey for "bookmarks": 0 bytes
303 1 changesets found
303 1 changesets found
304 list of changesets:
304 list of changesets:
305 7f4e523d01f2cc3765ac8934da3d14db775ff872
305 7f4e523d01f2cc3765ac8934da3d14db775ff872
306 bundle2-output-bundle: "HG20", 5 parts total
306 bundle2-output-bundle: "HG20", 5 parts total
307 bundle2-output-part: "replycaps" 188 bytes payload
307 bundle2-output-part: "replycaps" 188 bytes payload
308 bundle2-output-part: "check:phases" 24 bytes payload
308 bundle2-output-part: "check:phases" 24 bytes payload
309 bundle2-output-part: "check:heads" streamed payload
309 bundle2-output-part: "check:heads" streamed payload
310 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
310 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
311 bundle2-output-part: "phase-heads" 24 bytes payload
311 bundle2-output-part: "phase-heads" 24 bytes payload
312 sending unbundle command
312 sending unbundle command
313 sending 996 bytes
313 sending 996 bytes
314 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
314 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
315 devel-peer-request: Content-length 996
315 devel-peer-request: Content-length 996
316 devel-peer-request: Content-type application/mercurial-0.1
316 devel-peer-request: Content-type application/mercurial-0.1
317 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
317 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
318 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
318 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
319 devel-peer-request: 16 bytes of commands arguments in headers
319 devel-peer-request: 16 bytes of commands arguments in headers
320 devel-peer-request: 996 bytes of data
320 devel-peer-request: 996 bytes of data
321 devel-peer-request: finished in *.???? seconds (200) (glob)
321 devel-peer-request: finished in *.???? seconds (200) (glob)
322 bundle2-input-bundle: no-transaction
322 bundle2-input-bundle: no-transaction
323 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
323 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
324 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
324 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
325 bundle2-input-part: total payload size 100
325 bundle2-input-part: total payload size 100
326 remote: adding changesets
326 remote: adding changesets
327 remote: adding manifests
327 remote: adding manifests
328 remote: adding file changes
328 remote: adding file changes
329 remote: added 1 changesets with 1 changes to 1 files
329 remote: added 1 changesets with 1 changes to 1 files
330 bundle2-input-part: "output" (advisory) supported
330 bundle2-input-part: "output" (advisory) supported
331 bundle2-input-bundle: 2 parts total
331 bundle2-input-bundle: 2 parts total
332 preparing listkeys for "phases"
332 preparing listkeys for "phases"
333 sending listkeys command
333 sending listkeys command
334 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
334 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
335 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
335 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
336 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
336 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
337 devel-peer-request: 16 bytes of commands arguments in headers
337 devel-peer-request: 16 bytes of commands arguments in headers
338 devel-peer-request: finished in *.???? seconds (200) (glob)
338 devel-peer-request: finished in *.???? seconds (200) (glob)
339 received listkey for "phases": 15 bytes
339 received listkey for "phases": 15 bytes
340 $ hg rollback -q
340 $ hg rollback -q
341
341
342 $ sed 's/.*] "/"/' < ../access.log
342 $ sed 's/.*] "/"/' < ../access.log
343 "GET /?cmd=capabilities HTTP/1.1" 401 -
343 "GET /?cmd=capabilities HTTP/1.1" 401 -
344 "GET /?cmd=capabilities HTTP/1.1" 401 -
344 "GET /?cmd=capabilities HTTP/1.1" 401 -
345 "GET /?cmd=capabilities HTTP/1.1" 401 -
345 "GET /?cmd=capabilities HTTP/1.1" 401 -
346 "GET /?cmd=capabilities HTTP/1.1" 200 -
346 "GET /?cmd=capabilities HTTP/1.1" 200 -
347 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
347 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
348 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
348 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
349 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
349 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
350 "GET /?cmd=capabilities HTTP/1.1" 401 -
350 "GET /?cmd=capabilities HTTP/1.1" 401 -
351 "GET /?cmd=capabilities HTTP/1.1" 200 -
351 "GET /?cmd=capabilities HTTP/1.1" 200 -
352 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
352 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
353 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
353 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
354 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
354 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
355 "GET /?cmd=capabilities HTTP/1.1" 401 -
355 "GET /?cmd=capabilities HTTP/1.1" 401 -
356 "GET /?cmd=capabilities HTTP/1.1" 200 -
356 "GET /?cmd=capabilities HTTP/1.1" 200 -
357 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
357 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
358 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
358 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
359 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
359 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
360 "GET /?cmd=capabilities HTTP/1.1" 401 -
360 "GET /?cmd=capabilities HTTP/1.1" 401 -
361 "GET /?cmd=capabilities HTTP/1.1" 200 -
361 "GET /?cmd=capabilities HTTP/1.1" 200 -
362 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
362 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
363 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
363 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
364 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
364 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
365 "GET /?cmd=capabilities HTTP/1.1" 401 -
365 "GET /?cmd=capabilities HTTP/1.1" 401 -
366 "GET /?cmd=capabilities HTTP/1.1" 200 -
366 "GET /?cmd=capabilities HTTP/1.1" 200 -
367 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
367 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
368 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
368 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
369 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
369 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
370 "GET /?cmd=capabilities HTTP/1.1" 401 -
370 "GET /?cmd=capabilities HTTP/1.1" 401 -
371 "GET /?cmd=capabilities HTTP/1.1" 200 -
371 "GET /?cmd=capabilities HTTP/1.1" 200 -
372 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
372 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
373 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
373 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
374 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
374 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
376 "GET /?cmd=capabilities HTTP/1.1" 401 -
376 "GET /?cmd=capabilities HTTP/1.1" 401 -
377 "GET /?cmd=capabilities HTTP/1.1" 200 -
377 "GET /?cmd=capabilities HTTP/1.1" 200 -
378 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
378 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
379 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
379 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
380 "GET /?cmd=capabilities HTTP/1.1" 401 -
380 "GET /?cmd=capabilities HTTP/1.1" 401 -
381 "GET /?cmd=capabilities HTTP/1.1" 401 -
381 "GET /?cmd=capabilities HTTP/1.1" 401 -
382 "GET /?cmd=capabilities HTTP/1.1" 403 -
382 "GET /?cmd=capabilities HTTP/1.1" 403 -
383 "GET /?cmd=capabilities HTTP/1.1" 401 -
383 "GET /?cmd=capabilities HTTP/1.1" 401 -
384 "GET /?cmd=capabilities HTTP/1.1" 200 -
384 "GET /?cmd=capabilities HTTP/1.1" 200 -
385 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
385 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
386 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
386 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
387 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
387 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
391 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
391 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
392 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
392 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 "GET /?cmd=capabilities HTTP/1.1" 200 -
394 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
395 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
403
403
404 $ cd ..
404 $ cd ..
405
405
406 clone of serve with repo in root and unserved subrepo (issue2970)
406 clone of serve with repo in root and unserved subrepo (issue2970)
407
407
408 $ hg --cwd test init sub
408 $ hg --cwd test init sub
409 $ echo empty > test/sub/empty
409 $ echo empty > test/sub/empty
410 $ hg --cwd test/sub add empty
410 $ hg --cwd test/sub add empty
411 $ hg --cwd test/sub commit -qm 'add empty'
411 $ hg --cwd test/sub commit -qm 'add empty'
412 $ hg --cwd test/sub tag -r 0 something
412 $ hg --cwd test/sub tag -r 0 something
413 $ echo sub = sub > test/.hgsub
413 $ echo sub = sub > test/.hgsub
414 $ hg --cwd test add .hgsub
414 $ hg --cwd test add .hgsub
415 $ hg --cwd test commit -qm 'add subrepo'
415 $ hg --cwd test commit -qm 'add subrepo'
416 $ hg clone http://localhost:$HGPORT noslash-clone
416 $ hg clone http://localhost:$HGPORT noslash-clone
417 requesting all changes
417 requesting all changes
418 adding changesets
418 adding changesets
419 adding manifests
419 adding manifests
420 adding file changes
420 adding file changes
421 added 3 changesets with 7 changes to 7 files
421 added 3 changesets with 7 changes to 7 files
422 new changesets 8b6053c928fe:56f9bc90cce6
422 new changesets 8b6053c928fe:56f9bc90cce6
423 updating to branch default
423 updating to branch default
424 abort: HTTP Error 404: Not Found
424 abort: HTTP Error 404: Not Found
425 [255]
425 [255]
426 $ hg clone http://localhost:$HGPORT/ slash-clone
426 $ hg clone http://localhost:$HGPORT/ slash-clone
427 requesting all changes
427 requesting all changes
428 adding changesets
428 adding changesets
429 adding manifests
429 adding manifests
430 adding file changes
430 adding file changes
431 added 3 changesets with 7 changes to 7 files
431 added 3 changesets with 7 changes to 7 files
432 new changesets 8b6053c928fe:56f9bc90cce6
432 new changesets 8b6053c928fe:56f9bc90cce6
433 updating to branch default
433 updating to branch default
434 abort: HTTP Error 404: Not Found
434 abort: HTTP Error 404: Not Found
435 [255]
435 [255]
436
436
437 check error log
437 check error log
438
438
439 $ cat error.log
439 $ cat error.log
440
440
441 check abort error reporting while pulling/cloning
441 check abort error reporting while pulling/cloning
442
442
443 $ $RUNTESTDIR/killdaemons.py
443 $ $RUNTESTDIR/killdaemons.py
444 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
444 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
445 $ cat hg3.pid >> $DAEMON_PIDS
445 $ cat hg3.pid >> $DAEMON_PIDS
446 $ hg clone http://localhost:$HGPORT/ abort-clone
446 $ hg clone http://localhost:$HGPORT/ abort-clone
447 requesting all changes
447 requesting all changes
448 remote: abort: this is an exercise
448 remote: abort: this is an exercise
449 abort: pull failed on remote
449 abort: pull failed on remote
450 [255]
450 [255]
451 $ cat error.log
451 $ cat error.log
452
452
453 disable pull-based clones
453 disable pull-based clones
454
454
455 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
455 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
456 $ cat hg4.pid >> $DAEMON_PIDS
456 $ cat hg4.pid >> $DAEMON_PIDS
457 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
457 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
458 requesting all changes
458 requesting all changes
459 remote: abort: server has pull-based clones disabled
459 remote: abort: server has pull-based clones disabled
460 abort: pull failed on remote
460 abort: pull failed on remote
461 (remove --pull if specified or upgrade Mercurial)
461 (remove --pull if specified or upgrade Mercurial)
462 [255]
462 [255]
463
463
464 ... but keep stream clones working
464 ... but keep stream clones working
465
465
466 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
466 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
467 streaming all changes
467 streaming all changes
468 * files to transfer, * of data (glob)
468 * files to transfer, * of data (glob)
469 transferred * in * seconds (*/sec) (glob)
469 transferred * in * seconds (*/sec) (glob)
470 searching for changes
470 searching for changes
471 no changes found
471 no changes found
472 $ cat error.log
472 $ cat error.log
473
473
474 ... and also keep partial clones and pulls working
474 ... and also keep partial clones and pulls working
475 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
475 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
476 adding changesets
476 adding changesets
477 adding manifests
477 adding manifests
478 adding file changes
478 adding file changes
479 added 1 changesets with 4 changes to 4 files
479 added 1 changesets with 4 changes to 4 files
480 new changesets 8b6053c928fe
480 new changesets 8b6053c928fe
481 updating to branch default
481 updating to branch default
482 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 $ hg pull -R test-partial-clone
483 $ hg pull -R test-partial-clone
484 pulling from http://localhost:$HGPORT1/
484 pulling from http://localhost:$HGPORT1/
485 searching for changes
485 searching for changes
486 adding changesets
486 adding changesets
487 adding manifests
487 adding manifests
488 adding file changes
488 adding file changes
489 added 2 changesets with 3 changes to 3 files
489 added 2 changesets with 3 changes to 3 files
490 new changesets 5fed3813f7f5:56f9bc90cce6
490 new changesets 5fed3813f7f5:56f9bc90cce6
491 (run 'hg update' to get a working copy)
491 (run 'hg update' to get a working copy)
492
492
493 corrupt cookies file should yield a warning
493 corrupt cookies file should yield a warning
494
494
495 $ cat > $TESTTMP/cookies.txt << EOF
495 $ cat > $TESTTMP/cookies.txt << EOF
496 > bad format
496 > bad format
497 > EOF
497 > EOF
498
498
499 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
499 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
500 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
500 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
501 56f9bc90cce6
501 56f9bc90cce6
502
502
503 $ killdaemons.py
503 $ killdaemons.py
504
504
505 Create dummy authentication handler that looks for cookies. It doesn't do anything
505 Create dummy authentication handler that looks for cookies. It doesn't do anything
506 useful. It just raises an HTTP 500 with details about the Cookie request header.
506 useful. It just raises an HTTP 500 with details about the Cookie request header.
507 We raise HTTP 500 because its message is printed in the abort message.
507 We raise HTTP 500 because its message is printed in the abort message.
508
508
509 $ cat > cookieauth.py << EOF
509 $ cat > cookieauth.py << EOF
510 > from mercurial import util
510 > from mercurial import util
511 > from mercurial.hgweb import common
511 > from mercurial.hgweb import common
512 > def perform_authentication(hgweb, req, op):
512 > def perform_authentication(hgweb, req, op):
513 > cookie = req.env.get('HTTP_COOKIE')
513 > cookie = req.headers.get('Cookie')
514 > if not cookie:
514 > if not cookie:
515 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
515 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
516 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
516 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
517 > def extsetup():
517 > def extsetup():
518 > common.permhooks.insert(0, perform_authentication)
518 > common.permhooks.insert(0, perform_authentication)
519 > EOF
519 > EOF
520
520
521 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
521 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
522 $ cat pid > $DAEMON_PIDS
522 $ cat pid > $DAEMON_PIDS
523
523
524 Request without cookie sent should fail due to lack of cookie
524 Request without cookie sent should fail due to lack of cookie
525
525
526 $ hg id http://localhost:$HGPORT
526 $ hg id http://localhost:$HGPORT
527 abort: HTTP Error 500: no-cookie
527 abort: HTTP Error 500: no-cookie
528 [255]
528 [255]
529
529
530 Populate a cookies file
530 Populate a cookies file
531
531
532 $ cat > cookies.txt << EOF
532 $ cat > cookies.txt << EOF
533 > # HTTP Cookie File
533 > # HTTP Cookie File
534 > # Expiration is 2030-01-01 at midnight
534 > # Expiration is 2030-01-01 at midnight
535 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
535 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
536 > EOF
536 > EOF
537
537
538 Should not send a cookie for another domain
538 Should not send a cookie for another domain
539
539
540 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
540 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
541 abort: HTTP Error 500: no-cookie
541 abort: HTTP Error 500: no-cookie
542 [255]
542 [255]
543
543
544 Add a cookie entry for our test server and verify it is sent
544 Add a cookie entry for our test server and verify it is sent
545
545
546 $ cat >> cookies.txt << EOF
546 $ cat >> cookies.txt << EOF
547 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
547 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
548 > EOF
548 > EOF
549
549
550 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
550 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
551 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
551 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
552 [255]
552 [255]
@@ -1,464 +1,464
1 #testcases sshv1 sshv2
1 #testcases sshv1 sshv2
2
2
3 #if sshv2
3 #if sshv2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [experimental]
5 > [experimental]
6 > sshpeer.advertise-v2 = true
6 > sshpeer.advertise-v2 = true
7 > sshserver.support-v2 = true
7 > sshserver.support-v2 = true
8 > EOF
8 > EOF
9 #endif
9 #endif
10
10
11 This file contains testcases that tend to be related to the wire protocol part
11 This file contains testcases that tend to be related to the wire protocol part
12 of largefiles.
12 of largefiles.
13
13
14 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
14 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
15 $ mkdir "${USERCACHE}"
15 $ mkdir "${USERCACHE}"
16 $ cat >> $HGRCPATH <<EOF
16 $ cat >> $HGRCPATH <<EOF
17 > [extensions]
17 > [extensions]
18 > largefiles=
18 > largefiles=
19 > purge=
19 > purge=
20 > rebase=
20 > rebase=
21 > transplant=
21 > transplant=
22 > [phases]
22 > [phases]
23 > publish=False
23 > publish=False
24 > [largefiles]
24 > [largefiles]
25 > minsize=2
25 > minsize=2
26 > patterns=glob:**.dat
26 > patterns=glob:**.dat
27 > usercache=${USERCACHE}
27 > usercache=${USERCACHE}
28 > [web]
28 > [web]
29 > allow_archive = zip
29 > allow_archive = zip
30 > [hooks]
30 > [hooks]
31 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
31 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
32 > EOF
32 > EOF
33
33
34
34
35 #if serve
35 #if serve
36 vanilla clients not locked out from largefiles servers on vanilla repos
36 vanilla clients not locked out from largefiles servers on vanilla repos
37 $ mkdir r1
37 $ mkdir r1
38 $ cd r1
38 $ cd r1
39 $ hg init
39 $ hg init
40 $ echo c1 > f1
40 $ echo c1 > f1
41 $ hg add f1
41 $ hg add f1
42 $ hg commit -m "m1"
42 $ hg commit -m "m1"
43 Invoking status precommit hook
43 Invoking status precommit hook
44 A f1
44 A f1
45 $ cd ..
45 $ cd ..
46 $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
46 $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
47 $ cat hg.pid >> $DAEMON_PIDS
47 $ cat hg.pid >> $DAEMON_PIDS
48 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
48 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
49 requesting all changes
49 requesting all changes
50 adding changesets
50 adding changesets
51 adding manifests
51 adding manifests
52 adding file changes
52 adding file changes
53 added 1 changesets with 1 changes to 1 files
53 added 1 changesets with 1 changes to 1 files
54 new changesets b6eb3a2e2efe
54 new changesets b6eb3a2e2efe
55 updating to branch default
55 updating to branch default
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57
57
58 largefiles clients still work with vanilla servers
58 largefiles clients still work with vanilla servers
59 $ hg serve --config extensions.largefiles=! -R r1 -d -p $HGPORT1 --pid-file hg.pid
59 $ hg serve --config extensions.largefiles=! -R r1 -d -p $HGPORT1 --pid-file hg.pid
60 $ cat hg.pid >> $DAEMON_PIDS
60 $ cat hg.pid >> $DAEMON_PIDS
61 $ hg clone http://localhost:$HGPORT1 r3
61 $ hg clone http://localhost:$HGPORT1 r3
62 requesting all changes
62 requesting all changes
63 adding changesets
63 adding changesets
64 adding manifests
64 adding manifests
65 adding file changes
65 adding file changes
66 added 1 changesets with 1 changes to 1 files
66 added 1 changesets with 1 changes to 1 files
67 new changesets b6eb3a2e2efe
67 new changesets b6eb3a2e2efe
68 updating to branch default
68 updating to branch default
69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 #endif
70 #endif
71
71
72 vanilla clients locked out from largefiles http repos
72 vanilla clients locked out from largefiles http repos
73 $ mkdir r4
73 $ mkdir r4
74 $ cd r4
74 $ cd r4
75 $ hg init
75 $ hg init
76 $ echo c1 > f1
76 $ echo c1 > f1
77 $ hg add --large f1
77 $ hg add --large f1
78 $ hg commit -m "m1"
78 $ hg commit -m "m1"
79 Invoking status precommit hook
79 Invoking status precommit hook
80 A f1
80 A f1
81 $ cd ..
81 $ cd ..
82
82
83 largefiles can be pushed locally (issue3583)
83 largefiles can be pushed locally (issue3583)
84 $ hg init dest
84 $ hg init dest
85 $ cd r4
85 $ cd r4
86 $ hg outgoing ../dest
86 $ hg outgoing ../dest
87 comparing with ../dest
87 comparing with ../dest
88 searching for changes
88 searching for changes
89 changeset: 0:639881c12b4c
89 changeset: 0:639881c12b4c
90 tag: tip
90 tag: tip
91 user: test
91 user: test
92 date: Thu Jan 01 00:00:00 1970 +0000
92 date: Thu Jan 01 00:00:00 1970 +0000
93 summary: m1
93 summary: m1
94
94
95 $ hg push ../dest
95 $ hg push ../dest
96 pushing to ../dest
96 pushing to ../dest
97 searching for changes
97 searching for changes
98 adding changesets
98 adding changesets
99 adding manifests
99 adding manifests
100 adding file changes
100 adding file changes
101 added 1 changesets with 1 changes to 1 files
101 added 1 changesets with 1 changes to 1 files
102
102
103 exit code with nothing outgoing (issue3611)
103 exit code with nothing outgoing (issue3611)
104 $ hg outgoing ../dest
104 $ hg outgoing ../dest
105 comparing with ../dest
105 comparing with ../dest
106 searching for changes
106 searching for changes
107 no changes found
107 no changes found
108 [1]
108 [1]
109 $ cd ..
109 $ cd ..
110
110
111 #if serve
111 #if serve
112 $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
112 $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
113 $ cat hg.pid >> $DAEMON_PIDS
113 $ cat hg.pid >> $DAEMON_PIDS
114 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
114 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
115 abort: remote error:
115 abort: remote error:
116
116
117 This repository uses the largefiles extension.
117 This repository uses the largefiles extension.
118
118
119 Please enable it in your Mercurial config file.
119 Please enable it in your Mercurial config file.
120 [255]
120 [255]
121
121
122 used all HGPORTs, kill all daemons
122 used all HGPORTs, kill all daemons
123 $ killdaemons.py
123 $ killdaemons.py
124 #endif
124 #endif
125
125
126 vanilla clients locked out from largefiles ssh repos
126 vanilla clients locked out from largefiles ssh repos
127 $ hg --config extensions.largefiles=! clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
127 $ hg --config extensions.largefiles=! clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
128 remote:
128 remote:
129 remote: This repository uses the largefiles extension.
129 remote: This repository uses the largefiles extension.
130 remote:
130 remote:
131 remote: Please enable it in your Mercurial config file.
131 remote: Please enable it in your Mercurial config file.
132 remote:
132 remote:
133 remote: -
133 remote: -
134 abort: remote error
134 abort: remote error
135 (check previous remote output)
135 (check previous remote output)
136 [255]
136 [255]
137
137
138 #if serve
138 #if serve
139
139
140 largefiles clients refuse to push largefiles repos to vanilla servers
140 largefiles clients refuse to push largefiles repos to vanilla servers
141 $ mkdir r6
141 $ mkdir r6
142 $ cd r6
142 $ cd r6
143 $ hg init
143 $ hg init
144 $ echo c1 > f1
144 $ echo c1 > f1
145 $ hg add f1
145 $ hg add f1
146 $ hg commit -m "m1"
146 $ hg commit -m "m1"
147 Invoking status precommit hook
147 Invoking status precommit hook
148 A f1
148 A f1
149 $ cat >> .hg/hgrc <<!
149 $ cat >> .hg/hgrc <<!
150 > [web]
150 > [web]
151 > push_ssl = false
151 > push_ssl = false
152 > allow_push = *
152 > allow_push = *
153 > !
153 > !
154 $ cd ..
154 $ cd ..
155 $ hg clone r6 r7
155 $ hg clone r6 r7
156 updating to branch default
156 updating to branch default
157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 $ cd r7
158 $ cd r7
159 $ echo c2 > f2
159 $ echo c2 > f2
160 $ hg add --large f2
160 $ hg add --large f2
161 $ hg commit -m "m2"
161 $ hg commit -m "m2"
162 Invoking status precommit hook
162 Invoking status precommit hook
163 A f2
163 A f2
164 $ hg verify --large
164 $ hg verify --large
165 checking changesets
165 checking changesets
166 checking manifests
166 checking manifests
167 crosschecking files in changesets and manifests
167 crosschecking files in changesets and manifests
168 checking files
168 checking files
169 2 files, 2 changesets, 2 total revisions
169 2 files, 2 changesets, 2 total revisions
170 searching 1 changesets for largefiles
170 searching 1 changesets for largefiles
171 verified existence of 1 revisions of 1 largefiles
171 verified existence of 1 revisions of 1 largefiles
172 $ hg serve --config extensions.largefiles=! -R ../r6 -d -p $HGPORT --pid-file ../hg.pid
172 $ hg serve --config extensions.largefiles=! -R ../r6 -d -p $HGPORT --pid-file ../hg.pid
173 $ cat ../hg.pid >> $DAEMON_PIDS
173 $ cat ../hg.pid >> $DAEMON_PIDS
174 $ hg push http://localhost:$HGPORT
174 $ hg push http://localhost:$HGPORT
175 pushing to http://localhost:$HGPORT/
175 pushing to http://localhost:$HGPORT/
176 searching for changes
176 searching for changes
177 abort: http://localhost:$HGPORT/ does not appear to be a largefile store
177 abort: http://localhost:$HGPORT/ does not appear to be a largefile store
178 [255]
178 [255]
179 $ cd ..
179 $ cd ..
180
180
181 putlfile errors are shown (issue3123)
181 putlfile errors are shown (issue3123)
182 Corrupt the cached largefile in r7 and move it out of the servers usercache
182 Corrupt the cached largefile in r7 and move it out of the servers usercache
183 $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 .
183 $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 .
184 $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
184 $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
185 $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8"
185 $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8"
186 $ hg init empty
186 $ hg init empty
187 $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
187 $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
188 > --config 'web.allow_push=*' --config web.push_ssl=False
188 > --config 'web.allow_push=*' --config web.push_ssl=False
189 $ cat hg.pid >> $DAEMON_PIDS
189 $ cat hg.pid >> $DAEMON_PIDS
190 $ hg push -R r7 http://localhost:$HGPORT1
190 $ hg push -R r7 http://localhost:$HGPORT1
191 pushing to http://localhost:$HGPORT1/
191 pushing to http://localhost:$HGPORT1/
192 searching for changes
192 searching for changes
193 remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
193 remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
194 abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/
194 abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/
195 [255]
195 [255]
196 $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
196 $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
197 Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic
197 Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic
198 $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
198 $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
199 $ hg push -R r7 http://localhost:$HGPORT1
199 $ hg push -R r7 http://localhost:$HGPORT1
200 pushing to http://localhost:$HGPORT1/
200 pushing to http://localhost:$HGPORT1/
201 searching for changes
201 searching for changes
202 remote: adding changesets
202 remote: adding changesets
203 remote: adding manifests
203 remote: adding manifests
204 remote: adding file changes
204 remote: adding file changes
205 remote: added 2 changesets with 2 changes to 2 files
205 remote: added 2 changesets with 2 changes to 2 files
206 $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
206 $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
207 server side corruption
207 server side corruption
208 $ rm -rf empty
208 $ rm -rf empty
209
209
210 Push a largefiles repository to a served empty repository
210 Push a largefiles repository to a served empty repository
211 $ hg init r8
211 $ hg init r8
212 $ echo c3 > r8/f1
212 $ echo c3 > r8/f1
213 $ hg add --large r8/f1 -R r8
213 $ hg add --large r8/f1 -R r8
214 $ hg commit -m "m1" -R r8
214 $ hg commit -m "m1" -R r8
215 Invoking status precommit hook
215 Invoking status precommit hook
216 A f1
216 A f1
217 $ hg init empty
217 $ hg init empty
218 $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
218 $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
219 > --config 'web.allow_push=*' --config web.push_ssl=False
219 > --config 'web.allow_push=*' --config web.push_ssl=False
220 $ cat hg.pid >> $DAEMON_PIDS
220 $ cat hg.pid >> $DAEMON_PIDS
221 $ rm "${USERCACHE}"/*
221 $ rm "${USERCACHE}"/*
222 $ hg push -R r8 http://localhost:$HGPORT2/#default
222 $ hg push -R r8 http://localhost:$HGPORT2/#default
223 pushing to http://localhost:$HGPORT2/
223 pushing to http://localhost:$HGPORT2/
224 searching for changes
224 searching for changes
225 remote: adding changesets
225 remote: adding changesets
226 remote: adding manifests
226 remote: adding manifests
227 remote: adding file changes
227 remote: adding file changes
228 remote: added 1 changesets with 1 changes to 1 files
228 remote: added 1 changesets with 1 changes to 1 files
229 $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
229 $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
230 $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
230 $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
231
231
232 Clone over http, no largefiles pulled on clone.
232 Clone over http, no largefiles pulled on clone.
233
233
234 $ hg clone http://localhost:$HGPORT2/#default http-clone -U
234 $ hg clone http://localhost:$HGPORT2/#default http-clone -U
235 adding changesets
235 adding changesets
236 adding manifests
236 adding manifests
237 adding file changes
237 adding file changes
238 added 1 changesets with 1 changes to 1 files
238 added 1 changesets with 1 changes to 1 files
239 new changesets cf03e5bb9936
239 new changesets cf03e5bb9936
240
240
241 Archive contains largefiles
241 Archive contains largefiles
242 >>> import os
242 >>> import os
243 >>> import urllib2
243 >>> import urllib2
244 >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
244 >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
245 >>> with open('archive.zip', 'w') as f:
245 >>> with open('archive.zip', 'w') as f:
246 ... f.write(urllib2.urlopen(u).read())
246 ... f.write(urllib2.urlopen(u).read())
247 $ unzip -t archive.zip
247 $ unzip -t archive.zip
248 Archive: archive.zip
248 Archive: archive.zip
249 testing: empty-default/.hg_archival.txt*OK (glob)
249 testing: empty-default/.hg_archival.txt*OK (glob)
250 testing: empty-default/f1*OK (glob)
250 testing: empty-default/f1*OK (glob)
251 No errors detected in compressed data of archive.zip.
251 No errors detected in compressed data of archive.zip.
252
252
253 test 'verify' with remotestore:
253 test 'verify' with remotestore:
254
254
255 $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90
255 $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90
256 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
256 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
257 $ hg -R http-clone verify --large --lfa
257 $ hg -R http-clone verify --large --lfa
258 checking changesets
258 checking changesets
259 checking manifests
259 checking manifests
260 crosschecking files in changesets and manifests
260 crosschecking files in changesets and manifests
261 checking files
261 checking files
262 1 files, 1 changesets, 1 total revisions
262 1 files, 1 changesets, 1 total revisions
263 searching 1 changesets for largefiles
263 searching 1 changesets for largefiles
264 changeset 0:cf03e5bb9936: f1 missing
264 changeset 0:cf03e5bb9936: f1 missing
265 verified existence of 1 revisions of 1 largefiles
265 verified existence of 1 revisions of 1 largefiles
266 [1]
266 [1]
267 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
267 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
268 $ hg -R http-clone -q verify --large --lfa
268 $ hg -R http-clone -q verify --large --lfa
269
269
270 largefiles pulled on update - a largefile missing on the server:
270 largefiles pulled on update - a largefile missing on the server:
271 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
271 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
272 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
272 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
273 getting changed largefiles
273 getting changed largefiles
274 f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/
274 f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/
275 0 largefiles updated, 0 removed
275 0 largefiles updated, 0 removed
276 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 $ hg -R http-clone st
277 $ hg -R http-clone st
278 ! f1
278 ! f1
279 $ hg -R http-clone up -Cqr null
279 $ hg -R http-clone up -Cqr null
280
280
281 largefiles pulled on update - a largefile corrupted on the server:
281 largefiles pulled on update - a largefile corrupted on the server:
282 $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90
282 $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90
283 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
283 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
284 getting changed largefiles
284 getting changed largefiles
285 f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27)
285 f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27)
286 0 largefiles updated, 0 removed
286 0 largefiles updated, 0 removed
287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 $ hg -R http-clone st
288 $ hg -R http-clone st
289 ! f1
289 ! f1
290 $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
290 $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
291 $ [ ! -f http-clone/f1 ]
291 $ [ ! -f http-clone/f1 ]
292 $ [ ! -f http-clone-usercache ]
292 $ [ ! -f http-clone-usercache ]
293 $ hg -R http-clone verify --large --lfc
293 $ hg -R http-clone verify --large --lfc
294 checking changesets
294 checking changesets
295 checking manifests
295 checking manifests
296 crosschecking files in changesets and manifests
296 crosschecking files in changesets and manifests
297 checking files
297 checking files
298 1 files, 1 changesets, 1 total revisions
298 1 files, 1 changesets, 1 total revisions
299 searching 1 changesets for largefiles
299 searching 1 changesets for largefiles
300 verified contents of 1 revisions of 1 largefiles
300 verified contents of 1 revisions of 1 largefiles
301 $ hg -R http-clone up -Cqr null
301 $ hg -R http-clone up -Cqr null
302
302
303 largefiles pulled on update - no server side problems:
303 largefiles pulled on update - no server side problems:
304 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
304 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
305 $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache --config progress.debug=true
305 $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache --config progress.debug=true
306 resolving manifests
306 resolving manifests
307 branchmerge: False, force: False, partial: False
307 branchmerge: False, force: False, partial: False
308 ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
308 ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
309 .hglf/f1: remote created -> g
309 .hglf/f1: remote created -> g
310 getting .hglf/f1
310 getting .hglf/f1
311 updating: .hglf/f1 1/1 files (100.00%)
311 updating: .hglf/f1 1/1 files (100.00%)
312 getting changed largefiles
312 getting changed largefiles
313 using http://localhost:$HGPORT2/
313 using http://localhost:$HGPORT2/
314 sending capabilities command
314 sending capabilities command
315 sending batch command
315 sending batch command
316 getting largefiles: 0/1 files (0.00%)
316 getting largefiles: 0/1 files (0.00%)
317 getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90
317 getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90
318 sending getlfile command
318 sending getlfile command
319 found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store
319 found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store
320 1 largefiles updated, 0 removed
320 1 largefiles updated, 0 removed
321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
322
322
323 $ ls http-clone-usercache/*
323 $ ls http-clone-usercache/*
324 http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90
324 http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90
325
325
326 $ rm -rf empty http-clone*
326 $ rm -rf empty http-clone*
327
327
328 used all HGPORTs, kill all daemons
328 used all HGPORTs, kill all daemons
329 $ killdaemons.py
329 $ killdaemons.py
330
330
331 largefiles should batch verify remote calls
331 largefiles should batch verify remote calls
332
332
333 $ hg init batchverifymain
333 $ hg init batchverifymain
334 $ cd batchverifymain
334 $ cd batchverifymain
335 $ echo "aaa" >> a
335 $ echo "aaa" >> a
336 $ hg add --large a
336 $ hg add --large a
337 $ hg commit -m "a"
337 $ hg commit -m "a"
338 Invoking status precommit hook
338 Invoking status precommit hook
339 A a
339 A a
340 $ echo "bbb" >> b
340 $ echo "bbb" >> b
341 $ hg add --large b
341 $ hg add --large b
342 $ hg commit -m "b"
342 $ hg commit -m "b"
343 Invoking status precommit hook
343 Invoking status precommit hook
344 A b
344 A b
345 $ cd ..
345 $ cd ..
346 $ hg serve -R batchverifymain -d -p $HGPORT --pid-file hg.pid \
346 $ hg serve -R batchverifymain -d -p $HGPORT --pid-file hg.pid \
347 > -A access.log
347 > -A access.log
348 $ cat hg.pid >> $DAEMON_PIDS
348 $ cat hg.pid >> $DAEMON_PIDS
349 $ hg clone --noupdate http://localhost:$HGPORT batchverifyclone
349 $ hg clone --noupdate http://localhost:$HGPORT batchverifyclone
350 requesting all changes
350 requesting all changes
351 adding changesets
351 adding changesets
352 adding manifests
352 adding manifests
353 adding file changes
353 adding file changes
354 added 2 changesets with 2 changes to 2 files
354 added 2 changesets with 2 changes to 2 files
355 new changesets 567253b0f523:04d19c27a332
355 new changesets 567253b0f523:04d19c27a332
356 $ hg -R batchverifyclone verify --large --lfa
356 $ hg -R batchverifyclone verify --large --lfa
357 checking changesets
357 checking changesets
358 checking manifests
358 checking manifests
359 crosschecking files in changesets and manifests
359 crosschecking files in changesets and manifests
360 checking files
360 checking files
361 2 files, 2 changesets, 2 total revisions
361 2 files, 2 changesets, 2 total revisions
362 searching 2 changesets for largefiles
362 searching 2 changesets for largefiles
363 verified existence of 2 revisions of 2 largefiles
363 verified existence of 2 revisions of 2 largefiles
364 $ tail -1 access.log
364 $ tail -1 access.log
365 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3D972a1a11f19934401291cc99117ec614933374ce%3Bstatlfile+sha%3Dc801c9cfe94400963fcb683246217d5db77f9a9a x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
365 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3D972a1a11f19934401291cc99117ec614933374ce%3Bstatlfile+sha%3Dc801c9cfe94400963fcb683246217d5db77f9a9a x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
366 $ hg -R batchverifyclone update
366 $ hg -R batchverifyclone update
367 getting changed largefiles
367 getting changed largefiles
368 2 largefiles updated, 0 removed
368 2 largefiles updated, 0 removed
369 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
369 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
370
370
371 Clear log file before next test
371 Clear log file before next test
372
372
373 $ printf "" > access.log
373 $ printf "" > access.log
374
374
375 Verify should check file on remote server only when file is not
375 Verify should check file on remote server only when file is not
376 available locally.
376 available locally.
377
377
378 $ echo "ccc" >> batchverifymain/c
378 $ echo "ccc" >> batchverifymain/c
379 $ hg -R batchverifymain status
379 $ hg -R batchverifymain status
380 ? c
380 ? c
381 $ hg -R batchverifymain add --large batchverifymain/c
381 $ hg -R batchverifymain add --large batchverifymain/c
382 $ hg -R batchverifymain commit -m "c"
382 $ hg -R batchverifymain commit -m "c"
383 Invoking status precommit hook
383 Invoking status precommit hook
384 A c
384 A c
385 $ hg -R batchverifyclone pull
385 $ hg -R batchverifyclone pull
386 pulling from http://localhost:$HGPORT/
386 pulling from http://localhost:$HGPORT/
387 searching for changes
387 searching for changes
388 adding changesets
388 adding changesets
389 adding manifests
389 adding manifests
390 adding file changes
390 adding file changes
391 added 1 changesets with 1 changes to 1 files
391 added 1 changesets with 1 changes to 1 files
392 new changesets 6bba8cb6935d
392 new changesets 6bba8cb6935d
393 (run 'hg update' to get a working copy)
393 (run 'hg update' to get a working copy)
394 $ hg -R batchverifyclone verify --lfa
394 $ hg -R batchverifyclone verify --lfa
395 checking changesets
395 checking changesets
396 checking manifests
396 checking manifests
397 crosschecking files in changesets and manifests
397 crosschecking files in changesets and manifests
398 checking files
398 checking files
399 3 files, 3 changesets, 3 total revisions
399 3 files, 3 changesets, 3 total revisions
400 searching 3 changesets for largefiles
400 searching 3 changesets for largefiles
401 verified existence of 3 revisions of 3 largefiles
401 verified existence of 3 revisions of 3 largefiles
402 $ tail -1 access.log
402 $ tail -1 access.log
403 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3Dc8559c3c9cfb42131794b7d8009230403b9b454c x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
403 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3Dc8559c3c9cfb42131794b7d8009230403b9b454c x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
404
404
405 $ killdaemons.py
405 $ killdaemons.py
406
406
407 largefiles should not ask for password again after successful authorization
407 largefiles should not ask for password again after successful authorization
408
408
409 $ hg init credentialmain
409 $ hg init credentialmain
410 $ cd credentialmain
410 $ cd credentialmain
411 $ echo "aaa" >> a
411 $ echo "aaa" >> a
412 $ hg add --large a
412 $ hg add --large a
413 $ hg commit -m "a"
413 $ hg commit -m "a"
414 Invoking status precommit hook
414 Invoking status precommit hook
415 A a
415 A a
416
416
417 Before running server clear the user cache to force clone to download
417 Before running server clear the user cache to force clone to download
418 a large file from the server rather than to get it from the cache
418 a large file from the server rather than to get it from the cache
419
419
420 $ rm "${USERCACHE}"/*
420 $ rm "${USERCACHE}"/*
421
421
422 $ cd ..
422 $ cd ..
423 $ cat << EOT > userpass.py
423 $ cat << EOT > userpass.py
424 > import base64
424 > import base64
425 > from mercurial.hgweb import common
425 > from mercurial.hgweb import common
426 > def perform_authentication(hgweb, req, op):
426 > def perform_authentication(hgweb, req, op):
427 > auth = req.env.get('HTTP_AUTHORIZATION')
427 > auth = req.headers.get('Authorization')
428 > if not auth:
428 > if not auth:
429 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
429 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
430 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
430 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
431 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
431 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
432 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
432 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
433 > def extsetup():
433 > def extsetup():
434 > common.permhooks.insert(0, perform_authentication)
434 > common.permhooks.insert(0, perform_authentication)
435 > EOT
435 > EOT
436 $ hg serve --config extensions.x=userpass.py -R credentialmain \
436 $ hg serve --config extensions.x=userpass.py -R credentialmain \
437 > -d -p $HGPORT --pid-file hg.pid -A access.log
437 > -d -p $HGPORT --pid-file hg.pid -A access.log
438 $ cat hg.pid >> $DAEMON_PIDS
438 $ cat hg.pid >> $DAEMON_PIDS
439 $ cat << EOF > get_pass.py
439 $ cat << EOF > get_pass.py
440 > import getpass
440 > import getpass
441 > def newgetpass(arg):
441 > def newgetpass(arg):
442 > return "pass"
442 > return "pass"
443 > getpass.getpass = newgetpass
443 > getpass.getpass = newgetpass
444 > EOF
444 > EOF
445 $ hg clone --config ui.interactive=true --config extensions.getpass=get_pass.py \
445 $ hg clone --config ui.interactive=true --config extensions.getpass=get_pass.py \
446 > http://user@localhost:$HGPORT credentialclone
446 > http://user@localhost:$HGPORT credentialclone
447 http authorization required for http://localhost:$HGPORT/
447 http authorization required for http://localhost:$HGPORT/
448 realm: mercurial
448 realm: mercurial
449 user: user
449 user: user
450 password: requesting all changes
450 password: requesting all changes
451 adding changesets
451 adding changesets
452 adding manifests
452 adding manifests
453 adding file changes
453 adding file changes
454 added 1 changesets with 1 changes to 1 files
454 added 1 changesets with 1 changes to 1 files
455 new changesets 567253b0f523
455 new changesets 567253b0f523
456 updating to branch default
456 updating to branch default
457 getting changed largefiles
457 getting changed largefiles
458 1 largefiles updated, 0 removed
458 1 largefiles updated, 0 removed
459 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
460
460
461 $ killdaemons.py
461 $ killdaemons.py
462 $ rm hg.pid access.log
462 $ rm hg.pid access.log
463
463
464 #endif
464 #endif
General Comments 0
You need to be logged in to leave comments. Login now