##// END OF EJS Templates
hgweb: move HTTP content types out of header templates...
Dirkjan Ochtman -
r5928:3340aa5a default
parent child Browse files
Show More
@@ -1,908 +1,906 b''
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
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 import os, mimetypes, re, mimetools, cStringIO
9 import os, mimetypes, re
10 10 from mercurial.node import *
11 11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 12 from mercurial import revlog, templater
13 13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 14 from request import wsgirequest
15 15 import webcommands, protocol
16 16
17 17 shortcuts = {
18 18 'cl': [('cmd', ['changelog']), ('rev', None)],
19 19 'sl': [('cmd', ['shortlog']), ('rev', None)],
20 20 'cs': [('cmd', ['changeset']), ('node', None)],
21 21 'f': [('cmd', ['file']), ('filenode', None)],
22 22 'fl': [('cmd', ['filelog']), ('filenode', None)],
23 23 'fd': [('cmd', ['filediff']), ('node', None)],
24 24 'fa': [('cmd', ['annotate']), ('filenode', None)],
25 25 'mf': [('cmd', ['manifest']), ('manifest', None)],
26 26 'ca': [('cmd', ['archive']), ('node', None)],
27 27 'tags': [('cmd', ['tags'])],
28 28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
29 29 'static': [('cmd', ['static']), ('file', None)]
30 30 }
31 31
32 32 def _up(p):
33 33 if p[0] != "/":
34 34 p = "/" + p
35 35 if p[-1] == "/":
36 36 p = p[:-1]
37 37 up = os.path.dirname(p)
38 38 if up == "/":
39 39 return "/"
40 40 return up + "/"
41 41
42 42 def revnavgen(pos, pagelen, limit, nodefunc):
43 43 def seq(factor, limit=None):
44 44 if limit:
45 45 yield limit
46 46 if limit >= 20 and limit <= 40:
47 47 yield 50
48 48 else:
49 49 yield 1 * factor
50 50 yield 3 * factor
51 51 for f in seq(factor * 10):
52 52 yield f
53 53
54 54 def nav(**map):
55 55 l = []
56 56 last = 0
57 57 for f in seq(1, pagelen):
58 58 if f < pagelen or f <= last:
59 59 continue
60 60 if f > limit:
61 61 break
62 62 last = f
63 63 if pos + f < limit:
64 64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 65 if pos - f >= 0:
66 66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67 67
68 68 try:
69 69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
70 70
71 71 for label, node in l:
72 72 yield {"label": label, "node": node}
73 73
74 74 yield {"label": "tip", "node": "tip"}
75 75 except hg.RepoError:
76 76 pass
77 77
78 78 return nav
79 79
80 80 class hgweb(object):
81 81 def __init__(self, repo, name=None):
82 82 if isinstance(repo, str):
83 83 parentui = ui.ui(report_untrusted=False, interactive=False)
84 84 self.repo = hg.repository(parentui, repo)
85 85 else:
86 86 self.repo = repo
87 87
88 88 hook.redirect(True)
89 89 self.mtime = -1
90 90 self.reponame = name
91 91 self.archives = 'zip', 'gz', 'bz2'
92 92 self.stripecount = 1
93 93 # a repo owner may set web.templates in .hg/hgrc to get any file
94 94 # readable by the user running the CGI script
95 95 self.templatepath = self.config("web", "templates",
96 96 templater.templatepath(),
97 97 untrusted=False)
98 98
99 99 # The CGI scripts are often run by a user different from the repo owner.
100 100 # Trust the settings from the .hg/hgrc files by default.
101 101 def config(self, section, name, default=None, untrusted=True):
102 102 return self.repo.ui.config(section, name, default,
103 103 untrusted=untrusted)
104 104
105 105 def configbool(self, section, name, default=False, untrusted=True):
106 106 return self.repo.ui.configbool(section, name, default,
107 107 untrusted=untrusted)
108 108
109 109 def configlist(self, section, name, default=None, untrusted=True):
110 110 return self.repo.ui.configlist(section, name, default,
111 111 untrusted=untrusted)
112 112
113 113 def refresh(self):
114 114 mtime = get_mtime(self.repo.root)
115 115 if mtime != self.mtime:
116 116 self.mtime = mtime
117 117 self.repo = hg.repository(self.repo.ui, self.repo.root)
118 118 self.maxchanges = int(self.config("web", "maxchanges", 10))
119 119 self.stripecount = int(self.config("web", "stripes", 1))
120 120 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
121 121 self.maxfiles = int(self.config("web", "maxfiles", 10))
122 122 self.allowpull = self.configbool("web", "allowpull", True)
123 123 self.encoding = self.config("web", "encoding", util._encoding)
124 124
125 125 def run(self):
126 126 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
127 127 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
128 128 import mercurial.hgweb.wsgicgi as wsgicgi
129 129 wsgicgi.launch(self)
130 130
131 131 def __call__(self, env, respond):
132 132 req = wsgirequest(env, respond)
133 133 self.run_wsgi(req)
134 134 return req
135 135
136 136 def run_wsgi(self, req):
137 137
138 138 self.refresh()
139 139
140 140 # expand form shortcuts
141 141
142 142 for k in shortcuts.iterkeys():
143 143 if k in req.form:
144 144 for name, value in shortcuts[k]:
145 145 if value is None:
146 146 value = req.form[k]
147 147 req.form[name] = value
148 148 del req.form[k]
149 149
150 150 # work with CGI variables to create coherent structure
151 151 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
152 152
153 153 req.url = req.env['SCRIPT_NAME']
154 154 if not req.url.endswith('/'):
155 155 req.url += '/'
156 156 if 'REPO_NAME' in req.env:
157 157 req.url += req.env['REPO_NAME'] + '/'
158 158
159 159 if req.env.get('PATH_INFO'):
160 160 parts = req.env.get('PATH_INFO').strip('/').split('/')
161 161 repo_parts = req.env.get('REPO_NAME', '').split('/')
162 162 if parts[:len(repo_parts)] == repo_parts:
163 163 parts = parts[len(repo_parts):]
164 164 query = '/'.join(parts)
165 165 else:
166 166 query = req.env['QUERY_STRING'].split('&', 1)[0]
167 167 query = query.split(';', 1)[0]
168 168
169 169 # translate user-visible url structure to internal structure
170 170
171 171 args = query.split('/', 2)
172 172 if 'cmd' not in req.form and args and args[0]:
173 173
174 174 cmd = args.pop(0)
175 175 style = cmd.rfind('-')
176 176 if style != -1:
177 177 req.form['style'] = [cmd[:style]]
178 178 cmd = cmd[style+1:]
179 179
180 180 # avoid accepting e.g. style parameter as command
181 181 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
182 182 req.form['cmd'] = [cmd]
183 183
184 184 if args and args[0]:
185 185 node = args.pop(0)
186 186 req.form['node'] = [node]
187 187 if args:
188 188 req.form['file'] = args
189 189
190 190 if cmd == 'static':
191 191 req.form['file'] = req.form['node']
192 192 elif cmd == 'archive':
193 193 fn = req.form['node'][0]
194 194 for type_, spec in self.archive_specs.iteritems():
195 195 ext = spec[2]
196 196 if fn.endswith(ext):
197 197 req.form['node'] = [fn[:-len(ext)]]
198 198 req.form['type'] = [type_]
199 199
200 200 # actually process the request
201 201
202 202 try:
203 203
204 204 cmd = req.form.get('cmd', [''])[0]
205 205 if hasattr(protocol, cmd):
206 206 method = getattr(protocol, cmd)
207 207 method(self, req)
208 208 else:
209 209
210 210 tmpl = self.templater(req)
211 211 if cmd == '':
212 212 req.form['cmd'] = [tmpl.cache['default']]
213 213 cmd = req.form['cmd'][0]
214 214
215 215 if cmd == 'file' and 'raw' in req.form.get('style', []):
216 216 webcommands.rawfile(self, req, tmpl)
217 217 else:
218 218 getattr(webcommands, cmd)(self, req, tmpl)
219 219
220 220 del tmpl
221 221
222 222 except revlog.LookupError, err:
223 223 req.respond(404, tmpl(
224 224 'error', error='revision not found: %s' % err.name))
225 225 except (hg.RepoError, revlog.RevlogError), inst:
226 226 req.respond('500 Internal Server Error',
227 227 tmpl('error', error=str(inst)))
228 228 except ErrorResponse, inst:
229 229 req.respond(inst.code, tmpl('error', error=inst.message))
230 230 except AttributeError:
231 231 req.respond(400, tmpl('error', error='No such method: ' + cmd))
232 232
233 233 def templater(self, req):
234 234
235 235 # determine scheme, port and server name
236 236 # this is needed to create absolute urls
237 237
238 238 proto = req.env.get('wsgi.url_scheme')
239 239 if proto == 'https':
240 240 proto = 'https'
241 241 default_port = "443"
242 242 else:
243 243 proto = 'http'
244 244 default_port = "80"
245 245
246 246 port = req.env["SERVER_PORT"]
247 247 port = port != default_port and (":" + port) or ""
248 248 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
249 249 staticurl = self.config("web", "staticurl") or req.url + 'static/'
250 250 if not staticurl.endswith('/'):
251 251 staticurl += '/'
252 252
253 253 # some functions for the templater
254 254
255 255 def header(**map):
256 header_file = cStringIO.StringIO(
257 ''.join(tmpl("header", encoding=self.encoding, **map)))
258 msg = mimetools.Message(header_file, 0)
259 req.header(msg.items())
260 yield header_file.read()
256 ctype = tmpl('mimetype', encoding=self.encoding)
257 req.httphdr(templater.stringify(ctype))
258 yield tmpl('header', encoding=self.encoding, **map)
261 259
262 260 def footer(**map):
263 261 yield tmpl("footer", **map)
264 262
265 263 def motd(**map):
266 264 yield self.config("web", "motd", "")
267 265
268 266 def sessionvars(**map):
269 267 fields = []
270 268 if 'style' in req.form:
271 269 style = req.form['style'][0]
272 270 if style != self.config('web', 'style', ''):
273 271 fields.append(('style', style))
274 272
275 273 separator = req.url[-1] == '?' and ';' or '?'
276 274 for name, value in fields:
277 275 yield dict(name=name, value=value, separator=separator)
278 276 separator = ';'
279 277
280 278 # figure out which style to use
281 279
282 280 style = self.config("web", "style", "")
283 281 if 'style' in req.form:
284 282 style = req.form['style'][0]
285 283 mapfile = style_map(self.templatepath, style)
286 284
287 285 if not self.reponame:
288 286 self.reponame = (self.config("web", "name")
289 287 or req.env.get('REPO_NAME')
290 288 or req.url.strip('/') or self.repo.root)
291 289
292 290 # create the templater
293 291
294 292 tmpl = templater.templater(mapfile, templater.common_filters,
295 293 defaults={"url": req.url,
296 294 "staticurl": staticurl,
297 295 "urlbase": urlbase,
298 296 "repo": self.reponame,
299 297 "header": header,
300 298 "footer": footer,
301 299 "motd": motd,
302 300 "sessionvars": sessionvars
303 301 })
304 302 return tmpl
305 303
306 304 def archivelist(self, nodeid):
307 305 allowed = self.configlist("web", "allow_archive")
308 306 for i, spec in self.archive_specs.iteritems():
309 307 if i in allowed or self.configbool("web", "allow" + i):
310 308 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
311 309
312 310 def listfilediffs(self, tmpl, files, changeset):
313 311 for f in files[:self.maxfiles]:
314 312 yield tmpl("filedifflink", node=hex(changeset), file=f)
315 313 if len(files) > self.maxfiles:
316 314 yield tmpl("fileellipses")
317 315
318 316 def siblings(self, siblings=[], hiderev=None, **args):
319 317 siblings = [s for s in siblings if s.node() != nullid]
320 318 if len(siblings) == 1 and siblings[0].rev() == hiderev:
321 319 return
322 320 for s in siblings:
323 321 d = {'node': hex(s.node()), 'rev': s.rev()}
324 322 if hasattr(s, 'path'):
325 323 d['file'] = s.path()
326 324 d.update(args)
327 325 yield d
328 326
329 327 def renamelink(self, fl, node):
330 328 r = fl.renamed(node)
331 329 if r:
332 330 return [dict(file=r[0], node=hex(r[1]))]
333 331 return []
334 332
335 333 def nodetagsdict(self, node):
336 334 return [{"name": i} for i in self.repo.nodetags(node)]
337 335
338 336 def nodebranchdict(self, ctx):
339 337 branches = []
340 338 branch = ctx.branch()
341 339 # If this is an empty repo, ctx.node() == nullid,
342 340 # ctx.branch() == 'default', but branchtags() is
343 341 # an empty dict. Using dict.get avoids a traceback.
344 342 if self.repo.branchtags().get(branch) == ctx.node():
345 343 branches.append({"name": branch})
346 344 return branches
347 345
348 346 def showtag(self, tmpl, t1, node=nullid, **args):
349 347 for t in self.repo.nodetags(node):
350 348 yield tmpl(t1, tag=t, **args)
351 349
352 350 def diff(self, tmpl, node1, node2, files):
353 351 def filterfiles(filters, files):
354 352 l = [x for x in files if x in filters]
355 353
356 354 for t in filters:
357 355 if t and t[-1] != os.sep:
358 356 t += os.sep
359 357 l += [x for x in files if x.startswith(t)]
360 358 return l
361 359
362 360 parity = paritygen(self.stripecount)
363 361 def diffblock(diff, f, fn):
364 362 yield tmpl("diffblock",
365 363 lines=prettyprintlines(diff),
366 364 parity=parity.next(),
367 365 file=f,
368 366 filenode=hex(fn or nullid))
369 367
370 368 def prettyprintlines(diff):
371 369 for l in diff.splitlines(1):
372 370 if l.startswith('+'):
373 371 yield tmpl("difflineplus", line=l)
374 372 elif l.startswith('-'):
375 373 yield tmpl("difflineminus", line=l)
376 374 elif l.startswith('@'):
377 375 yield tmpl("difflineat", line=l)
378 376 else:
379 377 yield tmpl("diffline", line=l)
380 378
381 379 r = self.repo
382 380 c1 = r.changectx(node1)
383 381 c2 = r.changectx(node2)
384 382 date1 = util.datestr(c1.date())
385 383 date2 = util.datestr(c2.date())
386 384
387 385 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
388 386 if files:
389 387 modified, added, removed = map(lambda x: filterfiles(files, x),
390 388 (modified, added, removed))
391 389
392 390 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
393 391 for f in modified:
394 392 to = c1.filectx(f).data()
395 393 tn = c2.filectx(f).data()
396 394 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
397 395 opts=diffopts), f, tn)
398 396 for f in added:
399 397 to = None
400 398 tn = c2.filectx(f).data()
401 399 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
402 400 opts=diffopts), f, tn)
403 401 for f in removed:
404 402 to = c1.filectx(f).data()
405 403 tn = None
406 404 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
407 405 opts=diffopts), f, tn)
408 406
409 407 def changelog(self, tmpl, ctx, shortlog=False):
410 408 def changelist(limit=0,**map):
411 409 cl = self.repo.changelog
412 410 l = [] # build a list in forward order for efficiency
413 411 for i in xrange(start, end):
414 412 ctx = self.repo.changectx(i)
415 413 n = ctx.node()
416 414
417 415 l.insert(0, {"parity": parity.next(),
418 416 "author": ctx.user(),
419 417 "parent": self.siblings(ctx.parents(), i - 1),
420 418 "child": self.siblings(ctx.children(), i + 1),
421 419 "changelogtag": self.showtag("changelogtag",n),
422 420 "desc": ctx.description(),
423 421 "date": ctx.date(),
424 422 "files": self.listfilediffs(tmpl, ctx.files(), n),
425 423 "rev": i,
426 424 "node": hex(n),
427 425 "tags": self.nodetagsdict(n),
428 426 "branches": self.nodebranchdict(ctx)})
429 427
430 428 if limit > 0:
431 429 l = l[:limit]
432 430
433 431 for e in l:
434 432 yield e
435 433
436 434 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
437 435 cl = self.repo.changelog
438 436 count = cl.count()
439 437 pos = ctx.rev()
440 438 start = max(0, pos - maxchanges + 1)
441 439 end = min(count, start + maxchanges)
442 440 pos = end - 1
443 441 parity = paritygen(self.stripecount, offset=start-end)
444 442
445 443 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
446 444
447 445 return tmpl(shortlog and 'shortlog' or 'changelog',
448 446 changenav=changenav,
449 447 node=hex(cl.tip()),
450 448 rev=pos, changesets=count,
451 449 entries=lambda **x: changelist(limit=0,**x),
452 450 latestentry=lambda **x: changelist(limit=1,**x),
453 451 archives=self.archivelist("tip"))
454 452
455 453 def search(self, tmpl, query):
456 454
457 455 def changelist(**map):
458 456 cl = self.repo.changelog
459 457 count = 0
460 458 qw = query.lower().split()
461 459
462 460 def revgen():
463 461 for i in xrange(cl.count() - 1, 0, -100):
464 462 l = []
465 463 for j in xrange(max(0, i - 100), i):
466 464 ctx = self.repo.changectx(j)
467 465 l.append(ctx)
468 466 l.reverse()
469 467 for e in l:
470 468 yield e
471 469
472 470 for ctx in revgen():
473 471 miss = 0
474 472 for q in qw:
475 473 if not (q in ctx.user().lower() or
476 474 q in ctx.description().lower() or
477 475 q in " ".join(ctx.files()).lower()):
478 476 miss = 1
479 477 break
480 478 if miss:
481 479 continue
482 480
483 481 count += 1
484 482 n = ctx.node()
485 483
486 484 yield tmpl('searchentry',
487 485 parity=parity.next(),
488 486 author=ctx.user(),
489 487 parent=self.siblings(ctx.parents()),
490 488 child=self.siblings(ctx.children()),
491 489 changelogtag=self.showtag("changelogtag",n),
492 490 desc=ctx.description(),
493 491 date=ctx.date(),
494 492 files=self.listfilediffs(tmpl, ctx.files(), n),
495 493 rev=ctx.rev(),
496 494 node=hex(n),
497 495 tags=self.nodetagsdict(n),
498 496 branches=self.nodebranchdict(ctx))
499 497
500 498 if count >= self.maxchanges:
501 499 break
502 500
503 501 cl = self.repo.changelog
504 502 parity = paritygen(self.stripecount)
505 503
506 504 return tmpl('search',
507 505 query=query,
508 506 node=hex(cl.tip()),
509 507 entries=changelist,
510 508 archives=self.archivelist("tip"))
511 509
512 510 def changeset(self, tmpl, ctx):
513 511 n = ctx.node()
514 512 parents = ctx.parents()
515 513 p1 = parents[0].node()
516 514
517 515 files = []
518 516 parity = paritygen(self.stripecount)
519 517 for f in ctx.files():
520 518 files.append(tmpl("filenodelink",
521 519 node=hex(n), file=f,
522 520 parity=parity.next()))
523 521
524 522 def diff(**map):
525 523 yield self.diff(tmpl, p1, n, None)
526 524
527 525 return tmpl('changeset',
528 526 diff=diff,
529 527 rev=ctx.rev(),
530 528 node=hex(n),
531 529 parent=self.siblings(parents),
532 530 child=self.siblings(ctx.children()),
533 531 changesettag=self.showtag("changesettag",n),
534 532 author=ctx.user(),
535 533 desc=ctx.description(),
536 534 date=ctx.date(),
537 535 files=files,
538 536 archives=self.archivelist(hex(n)),
539 537 tags=self.nodetagsdict(n),
540 538 branches=self.nodebranchdict(ctx))
541 539
542 540 def filelog(self, tmpl, fctx):
543 541 f = fctx.path()
544 542 fl = fctx.filelog()
545 543 count = fl.count()
546 544 pagelen = self.maxshortchanges
547 545 pos = fctx.filerev()
548 546 start = max(0, pos - pagelen + 1)
549 547 end = min(count, start + pagelen)
550 548 pos = end - 1
551 549 parity = paritygen(self.stripecount, offset=start-end)
552 550
553 551 def entries(limit=0, **map):
554 552 l = []
555 553
556 554 for i in xrange(start, end):
557 555 ctx = fctx.filectx(i)
558 556 n = fl.node(i)
559 557
560 558 l.insert(0, {"parity": parity.next(),
561 559 "filerev": i,
562 560 "file": f,
563 561 "node": hex(ctx.node()),
564 562 "author": ctx.user(),
565 563 "date": ctx.date(),
566 564 "rename": self.renamelink(fl, n),
567 565 "parent": self.siblings(fctx.parents()),
568 566 "child": self.siblings(fctx.children()),
569 567 "desc": ctx.description()})
570 568
571 569 if limit > 0:
572 570 l = l[:limit]
573 571
574 572 for e in l:
575 573 yield e
576 574
577 575 nodefunc = lambda x: fctx.filectx(fileid=x)
578 576 nav = revnavgen(pos, pagelen, count, nodefunc)
579 577 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
580 578 entries=lambda **x: entries(limit=0, **x),
581 579 latestentry=lambda **x: entries(limit=1, **x))
582 580
583 581 def filerevision(self, tmpl, fctx):
584 582 f = fctx.path()
585 583 text = fctx.data()
586 584 fl = fctx.filelog()
587 585 n = fctx.filenode()
588 586 parity = paritygen(self.stripecount)
589 587
590 588 mt = mimetypes.guess_type(f)[0]
591 589 rawtext = text
592 590 if util.binary(text):
593 591 mt = mt or 'application/octet-stream'
594 592 text = "(binary:%s)" % mt
595 593 mt = mt or 'text/plain'
596 594
597 595 def lines():
598 596 for l, t in enumerate(text.splitlines(1)):
599 597 yield {"line": t,
600 598 "linenumber": "% 6d" % (l + 1),
601 599 "parity": parity.next()}
602 600
603 601 return tmpl("filerevision",
604 602 file=f,
605 603 path=_up(f),
606 604 text=lines(),
607 605 raw=rawtext,
608 606 mimetype=mt,
609 607 rev=fctx.rev(),
610 608 node=hex(fctx.node()),
611 609 author=fctx.user(),
612 610 date=fctx.date(),
613 611 desc=fctx.description(),
614 612 parent=self.siblings(fctx.parents()),
615 613 child=self.siblings(fctx.children()),
616 614 rename=self.renamelink(fl, n),
617 615 permissions=fctx.manifest().flags(f))
618 616
619 617 def fileannotate(self, tmpl, fctx):
620 618 f = fctx.path()
621 619 n = fctx.filenode()
622 620 fl = fctx.filelog()
623 621 parity = paritygen(self.stripecount)
624 622
625 623 def annotate(**map):
626 624 last = None
627 625 for f, l in fctx.annotate(follow=True):
628 626 fnode = f.filenode()
629 627 name = self.repo.ui.shortuser(f.user())
630 628
631 629 if last != fnode:
632 630 last = fnode
633 631
634 632 yield {"parity": parity.next(),
635 633 "node": hex(f.node()),
636 634 "rev": f.rev(),
637 635 "author": name,
638 636 "file": f.path(),
639 637 "line": l}
640 638
641 639 return tmpl("fileannotate",
642 640 file=f,
643 641 annotate=annotate,
644 642 path=_up(f),
645 643 rev=fctx.rev(),
646 644 node=hex(fctx.node()),
647 645 author=fctx.user(),
648 646 date=fctx.date(),
649 647 desc=fctx.description(),
650 648 rename=self.renamelink(fl, n),
651 649 parent=self.siblings(fctx.parents()),
652 650 child=self.siblings(fctx.children()),
653 651 permissions=fctx.manifest().flags(f))
654 652
655 653 def manifest(self, tmpl, ctx, path):
656 654 mf = ctx.manifest()
657 655 node = ctx.node()
658 656
659 657 files = {}
660 658 parity = paritygen(self.stripecount)
661 659
662 660 if path and path[-1] != "/":
663 661 path += "/"
664 662 l = len(path)
665 663 abspath = "/" + path
666 664
667 665 for f, n in mf.items():
668 666 if f[:l] != path:
669 667 continue
670 668 remain = f[l:]
671 669 if "/" in remain:
672 670 short = remain[:remain.index("/") + 1] # bleah
673 671 files[short] = (f, None)
674 672 else:
675 673 short = os.path.basename(remain)
676 674 files[short] = (f, n)
677 675
678 676 if not files:
679 677 raise ErrorResponse(404, 'Path not found: ' + path)
680 678
681 679 def filelist(**map):
682 680 fl = files.keys()
683 681 fl.sort()
684 682 for f in fl:
685 683 full, fnode = files[f]
686 684 if not fnode:
687 685 continue
688 686
689 687 fctx = ctx.filectx(full)
690 688 yield {"file": full,
691 689 "parity": parity.next(),
692 690 "basename": f,
693 691 "date": fctx.changectx().date(),
694 692 "size": fctx.size(),
695 693 "permissions": mf.flags(full)}
696 694
697 695 def dirlist(**map):
698 696 fl = files.keys()
699 697 fl.sort()
700 698 for f in fl:
701 699 full, fnode = files[f]
702 700 if fnode:
703 701 continue
704 702
705 703 yield {"parity": parity.next(),
706 704 "path": "%s%s" % (abspath, f),
707 705 "basename": f[:-1]}
708 706
709 707 return tmpl("manifest",
710 708 rev=ctx.rev(),
711 709 node=hex(node),
712 710 path=abspath,
713 711 up=_up(abspath),
714 712 upparity=parity.next(),
715 713 fentries=filelist,
716 714 dentries=dirlist,
717 715 archives=self.archivelist(hex(node)),
718 716 tags=self.nodetagsdict(node),
719 717 branches=self.nodebranchdict(ctx))
720 718
721 719 def tags(self, tmpl):
722 720 i = self.repo.tagslist()
723 721 i.reverse()
724 722 parity = paritygen(self.stripecount)
725 723
726 724 def entries(notip=False,limit=0, **map):
727 725 count = 0
728 726 for k, n in i:
729 727 if notip and k == "tip":
730 728 continue
731 729 if limit > 0 and count >= limit:
732 730 continue
733 731 count = count + 1
734 732 yield {"parity": parity.next(),
735 733 "tag": k,
736 734 "date": self.repo.changectx(n).date(),
737 735 "node": hex(n)}
738 736
739 737 return tmpl("tags",
740 738 node=hex(self.repo.changelog.tip()),
741 739 entries=lambda **x: entries(False,0, **x),
742 740 entriesnotip=lambda **x: entries(True,0, **x),
743 741 latestentry=lambda **x: entries(True,1, **x))
744 742
745 743 def summary(self, tmpl):
746 744 i = self.repo.tagslist()
747 745 i.reverse()
748 746
749 747 def tagentries(**map):
750 748 parity = paritygen(self.stripecount)
751 749 count = 0
752 750 for k, n in i:
753 751 if k == "tip": # skip tip
754 752 continue;
755 753
756 754 count += 1
757 755 if count > 10: # limit to 10 tags
758 756 break;
759 757
760 758 yield tmpl("tagentry",
761 759 parity=parity.next(),
762 760 tag=k,
763 761 node=hex(n),
764 762 date=self.repo.changectx(n).date())
765 763
766 764
767 765 def branches(**map):
768 766 parity = paritygen(self.stripecount)
769 767
770 768 b = self.repo.branchtags()
771 769 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
772 770 l.sort()
773 771
774 772 for r,n,t in l:
775 773 ctx = self.repo.changectx(n)
776 774
777 775 yield {'parity': parity.next(),
778 776 'branch': t,
779 777 'node': hex(n),
780 778 'date': ctx.date()}
781 779
782 780 def changelist(**map):
783 781 parity = paritygen(self.stripecount, offset=start-end)
784 782 l = [] # build a list in forward order for efficiency
785 783 for i in xrange(start, end):
786 784 ctx = self.repo.changectx(i)
787 785 n = ctx.node()
788 786 hn = hex(n)
789 787
790 788 l.insert(0, tmpl(
791 789 'shortlogentry',
792 790 parity=parity.next(),
793 791 author=ctx.user(),
794 792 desc=ctx.description(),
795 793 date=ctx.date(),
796 794 rev=i,
797 795 node=hn,
798 796 tags=self.nodetagsdict(n),
799 797 branches=self.nodebranchdict(ctx)))
800 798
801 799 yield l
802 800
803 801 cl = self.repo.changelog
804 802 count = cl.count()
805 803 start = max(0, count - self.maxchanges)
806 804 end = min(count, start + self.maxchanges)
807 805
808 806 return tmpl("summary",
809 807 desc=self.config("web", "description", "unknown"),
810 808 owner=get_contact(self.config) or "unknown",
811 809 lastchange=cl.read(cl.tip())[2],
812 810 tags=tagentries,
813 811 branches=branches,
814 812 shortlog=changelist,
815 813 node=hex(cl.tip()),
816 814 archives=self.archivelist("tip"))
817 815
818 816 def filediff(self, tmpl, fctx):
819 817 n = fctx.node()
820 818 path = fctx.path()
821 819 parents = fctx.parents()
822 820 p1 = parents and parents[0].node() or nullid
823 821
824 822 def diff(**map):
825 823 yield self.diff(tmpl, p1, n, [path])
826 824
827 825 return tmpl("filediff",
828 826 file=path,
829 827 node=hex(n),
830 828 rev=fctx.rev(),
831 829 parent=self.siblings(parents),
832 830 child=self.siblings(fctx.children()),
833 831 diff=diff)
834 832
835 833 archive_specs = {
836 834 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
837 835 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
838 836 'zip': ('application/zip', 'zip', '.zip', None),
839 837 }
840 838
841 839 def archive(self, tmpl, req, key, type_):
842 840 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
843 841 cnode = self.repo.lookup(key)
844 842 arch_version = key
845 843 if cnode == key or key == 'tip':
846 844 arch_version = short(cnode)
847 845 name = "%s-%s" % (reponame, arch_version)
848 846 mimetype, artype, extension, encoding = self.archive_specs[type_]
849 847 headers = [('Content-type', mimetype),
850 848 ('Content-disposition', 'attachment; filename=%s%s' %
851 849 (name, extension))]
852 850 if encoding:
853 851 headers.append(('Content-encoding', encoding))
854 852 req.header(headers)
855 853 archival.archive(self.repo, req, cnode, artype, prefix=name)
856 854
857 855 # add tags to things
858 856 # tags -> list of changesets corresponding to tags
859 857 # find tag, changeset, file
860 858
861 859 def cleanpath(self, path):
862 860 path = path.lstrip('/')
863 861 return util.canonpath(self.repo.root, '', path)
864 862
865 863 def changectx(self, req):
866 864 if 'node' in req.form:
867 865 changeid = req.form['node'][0]
868 866 elif 'manifest' in req.form:
869 867 changeid = req.form['manifest'][0]
870 868 else:
871 869 changeid = self.repo.changelog.count() - 1
872 870
873 871 try:
874 872 ctx = self.repo.changectx(changeid)
875 873 except hg.RepoError:
876 874 man = self.repo.manifest
877 875 mn = man.lookup(changeid)
878 876 ctx = self.repo.changectx(man.linkrev(mn))
879 877
880 878 return ctx
881 879
882 880 def filectx(self, req):
883 881 path = self.cleanpath(req.form['file'][0])
884 882 if 'node' in req.form:
885 883 changeid = req.form['node'][0]
886 884 else:
887 885 changeid = req.form['filenode'][0]
888 886 try:
889 887 ctx = self.repo.changectx(changeid)
890 888 fctx = ctx.filectx(path)
891 889 except hg.RepoError:
892 890 fctx = self.repo.filectx(path, fileid=changeid)
893 891
894 892 return fctx
895 893
896 894 def check_perm(self, req, op, default):
897 895 '''check permission for operation based on user auth.
898 896 return true if op allowed, else false.
899 897 default is policy to use if no config given.'''
900 898
901 899 user = req.env.get('REMOTE_USER')
902 900
903 901 deny = self.configlist('web', 'deny_' + op)
904 902 if deny and (not user or deny == ['*'] or user in deny):
905 903 return False
906 904
907 905 allow = self.configlist('web', 'allow_' + op)
908 906 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,276 +1,275 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 import os, mimetools, cStringIO
9 import os
10 10 from mercurial.i18n import gettext as _
11 11 from mercurial import ui, hg, util, templater
12 12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen, \
13 13 get_contact
14 14 from hgweb_mod import hgweb
15 15 from request import wsgirequest
16 16
17 17 # This is a stopgap
18 18 class hgwebdir(object):
19 19 def __init__(self, config, parentui=None):
20 20 def cleannames(items):
21 21 return [(util.pconvert(name).strip('/'), path)
22 22 for name, path in items]
23 23
24 24 self.parentui = parentui or ui.ui(report_untrusted=False,
25 25 interactive = False)
26 26 self.motd = None
27 27 self.style = None
28 28 self.stripecount = None
29 29 self.repos_sorted = ('name', False)
30 30 if isinstance(config, (list, tuple)):
31 31 self.repos = cleannames(config)
32 32 self.repos_sorted = ('', False)
33 33 elif isinstance(config, dict):
34 34 self.repos = cleannames(config.items())
35 35 self.repos.sort()
36 36 else:
37 37 if isinstance(config, util.configparser):
38 38 cp = config
39 39 else:
40 40 cp = util.configparser()
41 41 cp.read(config)
42 42 self.repos = []
43 43 if cp.has_section('web'):
44 44 if cp.has_option('web', 'motd'):
45 45 self.motd = cp.get('web', 'motd')
46 46 if cp.has_option('web', 'style'):
47 47 self.style = cp.get('web', 'style')
48 48 if cp.has_option('web', 'stripes'):
49 49 self.stripecount = int(cp.get('web', 'stripes'))
50 50 if cp.has_section('paths'):
51 51 self.repos.extend(cleannames(cp.items('paths')))
52 52 if cp.has_section('collections'):
53 53 for prefix, root in cp.items('collections'):
54 54 for path in util.walkrepos(root):
55 55 repo = os.path.normpath(path)
56 56 name = repo
57 57 if name.startswith(prefix):
58 58 name = name[len(prefix):]
59 59 self.repos.append((name.lstrip(os.sep), repo))
60 60 self.repos.sort()
61 61
62 62 def run(self):
63 63 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
64 64 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
65 65 import mercurial.hgweb.wsgicgi as wsgicgi
66 66 wsgicgi.launch(self)
67 67
68 68 def __call__(self, env, respond):
69 69 req = wsgirequest(env, respond)
70 70 self.run_wsgi(req)
71 71 return req
72 72
73 73 def run_wsgi(self, req):
74 74
75 75 try:
76 76 try:
77 77
78 78 virtual = req.env.get("PATH_INFO", "").strip('/')
79 79
80 80 # a static file
81 81 if virtual.startswith('static/') or 'static' in req.form:
82 82 static = os.path.join(templater.templatepath(), 'static')
83 83 if virtual.startswith('static/'):
84 84 fname = virtual[7:]
85 85 else:
86 86 fname = req.form['static'][0]
87 87 req.write(staticfile(static, fname, req))
88 88 return
89 89
90 90 # top-level index
91 91 elif not virtual:
92 92 tmpl = self.templater(req)
93 93 self.makeindex(req, tmpl)
94 94 return
95 95
96 96 # nested indexes and hgwebs
97 97 repos = dict(self.repos)
98 98 while virtual:
99 99 real = repos.get(virtual)
100 100 if real:
101 101 req.env['REPO_NAME'] = virtual
102 102 try:
103 103 repo = hg.repository(self.parentui, real)
104 104 hgweb(repo).run_wsgi(req)
105 105 return
106 106 except IOError, inst:
107 107 raise ErrorResponse(500, inst.strerror)
108 108 except hg.RepoError, inst:
109 109 raise ErrorResponse(500, str(inst))
110 110
111 111 # browse subdirectories
112 112 subdir = virtual + '/'
113 113 if [r for r in repos if r.startswith(subdir)]:
114 114 tmpl = self.templater(req)
115 115 self.makeindex(req, tmpl, subdir)
116 116 return
117 117
118 118 up = virtual.rfind('/')
119 119 if up < 0:
120 120 break
121 121 virtual = virtual[:up]
122 122
123 123 # prefixes not found
124 124 tmpl = self.templater(req)
125 125 req.respond(404, tmpl("notfound", repo=virtual))
126 126
127 127 except ErrorResponse, err:
128 128 tmpl = self.templater(req)
129 129 req.respond(err.code, tmpl('error', error=err.message or ''))
130 130 finally:
131 131 tmpl = None
132 132
133 133 def makeindex(self, req, tmpl, subdir=""):
134 134
135 135 def archivelist(ui, nodeid, url):
136 136 allowed = ui.configlist("web", "allow_archive", untrusted=True)
137 137 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
138 138 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
139 139 untrusted=True):
140 140 yield {"type" : i[0], "extension": i[1],
141 141 "node": nodeid, "url": url}
142 142
143 143 def entries(sortcolumn="", descending=False, subdir="", **map):
144 144 def sessionvars(**map):
145 145 fields = []
146 146 if 'style' in req.form:
147 147 style = req.form['style'][0]
148 148 if style != get('web', 'style', ''):
149 149 fields.append(('style', style))
150 150
151 151 separator = url[-1] == '?' and ';' or '?'
152 152 for name, value in fields:
153 153 yield dict(name=name, value=value, separator=separator)
154 154 separator = ';'
155 155
156 156 rows = []
157 157 parity = paritygen(self.stripecount)
158 158 for name, path in self.repos:
159 159 if not name.startswith(subdir):
160 160 continue
161 161 name = name[len(subdir):]
162 162
163 163 u = ui.ui(parentui=self.parentui)
164 164 try:
165 165 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
166 166 except Exception, e:
167 167 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
168 168 continue
169 169 def get(section, name, default=None):
170 170 return u.config(section, name, default, untrusted=True)
171 171
172 172 if u.configbool("web", "hidden", untrusted=True):
173 173 continue
174 174
175 175 parts = [req.env['PATH_INFO'], name]
176 176 if req.env['SCRIPT_NAME']:
177 177 parts.insert(0, req.env['SCRIPT_NAME'])
178 178 url = ('/'.join(parts).replace("//", "/")) + '/'
179 179
180 180 # update time with local timezone
181 181 try:
182 182 d = (get_mtime(path), util.makedate()[1])
183 183 except OSError:
184 184 continue
185 185
186 186 contact = get_contact(get)
187 187 description = get("web", "description", "")
188 188 name = get("web", "name", name)
189 189 row = dict(contact=contact or "unknown",
190 190 contact_sort=contact.upper() or "unknown",
191 191 name=name,
192 192 name_sort=name,
193 193 url=url,
194 194 description=description or "unknown",
195 195 description_sort=description.upper() or "unknown",
196 196 lastchange=d,
197 197 lastchange_sort=d[1]-d[0],
198 198 sessionvars=sessionvars,
199 199 archives=archivelist(u, "tip", url))
200 200 if (not sortcolumn
201 201 or (sortcolumn, descending) == self.repos_sorted):
202 202 # fast path for unsorted output
203 203 row['parity'] = parity.next()
204 204 yield row
205 205 else:
206 206 rows.append((row["%s_sort" % sortcolumn], row))
207 207 if rows:
208 208 rows.sort()
209 209 if descending:
210 210 rows.reverse()
211 211 for key, row in rows:
212 212 row['parity'] = parity.next()
213 213 yield row
214 214
215 215 sortable = ["name", "description", "contact", "lastchange"]
216 216 sortcolumn, descending = self.repos_sorted
217 217 if 'sort' in req.form:
218 218 sortcolumn = req.form['sort'][0]
219 219 descending = sortcolumn.startswith('-')
220 220 if descending:
221 221 sortcolumn = sortcolumn[1:]
222 222 if sortcolumn not in sortable:
223 223 sortcolumn = ""
224 224
225 225 sort = [("sort_%s" % column,
226 226 "%s%s" % ((not descending and column == sortcolumn)
227 227 and "-" or "", column))
228 228 for column in sortable]
229
229 230 req.write(tmpl("index", entries=entries, subdir=subdir,
230 231 sortcolumn=sortcolumn, descending=descending,
231 232 **dict(sort)))
232 233
233 234 def templater(self, req):
234 235
235 236 def header(**map):
236 header_file = cStringIO.StringIO(
237 ''.join(tmpl("header", encoding=util._encoding, **map)))
238 msg = mimetools.Message(header_file, 0)
239 req.header(msg.items())
240 yield header_file.read()
237 ctype = tmpl('mimetype', encoding=util._encoding)
238 req.httphdr(templater.stringify(ctype))
239 yield tmpl('header', encoding=util._encoding, **map)
241 240
242 241 def footer(**map):
243 242 yield tmpl("footer", **map)
244 243
245 244 def motd(**map):
246 245 if self.motd is not None:
247 246 yield self.motd
248 247 else:
249 248 yield config('web', 'motd', '')
250 249
251 250 def config(section, name, default=None, untrusted=True):
252 251 return self.parentui.config(section, name, default, untrusted)
253 252
254 253 url = req.env.get('SCRIPT_NAME', '')
255 254 if not url.endswith('/'):
256 255 url += '/'
257 256
258 257 staticurl = config('web', 'staticurl') or url + 'static/'
259 258 if not staticurl.endswith('/'):
260 259 staticurl += '/'
261 260
262 261 style = self.style
263 262 if style is None:
264 263 style = config('web', 'style', '')
265 264 if 'style' in req.form:
266 265 style = req.form['style'][0]
267 266 if self.stripecount is None:
268 267 self.stripecount = int(config('web', 'stripes', 1))
269 268 mapfile = style_map(templater.templatepath(), style)
270 269 tmpl = templater.templater(mapfile, templater.common_filters,
271 270 defaults={"header": header,
272 271 "footer": footer,
273 272 "motd": motd,
274 273 "url": url,
275 274 "staticurl": staticurl})
276 275 return tmpl
@@ -1,4 +1,2 b''
1 Content-type: application/atom+xml; charset={encoding}
2
3 1 <?xml version="1.0" encoding="{encoding}"?>
4 2 <feed xmlns="http://www.w3.org/2005/Atom"> No newline at end of file
@@ -1,9 +1,10 b''
1 1 default = 'changelog'
2 2 feedupdated = '<updated>#date|rfc3339date#</updated>'
3 mimetype = 'application/atom+xml; charset={encoding}'
3 4 header = header.tmpl
4 5 changelog = changelog.tmpl
5 6 changelogentry = changelogentry.tmpl
6 7 filelog = filelog.tmpl
7 8 filelogentry = filelogentry.tmpl
8 9 tags = tags.tmpl
9 10 tagentry = tagentry.tmpl
@@ -1,10 +1,8 b''
1 Content-type: text/html; charset={encoding}
2
3 1 <?xml version="1.0" encoding="{encoding}"?>
4 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
5 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
6 4 <head>
7 5 <link rel="icon" href="{staticurl}hgicon.png" type="image/png" />
8 6 <meta name="robots" content="index, nofollow"/>
9 7 <link rel="stylesheet" href="{staticurl}style-gitweb.css" type="text/css" />
10 8
@@ -1,58 +1,59 b''
1 1 default = 'summary'
2 mimetype = 'text/html; charset={encoding}'
2 3 header = header.tmpl
3 4 footer = footer.tmpl
4 5 search = search.tmpl
5 6 changelog = changelog.tmpl
6 7 summary = summary.tmpl
7 8 error = error.tmpl
8 9 notfound = notfound.tmpl
9 10 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 11 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
11 12 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
12 13 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 14 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
14 15 fileellipses = '...'
15 16 changelogentry = changelogentry.tmpl
16 17 searchentry = changelogentry.tmpl
17 18 changeset = changeset.tmpl
18 19 manifest = manifest.tmpl
19 20 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
20 21 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#date|isodate#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
21 22 filerevision = filerevision.tmpl
22 23 fileannotate = fileannotate.tmpl
23 24 filediff = filediff.tmpl
24 25 filelog = filelog.tmpl
25 26 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
26 27 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 28 difflineplus = '<span style="color:#008800;">#line|escape#</span>'
28 29 difflineminus = '<span style="color:#cc0000;">#line|escape#</span>'
29 30 difflineat = '<span style="color:#990099;">#line|escape#</span>'
30 31 diffline = '<span>#line|escape#</span>'
31 32 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 33 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
33 34 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 35 filerename = '{file|escape}@'
35 36 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
36 37 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 39 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 40 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 41 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
41 42 tags = tags.tmpl
42 43 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
43 44 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></a></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
44 45 diffblock = '<pre>#lines#</pre>'
45 46 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
46 47 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 48 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
48 49 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 50 shortlog = shortlog.tmpl
50 51 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
51 52 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
52 53 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
53 54 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
54 55 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
55 56 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks">#archives%archiveentry#</td><td><a class="rss_logo" href="#url#rss-log">RSS</a> <a class="rss_logo" href="#url#atom-log">Atom</a></td></tr>'
56 57 index = index.tmpl
57 58 urlparameter = '#separator##name#=#value|urlescape#'
58 59 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,8 +1,6 b''
1 Content-type: text/html; charset={encoding}
2
3 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 2 <html>
5 3 <head>
6 4 <link rel="icon" href="#staticurl#hgicon.png" type="image/png">
7 5 <meta name="robots" content="index, nofollow" />
8 6 <link rel="stylesheet" href="#staticurl#style.css" type="text/css" />
@@ -1,56 +1,57 b''
1 1 default = 'shortlog'
2 mimetype = 'text/html; charset={encoding}'
2 3 header = header.tmpl
3 4 footer = footer.tmpl
4 5 search = search.tmpl
5 6 changelog = changelog.tmpl
6 7 shortlog = shortlog.tmpl
7 8 shortlogentry = shortlogentry.tmpl
8 9 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 10 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 11 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 12 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 13 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 14 fileellipses = '...'
14 15 changelogentry = changelogentry.tmpl
15 16 searchentry = changelogentry.tmpl
16 17 changeset = changeset.tmpl
17 18 manifest = manifest.tmpl
18 19 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
19 20 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt class="date">#date|isodate#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
20 21 filerevision = filerevision.tmpl
21 22 fileannotate = fileannotate.tmpl
22 23 filediff = filediff.tmpl
23 24 filelog = filelog.tmpl
24 25 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
25 26 filelogentry = filelogentry.tmpl
26 27 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 28 difflineplus = '<span class="plusline">#line|escape#</span>'
28 29 difflineminus = '<span class="minusline">#line|escape#</span>'
29 30 difflineat = '<span class="atline">#line|escape#</span>'
30 31 diffline = '#line|escape#'
31 32 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 33 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 34 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 35 filerename = '{file|escape}@'
35 36 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
36 37 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 38 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 39 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 40 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 41 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 42 tags = tags.tmpl
42 43 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
43 44 diffblock = '<pre class="parity#parity#">#lines#</pre>'
44 45 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 46 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 47 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 48 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 49 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 50 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 51 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> <a href="#url#atom-log">Atom</a> #archives%archiveentry#</td></tr>'
51 52 index = index.tmpl
52 53 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
53 54 notfound = notfound.tmpl
54 55 error = error.tmpl
55 56 urlparameter = '#separator##name#=#value|urlescape#'
56 57 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,8 +1,6 b''
1 Content-type: text/html
2
3 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 2 <html>
5 3 <head>
6 4 <link rel="icon" href="?static=hgicon.png" type="image/png">
7 5 <meta name="robots" content="index, nofollow" />
8 6 <link rel="stylesheet" href="?static=style.css" type="text/css" />
@@ -1,53 +1,54 b''
1 1 default = 'changelog'
2 mimetype = 'text/html'
2 3 header = header.tmpl
3 4 footer = footer.tmpl
4 5 search = search.tmpl
5 6 changelog = changelog.tmpl
6 7 shortlog = shortlog.tmpl
7 8 shortlogentry = shortlogentry.tmpl
8 9 naventry = '<a href="?cl={node|short}">{label|escape}</a> '
9 10 navshortentry = '<a href="?sl={node|short}">{label|escape}</a> '
10 11 filedifflink = '<a href="?fd=#node|short#;file=#file|urlescape#">#file|escape#</a> '
11 12 filenodelink = '<a href="?f=#node|short#;file=#file|urlescape#">#file|escape#</a> '
12 13 fileellipses = '...'
13 14 changelogentry = changelogentry.tmpl
14 15 searchentry = changelogentry.tmpl
15 16 changeset = changeset.tmpl
16 17 manifest = manifest.tmpl
17 18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td><a href="?mf=#node|short#;path=#path|urlescape#">#basename|escape#/</a>'
18 19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td><a href="?f=#node|short#;file=#file|urlescape#">#basename|escape#</a>'
19 20 filerevision = filerevision.tmpl
20 21 fileannotate = fileannotate.tmpl
21 22 filediff = filediff.tmpl
22 23 filelog = filelog.tmpl
23 24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
24 25 filelogentry = filelogentry.tmpl
25 26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="?fa=#node|short#;file=#file|urlescape#">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 27 difflineplus = '<span class="plusline">#line|escape#</span>'
27 28 difflineminus = '<span class="minusline">#line|escape#</span>'
28 29 difflineat = '<span class="atline">#line|escape#</span>'
29 30 diffline = '#line|escape#'
30 31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
31 32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
32 33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
33 34 filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
34 35 filelogrename = '<tr><th>base:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
35 36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?fa=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
36 37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
37 38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
38 39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
39 40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?fa=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
40 41 tags = tags.tmpl
41 42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="?cs=#node|short#">#tag|escape#</a></li>'
42 43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
43 44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
44 45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
46 47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
47 48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
48 49 filelogchild = '<tr><th>child #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
49 50 indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#?cl=tip;style=rss">RSS</a> <a href="#url#atom-log">Atom</a> #archives%archiveentry#</td></tr>'
50 51 index = index.tmpl
51 52 archiveentry = '<a href="#url#?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
52 53 notfound = notfound.tmpl
53 54 error = error.tmpl
@@ -1,22 +1,23 b''
1 header = header.tmpl
1 mimetype = 'text/plain; charset={encoding}'
2 header = ''
2 3 footer = ''
3 4 changeset = changeset.tmpl
4 5 difflineplus = '#line#'
5 6 difflineminus = '#line#'
6 7 difflineat = '#line#'
7 8 diffline = '#line#'
8 9 changesetparent = '# Parent #node#'
9 10 changesetchild = '# Child #node#'
10 11 filenodelink = ''
11 12 fileline = '#line#'
12 13 diffblock = '#lines#'
13 14 filediff = filediff.tmpl
14 15 fileannotate = fileannotate.tmpl
15 16 annotateline = '#author#@#rev#: #line#'
16 17 manifest = manifest.tmpl
17 18 manifestdirentry = 'drwxr-xr-x {basename}\n'
18 19 manifestfileentry = '{permissions|permissions} {size} {basename}\n'
19 20 index = index.tmpl
20 21 notfound = notfound.tmpl
21 22 error = error.tmpl
22 23 indexentry = '#url#\n'
@@ -1,7 +1,5 b''
1 Content-type: text/xml; charset={encoding}
2
3 1 <?xml version="1.0" encoding="{encoding}"?>
4 2 <rss version="2.0">
5 3 <channel>
6 4 <link>{urlbase}{url}</link>
7 5 <language>en-us</language>
@@ -1,8 +1,9 b''
1 1 default = 'changelog'
2 mimetype = 'text/xml; charset={encoding}'
2 3 header = header.tmpl
3 4 changelog = changelog.tmpl
4 5 changelogentry = changelogentry.tmpl
5 6 filelog = filelog.tmpl
6 7 filelogentry = filelogentry.tmpl
7 8 tags = tags.tmpl
8 9 tagentry = tagentry.tmpl
@@ -1,72 +1,72 b''
1 1 changeset: 0:4cbec7e6f8c4
2 2 tag: tip
3 3 user: Testing
4 4 date: Thu Jan 01 00:00:00 1970 +0000
5 5 summary: test
6 6
7 7 ---- HEADERS
8 8 200 Script output follows
9 9 ---- DATA
10 [('content-type', 'application/atom+xml; charset=ascii')]
10 [('Content-type', 'application/atom+xml; charset=ascii')]
11 11 <?xml version="1.0" encoding="ascii"?>
12 12 <feed xmlns="http://www.w3.org/2005/Atom">
13 13 <!-- Changelog -->
14 14 <id>http://127.0.0.1/</id>
15 15 <link rel="self" href="http://127.0.0.1/atom-log"/>
16 16 <link rel="alternate" href="http://127.0.0.1/"/>
17 17 <title>repo Changelog</title>
18 18 <updated>1970-01-01T00:00:00+00:00</updated>
19 19
20 20 <entry>
21 21 <title>test</title>
22 22 <id>http://www.selenic.com/mercurial/#changeset-4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e</id>
23 23 <link href="http://127.0.0.1/rev/4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e"/>
24 24 <author>
25 25 <name>Testing</name>
26 26 <email>&#84;&#101;&#115;&#116;&#105;&#110;&#103;</email>
27 27 </author>
28 28 <updated>1970-01-01T00:00:00+00:00</updated>
29 29 <published>1970-01-01T00:00:00+00:00</published>
30 30 <content type="xhtml">
31 31 <div xmlns="http://www.w3.org/1999/xhtml">
32 32 <pre xml:space="preserve">test</pre>
33 33 </div>
34 34 </content>
35 35 </entry>
36 36
37 37 </feed>
38 38
39 39 ---- ERRORS
40 40
41 41 ---- HEADERS
42 42 200 Script output follows
43 43 ---- DATA
44 [('content-type', 'text/plain; charset=ascii')]
44 [('Content-type', 'text/plain; charset=ascii')]
45 45
46 46 -rw-r--r-- 4 bar
47 47
48 48
49 49
50 50 ---- ERRORS
51 51
52 52 ---- HEADERS
53 53 200 Script output follows
54 54 ---- DATA
55 [('content-type', 'text/plain; charset=ascii')]
55 [('Content-type', 'text/plain; charset=ascii')]
56 56
57 57 /repo/
58 58
59 59
60 60 ---- ERRORS
61 61
62 62 ---- HEADERS
63 63 200 Script output follows
64 64 ---- DATA
65 [('content-type', 'text/plain; charset=ascii')]
65 [('Content-type', 'text/plain; charset=ascii')]
66 66
67 67 -rw-r--r-- 4 bar
68 68
69 69
70 70
71 71 ---- ERRORS
72 72
@@ -1,12 +1,12 b''
1 1 changeset: 0:61c9426e69fe
2 2 tag: tip
3 3 user: test
4 4 date: Thu Jan 01 00:00:00 1970 +0000
5 5 summary: test
6 6
7 7 ---- HEADERS
8 8 200 Script output follows
9 9 ---- DATA
10 [('content-type', 'text/html; charset=ascii')]
10 [('Content-type', 'text/html; charset=ascii')]
11 11 ---- ERRORS
12 12
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now