##// END OF EJS Templates
hgweb: simplify wsgirequest header handling...
Mads Kiilerich -
r18348:764a7587 default
parent child Browse files
Show More
@@ -1,144 +1,135
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 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 9 import socket, cgi, errno
10 10 from mercurial import util
11 11 from common import ErrorResponse, statusmessage, HTTP_NOT_MODIFIED
12 12
13 13 shortcuts = {
14 14 'cl': [('cmd', ['changelog']), ('rev', None)],
15 15 'sl': [('cmd', ['shortlog']), ('rev', None)],
16 16 'cs': [('cmd', ['changeset']), ('node', None)],
17 17 'f': [('cmd', ['file']), ('filenode', None)],
18 18 'fl': [('cmd', ['filelog']), ('filenode', None)],
19 19 'fd': [('cmd', ['filediff']), ('node', None)],
20 20 'fa': [('cmd', ['annotate']), ('filenode', None)],
21 21 'mf': [('cmd', ['manifest']), ('manifest', None)],
22 22 'ca': [('cmd', ['archive']), ('node', None)],
23 23 'tags': [('cmd', ['tags'])],
24 24 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
25 25 'static': [('cmd', ['static']), ('file', None)]
26 26 }
27 27
28 28 def normalize(form):
29 29 # first expand the shortcuts
30 30 for k in shortcuts.iterkeys():
31 31 if k in form:
32 32 for name, value in shortcuts[k]:
33 33 if value is None:
34 34 value = form[k]
35 35 form[name] = value
36 36 del form[k]
37 37 # And strip the values
38 38 for k, v in form.iteritems():
39 39 form[k] = [i.strip() for i in v]
40 40 return form
41 41
42 42 class wsgirequest(object):
43 43 def __init__(self, wsgienv, start_response):
44 44 version = wsgienv['wsgi.version']
45 45 if (version < (1, 0)) or (version >= (2, 0)):
46 46 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
47 47 % version)
48 48 self.inp = wsgienv['wsgi.input']
49 49 self.err = wsgienv['wsgi.errors']
50 50 self.threaded = wsgienv['wsgi.multithread']
51 51 self.multiprocess = wsgienv['wsgi.multiprocess']
52 52 self.run_once = wsgienv['wsgi.run_once']
53 53 self.env = wsgienv
54 54 self.form = normalize(cgi.parse(self.inp,
55 55 self.env,
56 56 keep_blank_values=1))
57 57 self._start_response = start_response
58 58 self.server_write = None
59 59 self.headers = []
60 60
61 61 def __iter__(self):
62 62 return iter([])
63 63
64 64 def read(self, count=-1):
65 65 return self.inp.read(count)
66 66
67 67 def drain(self):
68 68 '''need to read all data from request, httplib is half-duplex'''
69 69 length = int(self.env.get('CONTENT_LENGTH') or 0)
70 70 for s in util.filechunkiter(self.inp, limit=length):
71 71 pass
72 72
73 73 def respond(self, status, type, filename=None, length=None):
74 74 if self._start_response is not None:
75
76 self.httphdr(type, filename, length)
75 self.headers.append(('Content-Type', type))
76 if filename:
77 filename = (filename.split('/')[-1]
78 .replace('\\', '\\\\').replace('"', '\\"'))
79 self.headers.append(('Content-Disposition',
80 'inline; filename="%s"' % filename))
81 if length is not None:
82 self.headers.append(('Content-Length', str(length)))
77 83
78 84 for k, v in self.headers:
79 85 if not isinstance(v, str):
80 raise TypeError('header value must be string: %r' % v)
86 raise TypeError('header value must be string: %r' % (v,))
81 87
82 88 if isinstance(status, ErrorResponse):
83 self.header(status.headers)
89 self.headers.extend(status.headers)
84 90 if status.code == HTTP_NOT_MODIFIED:
85 91 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
86 92 # it MUST NOT include any headers other than these and no
87 93 # body
88 94 self.headers = [(k, v) for (k, v) in self.headers if
89 95 k in ('Date', 'ETag', 'Expires',
90 96 'Cache-Control', 'Vary')]
91 97 status = statusmessage(status.code, status.message)
92 98 elif status == 200:
93 99 status = '200 Script output follows'
94 100 elif isinstance(status, int):
95 101 status = statusmessage(status)
96 102
97 103 self.server_write = self._start_response(status, self.headers)
98 104 self._start_response = None
99 105 self.headers = []
100 106
101 107 def write(self, thing):
102 108 if util.safehasattr(thing, "__iter__"):
103 109 for part in thing:
104 110 self.write(part)
105 111 else:
106 112 thing = str(thing)
107 113 try:
108 114 self.server_write(thing)
109 115 except socket.error, inst:
110 116 if inst[0] != errno.ECONNRESET:
111 117 raise
112 118
113 119 def writelines(self, lines):
114 120 for line in lines:
115 121 self.write(line)
116 122
117 123 def flush(self):
118 124 return None
119 125
120 126 def close(self):
121 127 return None
122 128
123 def header(self, headers=[('Content-Type','text/html')]):
124 self.headers.extend(headers)
125
126 def httphdr(self, type, filename=None, length=None, headers={}):
127 headers = headers.items()
128 headers.append(('Content-Type', type))
129 if filename:
130 filename = (filename.split('/')[-1]
131 .replace('\\', '\\\\').replace('"', '\\"'))
132 headers.append(('Content-Disposition',
133 'inline; filename="%s"' % filename))
134 if length is not None:
135 headers.append(('Content-Length', str(length)))
136 self.header(headers)
137
138 129 def wsgiapplication(app_maker):
139 130 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
140 131 can and should now be used as a WSGI application.'''
141 132 application = app_maker()
142 133 def run_wsgi(env, respond):
143 134 return application(env, respond)
144 135 return run_wsgi
@@ -1,981 +1,981
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 os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, encoding, archival, templater, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 12 from mercurial.util import binary
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod, patch
16 16 from mercurial import help as helpmod
17 17 from mercurial import scmutil
18 18 from mercurial.i18n import _
19 19
20 20 # __all__ is populated with the allowed commands. Be sure to add to it if
21 21 # you're adding a new command, or the new command won't work.
22 22
23 23 __all__ = [
24 24 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
25 25 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
26 26 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
27 27 ]
28 28
29 29 def log(web, req, tmpl):
30 30 if 'file' in req.form and req.form['file'][0]:
31 31 return filelog(web, req, tmpl)
32 32 else:
33 33 return changelog(web, req, tmpl)
34 34
35 35 def rawfile(web, req, tmpl):
36 36 guessmime = web.configbool('web', 'guessmime', False)
37 37
38 38 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
39 39 if not path:
40 40 content = manifest(web, req, tmpl)
41 41 req.respond(HTTP_OK, web.ctype)
42 42 return content
43 43
44 44 try:
45 45 fctx = webutil.filectx(web.repo, req)
46 46 except error.LookupError, inst:
47 47 try:
48 48 content = manifest(web, req, tmpl)
49 49 req.respond(HTTP_OK, web.ctype)
50 50 return content
51 51 except ErrorResponse:
52 52 raise inst
53 53
54 54 path = fctx.path()
55 55 text = fctx.data()
56 56 mt = 'application/binary'
57 57 if guessmime:
58 58 mt = mimetypes.guess_type(path)[0]
59 59 if mt is None:
60 60 mt = binary(text) and 'application/binary' or 'text/plain'
61 61 if mt.startswith('text/'):
62 62 mt += '; charset="%s"' % encoding.encoding
63 63
64 64 req.respond(HTTP_OK, mt, path, len(text))
65 65 return [text]
66 66
67 67 def _filerevision(web, tmpl, fctx):
68 68 f = fctx.path()
69 69 text = fctx.data()
70 70 parity = paritygen(web.stripecount)
71 71
72 72 if binary(text):
73 73 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
74 74 text = '(binary:%s)' % mt
75 75
76 76 def lines():
77 77 for lineno, t in enumerate(text.splitlines(True)):
78 78 yield {"line": t,
79 79 "lineid": "l%d" % (lineno + 1),
80 80 "linenumber": "% 6d" % (lineno + 1),
81 81 "parity": parity.next()}
82 82
83 83 return tmpl("filerevision",
84 84 file=f,
85 85 path=webutil.up(f),
86 86 text=lines(),
87 87 rev=fctx.rev(),
88 88 node=fctx.hex(),
89 89 author=fctx.user(),
90 90 date=fctx.date(),
91 91 desc=fctx.description(),
92 92 branch=webutil.nodebranchnodefault(fctx),
93 93 parent=webutil.parents(fctx),
94 94 child=webutil.children(fctx),
95 95 rename=webutil.renamelink(fctx),
96 96 permissions=fctx.manifest().flags(f))
97 97
98 98 def file(web, req, tmpl):
99 99 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
100 100 if not path:
101 101 return manifest(web, req, tmpl)
102 102 try:
103 103 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
104 104 except error.LookupError, inst:
105 105 try:
106 106 return manifest(web, req, tmpl)
107 107 except ErrorResponse:
108 108 raise inst
109 109
110 110 def _search(web, req, tmpl):
111 111
112 112 query = req.form['rev'][0]
113 113 revcount = web.maxchanges
114 114 if 'revcount' in req.form:
115 115 revcount = int(req.form.get('revcount', [revcount])[0])
116 116 revcount = max(revcount, 1)
117 117 tmpl.defaults['sessionvars']['revcount'] = revcount
118 118
119 119 lessvars = copy.copy(tmpl.defaults['sessionvars'])
120 120 lessvars['revcount'] = max(revcount / 2, 1)
121 121 lessvars['rev'] = query
122 122 morevars = copy.copy(tmpl.defaults['sessionvars'])
123 123 morevars['revcount'] = revcount * 2
124 124 morevars['rev'] = query
125 125
126 126 def changelist(**map):
127 127 count = 0
128 128 lower = encoding.lower
129 129 qw = lower(query).split()
130 130
131 131 def revgen():
132 132 for i in xrange(len(web.repo) - 1, 0, -100):
133 133 l = []
134 134 for j in xrange(max(0, i - 100), i + 1):
135 135 ctx = web.repo[j]
136 136 l.append(ctx)
137 137 l.reverse()
138 138 for e in l:
139 139 yield e
140 140
141 141 for ctx in revgen():
142 142 miss = 0
143 143 for q in qw:
144 144 if not (q in lower(ctx.user()) or
145 145 q in lower(ctx.description()) or
146 146 q in lower(" ".join(ctx.files()))):
147 147 miss = 1
148 148 break
149 149 if miss:
150 150 continue
151 151
152 152 count += 1
153 153 n = ctx.node()
154 154 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
155 155 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
156 156
157 157 yield tmpl('searchentry',
158 158 parity=parity.next(),
159 159 author=ctx.user(),
160 160 parent=webutil.parents(ctx),
161 161 child=webutil.children(ctx),
162 162 changelogtag=showtags,
163 163 desc=ctx.description(),
164 164 date=ctx.date(),
165 165 files=files,
166 166 rev=ctx.rev(),
167 167 node=hex(n),
168 168 tags=webutil.nodetagsdict(web.repo, n),
169 169 bookmarks=webutil.nodebookmarksdict(web.repo, n),
170 170 inbranch=webutil.nodeinbranch(web.repo, ctx),
171 171 branches=webutil.nodebranchdict(web.repo, ctx))
172 172
173 173 if count >= revcount:
174 174 break
175 175
176 176 tip = web.repo['tip']
177 177 parity = paritygen(web.stripecount)
178 178
179 179 return tmpl('search', query=query, node=tip.hex(),
180 180 entries=changelist, archives=web.archivelist("tip"),
181 181 morevars=morevars, lessvars=lessvars)
182 182
183 183 def changelog(web, req, tmpl, shortlog=False):
184 184
185 185 if 'node' in req.form:
186 186 ctx = webutil.changectx(web.repo, req)
187 187 else:
188 188 if 'rev' in req.form:
189 189 hi = req.form['rev'][0]
190 190 else:
191 191 hi = len(web.repo) - 1
192 192 try:
193 193 ctx = web.repo[hi]
194 194 except error.RepoError:
195 195 return _search(web, req, tmpl) # XXX redirect to 404 page?
196 196
197 197 def changelist(limit=0, **map):
198 198 l = [] # build a list in forward order for efficiency
199 199 for i in xrange(start, end):
200 200 ctx = web.repo[i]
201 201 n = ctx.node()
202 202 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
203 203 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
204 204
205 205 l.append({"parity": parity.next(),
206 206 "author": ctx.user(),
207 207 "parent": webutil.parents(ctx, i - 1),
208 208 "child": webutil.children(ctx, i + 1),
209 209 "changelogtag": showtags,
210 210 "desc": ctx.description(),
211 211 "date": ctx.date(),
212 212 "files": files,
213 213 "rev": i,
214 214 "node": hex(n),
215 215 "tags": webutil.nodetagsdict(web.repo, n),
216 216 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
217 217 "inbranch": webutil.nodeinbranch(web.repo, ctx),
218 218 "branches": webutil.nodebranchdict(web.repo, ctx)
219 219 })
220 220 if limit > 0:
221 221 l = l[-limit:]
222 222
223 223 for e in reversed(l):
224 224 yield e
225 225
226 226 revcount = shortlog and web.maxshortchanges or web.maxchanges
227 227 if 'revcount' in req.form:
228 228 revcount = int(req.form.get('revcount', [revcount])[0])
229 229 revcount = max(revcount, 1)
230 230 tmpl.defaults['sessionvars']['revcount'] = revcount
231 231
232 232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
233 233 lessvars['revcount'] = max(revcount / 2, 1)
234 234 morevars = copy.copy(tmpl.defaults['sessionvars'])
235 235 morevars['revcount'] = revcount * 2
236 236
237 237 count = len(web.repo)
238 238 pos = ctx.rev()
239 239 start = max(0, pos - revcount + 1)
240 240 end = min(count, start + revcount)
241 241 pos = end - 1
242 242 parity = paritygen(web.stripecount, offset=start - end)
243 243
244 244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
245 245
246 246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
247 247 node=ctx.hex(), rev=pos, changesets=count,
248 248 entries=lambda **x: changelist(limit=0,**x),
249 249 latestentry=lambda **x: changelist(limit=1,**x),
250 250 archives=web.archivelist("tip"), revcount=revcount,
251 251 morevars=morevars, lessvars=lessvars)
252 252
253 253 def shortlog(web, req, tmpl):
254 254 return changelog(web, req, tmpl, shortlog = True)
255 255
256 256 def changeset(web, req, tmpl):
257 257 ctx = webutil.changectx(web.repo, req)
258 258 basectx = webutil.basechangectx(web.repo, req)
259 259 if basectx is None:
260 260 basectx = ctx.p1()
261 261 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
262 262 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
263 263 ctx.node())
264 264 showbranch = webutil.nodebranchnodefault(ctx)
265 265
266 266 files = []
267 267 parity = paritygen(web.stripecount)
268 268 for blockno, f in enumerate(ctx.files()):
269 269 template = f in ctx and 'filenodelink' or 'filenolink'
270 270 files.append(tmpl(template,
271 271 node=ctx.hex(), file=f, blockno=blockno + 1,
272 272 parity=parity.next()))
273 273
274 274 style = web.config('web', 'style', 'paper')
275 275 if 'style' in req.form:
276 276 style = req.form['style'][0]
277 277
278 278 parity = paritygen(web.stripecount)
279 279 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
280 280
281 281 parity = paritygen(web.stripecount)
282 282 diffstatgen = webutil.diffstatgen(ctx, basectx)
283 283 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
284 284
285 285 return tmpl('changeset',
286 286 diff=diffs,
287 287 rev=ctx.rev(),
288 288 node=ctx.hex(),
289 289 parent=webutil.parents(ctx),
290 290 child=webutil.children(ctx),
291 291 currentbaseline=basectx.hex(),
292 292 changesettag=showtags,
293 293 changesetbookmark=showbookmarks,
294 294 changesetbranch=showbranch,
295 295 author=ctx.user(),
296 296 desc=ctx.description(),
297 297 date=ctx.date(),
298 298 files=files,
299 299 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
300 300 diffstat=diffstat,
301 301 archives=web.archivelist(ctx.hex()),
302 302 tags=webutil.nodetagsdict(web.repo, ctx.node()),
303 303 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
304 304 branch=webutil.nodebranchnodefault(ctx),
305 305 inbranch=webutil.nodeinbranch(web.repo, ctx),
306 306 branches=webutil.nodebranchdict(web.repo, ctx))
307 307
308 308 rev = changeset
309 309
310 310 def decodepath(path):
311 311 """Hook for mapping a path in the repository to a path in the
312 312 working copy.
313 313
314 314 Extensions (e.g., largefiles) can override this to remap files in
315 315 the virtual file system presented by the manifest command below."""
316 316 return path
317 317
318 318 def manifest(web, req, tmpl):
319 319 ctx = webutil.changectx(web.repo, req)
320 320 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
321 321 mf = ctx.manifest()
322 322 node = ctx.node()
323 323
324 324 files = {}
325 325 dirs = {}
326 326 parity = paritygen(web.stripecount)
327 327
328 328 if path and path[-1] != "/":
329 329 path += "/"
330 330 l = len(path)
331 331 abspath = "/" + path
332 332
333 333 for full, n in mf.iteritems():
334 334 # the virtual path (working copy path) used for the full
335 335 # (repository) path
336 336 f = decodepath(full)
337 337
338 338 if f[:l] != path:
339 339 continue
340 340 remain = f[l:]
341 341 elements = remain.split('/')
342 342 if len(elements) == 1:
343 343 files[remain] = full
344 344 else:
345 345 h = dirs # need to retain ref to dirs (root)
346 346 for elem in elements[0:-1]:
347 347 if elem not in h:
348 348 h[elem] = {}
349 349 h = h[elem]
350 350 if len(h) > 1:
351 351 break
352 352 h[None] = None # denotes files present
353 353
354 354 if mf and not files and not dirs:
355 355 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
356 356
357 357 def filelist(**map):
358 358 for f in sorted(files):
359 359 full = files[f]
360 360
361 361 fctx = ctx.filectx(full)
362 362 yield {"file": full,
363 363 "parity": parity.next(),
364 364 "basename": f,
365 365 "date": fctx.date(),
366 366 "size": fctx.size(),
367 367 "permissions": mf.flags(full)}
368 368
369 369 def dirlist(**map):
370 370 for d in sorted(dirs):
371 371
372 372 emptydirs = []
373 373 h = dirs[d]
374 374 while isinstance(h, dict) and len(h) == 1:
375 375 k, v = h.items()[0]
376 376 if v:
377 377 emptydirs.append(k)
378 378 h = v
379 379
380 380 path = "%s%s" % (abspath, d)
381 381 yield {"parity": parity.next(),
382 382 "path": path,
383 383 "emptydirs": "/".join(emptydirs),
384 384 "basename": d}
385 385
386 386 return tmpl("manifest",
387 387 rev=ctx.rev(),
388 388 node=hex(node),
389 389 path=abspath,
390 390 up=webutil.up(abspath),
391 391 upparity=parity.next(),
392 392 fentries=filelist,
393 393 dentries=dirlist,
394 394 archives=web.archivelist(hex(node)),
395 395 tags=webutil.nodetagsdict(web.repo, node),
396 396 bookmarks=webutil.nodebookmarksdict(web.repo, node),
397 397 inbranch=webutil.nodeinbranch(web.repo, ctx),
398 398 branches=webutil.nodebranchdict(web.repo, ctx))
399 399
400 400 def tags(web, req, tmpl):
401 401 i = list(reversed(web.repo.tagslist()))
402 402 parity = paritygen(web.stripecount)
403 403
404 404 def entries(notip=False, limit=0, **map):
405 405 count = 0
406 406 for k, n in i:
407 407 if notip and k == "tip":
408 408 continue
409 409 if limit > 0 and count >= limit:
410 410 continue
411 411 count = count + 1
412 412 yield {"parity": parity.next(),
413 413 "tag": k,
414 414 "date": web.repo[n].date(),
415 415 "node": hex(n)}
416 416
417 417 return tmpl("tags",
418 418 node=hex(web.repo.changelog.tip()),
419 419 entries=lambda **x: entries(False, 0, **x),
420 420 entriesnotip=lambda **x: entries(True, 0, **x),
421 421 latestentry=lambda **x: entries(True, 1, **x))
422 422
423 423 def bookmarks(web, req, tmpl):
424 424 i = web.repo._bookmarks.items()
425 425 parity = paritygen(web.stripecount)
426 426
427 427 def entries(limit=0, **map):
428 428 count = 0
429 429 for k, n in sorted(i):
430 430 if limit > 0 and count >= limit:
431 431 continue
432 432 count = count + 1
433 433 yield {"parity": parity.next(),
434 434 "bookmark": k,
435 435 "date": web.repo[n].date(),
436 436 "node": hex(n)}
437 437
438 438 return tmpl("bookmarks",
439 439 node=hex(web.repo.changelog.tip()),
440 440 entries=lambda **x: entries(0, **x),
441 441 latestentry=lambda **x: entries(1, **x))
442 442
443 443 def branches(web, req, tmpl):
444 444 tips = []
445 445 heads = web.repo.heads()
446 446 parity = paritygen(web.stripecount)
447 447 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
448 448
449 449 def entries(limit, **map):
450 450 count = 0
451 451 if not tips:
452 452 for t, n in web.repo.branchtags().iteritems():
453 453 tips.append(web.repo[n])
454 454 for ctx in sorted(tips, key=sortkey, reverse=True):
455 455 if limit > 0 and count >= limit:
456 456 return
457 457 count += 1
458 458 if not web.repo.branchheads(ctx.branch()):
459 459 status = 'closed'
460 460 elif ctx.node() not in heads:
461 461 status = 'inactive'
462 462 else:
463 463 status = 'open'
464 464 yield {'parity': parity.next(),
465 465 'branch': ctx.branch(),
466 466 'status': status,
467 467 'node': ctx.hex(),
468 468 'date': ctx.date()}
469 469
470 470 return tmpl('branches', node=hex(web.repo.changelog.tip()),
471 471 entries=lambda **x: entries(0, **x),
472 472 latestentry=lambda **x: entries(1, **x))
473 473
474 474 def summary(web, req, tmpl):
475 475 i = reversed(web.repo.tagslist())
476 476
477 477 def tagentries(**map):
478 478 parity = paritygen(web.stripecount)
479 479 count = 0
480 480 for k, n in i:
481 481 if k == "tip": # skip tip
482 482 continue
483 483
484 484 count += 1
485 485 if count > 10: # limit to 10 tags
486 486 break
487 487
488 488 yield tmpl("tagentry",
489 489 parity=parity.next(),
490 490 tag=k,
491 491 node=hex(n),
492 492 date=web.repo[n].date())
493 493
494 494 def bookmarks(**map):
495 495 parity = paritygen(web.stripecount)
496 496 b = web.repo._bookmarks.items()
497 497 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
498 498 yield {'parity': parity.next(),
499 499 'bookmark': k,
500 500 'date': web.repo[n].date(),
501 501 'node': hex(n)}
502 502
503 503 def branches(**map):
504 504 parity = paritygen(web.stripecount)
505 505
506 506 b = web.repo.branchtags()
507 507 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
508 508 for r, n, t in sorted(l):
509 509 yield {'parity': parity.next(),
510 510 'branch': t,
511 511 'node': hex(n),
512 512 'date': web.repo[n].date()}
513 513
514 514 def changelist(**map):
515 515 parity = paritygen(web.stripecount, offset=start - end)
516 516 l = [] # build a list in forward order for efficiency
517 517 for i in xrange(start, end):
518 518 ctx = web.repo[i]
519 519 n = ctx.node()
520 520 hn = hex(n)
521 521
522 522 l.append(tmpl(
523 523 'shortlogentry',
524 524 parity=parity.next(),
525 525 author=ctx.user(),
526 526 desc=ctx.description(),
527 527 date=ctx.date(),
528 528 rev=i,
529 529 node=hn,
530 530 tags=webutil.nodetagsdict(web.repo, n),
531 531 bookmarks=webutil.nodebookmarksdict(web.repo, n),
532 532 inbranch=webutil.nodeinbranch(web.repo, ctx),
533 533 branches=webutil.nodebranchdict(web.repo, ctx)))
534 534
535 535 l.reverse()
536 536 yield l
537 537
538 538 tip = web.repo['tip']
539 539 count = len(web.repo)
540 540 start = max(0, count - web.maxchanges)
541 541 end = min(count, start + web.maxchanges)
542 542
543 543 return tmpl("summary",
544 544 desc=web.config("web", "description", "unknown"),
545 545 owner=get_contact(web.config) or "unknown",
546 546 lastchange=tip.date(),
547 547 tags=tagentries,
548 548 bookmarks=bookmarks,
549 549 branches=branches,
550 550 shortlog=changelist,
551 551 node=tip.hex(),
552 552 archives=web.archivelist("tip"))
553 553
554 554 def filediff(web, req, tmpl):
555 555 fctx, ctx = None, None
556 556 try:
557 557 fctx = webutil.filectx(web.repo, req)
558 558 except LookupError:
559 559 ctx = webutil.changectx(web.repo, req)
560 560 path = webutil.cleanpath(web.repo, req.form['file'][0])
561 561 if path not in ctx.files():
562 562 raise
563 563
564 564 if fctx is not None:
565 565 n = fctx.node()
566 566 path = fctx.path()
567 567 ctx = fctx.changectx()
568 568 else:
569 569 n = ctx.node()
570 570 # path already defined in except clause
571 571
572 572 parity = paritygen(web.stripecount)
573 573 style = web.config('web', 'style', 'paper')
574 574 if 'style' in req.form:
575 575 style = req.form['style'][0]
576 576
577 577 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
578 578 rename = fctx and webutil.renamelink(fctx) or []
579 579 ctx = fctx and fctx or ctx
580 580 return tmpl("filediff",
581 581 file=path,
582 582 node=hex(n),
583 583 rev=ctx.rev(),
584 584 date=ctx.date(),
585 585 desc=ctx.description(),
586 586 author=ctx.user(),
587 587 rename=rename,
588 588 branch=webutil.nodebranchnodefault(ctx),
589 589 parent=webutil.parents(ctx),
590 590 child=webutil.children(ctx),
591 591 diff=diffs)
592 592
593 593 diff = filediff
594 594
595 595 def comparison(web, req, tmpl):
596 596 ctx = webutil.changectx(web.repo, req)
597 597 if 'file' not in req.form:
598 598 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
599 599 path = webutil.cleanpath(web.repo, req.form['file'][0])
600 600 rename = path in ctx and webutil.renamelink(ctx[path]) or []
601 601
602 602 parsecontext = lambda v: v == 'full' and -1 or int(v)
603 603 if 'context' in req.form:
604 604 context = parsecontext(req.form['context'][0])
605 605 else:
606 606 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
607 607
608 608 def filelines(f):
609 609 if binary(f.data()):
610 610 mt = mimetypes.guess_type(f.path())[0]
611 611 if not mt:
612 612 mt = 'application/octet-stream'
613 613 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
614 614 return f.data().splitlines()
615 615
616 616 if path in ctx:
617 617 fctx = ctx[path]
618 618 rightrev = fctx.filerev()
619 619 rightnode = fctx.filenode()
620 620 rightlines = filelines(fctx)
621 621 parents = fctx.parents()
622 622 if not parents:
623 623 leftrev = -1
624 624 leftnode = nullid
625 625 leftlines = ()
626 626 else:
627 627 pfctx = parents[0]
628 628 leftrev = pfctx.filerev()
629 629 leftnode = pfctx.filenode()
630 630 leftlines = filelines(pfctx)
631 631 else:
632 632 rightrev = -1
633 633 rightnode = nullid
634 634 rightlines = ()
635 635 fctx = ctx.parents()[0][path]
636 636 leftrev = fctx.filerev()
637 637 leftnode = fctx.filenode()
638 638 leftlines = filelines(fctx)
639 639
640 640 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
641 641 return tmpl('filecomparison',
642 642 file=path,
643 643 node=hex(ctx.node()),
644 644 rev=ctx.rev(),
645 645 date=ctx.date(),
646 646 desc=ctx.description(),
647 647 author=ctx.user(),
648 648 rename=rename,
649 649 branch=webutil.nodebranchnodefault(ctx),
650 650 parent=webutil.parents(fctx),
651 651 child=webutil.children(fctx),
652 652 leftrev=leftrev,
653 653 leftnode=hex(leftnode),
654 654 rightrev=rightrev,
655 655 rightnode=hex(rightnode),
656 656 comparison=comparison)
657 657
658 658 def annotate(web, req, tmpl):
659 659 fctx = webutil.filectx(web.repo, req)
660 660 f = fctx.path()
661 661 parity = paritygen(web.stripecount)
662 662 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
663 663
664 664 def annotate(**map):
665 665 last = None
666 666 if binary(fctx.data()):
667 667 mt = (mimetypes.guess_type(fctx.path())[0]
668 668 or 'application/octet-stream')
669 669 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
670 670 '(binary:%s)' % mt)])
671 671 else:
672 672 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
673 673 diffopts=diffopts))
674 674 for lineno, ((f, targetline), l) in lines:
675 675 fnode = f.filenode()
676 676
677 677 if last != fnode:
678 678 last = fnode
679 679
680 680 yield {"parity": parity.next(),
681 681 "node": f.hex(),
682 682 "rev": f.rev(),
683 683 "author": f.user(),
684 684 "desc": f.description(),
685 685 "file": f.path(),
686 686 "targetline": targetline,
687 687 "line": l,
688 688 "lineid": "l%d" % (lineno + 1),
689 689 "linenumber": "% 6d" % (lineno + 1),
690 690 "revdate": f.date()}
691 691
692 692 return tmpl("fileannotate",
693 693 file=f,
694 694 annotate=annotate,
695 695 path=webutil.up(f),
696 696 rev=fctx.rev(),
697 697 node=fctx.hex(),
698 698 author=fctx.user(),
699 699 date=fctx.date(),
700 700 desc=fctx.description(),
701 701 rename=webutil.renamelink(fctx),
702 702 branch=webutil.nodebranchnodefault(fctx),
703 703 parent=webutil.parents(fctx),
704 704 child=webutil.children(fctx),
705 705 permissions=fctx.manifest().flags(f))
706 706
707 707 def filelog(web, req, tmpl):
708 708
709 709 try:
710 710 fctx = webutil.filectx(web.repo, req)
711 711 f = fctx.path()
712 712 fl = fctx.filelog()
713 713 except error.LookupError:
714 714 f = webutil.cleanpath(web.repo, req.form['file'][0])
715 715 fl = web.repo.file(f)
716 716 numrevs = len(fl)
717 717 if not numrevs: # file doesn't exist at all
718 718 raise
719 719 rev = webutil.changectx(web.repo, req).rev()
720 720 first = fl.linkrev(0)
721 721 if rev < first: # current rev is from before file existed
722 722 raise
723 723 frev = numrevs - 1
724 724 while fl.linkrev(frev) > rev:
725 725 frev -= 1
726 726 fctx = web.repo.filectx(f, fl.linkrev(frev))
727 727
728 728 revcount = web.maxshortchanges
729 729 if 'revcount' in req.form:
730 730 revcount = int(req.form.get('revcount', [revcount])[0])
731 731 revcount = max(revcount, 1)
732 732 tmpl.defaults['sessionvars']['revcount'] = revcount
733 733
734 734 lessvars = copy.copy(tmpl.defaults['sessionvars'])
735 735 lessvars['revcount'] = max(revcount / 2, 1)
736 736 morevars = copy.copy(tmpl.defaults['sessionvars'])
737 737 morevars['revcount'] = revcount * 2
738 738
739 739 count = fctx.filerev() + 1
740 740 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
741 741 end = min(count, start + revcount) # last rev on this page
742 742 parity = paritygen(web.stripecount, offset=start - end)
743 743
744 744 def entries(limit=0, **map):
745 745 l = []
746 746
747 747 repo = web.repo
748 748 for i in xrange(start, end):
749 749 iterfctx = fctx.filectx(i)
750 750
751 751 l.append({"parity": parity.next(),
752 752 "filerev": i,
753 753 "file": f,
754 754 "node": iterfctx.hex(),
755 755 "author": iterfctx.user(),
756 756 "date": iterfctx.date(),
757 757 "rename": webutil.renamelink(iterfctx),
758 758 "parent": webutil.parents(iterfctx),
759 759 "child": webutil.children(iterfctx),
760 760 "desc": iterfctx.description(),
761 761 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
762 762 "bookmarks": webutil.nodebookmarksdict(
763 763 repo, iterfctx.node()),
764 764 "branch": webutil.nodebranchnodefault(iterfctx),
765 765 "inbranch": webutil.nodeinbranch(repo, iterfctx),
766 766 "branches": webutil.nodebranchdict(repo, iterfctx)})
767 767
768 768 if limit > 0:
769 769 l = l[-limit:]
770 770
771 771 for e in reversed(l):
772 772 yield e
773 773
774 774 nodefunc = lambda x: fctx.filectx(fileid=x)
775 775 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
776 776 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
777 777 entries=lambda **x: entries(limit=0, **x),
778 778 latestentry=lambda **x: entries(limit=1, **x),
779 779 revcount=revcount, morevars=morevars, lessvars=lessvars)
780 780
781 781 def archive(web, req, tmpl):
782 782 type_ = req.form.get('type', [None])[0]
783 783 allowed = web.configlist("web", "allow_archive")
784 784 key = req.form['node'][0]
785 785
786 786 if type_ not in web.archives:
787 787 msg = 'Unsupported archive type: %s' % type_
788 788 raise ErrorResponse(HTTP_NOT_FOUND, msg)
789 789
790 790 if not ((type_ in allowed or
791 791 web.configbool("web", "allow" + type_, False))):
792 792 msg = 'Archive type not allowed: %s' % type_
793 793 raise ErrorResponse(HTTP_FORBIDDEN, msg)
794 794
795 795 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
796 796 cnode = web.repo.lookup(key)
797 797 arch_version = key
798 798 if cnode == key or key == 'tip':
799 799 arch_version = short(cnode)
800 800 name = "%s-%s" % (reponame, arch_version)
801 801 mimetype, artype, extension, encoding = web.archive_specs[type_]
802 802 headers = [
803 803 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
804 804 ]
805 805 if encoding:
806 806 headers.append(('Content-Encoding', encoding))
807 req.header(headers)
807 req.headers.extend(headers)
808 808 req.respond(HTTP_OK, mimetype)
809 809
810 810 ctx = webutil.changectx(web.repo, req)
811 811 archival.archive(web.repo, req, cnode, artype, prefix=name,
812 812 matchfn=scmutil.match(ctx, []),
813 813 subrepos=web.configbool("web", "archivesubrepos"))
814 814 return []
815 815
816 816
817 817 def static(web, req, tmpl):
818 818 fname = req.form['file'][0]
819 819 # a repo owner may set web.static in .hg/hgrc to get any file
820 820 # readable by the user running the CGI script
821 821 static = web.config("web", "static", None, untrusted=False)
822 822 if not static:
823 823 tp = web.templatepath or templater.templatepath()
824 824 if isinstance(tp, str):
825 825 tp = [tp]
826 826 static = [os.path.join(p, 'static') for p in tp]
827 827 return [staticfile(static, fname, req)]
828 828
829 829 def graph(web, req, tmpl):
830 830
831 831 ctx = webutil.changectx(web.repo, req)
832 832 rev = ctx.rev()
833 833
834 834 bg_height = 39
835 835 revcount = web.maxshortchanges
836 836 if 'revcount' in req.form:
837 837 revcount = int(req.form.get('revcount', [revcount])[0])
838 838 revcount = max(revcount, 1)
839 839 tmpl.defaults['sessionvars']['revcount'] = revcount
840 840
841 841 lessvars = copy.copy(tmpl.defaults['sessionvars'])
842 842 lessvars['revcount'] = max(revcount / 2, 1)
843 843 morevars = copy.copy(tmpl.defaults['sessionvars'])
844 844 morevars['revcount'] = revcount * 2
845 845
846 846 count = len(web.repo)
847 847 pos = rev
848 848 start = max(0, pos - revcount + 1)
849 849 end = min(count, start + revcount)
850 850 pos = end - 1
851 851
852 852 uprev = min(max(0, count - 1), rev + revcount)
853 853 downrev = max(0, rev - revcount)
854 854 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
855 855
856 856 dag = graphmod.dagwalker(web.repo, range(start, end)[::-1])
857 857 tree = list(graphmod.colored(dag, web.repo))
858 858
859 859 def getcolumns(tree):
860 860 cols = 0
861 861 for (id, type, ctx, vtx, edges) in tree:
862 862 if type != graphmod.CHANGESET:
863 863 continue
864 864 cols = max(cols, max([edge[0] for edge in edges] or [0]),
865 865 max([edge[1] for edge in edges] or [0]))
866 866 return cols
867 867
868 868 def graphdata(usetuples, **map):
869 869 data = []
870 870
871 871 row = 0
872 872 for (id, type, ctx, vtx, edges) in tree:
873 873 if type != graphmod.CHANGESET:
874 874 continue
875 875 node = str(ctx)
876 876 age = templatefilters.age(ctx.date())
877 877 desc = templatefilters.firstline(ctx.description())
878 878 desc = cgi.escape(templatefilters.nonempty(desc))
879 879 user = cgi.escape(templatefilters.person(ctx.user()))
880 880 branch = ctx.branch()
881 881 try:
882 882 branchnode = web.repo.branchtip(branch)
883 883 except error.RepoLookupError:
884 884 branchnode = None
885 885 branch = branch, branchnode == ctx.node()
886 886
887 887 if usetuples:
888 888 data.append((node, vtx, edges, desc, user, age, branch,
889 889 ctx.tags(), ctx.bookmarks()))
890 890 else:
891 891 edgedata = [dict(col=edge[0], nextcol=edge[1],
892 892 color=(edge[2] - 1) % 6 + 1,
893 893 width=edge[3], bcolor=edge[4])
894 894 for edge in edges]
895 895
896 896 data.append(
897 897 dict(node=node,
898 898 col=vtx[0],
899 899 color=(vtx[1] - 1) % 6 + 1,
900 900 edges=edgedata,
901 901 row=row,
902 902 nextrow=row + 1,
903 903 desc=desc,
904 904 user=user,
905 905 age=age,
906 906 bookmarks=webutil.nodebookmarksdict(
907 907 web.repo, ctx.node()),
908 908 branches=webutil.nodebranchdict(web.repo, ctx),
909 909 inbranch=webutil.nodeinbranch(web.repo, ctx),
910 910 tags=webutil.nodetagsdict(web.repo, ctx.node())))
911 911
912 912 row += 1
913 913
914 914 return data
915 915
916 916 cols = getcolumns(tree)
917 917 rows = len(tree)
918 918 canvasheight = (rows + 1) * bg_height - 27
919 919
920 920 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
921 921 lessvars=lessvars, morevars=morevars, downrev=downrev,
922 922 cols=cols, rows=rows,
923 923 canvaswidth=(cols + 1) * bg_height,
924 924 truecanvasheight=rows * bg_height,
925 925 canvasheight=canvasheight, bg_height=bg_height,
926 926 jsdata=lambda **x: graphdata(True, **x),
927 927 nodes=lambda **x: graphdata(False, **x),
928 928 node=ctx.hex(), changenav=changenav)
929 929
930 930 def _getdoc(e):
931 931 doc = e[0].__doc__
932 932 if doc:
933 933 doc = _(doc).split('\n')[0]
934 934 else:
935 935 doc = _('(no help text available)')
936 936 return doc
937 937
938 938 def help(web, req, tmpl):
939 939 from mercurial import commands # avoid cycle
940 940
941 941 topicname = req.form.get('node', [None])[0]
942 942 if not topicname:
943 943 def topics(**map):
944 944 for entries, summary, _ in helpmod.helptable:
945 945 yield {'topic': entries[0], 'summary': summary}
946 946
947 947 early, other = [], []
948 948 primary = lambda s: s.split('|')[0]
949 949 for c, e in commands.table.iteritems():
950 950 doc = _getdoc(e)
951 951 if 'DEPRECATED' in doc or c.startswith('debug'):
952 952 continue
953 953 cmd = primary(c)
954 954 if cmd.startswith('^'):
955 955 early.append((cmd[1:], doc))
956 956 else:
957 957 other.append((cmd, doc))
958 958
959 959 early.sort()
960 960 other.sort()
961 961
962 962 def earlycommands(**map):
963 963 for c, doc in early:
964 964 yield {'topic': c, 'summary': doc}
965 965
966 966 def othercommands(**map):
967 967 for c, doc in other:
968 968 yield {'topic': c, 'summary': doc}
969 969
970 970 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
971 971 othercommands=othercommands, title='Index')
972 972
973 973 u = webutil.wsgiui()
974 974 u.pushbuffer()
975 975 u.verbose = True
976 976 try:
977 977 commands.help_(u, topicname)
978 978 except error.UnknownCommand:
979 979 raise ErrorResponse(HTTP_NOT_FOUND)
980 980 doc = u.popbuffer()
981 981 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now