Show More
@@ -1,320 +1,330 | |||
|
1 | 1 | # hgweb/hgweb_mod.py - Web interface for a repository. |
|
2 | 2 | # |
|
3 | 3 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
4 | 4 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
5 | 5 | # |
|
6 | 6 | # This software may be used and distributed according to the terms of the |
|
7 | 7 | # GNU General Public License version 2 or any later version. |
|
8 | 8 | |
|
9 | import os | |
|
9 | import os, zlib | |
|
10 | 10 | from mercurial import ui, hg, hook, error, encoding, templater, wireproto |
|
11 | 11 | from common import get_mtime, ErrorResponse, permhooks |
|
12 | 12 | from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR |
|
13 | 13 | from request import wsgirequest |
|
14 | 14 | import webcommands, protocol, webutil |
|
15 | 15 | |
|
16 | 16 | perms = { |
|
17 | 17 | 'changegroup': 'pull', |
|
18 | 18 | 'changegroupsubset': 'pull', |
|
19 | 19 | 'stream_out': 'pull', |
|
20 | 20 | 'listkeys': 'pull', |
|
21 | 21 | 'unbundle': 'push', |
|
22 | 22 | 'pushkey': 'push', |
|
23 | 23 | } |
|
24 | 24 | |
|
25 | HGTYPE = 'application/mercurial-0.1' | |
|
25 | 26 | class webproto(object): |
|
26 | 27 | def __init__(self, req): |
|
27 | 28 | self.req = req |
|
28 | 29 | self.response = '' |
|
29 | 30 | def getargs(self, args): |
|
30 | 31 | data = {} |
|
31 | 32 | keys = args.split() |
|
32 | 33 | for k in keys: |
|
33 | 34 | if k == '*': |
|
34 | 35 | star = {} |
|
35 | 36 | for key in self.req.form.keys(): |
|
36 | 37 | if key not in keys: |
|
37 | 38 | star[key] = self.req.form[key][0] |
|
38 | 39 | data['*'] = star |
|
39 | 40 | else: |
|
40 | 41 | data[k] = self.req.form[k][0] |
|
41 | 42 | return [data[k] for k in keys] |
|
43 | def sendchangegroup(self, cg): | |
|
44 | self.req.respond(HTTP_OK, HGTYPE) | |
|
45 | z = zlib.compressobj() | |
|
46 | while 1: | |
|
47 | chunk = cg.read(4096) | |
|
48 | if not chunk: | |
|
49 | break | |
|
50 | self.req.write(z.compress(chunk)) | |
|
51 | self.req.write(z.flush()) | |
|
52 | ||
|
42 | 53 | def respond(self, s): |
|
43 | HGTYPE = 'application/mercurial-0.1' | |
|
44 | 54 | self.req.respond(HTTP_OK, HGTYPE, length=len(s)) |
|
45 | 55 | self.response = s |
|
46 | 56 | |
|
47 | 57 | def callproto(repo, req, cmd): |
|
48 | 58 | p = webproto(req) |
|
49 | 59 | r = wireproto.dispatch(repo, p, cmd) |
|
50 | 60 | yield p.response |
|
51 | 61 | |
|
52 | 62 | class hgweb(object): |
|
53 | 63 | def __init__(self, repo, name=None, baseui=None): |
|
54 | 64 | if isinstance(repo, str): |
|
55 | 65 | if baseui: |
|
56 | 66 | u = baseui.copy() |
|
57 | 67 | else: |
|
58 | 68 | u = ui.ui() |
|
59 | 69 | self.repo = hg.repository(u, repo) |
|
60 | 70 | else: |
|
61 | 71 | self.repo = repo |
|
62 | 72 | |
|
63 | 73 | self.repo.ui.setconfig('ui', 'report_untrusted', 'off') |
|
64 | 74 | self.repo.ui.setconfig('ui', 'interactive', 'off') |
|
65 | 75 | hook.redirect(True) |
|
66 | 76 | self.mtime = -1 |
|
67 | 77 | self.reponame = name |
|
68 | 78 | self.archives = 'zip', 'gz', 'bz2' |
|
69 | 79 | self.stripecount = 1 |
|
70 | 80 | # a repo owner may set web.templates in .hg/hgrc to get any file |
|
71 | 81 | # readable by the user running the CGI script |
|
72 | 82 | self.templatepath = self.config('web', 'templates') |
|
73 | 83 | |
|
74 | 84 | # The CGI scripts are often run by a user different from the repo owner. |
|
75 | 85 | # Trust the settings from the .hg/hgrc files by default. |
|
76 | 86 | def config(self, section, name, default=None, untrusted=True): |
|
77 | 87 | return self.repo.ui.config(section, name, default, |
|
78 | 88 | untrusted=untrusted) |
|
79 | 89 | |
|
80 | 90 | def configbool(self, section, name, default=False, untrusted=True): |
|
81 | 91 | return self.repo.ui.configbool(section, name, default, |
|
82 | 92 | untrusted=untrusted) |
|
83 | 93 | |
|
84 | 94 | def configlist(self, section, name, default=None, untrusted=True): |
|
85 | 95 | return self.repo.ui.configlist(section, name, default, |
|
86 | 96 | untrusted=untrusted) |
|
87 | 97 | |
|
88 | 98 | def refresh(self, request=None): |
|
89 | 99 | if request: |
|
90 | 100 | self.repo.ui.environ = request.env |
|
91 | 101 | mtime = get_mtime(self.repo.spath) |
|
92 | 102 | if mtime != self.mtime: |
|
93 | 103 | self.mtime = mtime |
|
94 | 104 | self.repo = hg.repository(self.repo.ui, self.repo.root) |
|
95 | 105 | self.maxchanges = int(self.config("web", "maxchanges", 10)) |
|
96 | 106 | self.stripecount = int(self.config("web", "stripes", 1)) |
|
97 | 107 | self.maxshortchanges = int(self.config("web", "maxshortchanges", 60)) |
|
98 | 108 | self.maxfiles = int(self.config("web", "maxfiles", 10)) |
|
99 | 109 | self.allowpull = self.configbool("web", "allowpull", True) |
|
100 | 110 | encoding.encoding = self.config("web", "encoding", |
|
101 | 111 | encoding.encoding) |
|
102 | 112 | |
|
103 | 113 | def run(self): |
|
104 | 114 | if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): |
|
105 | 115 | raise RuntimeError("This function is only intended to be " |
|
106 | 116 | "called while running as a CGI script.") |
|
107 | 117 | import mercurial.hgweb.wsgicgi as wsgicgi |
|
108 | 118 | wsgicgi.launch(self) |
|
109 | 119 | |
|
110 | 120 | def __call__(self, env, respond): |
|
111 | 121 | req = wsgirequest(env, respond) |
|
112 | 122 | return self.run_wsgi(req) |
|
113 | 123 | |
|
114 | 124 | def run_wsgi(self, req): |
|
115 | 125 | |
|
116 | 126 | self.refresh(req) |
|
117 | 127 | |
|
118 | 128 | # work with CGI variables to create coherent structure |
|
119 | 129 | # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME |
|
120 | 130 | |
|
121 | 131 | req.url = req.env['SCRIPT_NAME'] |
|
122 | 132 | if not req.url.endswith('/'): |
|
123 | 133 | req.url += '/' |
|
124 | 134 | if 'REPO_NAME' in req.env: |
|
125 | 135 | req.url += req.env['REPO_NAME'] + '/' |
|
126 | 136 | |
|
127 | 137 | if 'PATH_INFO' in req.env: |
|
128 | 138 | parts = req.env['PATH_INFO'].strip('/').split('/') |
|
129 | 139 | repo_parts = req.env.get('REPO_NAME', '').split('/') |
|
130 | 140 | if parts[:len(repo_parts)] == repo_parts: |
|
131 | 141 | parts = parts[len(repo_parts):] |
|
132 | 142 | query = '/'.join(parts) |
|
133 | 143 | else: |
|
134 | 144 | query = req.env['QUERY_STRING'].split('&', 1)[0] |
|
135 | 145 | query = query.split(';', 1)[0] |
|
136 | 146 | |
|
137 | 147 | # process this if it's a protocol request |
|
138 | 148 | # protocol bits don't need to create any URLs |
|
139 | 149 | # and the clients always use the old URL structure |
|
140 | 150 | |
|
141 | 151 | cmd = req.form.get('cmd', [''])[0] |
|
142 | 152 | if cmd and cmd in protocol.__all__: |
|
143 | 153 | if query: |
|
144 | 154 | raise ErrorResponse(HTTP_NOT_FOUND) |
|
145 | 155 | try: |
|
146 | 156 | if cmd in perms: |
|
147 | 157 | try: |
|
148 | 158 | self.check_perm(req, perms[cmd]) |
|
149 | 159 | except ErrorResponse, inst: |
|
150 | 160 | if cmd == 'unbundle': |
|
151 | 161 | req.drain() |
|
152 | 162 | raise |
|
153 | 163 | if cmd in wireproto.commands: |
|
154 | 164 | return callproto(self.repo, req, cmd) |
|
155 | 165 | method = getattr(protocol, cmd) |
|
156 | 166 | return method(self.repo, req) |
|
157 | 167 | except ErrorResponse, inst: |
|
158 | 168 | req.respond(inst, protocol.HGTYPE) |
|
159 | 169 | if not inst.message: |
|
160 | 170 | return [] |
|
161 | 171 | return '0\n%s\n' % inst.message, |
|
162 | 172 | |
|
163 | 173 | # translate user-visible url structure to internal structure |
|
164 | 174 | |
|
165 | 175 | args = query.split('/', 2) |
|
166 | 176 | if 'cmd' not in req.form and args and args[0]: |
|
167 | 177 | |
|
168 | 178 | cmd = args.pop(0) |
|
169 | 179 | style = cmd.rfind('-') |
|
170 | 180 | if style != -1: |
|
171 | 181 | req.form['style'] = [cmd[:style]] |
|
172 | 182 | cmd = cmd[style + 1:] |
|
173 | 183 | |
|
174 | 184 | # avoid accepting e.g. style parameter as command |
|
175 | 185 | if hasattr(webcommands, cmd): |
|
176 | 186 | req.form['cmd'] = [cmd] |
|
177 | 187 | else: |
|
178 | 188 | cmd = '' |
|
179 | 189 | |
|
180 | 190 | if cmd == 'static': |
|
181 | 191 | req.form['file'] = ['/'.join(args)] |
|
182 | 192 | else: |
|
183 | 193 | if args and args[0]: |
|
184 | 194 | node = args.pop(0) |
|
185 | 195 | req.form['node'] = [node] |
|
186 | 196 | if args: |
|
187 | 197 | req.form['file'] = args |
|
188 | 198 | |
|
189 | 199 | ua = req.env.get('HTTP_USER_AGENT', '') |
|
190 | 200 | if cmd == 'rev' and 'mercurial' in ua: |
|
191 | 201 | req.form['style'] = ['raw'] |
|
192 | 202 | |
|
193 | 203 | if cmd == 'archive': |
|
194 | 204 | fn = req.form['node'][0] |
|
195 | 205 | for type_, spec in self.archive_specs.iteritems(): |
|
196 | 206 | ext = spec[2] |
|
197 | 207 | if fn.endswith(ext): |
|
198 | 208 | req.form['node'] = [fn[:-len(ext)]] |
|
199 | 209 | req.form['type'] = [type_] |
|
200 | 210 | |
|
201 | 211 | # process the web interface request |
|
202 | 212 | |
|
203 | 213 | try: |
|
204 | 214 | tmpl = self.templater(req) |
|
205 | 215 | ctype = tmpl('mimetype', encoding=encoding.encoding) |
|
206 | 216 | ctype = templater.stringify(ctype) |
|
207 | 217 | |
|
208 | 218 | # check read permissions non-static content |
|
209 | 219 | if cmd != 'static': |
|
210 | 220 | self.check_perm(req, None) |
|
211 | 221 | |
|
212 | 222 | if cmd == '': |
|
213 | 223 | req.form['cmd'] = [tmpl.cache['default']] |
|
214 | 224 | cmd = req.form['cmd'][0] |
|
215 | 225 | |
|
216 | 226 | if cmd not in webcommands.__all__: |
|
217 | 227 | msg = 'no such method: %s' % cmd |
|
218 | 228 | raise ErrorResponse(HTTP_BAD_REQUEST, msg) |
|
219 | 229 | elif cmd == 'file' and 'raw' in req.form.get('style', []): |
|
220 | 230 | self.ctype = ctype |
|
221 | 231 | content = webcommands.rawfile(self, req, tmpl) |
|
222 | 232 | else: |
|
223 | 233 | content = getattr(webcommands, cmd)(self, req, tmpl) |
|
224 | 234 | req.respond(HTTP_OK, ctype) |
|
225 | 235 | |
|
226 | 236 | return content |
|
227 | 237 | |
|
228 | 238 | except error.LookupError, err: |
|
229 | 239 | req.respond(HTTP_NOT_FOUND, ctype) |
|
230 | 240 | msg = str(err) |
|
231 | 241 | if 'manifest' not in msg: |
|
232 | 242 | msg = 'revision not found: %s' % err.name |
|
233 | 243 | return tmpl('error', error=msg) |
|
234 | 244 | except (error.RepoError, error.RevlogError), inst: |
|
235 | 245 | req.respond(HTTP_SERVER_ERROR, ctype) |
|
236 | 246 | return tmpl('error', error=str(inst)) |
|
237 | 247 | except ErrorResponse, inst: |
|
238 | 248 | req.respond(inst, ctype) |
|
239 | 249 | return tmpl('error', error=inst.message) |
|
240 | 250 | |
|
241 | 251 | def templater(self, req): |
|
242 | 252 | |
|
243 | 253 | # determine scheme, port and server name |
|
244 | 254 | # this is needed to create absolute urls |
|
245 | 255 | |
|
246 | 256 | proto = req.env.get('wsgi.url_scheme') |
|
247 | 257 | if proto == 'https': |
|
248 | 258 | proto = 'https' |
|
249 | 259 | default_port = "443" |
|
250 | 260 | else: |
|
251 | 261 | proto = 'http' |
|
252 | 262 | default_port = "80" |
|
253 | 263 | |
|
254 | 264 | port = req.env["SERVER_PORT"] |
|
255 | 265 | port = port != default_port and (":" + port) or "" |
|
256 | 266 | urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) |
|
257 | 267 | staticurl = self.config("web", "staticurl") or req.url + 'static/' |
|
258 | 268 | if not staticurl.endswith('/'): |
|
259 | 269 | staticurl += '/' |
|
260 | 270 | |
|
261 | 271 | # some functions for the templater |
|
262 | 272 | |
|
263 | 273 | def header(**map): |
|
264 | 274 | yield tmpl('header', encoding=encoding.encoding, **map) |
|
265 | 275 | |
|
266 | 276 | def footer(**map): |
|
267 | 277 | yield tmpl("footer", **map) |
|
268 | 278 | |
|
269 | 279 | def motd(**map): |
|
270 | 280 | yield self.config("web", "motd", "") |
|
271 | 281 | |
|
272 | 282 | # figure out which style to use |
|
273 | 283 | |
|
274 | 284 | vars = {} |
|
275 | 285 | styles = ( |
|
276 | 286 | req.form.get('style', [None])[0], |
|
277 | 287 | self.config('web', 'style'), |
|
278 | 288 | 'paper', |
|
279 | 289 | ) |
|
280 | 290 | style, mapfile = templater.stylemap(styles, self.templatepath) |
|
281 | 291 | if style == styles[0]: |
|
282 | 292 | vars['style'] = style |
|
283 | 293 | |
|
284 | 294 | start = req.url[-1] == '?' and '&' or '?' |
|
285 | 295 | sessionvars = webutil.sessionvars(vars, start) |
|
286 | 296 | |
|
287 | 297 | if not self.reponame: |
|
288 | 298 | self.reponame = (self.config("web", "name") |
|
289 | 299 | or req.env.get('REPO_NAME') |
|
290 | 300 | or req.url.strip('/') or self.repo.root) |
|
291 | 301 | |
|
292 | 302 | # create the templater |
|
293 | 303 | |
|
294 | 304 | tmpl = templater.templater(mapfile, |
|
295 | 305 | defaults={"url": req.url, |
|
296 | 306 | "staticurl": staticurl, |
|
297 | 307 | "urlbase": urlbase, |
|
298 | 308 | "repo": self.reponame, |
|
299 | 309 | "header": header, |
|
300 | 310 | "footer": footer, |
|
301 | 311 | "motd": motd, |
|
302 | 312 | "sessionvars": sessionvars |
|
303 | 313 | }) |
|
304 | 314 | return tmpl |
|
305 | 315 | |
|
306 | 316 | def archivelist(self, nodeid): |
|
307 | 317 | allowed = self.configlist("web", "allow_archive") |
|
308 | 318 | for i, spec in self.archive_specs.iteritems(): |
|
309 | 319 | if i in allowed or self.configbool("web", "allow" + i): |
|
310 | 320 | yield {"type" : i, "extension" : spec[2], "node" : nodeid} |
|
311 | 321 | |
|
312 | 322 | archive_specs = { |
|
313 | 323 | 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), |
|
314 | 324 | 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), |
|
315 | 325 | 'zip': ('application/zip', 'zip', '.zip', None), |
|
316 | 326 | } |
|
317 | 327 | |
|
318 | 328 | def check_perm(self, req, op): |
|
319 | 329 | for hook in permhooks: |
|
320 | 330 | hook(self, req, op) |
@@ -1,161 +1,124 | |||
|
1 | 1 | # |
|
2 | 2 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
3 | 3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | import cStringIO, zlib, tempfile, errno, os, sys, urllib, copy |
|
9 | 9 | from mercurial import util, streamclone, pushkey |
|
10 | 10 | from mercurial.node import bin, hex |
|
11 | 11 | from mercurial import changegroup as changegroupmod |
|
12 | 12 | from common import ErrorResponse, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR |
|
13 | 13 | |
|
14 | 14 | # __all__ is populated with the allowed commands. Be sure to add to it if |
|
15 | 15 | # you're adding a new command, or the new command won't work. |
|
16 | 16 | |
|
17 | 17 | __all__ = [ |
|
18 | 18 | 'lookup', 'heads', 'branches', 'between', 'changegroup', |
|
19 | 19 | 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out', |
|
20 | 20 | 'branchmap', 'pushkey', 'listkeys' |
|
21 | 21 | ] |
|
22 | 22 | |
|
23 | 23 | HGTYPE = 'application/mercurial-0.1' |
|
24 | 24 | basecaps = 'lookup changegroupsubset branchmap pushkey'.split() |
|
25 | 25 | |
|
26 | def changegroup(repo, req): | |
|
27 | req.respond(HTTP_OK, HGTYPE) | |
|
28 | nodes = [] | |
|
29 | ||
|
30 | if 'roots' in req.form: | |
|
31 | nodes = map(bin, req.form['roots'][0].split(" ")) | |
|
32 | ||
|
33 | z = zlib.compressobj() | |
|
34 | f = repo.changegroup(nodes, 'serve') | |
|
35 | while 1: | |
|
36 | chunk = f.read(4096) | |
|
37 | if not chunk: | |
|
38 | break | |
|
39 | yield z.compress(chunk) | |
|
40 | ||
|
41 | yield z.flush() | |
|
42 | ||
|
43 | def changegroupsubset(repo, req): | |
|
44 | req.respond(HTTP_OK, HGTYPE) | |
|
45 | bases = [] | |
|
46 | heads = [] | |
|
47 | ||
|
48 | if 'bases' in req.form: | |
|
49 | bases = [bin(x) for x in req.form['bases'][0].split(' ')] | |
|
50 | if 'heads' in req.form: | |
|
51 | heads = [bin(x) for x in req.form['heads'][0].split(' ')] | |
|
52 | ||
|
53 | z = zlib.compressobj() | |
|
54 | f = repo.changegroupsubset(bases, heads, 'serve') | |
|
55 | while 1: | |
|
56 | chunk = f.read(4096) | |
|
57 | if not chunk: | |
|
58 | break | |
|
59 | yield z.compress(chunk) | |
|
60 | ||
|
61 | yield z.flush() | |
|
62 | ||
|
63 | 26 | def capabilities(repo, req): |
|
64 | 27 | caps = copy.copy(basecaps) |
|
65 | 28 | if streamclone.allowed(repo.ui): |
|
66 | 29 | caps.append('stream=%d' % repo.changelog.version) |
|
67 | 30 | if changegroupmod.bundlepriority: |
|
68 | 31 | caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority)) |
|
69 | 32 | rsp = ' '.join(caps) |
|
70 | 33 | req.respond(HTTP_OK, HGTYPE, length=len(rsp)) |
|
71 | 34 | yield rsp |
|
72 | 35 | |
|
73 | 36 | def unbundle(repo, req): |
|
74 | 37 | |
|
75 | 38 | proto = req.env.get('wsgi.url_scheme') or 'http' |
|
76 | 39 | their_heads = req.form['heads'][0].split(' ') |
|
77 | 40 | |
|
78 | 41 | def check_heads(): |
|
79 | 42 | heads = map(hex, repo.heads()) |
|
80 | 43 | return their_heads == [hex('force')] or their_heads == heads |
|
81 | 44 | |
|
82 | 45 | # fail early if possible |
|
83 | 46 | if not check_heads(): |
|
84 | 47 | req.drain() |
|
85 | 48 | raise ErrorResponse(HTTP_OK, 'unsynced changes') |
|
86 | 49 | |
|
87 | 50 | # do not lock repo until all changegroup data is |
|
88 | 51 | # streamed. save to temporary file. |
|
89 | 52 | |
|
90 | 53 | fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') |
|
91 | 54 | fp = os.fdopen(fd, 'wb+') |
|
92 | 55 | try: |
|
93 | 56 | length = int(req.env['CONTENT_LENGTH']) |
|
94 | 57 | for s in util.filechunkiter(req, limit=length): |
|
95 | 58 | fp.write(s) |
|
96 | 59 | |
|
97 | 60 | try: |
|
98 | 61 | lock = repo.lock() |
|
99 | 62 | try: |
|
100 | 63 | if not check_heads(): |
|
101 | 64 | raise ErrorResponse(HTTP_OK, 'unsynced changes') |
|
102 | 65 | |
|
103 | 66 | fp.seek(0) |
|
104 | 67 | header = fp.read(6) |
|
105 | 68 | if header.startswith('HG') and not header.startswith('HG10'): |
|
106 | 69 | raise ValueError('unknown bundle version') |
|
107 | 70 | elif header not in changegroupmod.bundletypes: |
|
108 | 71 | raise ValueError('unknown bundle compression type') |
|
109 | 72 | gen = changegroupmod.unbundle(header, fp) |
|
110 | 73 | |
|
111 | 74 | # send addchangegroup output to client |
|
112 | 75 | |
|
113 | 76 | oldio = sys.stdout, sys.stderr |
|
114 | 77 | sys.stderr = sys.stdout = cStringIO.StringIO() |
|
115 | 78 | |
|
116 | 79 | try: |
|
117 | 80 | url = 'remote:%s:%s:%s' % ( |
|
118 | 81 | proto, |
|
119 | 82 | urllib.quote(req.env.get('REMOTE_HOST', '')), |
|
120 | 83 | urllib.quote(req.env.get('REMOTE_USER', ''))) |
|
121 | 84 | try: |
|
122 | 85 | ret = repo.addchangegroup(gen, 'serve', url, lock=lock) |
|
123 | 86 | except util.Abort, inst: |
|
124 | 87 | sys.stdout.write("abort: %s\n" % inst) |
|
125 | 88 | ret = 0 |
|
126 | 89 | finally: |
|
127 | 90 | val = sys.stdout.getvalue() |
|
128 | 91 | sys.stdout, sys.stderr = oldio |
|
129 | 92 | req.respond(HTTP_OK, HGTYPE) |
|
130 | 93 | return '%d\n%s' % (ret, val), |
|
131 | 94 | finally: |
|
132 | 95 | lock.release() |
|
133 | 96 | except ValueError, inst: |
|
134 | 97 | raise ErrorResponse(HTTP_OK, inst) |
|
135 | 98 | except (OSError, IOError), inst: |
|
136 | 99 | error = getattr(inst, 'strerror', 'Unknown error') |
|
137 | 100 | if not isinstance(error, str): |
|
138 | 101 | error = 'Error: %s' % str(error) |
|
139 | 102 | if inst.errno == errno.ENOENT: |
|
140 | 103 | code = HTTP_NOT_FOUND |
|
141 | 104 | else: |
|
142 | 105 | code = HTTP_SERVER_ERROR |
|
143 | 106 | filename = getattr(inst, 'filename', '') |
|
144 | 107 | # Don't send our filesystem layout to the client |
|
145 | 108 | if filename and filename.startswith(repo.root): |
|
146 | 109 | filename = filename[len(repo.root)+1:] |
|
147 | 110 | text = '%s: %s' % (error, filename) |
|
148 | 111 | else: |
|
149 | 112 | text = error.replace(repo.root + os.path.sep, '') |
|
150 | 113 | raise ErrorResponse(code, text) |
|
151 | 114 | finally: |
|
152 | 115 | fp.close() |
|
153 | 116 | os.unlink(tempname) |
|
154 | 117 | |
|
155 | 118 | def stream_out(repo, req): |
|
156 | 119 | req.respond(HTTP_OK, HGTYPE) |
|
157 | 120 | try: |
|
158 | 121 | for chunk in streamclone.stream_out(repo): |
|
159 | 122 | yield chunk |
|
160 | 123 | except streamclone.StreamException, inst: |
|
161 | 124 | yield str(inst) |
@@ -1,207 +1,188 | |||
|
1 | 1 | # sshserver.py - ssh protocol server support for mercurial |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> |
|
5 | 5 | # |
|
6 | 6 | # This software may be used and distributed according to the terms of the |
|
7 | 7 | # GNU General Public License version 2 or any later version. |
|
8 | 8 | |
|
9 | 9 | from i18n import _ |
|
10 | 10 | from node import bin, hex |
|
11 | 11 | import streamclone, util, hook, pushkey, wireproto |
|
12 | 12 | import os, sys, tempfile, urllib, copy |
|
13 | 13 | |
|
14 | 14 | class sshserver(object): |
|
15 | 15 | |
|
16 | 16 | caps = 'unbundle lookup changegroupsubset branchmap pushkey'.split() |
|
17 | 17 | |
|
18 | 18 | def __init__(self, ui, repo): |
|
19 | 19 | self.ui = ui |
|
20 | 20 | self.repo = repo |
|
21 | 21 | self.lock = None |
|
22 | 22 | self.fin = sys.stdin |
|
23 | 23 | self.fout = sys.stdout |
|
24 | 24 | |
|
25 | 25 | hook.redirect(True) |
|
26 | 26 | sys.stdout = sys.stderr |
|
27 | 27 | |
|
28 | 28 | # Prevent insertion/deletion of CRs |
|
29 | 29 | util.set_binary(self.fin) |
|
30 | 30 | util.set_binary(self.fout) |
|
31 | 31 | |
|
32 | 32 | def getargs(self, args): |
|
33 | 33 | data = {} |
|
34 | 34 | keys = args.split() |
|
35 | 35 | count = len(keys) |
|
36 | 36 | for n in xrange(len(keys)): |
|
37 | 37 | argline = self.fin.readline()[:-1] |
|
38 | 38 | arg, l = argline.split() |
|
39 | 39 | val = self.fin.read(int(l)) |
|
40 | 40 | if arg not in keys: |
|
41 | 41 | raise util.Abort("unexpected parameter %r" % arg) |
|
42 | 42 | if arg == '*': |
|
43 | 43 | star = {} |
|
44 | 44 | for n in xrange(int(l)): |
|
45 | 45 | arg, l = argline.split() |
|
46 | 46 | val = self.fin.read(int(l)) |
|
47 | 47 | star[arg] = val |
|
48 | 48 | data['*'] = star |
|
49 | 49 | else: |
|
50 | 50 | data[arg] = val |
|
51 | 51 | return [data[k] for k in keys] |
|
52 | 52 | |
|
53 | 53 | def getarg(self, name): |
|
54 | 54 | return self.getargs(name)[0] |
|
55 | 55 | |
|
56 | 56 | def respond(self, v): |
|
57 | 57 | self.fout.write("%d\n" % len(v)) |
|
58 | 58 | self.fout.write(v) |
|
59 | 59 | self.fout.flush() |
|
60 | 60 | |
|
61 | def sendchangegroup(self, changegroup): | |
|
62 | while True: | |
|
63 | d = changegroup.read(4096) | |
|
64 | if not d: | |
|
65 | break | |
|
66 | self.fout.write(d) | |
|
67 | ||
|
68 | self.fout.flush() | |
|
69 | ||
|
61 | 70 | def serve_forever(self): |
|
62 | 71 | try: |
|
63 | 72 | while self.serve_one(): |
|
64 | 73 | pass |
|
65 | 74 | finally: |
|
66 | 75 | if self.lock is not None: |
|
67 | 76 | self.lock.release() |
|
68 | 77 | sys.exit(0) |
|
69 | 78 | |
|
70 | 79 | def serve_one(self): |
|
71 | 80 | cmd = self.fin.readline()[:-1] |
|
72 | 81 | if cmd and not wireproto.dispatch(self.repo, self, cmd): |
|
73 | 82 | impl = getattr(self, 'do_' + cmd, None) |
|
74 | 83 | if impl: |
|
75 | 84 | r = impl() |
|
76 | 85 | if r is not None: |
|
77 | 86 | self.respond(r) |
|
78 | 87 | else: self.respond("") |
|
79 | 88 | return cmd != '' |
|
80 | 89 | |
|
81 | 90 | def do_hello(self): |
|
82 | 91 | '''the hello command returns a set of lines describing various |
|
83 | 92 | interesting things about the server, in an RFC822-like format. |
|
84 | 93 | Currently the only one defined is "capabilities", which |
|
85 | 94 | consists of a line in the form: |
|
86 | 95 | |
|
87 | 96 | capabilities: space separated list of tokens |
|
88 | 97 | ''' |
|
89 | 98 | caps = copy.copy(self.caps) |
|
90 | 99 | if streamclone.allowed(self.repo.ui): |
|
91 | 100 | caps.append('stream=%d' % self.repo.changelog.version) |
|
92 | 101 | return "capabilities: %s\n" % (' '.join(caps),) |
|
93 | 102 | |
|
94 | 103 | def do_lock(self): |
|
95 | 104 | '''DEPRECATED - allowing remote client to lock repo is not safe''' |
|
96 | 105 | |
|
97 | 106 | self.lock = self.repo.lock() |
|
98 | 107 | return "" |
|
99 | 108 | |
|
100 | 109 | def do_unlock(self): |
|
101 | 110 | '''DEPRECATED''' |
|
102 | 111 | |
|
103 | 112 | if self.lock: |
|
104 | 113 | self.lock.release() |
|
105 | 114 | self.lock = None |
|
106 | 115 | return "" |
|
107 | 116 | |
|
108 | def do_changegroup(self): | |
|
109 | nodes = [] | |
|
110 | roots = self.getarg('roots') | |
|
111 | nodes = map(bin, roots.split(" ")) | |
|
112 | ||
|
113 | cg = self.repo.changegroup(nodes, 'serve') | |
|
114 | while True: | |
|
115 | d = cg.read(4096) | |
|
116 | if not d: | |
|
117 | break | |
|
118 | self.fout.write(d) | |
|
119 | ||
|
120 | self.fout.flush() | |
|
121 | ||
|
122 | def do_changegroupsubset(self): | |
|
123 | bases, heads = self.getargs('bases heads') | |
|
124 | bases = [bin(n) for n in bases.split(' ')] | |
|
125 | heads = [bin(n) for n in heads.split(' ')] | |
|
126 | ||
|
127 | cg = self.repo.changegroupsubset(bases, heads, 'serve') | |
|
128 | while True: | |
|
129 | d = cg.read(4096) | |
|
130 | if not d: | |
|
131 | break | |
|
132 | self.fout.write(d) | |
|
133 | ||
|
134 | self.fout.flush() | |
|
135 | ||
|
136 | 117 | def do_addchangegroup(self): |
|
137 | 118 | '''DEPRECATED''' |
|
138 | 119 | |
|
139 | 120 | if not self.lock: |
|
140 | 121 | self.respond("not locked") |
|
141 | 122 | return |
|
142 | 123 | |
|
143 | 124 | self.respond("") |
|
144 | 125 | r = self.repo.addchangegroup(self.fin, 'serve', self.client_url(), |
|
145 | 126 | lock=self.lock) |
|
146 | 127 | return str(r) |
|
147 | 128 | |
|
148 | 129 | def client_url(self): |
|
149 | 130 | client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0] |
|
150 | 131 | return 'remote:ssh:' + client |
|
151 | 132 | |
|
152 | 133 | def do_unbundle(self): |
|
153 | 134 | their_heads = self.getarg('heads').split() |
|
154 | 135 | |
|
155 | 136 | def check_heads(): |
|
156 | 137 | heads = map(hex, self.repo.heads()) |
|
157 | 138 | return their_heads == [hex('force')] or their_heads == heads |
|
158 | 139 | |
|
159 | 140 | # fail early if possible |
|
160 | 141 | if not check_heads(): |
|
161 | 142 | self.respond(_('unsynced changes')) |
|
162 | 143 | return |
|
163 | 144 | |
|
164 | 145 | self.respond('') |
|
165 | 146 | |
|
166 | 147 | # write bundle data to temporary file because it can be big |
|
167 | 148 | fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') |
|
168 | 149 | fp = os.fdopen(fd, 'wb+') |
|
169 | 150 | try: |
|
170 | 151 | count = int(self.fin.readline()) |
|
171 | 152 | while count: |
|
172 | 153 | fp.write(self.fin.read(count)) |
|
173 | 154 | count = int(self.fin.readline()) |
|
174 | 155 | |
|
175 | 156 | was_locked = self.lock is not None |
|
176 | 157 | if not was_locked: |
|
177 | 158 | self.lock = self.repo.lock() |
|
178 | 159 | try: |
|
179 | 160 | if not check_heads(): |
|
180 | 161 | # someone else committed/pushed/unbundled while we |
|
181 | 162 | # were transferring data |
|
182 | 163 | self.respond(_('unsynced changes')) |
|
183 | 164 | return |
|
184 | 165 | self.respond('') |
|
185 | 166 | |
|
186 | 167 | # push can proceed |
|
187 | 168 | |
|
188 | 169 | fp.seek(0) |
|
189 | 170 | r = self.repo.addchangegroup(fp, 'serve', self.client_url(), |
|
190 | 171 | lock=self.lock) |
|
191 | 172 | self.respond(str(r)) |
|
192 | 173 | finally: |
|
193 | 174 | if not was_locked: |
|
194 | 175 | self.lock.release() |
|
195 | 176 | self.lock = None |
|
196 | 177 | finally: |
|
197 | 178 | fp.close() |
|
198 | 179 | os.unlink(tempname) |
|
199 | 180 | |
|
200 | 181 | def do_stream_out(self): |
|
201 | 182 | try: |
|
202 | 183 | for chunk in streamclone.stream_out(self.repo): |
|
203 | 184 | self.fout.write(chunk) |
|
204 | 185 | self.fout.flush() |
|
205 | 186 | except streamclone.StreamException, inst: |
|
206 | 187 | self.fout.write(str(inst)) |
|
207 | 188 | self.fout.flush() |
@@ -1,75 +1,90 | |||
|
1 | 1 | # wireproto.py - generic wire protocol support functions |
|
2 | 2 | # |
|
3 | 3 | # Copyright 2005-2010 Matt Mackall <mpm@selenic.com> |
|
4 | 4 | # |
|
5 | 5 | # This software may be used and distributed according to the terms of the |
|
6 | 6 | # GNU General Public License version 2 or any later version. |
|
7 | 7 | |
|
8 | 8 | from i18n import _ |
|
9 | 9 | from node import bin, hex |
|
10 | 10 | import urllib |
|
11 | 11 | import pushkey as pushkey_ |
|
12 | 12 | |
|
13 | 13 | def dispatch(repo, proto, command): |
|
14 | 14 | if command not in commands: |
|
15 | 15 | return False |
|
16 | 16 | func, spec = commands[command] |
|
17 | 17 | args = proto.getargs(spec) |
|
18 |
|
|
|
18 | r = func(repo, proto, *args) | |
|
19 | if r != None: | |
|
20 | proto.respond(r) | |
|
19 | 21 | return True |
|
20 | 22 | |
|
21 | 23 | def between(repo, proto, pairs): |
|
22 | 24 | pairs = [map(bin, p.split("-")) for p in pairs.split(" ")] |
|
23 | 25 | r = [] |
|
24 | 26 | for b in repo.between(pairs): |
|
25 | 27 | r.append(" ".join(map(hex, b)) + "\n") |
|
26 | 28 | return "".join(r) |
|
27 | 29 | |
|
28 | 30 | def branchmap(repo, proto): |
|
29 | 31 | branchmap = repo.branchmap() |
|
30 | 32 | heads = [] |
|
31 | 33 | for branch, nodes in branchmap.iteritems(): |
|
32 | 34 | branchname = urllib.quote(branch) |
|
33 | 35 | branchnodes = [hex(node) for node in nodes] |
|
34 | 36 | heads.append('%s %s' % (branchname, ' '.join(branchnodes))) |
|
35 | 37 | return '\n'.join(heads) |
|
36 | 38 | |
|
37 | 39 | def branches(repo, proto, nodes): |
|
38 | 40 | nodes = map(bin, nodes.split(" ")) |
|
39 | 41 | r = [] |
|
40 | 42 | for b in repo.branches(nodes): |
|
41 | 43 | r.append(" ".join(map(hex, b)) + "\n") |
|
42 | 44 | return "".join(r) |
|
43 | 45 | |
|
46 | def changegroup(repo, proto, roots): | |
|
47 | nodes = map(bin, roots.split(" ")) | |
|
48 | cg = repo.changegroup(nodes, 'serve') | |
|
49 | proto.sendchangegroup(cg) | |
|
50 | ||
|
51 | def changegroupsubset(repo, proto, bases, heads): | |
|
52 | bases = [bin(n) for n in bases.split(' ')] | |
|
53 | heads = [bin(n) for n in heads.split(' ')] | |
|
54 | cg = repo.changegroupsubset(bases, heads, 'serve') | |
|
55 | proto.sendchangegroup(cg) | |
|
56 | ||
|
44 | 57 | def heads(repo, proto): |
|
45 | 58 | h = repo.heads() |
|
46 | 59 | return " ".join(map(hex, h)) + "\n" |
|
47 | 60 | |
|
48 | 61 | def listkeys(repo, proto, namespace): |
|
49 | 62 | d = pushkey_.list(repo, namespace).items() |
|
50 | 63 | t = '\n'.join(['%s\t%s' % (k.encode('string-escape'), |
|
51 | 64 | v.encode('string-escape')) for k, v in d]) |
|
52 | 65 | return t |
|
53 | 66 | |
|
54 | 67 | def lookup(repo, proto, key): |
|
55 | 68 | try: |
|
56 | 69 | r = hex(repo.lookup(key)) |
|
57 | 70 | success = 1 |
|
58 | 71 | except Exception, inst: |
|
59 | 72 | r = str(inst) |
|
60 | 73 | success = 0 |
|
61 | 74 | return "%s %s\n" % (success, r) |
|
62 | 75 | |
|
63 | 76 | def pushkey(repo, proto, namespace, key, old, new): |
|
64 | 77 | r = pushkey_.push(repo, namespace, key, old, new) |
|
65 | 78 | return '%s\n' % int(r) |
|
66 | 79 | |
|
67 | 80 | commands = { |
|
68 | 81 | 'between': (between, 'pairs'), |
|
69 | 82 | 'branchmap': (branchmap, ''), |
|
70 | 83 | 'branches': (branches, 'nodes'), |
|
84 | 'changegroup': (changegroup, 'roots'), | |
|
85 | 'changegroupsubset': (changegroupsubset, 'bases heads'), | |
|
71 | 86 | 'heads': (heads, ''), |
|
72 | 87 | 'listkeys': (listkeys, 'namespace'), |
|
73 | 88 | 'lookup': (lookup, 'key'), |
|
74 | 89 | 'pushkey': (pushkey, 'namespace key old new'), |
|
75 | 90 | } |
@@ -1,62 +1,62 | |||
|
1 | 1 | #!/bin/sh |
|
2 | 2 | # This is a test of the wire protocol over CGI-based hgweb. |
|
3 | 3 | |
|
4 | 4 | echo % initialize repository |
|
5 | 5 | hg init test |
|
6 | 6 | cd test |
|
7 | 7 | echo a > a |
|
8 | 8 | hg ci -Ama |
|
9 | 9 | cd .. |
|
10 | 10 | |
|
11 | 11 | cat >hgweb.cgi <<HGWEB |
|
12 | 12 | #!/usr/bin/env python |
|
13 | 13 | # |
|
14 | 14 | # An example CGI script to use hgweb, edit as necessary |
|
15 | 15 | |
|
16 | 16 | import cgitb |
|
17 | 17 | cgitb.enable() |
|
18 | 18 | |
|
19 | 19 | from mercurial import demandimport; demandimport.enable() |
|
20 | 20 | from mercurial.hgweb import hgweb |
|
21 | 21 | from mercurial.hgweb import wsgicgi |
|
22 | 22 | |
|
23 | 23 | application = hgweb("test", "Empty test repository") |
|
24 | 24 | wsgicgi.launch(application) |
|
25 | 25 | HGWEB |
|
26 | 26 | chmod 755 hgweb.cgi |
|
27 | 27 | |
|
28 | 28 | DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT |
|
29 | 29 | GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE |
|
30 | 30 | HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT |
|
31 | 31 | HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET |
|
32 | 32 | HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING |
|
33 | 33 | HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE |
|
34 | 34 | HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL |
|
35 | 35 | HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION |
|
36 | 36 | HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST |
|
37 | 37 | HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE |
|
38 | 38 | HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT |
|
39 | 39 | PATH_INFO="/"; export PATH_INFO |
|
40 | 40 | PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED |
|
41 | 41 | REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR |
|
42 | 42 | REMOTE_PORT="44703"; export REMOTE_PORT |
|
43 | 43 | REQUEST_METHOD="GET"; export REQUEST_METHOD |
|
44 | 44 | REQUEST_URI="/test/"; export REQUEST_URI |
|
45 | 45 | SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME |
|
46 | 46 | SCRIPT_NAME="/test"; export SCRIPT_NAME |
|
47 | 47 | SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI |
|
48 | 48 | SCRIPT_URL="/test/"; export SCRIPT_URL |
|
49 | 49 | SERVER_ADDR="127.0.0.1"; export SERVER_ADDR |
|
50 | 50 | SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN |
|
51 | 51 | SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME |
|
52 | 52 | SERVER_PORT="80"; export SERVER_PORT |
|
53 | 53 | SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL |
|
54 | 54 | SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>"; export SERVER_SIGNATURE |
|
55 | 55 | SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE |
|
56 | 56 | |
|
57 | 57 | echo % try hgweb request |
|
58 | QUERY_STRING="cmd=changegroup"; export QUERY_STRING | |
|
58 | QUERY_STRING="cmd=changegroup&roots=0000000000000000000000000000000000000000"; export QUERY_STRING | |
|
59 | 59 | python hgweb.cgi >page1 2>&1 ; echo $? |
|
60 | 60 | python "$TESTDIR/md5sum.py" page1 |
|
61 | 61 | |
|
62 | 62 | exit 0 |
@@ -1,64 +1,64 | |||
|
1 | 1 | #!/bin/sh |
|
2 | 2 | # An attempt at more fully testing the hgweb web interface. |
|
3 | 3 | # The following things are tested elsewhere and are therefore omitted: |
|
4 | 4 | # - archive, tested in test-archive |
|
5 | 5 | # - unbundle, tested in test-push-http |
|
6 | 6 | # - changegroupsubset, tested in test-pull |
|
7 | 7 | |
|
8 | 8 | echo % Set up the repo |
|
9 | 9 | hg init test |
|
10 | 10 | cd test |
|
11 | 11 | mkdir da |
|
12 | 12 | echo foo > da/foo |
|
13 | 13 | echo foo > foo |
|
14 | 14 | hg ci -Ambase |
|
15 | 15 | hg tag 1.0 |
|
16 | 16 | echo another > foo |
|
17 | 17 | hg branch stable |
|
18 | 18 | hg ci -Ambranch |
|
19 | 19 | hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log |
|
20 | 20 | cat hg.pid >> $DAEMON_PIDS |
|
21 | 21 | |
|
22 | 22 | echo % Logs and changes |
|
23 | 23 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//" |
|
24 | 24 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//" |
|
25 | 25 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//" |
|
26 | 26 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/' |
|
27 | 27 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/' |
|
28 | 28 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw' |
|
29 | 29 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log?rev=base' |
|
30 | 30 | |
|
31 | 31 | echo % File-related |
|
32 | 32 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw' |
|
33 | 33 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw' |
|
34 | 34 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw' |
|
35 | 35 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo' |
|
36 | 36 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw' |
|
37 | 37 | |
|
38 | 38 | echo % Overviews |
|
39 | 39 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-tags' |
|
40 | 40 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-branches' |
|
41 | 41 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' |
|
42 | 42 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb' |
|
43 | 43 | |
|
44 | 44 | echo % capabilities |
|
45 | 45 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities' |
|
46 | 46 | echo % heads |
|
47 | 47 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads' |
|
48 | 48 | echo % lookup |
|
49 | 49 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&key=1' |
|
50 | 50 | echo % branches |
|
51 | 51 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000' |
|
52 | 52 | echo % changegroup |
|
53 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup' \ | |
|
53 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000' \ | |
|
54 | 54 | | $TESTDIR/printrepr.py |
|
55 | 55 | echo % stream_out |
|
56 | 56 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out' |
|
57 | 57 | echo % failing unbundle, requires POST request |
|
58 | 58 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle' |
|
59 | 59 | |
|
60 | 60 | echo % Static files |
|
61 | 61 | "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css' |
|
62 | 62 | |
|
63 | 63 | echo % ERRORS ENCOUNTERED |
|
64 | 64 | cat errors.log |
General Comments 0
You need to be logged in to leave comments.
Login now