##// END OF EJS Templates
Add line anchors to annotate, changeset, diff, file views for hgweb
Edward Lee -
r6122:800e2756 default
parent child Browse files
Show More
@@ -1,78 +1,84 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
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 9 import os, mimetypes
10 10
11 11 def get_mtime(repo_path):
12 12 store_path = os.path.join(repo_path, ".hg")
13 13 if not os.path.isdir(os.path.join(store_path, "data")):
14 14 store_path = os.path.join(store_path, "store")
15 15 cl_path = os.path.join(store_path, "00changelog.i")
16 16 if os.path.exists(cl_path):
17 17 return os.stat(cl_path).st_mtime
18 18 else:
19 19 return os.stat(store_path).st_mtime
20 20
21 21 def staticfile(directory, fname, req):
22 22 """return a file inside directory with guessed content-type header
23 23
24 24 fname always uses '/' as directory separator and isn't allowed to
25 25 contain unusual path components.
26 26 Content-type is guessed using the mimetypes module.
27 27 Return an empty string if fname is illegal or file not found.
28 28
29 29 """
30 30 parts = fname.split('/')
31 31 path = directory
32 32 for part in parts:
33 33 if (part in ('', os.curdir, os.pardir) or
34 34 os.sep in part or os.altsep is not None and os.altsep in part):
35 35 return ""
36 36 path = os.path.join(path, part)
37 37 try:
38 38 os.stat(path)
39 39 ct = mimetypes.guess_type(path)[0] or "text/plain"
40 40 req.header([('Content-type', ct),
41 41 ('Content-length', str(os.path.getsize(path)))])
42 42 return file(path, 'rb').read()
43 43 except (TypeError, OSError):
44 44 # illegal fname or unreadable file
45 45 return ""
46 46
47 47 def style_map(templatepath, style):
48 48 """Return path to mapfile for a given style.
49 49
50 50 Searches mapfile in the following locations:
51 51 1. templatepath/style/map
52 52 2. templatepath/map-style
53 53 3. templatepath/map
54 54 """
55 55 locations = style and [os.path.join(style, "map"), "map-"+style] or []
56 56 locations.append("map")
57 57 for location in locations:
58 58 mapfile = os.path.join(templatepath, location)
59 59 if os.path.isfile(mapfile):
60 60 return mapfile
61 61 raise RuntimeError("No hgweb templates found in %r" % templatepath)
62 62
63 63 def paritygen(stripecount, offset=0):
64 64 """count parity of horizontal stripes for easier reading"""
65 65 if stripecount and offset:
66 66 # account for offset, e.g. due to building the list in reverse
67 67 count = (stripecount + offset) % stripecount
68 68 parity = (stripecount + offset) / stripecount & 1
69 69 else:
70 70 count = 0
71 71 parity = 0
72 72 while True:
73 73 yield parity
74 74 count += 1
75 75 if stripecount and count >= stripecount:
76 76 parity = 1 - parity
77 77 count = 0
78 78
79 def countgen(start=0, step=1):
80 """count forever -- useful for line numbers"""
81 while True:
82 yield start
83 start += step
84
@@ -1,1208 +1,1220 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 9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 from common import get_mtime, staticfile, style_map, paritygen
15 from common import get_mtime, staticfile, style_map, paritygen, countgen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if isinstance(repo, str):
68 68 parentui = ui.ui(report_untrusted=False, interactive=False)
69 69 self.repo = hg.repository(parentui, repo)
70 70 else:
71 71 self.repo = repo
72 72
73 73 self.mtime = -1
74 74 self.reponame = name
75 75 self.archives = 'zip', 'gz', 'bz2'
76 76 self.stripecount = 1
77 77 # a repo owner may set web.templates in .hg/hgrc to get any file
78 78 # readable by the user running the CGI script
79 79 self.templatepath = self.config("web", "templates",
80 80 templater.templatepath(),
81 81 untrusted=False)
82 82
83 83 # The CGI scripts are often run by a user different from the repo owner.
84 84 # Trust the settings from the .hg/hgrc files by default.
85 85 def config(self, section, name, default=None, untrusted=True):
86 86 return self.repo.ui.config(section, name, default,
87 87 untrusted=untrusted)
88 88
89 89 def configbool(self, section, name, default=False, untrusted=True):
90 90 return self.repo.ui.configbool(section, name, default,
91 91 untrusted=untrusted)
92 92
93 93 def configlist(self, section, name, default=None, untrusted=True):
94 94 return self.repo.ui.configlist(section, name, default,
95 95 untrusted=untrusted)
96 96
97 97 def refresh(self):
98 98 mtime = get_mtime(self.repo.root)
99 99 if mtime != self.mtime:
100 100 self.mtime = mtime
101 101 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 102 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 103 self.stripecount = int(self.config("web", "stripes", 1))
104 104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 105 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 106 self.allowpull = self.configbool("web", "allowpull", True)
107 107 self.encoding = self.config("web", "encoding", util._encoding)
108 108
109 109 def archivelist(self, nodeid):
110 110 allowed = self.configlist("web", "allow_archive")
111 111 for i, spec in self.archive_specs.iteritems():
112 112 if i in allowed or self.configbool("web", "allow" + i):
113 113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114 114
115 115 def listfilediffs(self, files, changeset):
116 116 for f in files[:self.maxfiles]:
117 117 yield self.t("filedifflink", node=hex(changeset), file=f)
118 118 if len(files) > self.maxfiles:
119 119 yield self.t("fileellipses")
120 120
121 121 def siblings(self, siblings=[], hiderev=None, **args):
122 122 siblings = [s for s in siblings if s.node() != nullid]
123 123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 124 return
125 125 for s in siblings:
126 126 d = {'node': hex(s.node()), 'rev': s.rev()}
127 127 if hasattr(s, 'path'):
128 128 d['file'] = s.path()
129 129 d.update(args)
130 130 yield d
131 131
132 132 def renamelink(self, fl, node):
133 133 r = fl.renamed(node)
134 134 if r:
135 135 return [dict(file=r[0], node=hex(r[1]))]
136 136 return []
137 137
138 138 def nodetagsdict(self, node):
139 139 return [{"name": i} for i in self.repo.nodetags(node)]
140 140
141 141 def nodebranchdict(self, ctx):
142 142 branches = []
143 143 branch = ctx.branch()
144 144 # If this is an empty repo, ctx.node() == nullid,
145 145 # ctx.branch() == 'default', but branchtags() is
146 146 # an empty dict. Using dict.get avoids a traceback.
147 147 if self.repo.branchtags().get(branch) == ctx.node():
148 148 branches.append({"name": branch})
149 149 return branches
150 150
151 151 def showtag(self, t1, node=nullid, **args):
152 152 for t in self.repo.nodetags(node):
153 153 yield self.t(t1, tag=t, **args)
154 154
155 155 def diff(self, node1, node2, files):
156 156 def filterfiles(filters, files):
157 157 l = [x for x in files if x in filters]
158 158
159 159 for t in filters:
160 160 if t and t[-1] != os.sep:
161 161 t += os.sep
162 162 l += [x for x in files if x.startswith(t)]
163 163 return l
164 164
165 165 parity = paritygen(self.stripecount)
166 166 def diffblock(diff, f, fn):
167 167 yield self.t("diffblock",
168 168 lines=prettyprintlines(diff),
169 169 parity=parity.next(),
170 170 file=f,
171 171 filenode=hex(fn or nullid))
172 172
173 blockcount = countgen()
173 174 def prettyprintlines(diff):
174 for l in diff.splitlines(1):
175 blockno = blockcount.next()
176 for lineno, l in enumerate(diff.splitlines(1)):
177 if blockno == 0:
178 lineno = lineno + 1
179 else:
180 lineno = "%d.%d" % (blockno, lineno + 1)
181 type = "diffline"
175 182 if l.startswith('+'):
176 yield self.t("difflineplus", line=l)
183 type = "difflineplus"
177 184 elif l.startswith('-'):
178 yield self.t("difflineminus", line=l)
185 type = "difflineminus"
179 186 elif l.startswith('@'):
180 yield self.t("difflineat", line=l)
181 else:
182 yield self.t("diffline", line=l)
187 type = "difflineat"
188 yield self.t(type,
189 line=l,
190 lineid="l%s" % lineno,
191 linenumber="% 8s" % lineno)
183 192
184 193 r = self.repo
185 194 c1 = r.changectx(node1)
186 195 c2 = r.changectx(node2)
187 196 date1 = util.datestr(c1.date())
188 197 date2 = util.datestr(c2.date())
189 198
190 199 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 200 if files:
192 201 modified, added, removed = map(lambda x: filterfiles(files, x),
193 202 (modified, added, removed))
194 203
195 204 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 205 for f in modified:
197 206 to = c1.filectx(f).data()
198 207 tn = c2.filectx(f).data()
199 208 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
200 209 opts=diffopts), f, tn)
201 210 for f in added:
202 211 to = None
203 212 tn = c2.filectx(f).data()
204 213 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
205 214 opts=diffopts), f, tn)
206 215 for f in removed:
207 216 to = c1.filectx(f).data()
208 217 tn = None
209 218 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
210 219 opts=diffopts), f, tn)
211 220
212 221 def changelog(self, ctx, shortlog=False):
213 222 def changelist(limit=0,**map):
214 223 cl = self.repo.changelog
215 224 l = [] # build a list in forward order for efficiency
216 225 for i in xrange(start, end):
217 226 ctx = self.repo.changectx(i)
218 227 n = ctx.node()
219 228
220 229 l.insert(0, {"parity": parity.next(),
221 230 "author": ctx.user(),
222 231 "parent": self.siblings(ctx.parents(), i - 1),
223 232 "child": self.siblings(ctx.children(), i + 1),
224 233 "changelogtag": self.showtag("changelogtag",n),
225 234 "desc": ctx.description(),
226 235 "date": ctx.date(),
227 236 "files": self.listfilediffs(ctx.files(), n),
228 237 "rev": i,
229 238 "node": hex(n),
230 239 "tags": self.nodetagsdict(n),
231 240 "branches": self.nodebranchdict(ctx)})
232 241
233 242 if limit > 0:
234 243 l = l[:limit]
235 244
236 245 for e in l:
237 246 yield e
238 247
239 248 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 249 cl = self.repo.changelog
241 250 count = cl.count()
242 251 pos = ctx.rev()
243 252 start = max(0, pos - maxchanges + 1)
244 253 end = min(count, start + maxchanges)
245 254 pos = end - 1
246 255 parity = paritygen(self.stripecount, offset=start-end)
247 256
248 257 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249 258
250 259 yield self.t(shortlog and 'shortlog' or 'changelog',
251 260 changenav=changenav,
252 261 node=hex(cl.tip()),
253 262 rev=pos, changesets=count,
254 263 entries=lambda **x: changelist(limit=0,**x),
255 264 latestentry=lambda **x: changelist(limit=1,**x),
256 265 archives=self.archivelist("tip"))
257 266
258 267 def search(self, query):
259 268
260 269 def changelist(**map):
261 270 cl = self.repo.changelog
262 271 count = 0
263 272 qw = query.lower().split()
264 273
265 274 def revgen():
266 275 for i in xrange(cl.count() - 1, 0, -100):
267 276 l = []
268 277 for j in xrange(max(0, i - 100), i):
269 278 ctx = self.repo.changectx(j)
270 279 l.append(ctx)
271 280 l.reverse()
272 281 for e in l:
273 282 yield e
274 283
275 284 for ctx in revgen():
276 285 miss = 0
277 286 for q in qw:
278 287 if not (q in ctx.user().lower() or
279 288 q in ctx.description().lower() or
280 289 q in " ".join(ctx.files()).lower()):
281 290 miss = 1
282 291 break
283 292 if miss:
284 293 continue
285 294
286 295 count += 1
287 296 n = ctx.node()
288 297
289 298 yield self.t('searchentry',
290 299 parity=parity.next(),
291 300 author=ctx.user(),
292 301 parent=self.siblings(ctx.parents()),
293 302 child=self.siblings(ctx.children()),
294 303 changelogtag=self.showtag("changelogtag",n),
295 304 desc=ctx.description(),
296 305 date=ctx.date(),
297 306 files=self.listfilediffs(ctx.files(), n),
298 307 rev=ctx.rev(),
299 308 node=hex(n),
300 309 tags=self.nodetagsdict(n),
301 310 branches=self.nodebranchdict(ctx))
302 311
303 312 if count >= self.maxchanges:
304 313 break
305 314
306 315 cl = self.repo.changelog
307 316 parity = paritygen(self.stripecount)
308 317
309 318 yield self.t('search',
310 319 query=query,
311 320 node=hex(cl.tip()),
312 321 entries=changelist,
313 322 archives=self.archivelist("tip"))
314 323
315 324 def changeset(self, ctx):
316 325 n = ctx.node()
317 326 parents = ctx.parents()
318 327 p1 = parents[0].node()
319 328
320 329 files = []
321 330 parity = paritygen(self.stripecount)
322 331 for f in ctx.files():
323 332 files.append(self.t("filenodelink",
324 333 node=hex(n), file=f,
325 334 parity=parity.next()))
326 335
327 336 def diff(**map):
328 337 yield self.diff(p1, n, None)
329 338
330 339 yield self.t('changeset',
331 340 diff=diff,
332 341 rev=ctx.rev(),
333 342 node=hex(n),
334 343 parent=self.siblings(parents),
335 344 child=self.siblings(ctx.children()),
336 345 changesettag=self.showtag("changesettag",n),
337 346 author=ctx.user(),
338 347 desc=ctx.description(),
339 348 date=ctx.date(),
340 349 files=files,
341 350 archives=self.archivelist(hex(n)),
342 351 tags=self.nodetagsdict(n),
343 352 branches=self.nodebranchdict(ctx))
344 353
345 354 def filelog(self, fctx):
346 355 f = fctx.path()
347 356 fl = fctx.filelog()
348 357 count = fl.count()
349 358 pagelen = self.maxshortchanges
350 359 pos = fctx.filerev()
351 360 start = max(0, pos - pagelen + 1)
352 361 end = min(count, start + pagelen)
353 362 pos = end - 1
354 363 parity = paritygen(self.stripecount, offset=start-end)
355 364
356 365 def entries(limit=0, **map):
357 366 l = []
358 367
359 368 for i in xrange(start, end):
360 369 ctx = fctx.filectx(i)
361 370 n = fl.node(i)
362 371
363 372 l.insert(0, {"parity": parity.next(),
364 373 "filerev": i,
365 374 "file": f,
366 375 "node": hex(ctx.node()),
367 376 "author": ctx.user(),
368 377 "date": ctx.date(),
369 378 "rename": self.renamelink(fl, n),
370 379 "parent": self.siblings(fctx.parents()),
371 380 "child": self.siblings(fctx.children()),
372 381 "desc": ctx.description()})
373 382
374 383 if limit > 0:
375 384 l = l[:limit]
376 385
377 386 for e in l:
378 387 yield e
379 388
380 389 nodefunc = lambda x: fctx.filectx(fileid=x)
381 390 nav = revnavgen(pos, pagelen, count, nodefunc)
382 391 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 392 entries=lambda **x: entries(limit=0, **x),
384 393 latestentry=lambda **x: entries(limit=1, **x))
385 394
386 395 def filerevision(self, fctx):
387 396 f = fctx.path()
388 397 text = fctx.data()
389 398 fl = fctx.filelog()
390 399 n = fctx.filenode()
391 400 parity = paritygen(self.stripecount)
392 401
393 402 mt = mimetypes.guess_type(f)[0]
394 403 rawtext = text
395 404 if util.binary(text):
396 405 mt = mt or 'application/octet-stream'
397 406 text = "(binary:%s)" % mt
398 407 mt = mt or 'text/plain'
399 408
400 409 def lines():
401 for l, t in enumerate(text.splitlines(1)):
410 for lineno, t in enumerate(text.splitlines(1)):
402 411 yield {"line": t,
403 "linenumber": "% 6d" % (l + 1),
412 "lineid": "l%d" % (lineno + 1),
413 "linenumber": "% 6d" % (lineno + 1),
404 414 "parity": parity.next()}
405 415
406 416 yield self.t("filerevision",
407 417 file=f,
408 418 path=_up(f),
409 419 text=lines(),
410 420 raw=rawtext,
411 421 mimetype=mt,
412 422 rev=fctx.rev(),
413 423 node=hex(fctx.node()),
414 424 author=fctx.user(),
415 425 date=fctx.date(),
416 426 desc=fctx.description(),
417 427 parent=self.siblings(fctx.parents()),
418 428 child=self.siblings(fctx.children()),
419 429 rename=self.renamelink(fl, n),
420 430 permissions=fctx.manifest().flags(f))
421 431
422 432 def fileannotate(self, fctx):
423 433 f = fctx.path()
424 434 n = fctx.filenode()
425 435 fl = fctx.filelog()
426 436 parity = paritygen(self.stripecount)
427 437
428 438 def annotate(**map):
429 439 last = None
430 for f, l in fctx.annotate(follow=True):
440 for lineno, (f, l) in enumerate(fctx.annotate(follow=True)):
431 441 fnode = f.filenode()
432 442 name = self.repo.ui.shortuser(f.user())
433 443
434 444 if last != fnode:
435 445 last = fnode
436 446
437 447 yield {"parity": parity.next(),
438 448 "node": hex(f.node()),
439 449 "rev": f.rev(),
440 450 "author": name,
441 451 "file": f.path(),
442 "line": l}
452 "line": l,
453 "lineid": "l%d" % (lineno + 1),
454 "linenumber": "% 6d" % (lineno + 1)}
443 455
444 456 yield self.t("fileannotate",
445 457 file=f,
446 458 annotate=annotate,
447 459 path=_up(f),
448 460 rev=fctx.rev(),
449 461 node=hex(fctx.node()),
450 462 author=fctx.user(),
451 463 date=fctx.date(),
452 464 desc=fctx.description(),
453 465 rename=self.renamelink(fl, n),
454 466 parent=self.siblings(fctx.parents()),
455 467 child=self.siblings(fctx.children()),
456 468 permissions=fctx.manifest().flags(f))
457 469
458 470 def manifest(self, ctx, path):
459 471 mf = ctx.manifest()
460 472 node = ctx.node()
461 473
462 474 files = {}
463 475 parity = paritygen(self.stripecount)
464 476
465 477 if path and path[-1] != "/":
466 478 path += "/"
467 479 l = len(path)
468 480 abspath = "/" + path
469 481
470 482 for f, n in mf.items():
471 483 if f[:l] != path:
472 484 continue
473 485 remain = f[l:]
474 486 if "/" in remain:
475 487 short = remain[:remain.index("/") + 1] # bleah
476 488 files[short] = (f, None)
477 489 else:
478 490 short = os.path.basename(remain)
479 491 files[short] = (f, n)
480 492
481 493 def filelist(**map):
482 494 fl = files.keys()
483 495 fl.sort()
484 496 for f in fl:
485 497 full, fnode = files[f]
486 498 if not fnode:
487 499 continue
488 500
489 501 fctx = ctx.filectx(full)
490 502 yield {"file": full,
491 503 "parity": parity.next(),
492 504 "basename": f,
493 505 "date": fctx.changectx().date(),
494 506 "size": fctx.size(),
495 507 "permissions": mf.flags(full)}
496 508
497 509 def dirlist(**map):
498 510 fl = files.keys()
499 511 fl.sort()
500 512 for f in fl:
501 513 full, fnode = files[f]
502 514 if fnode:
503 515 continue
504 516
505 517 yield {"parity": parity.next(),
506 518 "path": "%s%s" % (abspath, f),
507 519 "basename": f[:-1]}
508 520
509 521 yield self.t("manifest",
510 522 rev=ctx.rev(),
511 523 node=hex(node),
512 524 path=abspath,
513 525 up=_up(abspath),
514 526 upparity=parity.next(),
515 527 fentries=filelist,
516 528 dentries=dirlist,
517 529 archives=self.archivelist(hex(node)),
518 530 tags=self.nodetagsdict(node),
519 531 branches=self.nodebranchdict(ctx))
520 532
521 533 def tags(self):
522 534 i = self.repo.tagslist()
523 535 i.reverse()
524 536 parity = paritygen(self.stripecount)
525 537
526 538 def entries(notip=False,limit=0, **map):
527 539 count = 0
528 540 for k, n in i:
529 541 if notip and k == "tip":
530 542 continue
531 543 if limit > 0 and count >= limit:
532 544 continue
533 545 count = count + 1
534 546 yield {"parity": parity.next(),
535 547 "tag": k,
536 548 "date": self.repo.changectx(n).date(),
537 549 "node": hex(n)}
538 550
539 551 yield self.t("tags",
540 552 node=hex(self.repo.changelog.tip()),
541 553 entries=lambda **x: entries(False,0, **x),
542 554 entriesnotip=lambda **x: entries(True,0, **x),
543 555 latestentry=lambda **x: entries(True,1, **x))
544 556
545 557 def summary(self):
546 558 i = self.repo.tagslist()
547 559 i.reverse()
548 560
549 561 def tagentries(**map):
550 562 parity = paritygen(self.stripecount)
551 563 count = 0
552 564 for k, n in i:
553 565 if k == "tip": # skip tip
554 566 continue;
555 567
556 568 count += 1
557 569 if count > 10: # limit to 10 tags
558 570 break;
559 571
560 572 yield self.t("tagentry",
561 573 parity=parity.next(),
562 574 tag=k,
563 575 node=hex(n),
564 576 date=self.repo.changectx(n).date())
565 577
566 578
567 579 def branches(**map):
568 580 parity = paritygen(self.stripecount)
569 581
570 582 b = self.repo.branchtags()
571 583 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
572 584 l.sort()
573 585
574 586 for r,n,t in l:
575 587 ctx = self.repo.changectx(n)
576 588
577 589 yield {'parity': parity.next(),
578 590 'branch': t,
579 591 'node': hex(n),
580 592 'date': ctx.date()}
581 593
582 594 def changelist(**map):
583 595 parity = paritygen(self.stripecount, offset=start-end)
584 596 l = [] # build a list in forward order for efficiency
585 597 for i in xrange(start, end):
586 598 ctx = self.repo.changectx(i)
587 599 n = ctx.node()
588 600 hn = hex(n)
589 601
590 602 l.insert(0, self.t(
591 603 'shortlogentry',
592 604 parity=parity.next(),
593 605 author=ctx.user(),
594 606 desc=ctx.description(),
595 607 date=ctx.date(),
596 608 rev=i,
597 609 node=hn,
598 610 tags=self.nodetagsdict(n),
599 611 branches=self.nodebranchdict(ctx)))
600 612
601 613 yield l
602 614
603 615 cl = self.repo.changelog
604 616 count = cl.count()
605 617 start = max(0, count - self.maxchanges)
606 618 end = min(count, start + self.maxchanges)
607 619
608 620 yield self.t("summary",
609 621 desc=self.config("web", "description", "unknown"),
610 622 owner=(self.config("ui", "username") or # preferred
611 623 self.config("web", "contact") or # deprecated
612 624 self.config("web", "author", "unknown")), # also
613 625 lastchange=cl.read(cl.tip())[2],
614 626 tags=tagentries,
615 627 branches=branches,
616 628 shortlog=changelist,
617 629 node=hex(cl.tip()),
618 630 archives=self.archivelist("tip"))
619 631
620 632 def filediff(self, fctx):
621 633 n = fctx.node()
622 634 path = fctx.path()
623 635 parents = fctx.parents()
624 636 p1 = parents and parents[0].node() or nullid
625 637
626 638 def diff(**map):
627 639 yield self.diff(p1, n, [path])
628 640
629 641 yield self.t("filediff",
630 642 file=path,
631 643 node=hex(n),
632 644 rev=fctx.rev(),
633 645 parent=self.siblings(parents),
634 646 child=self.siblings(fctx.children()),
635 647 diff=diff)
636 648
637 649 archive_specs = {
638 650 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
639 651 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
640 652 'zip': ('application/zip', 'zip', '.zip', None),
641 653 }
642 654
643 655 def archive(self, req, key, type_):
644 656 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
645 657 cnode = self.repo.lookup(key)
646 658 arch_version = key
647 659 if cnode == key or key == 'tip':
648 660 arch_version = short(cnode)
649 661 name = "%s-%s" % (reponame, arch_version)
650 662 mimetype, artype, extension, encoding = self.archive_specs[type_]
651 663 headers = [('Content-type', mimetype),
652 664 ('Content-disposition', 'attachment; filename=%s%s' %
653 665 (name, extension))]
654 666 if encoding:
655 667 headers.append(('Content-encoding', encoding))
656 668 req.header(headers)
657 669 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
658 670
659 671 # add tags to things
660 672 # tags -> list of changesets corresponding to tags
661 673 # find tag, changeset, file
662 674
663 675 def cleanpath(self, path):
664 676 path = path.lstrip('/')
665 677 return util.canonpath(self.repo.root, '', path)
666 678
667 679 def run(self):
668 680 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
669 681 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
670 682 import mercurial.hgweb.wsgicgi as wsgicgi
671 683 from request import wsgiapplication
672 684 def make_web_app():
673 685 return self
674 686 wsgicgi.launch(wsgiapplication(make_web_app))
675 687
676 688 def run_wsgi(self, req):
677 689 def header(**map):
678 690 header_file = cStringIO.StringIO(
679 691 ''.join(self.t("header", encoding=self.encoding, **map)))
680 692 msg = mimetools.Message(header_file, 0)
681 693 req.header(msg.items())
682 694 yield header_file.read()
683 695
684 696 def rawfileheader(**map):
685 697 req.header([('Content-type', map['mimetype']),
686 698 ('Content-disposition', 'filename=%s' % map['file']),
687 699 ('Content-length', str(len(map['raw'])))])
688 700 yield ''
689 701
690 702 def footer(**map):
691 703 yield self.t("footer", **map)
692 704
693 705 def motd(**map):
694 706 yield self.config("web", "motd", "")
695 707
696 708 def expand_form(form):
697 709 shortcuts = {
698 710 'cl': [('cmd', ['changelog']), ('rev', None)],
699 711 'sl': [('cmd', ['shortlog']), ('rev', None)],
700 712 'cs': [('cmd', ['changeset']), ('node', None)],
701 713 'f': [('cmd', ['file']), ('filenode', None)],
702 714 'fl': [('cmd', ['filelog']), ('filenode', None)],
703 715 'fd': [('cmd', ['filediff']), ('node', None)],
704 716 'fa': [('cmd', ['annotate']), ('filenode', None)],
705 717 'mf': [('cmd', ['manifest']), ('manifest', None)],
706 718 'ca': [('cmd', ['archive']), ('node', None)],
707 719 'tags': [('cmd', ['tags'])],
708 720 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
709 721 'static': [('cmd', ['static']), ('file', None)]
710 722 }
711 723
712 724 for k in shortcuts.iterkeys():
713 725 if form.has_key(k):
714 726 for name, value in shortcuts[k]:
715 727 if value is None:
716 728 value = form[k]
717 729 form[name] = value
718 730 del form[k]
719 731
720 732 def rewrite_request(req):
721 733 '''translate new web interface to traditional format'''
722 734
723 735 def spliturl(req):
724 736 def firstitem(query):
725 737 return query.split('&', 1)[0].split(';', 1)[0]
726 738
727 739 def normurl(url):
728 740 inner = '/'.join([x for x in url.split('/') if x])
729 741 tl = len(url) > 1 and url.endswith('/') and '/' or ''
730 742
731 743 return '%s%s%s' % (url.startswith('/') and '/' or '',
732 744 inner, tl)
733 745
734 746 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
735 747 pi = normurl(req.env.get('PATH_INFO', ''))
736 748 if pi:
737 749 # strip leading /
738 750 pi = pi[1:]
739 751 if pi:
740 752 root = root[:root.rfind(pi)]
741 753 if req.env.has_key('REPO_NAME'):
742 754 rn = req.env['REPO_NAME'] + '/'
743 755 root += rn
744 756 query = pi[len(rn):]
745 757 else:
746 758 query = pi
747 759 else:
748 760 root += '?'
749 761 query = firstitem(req.env['QUERY_STRING'])
750 762
751 763 return (root, query)
752 764
753 765 req.url, query = spliturl(req)
754 766
755 767 if req.form.has_key('cmd'):
756 768 # old style
757 769 return
758 770
759 771 args = query.split('/', 2)
760 772 if not args or not args[0]:
761 773 return
762 774
763 775 cmd = args.pop(0)
764 776 style = cmd.rfind('-')
765 777 if style != -1:
766 778 req.form['style'] = [cmd[:style]]
767 779 cmd = cmd[style+1:]
768 780 # avoid accepting e.g. style parameter as command
769 781 if hasattr(self, 'do_' + cmd):
770 782 req.form['cmd'] = [cmd]
771 783
772 784 if args and args[0]:
773 785 node = args.pop(0)
774 786 req.form['node'] = [node]
775 787 if args:
776 788 req.form['file'] = args
777 789
778 790 if cmd == 'static':
779 791 req.form['file'] = req.form['node']
780 792 elif cmd == 'archive':
781 793 fn = req.form['node'][0]
782 794 for type_, spec in self.archive_specs.iteritems():
783 795 ext = spec[2]
784 796 if fn.endswith(ext):
785 797 req.form['node'] = [fn[:-len(ext)]]
786 798 req.form['type'] = [type_]
787 799
788 800 def sessionvars(**map):
789 801 fields = []
790 802 if req.form.has_key('style'):
791 803 style = req.form['style'][0]
792 804 if style != self.config('web', 'style', ''):
793 805 fields.append(('style', style))
794 806
795 807 separator = req.url[-1] == '?' and ';' or '?'
796 808 for name, value in fields:
797 809 yield dict(name=name, value=value, separator=separator)
798 810 separator = ';'
799 811
800 812 self.refresh()
801 813
802 814 expand_form(req.form)
803 815 rewrite_request(req)
804 816
805 817 style = self.config("web", "style", "")
806 818 if req.form.has_key('style'):
807 819 style = req.form['style'][0]
808 820 mapfile = style_map(self.templatepath, style)
809 821
810 822 proto = req.env.get('wsgi.url_scheme')
811 823 if proto == 'https':
812 824 proto = 'https'
813 825 default_port = "443"
814 826 else:
815 827 proto = 'http'
816 828 default_port = "80"
817 829
818 830 port = req.env["SERVER_PORT"]
819 831 port = port != default_port and (":" + port) or ""
820 832 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
821 833 staticurl = self.config("web", "staticurl") or req.url + 'static/'
822 834 if not staticurl.endswith('/'):
823 835 staticurl += '/'
824 836
825 837 if not self.reponame:
826 838 self.reponame = (self.config("web", "name")
827 839 or req.env.get('REPO_NAME')
828 840 or req.url.strip('/') or self.repo.root)
829 841
830 842 self.t = templater.templater(mapfile, templater.common_filters,
831 843 defaults={"url": req.url,
832 844 "staticurl": staticurl,
833 845 "urlbase": urlbase,
834 846 "repo": self.reponame,
835 847 "header": header,
836 848 "footer": footer,
837 849 "motd": motd,
838 850 "rawfileheader": rawfileheader,
839 851 "sessionvars": sessionvars
840 852 })
841 853
842 854 try:
843 855 if not req.form.has_key('cmd'):
844 856 req.form['cmd'] = [self.t.cache['default']]
845 857
846 858 cmd = req.form['cmd'][0]
847 859
848 860 method = getattr(self, 'do_' + cmd, None)
849 861 if method:
850 862 try:
851 863 method(req)
852 864 except (hg.RepoError, revlog.RevlogError), inst:
853 865 req.write(self.t("error", error=str(inst)))
854 866 else:
855 867 req.write(self.t("error", error='No such method: ' + cmd))
856 868 finally:
857 869 self.t = None
858 870
859 871 def changectx(self, req):
860 872 if req.form.has_key('node'):
861 873 changeid = req.form['node'][0]
862 874 elif req.form.has_key('manifest'):
863 875 changeid = req.form['manifest'][0]
864 876 else:
865 877 changeid = self.repo.changelog.count() - 1
866 878
867 879 try:
868 880 ctx = self.repo.changectx(changeid)
869 881 except hg.RepoError:
870 882 man = self.repo.manifest
871 883 mn = man.lookup(changeid)
872 884 ctx = self.repo.changectx(man.linkrev(mn))
873 885
874 886 return ctx
875 887
876 888 def filectx(self, req):
877 889 path = self.cleanpath(req.form['file'][0])
878 890 if req.form.has_key('node'):
879 891 changeid = req.form['node'][0]
880 892 else:
881 893 changeid = req.form['filenode'][0]
882 894 try:
883 895 ctx = self.repo.changectx(changeid)
884 896 fctx = ctx.filectx(path)
885 897 except hg.RepoError:
886 898 fctx = self.repo.filectx(path, fileid=changeid)
887 899
888 900 return fctx
889 901
890 902 def do_log(self, req):
891 903 if req.form.has_key('file') and req.form['file'][0]:
892 904 self.do_filelog(req)
893 905 else:
894 906 self.do_changelog(req)
895 907
896 908 def do_rev(self, req):
897 909 self.do_changeset(req)
898 910
899 911 def do_file(self, req):
900 912 path = self.cleanpath(req.form.get('file', [''])[0])
901 913 if path:
902 914 try:
903 915 req.write(self.filerevision(self.filectx(req)))
904 916 return
905 917 except revlog.LookupError:
906 918 pass
907 919
908 920 req.write(self.manifest(self.changectx(req), path))
909 921
910 922 def do_diff(self, req):
911 923 self.do_filediff(req)
912 924
913 925 def do_changelog(self, req, shortlog = False):
914 926 if req.form.has_key('node'):
915 927 ctx = self.changectx(req)
916 928 else:
917 929 if req.form.has_key('rev'):
918 930 hi = req.form['rev'][0]
919 931 else:
920 932 hi = self.repo.changelog.count() - 1
921 933 try:
922 934 ctx = self.repo.changectx(hi)
923 935 except hg.RepoError:
924 936 req.write(self.search(hi)) # XXX redirect to 404 page?
925 937 return
926 938
927 939 req.write(self.changelog(ctx, shortlog = shortlog))
928 940
929 941 def do_shortlog(self, req):
930 942 self.do_changelog(req, shortlog = True)
931 943
932 944 def do_changeset(self, req):
933 945 req.write(self.changeset(self.changectx(req)))
934 946
935 947 def do_manifest(self, req):
936 948 req.write(self.manifest(self.changectx(req),
937 949 self.cleanpath(req.form['path'][0])))
938 950
939 951 def do_tags(self, req):
940 952 req.write(self.tags())
941 953
942 954 def do_summary(self, req):
943 955 req.write(self.summary())
944 956
945 957 def do_filediff(self, req):
946 958 req.write(self.filediff(self.filectx(req)))
947 959
948 960 def do_annotate(self, req):
949 961 req.write(self.fileannotate(self.filectx(req)))
950 962
951 963 def do_filelog(self, req):
952 964 req.write(self.filelog(self.filectx(req)))
953 965
954 966 def do_lookup(self, req):
955 967 try:
956 968 r = hex(self.repo.lookup(req.form['key'][0]))
957 969 success = 1
958 970 except Exception,inst:
959 971 r = str(inst)
960 972 success = 0
961 973 resp = "%s %s\n" % (success, r)
962 974 req.httphdr("application/mercurial-0.1", length=len(resp))
963 975 req.write(resp)
964 976
965 977 def do_heads(self, req):
966 978 resp = " ".join(map(hex, self.repo.heads())) + "\n"
967 979 req.httphdr("application/mercurial-0.1", length=len(resp))
968 980 req.write(resp)
969 981
970 982 def do_branches(self, req):
971 983 nodes = []
972 984 if req.form.has_key('nodes'):
973 985 nodes = map(bin, req.form['nodes'][0].split(" "))
974 986 resp = cStringIO.StringIO()
975 987 for b in self.repo.branches(nodes):
976 988 resp.write(" ".join(map(hex, b)) + "\n")
977 989 resp = resp.getvalue()
978 990 req.httphdr("application/mercurial-0.1", length=len(resp))
979 991 req.write(resp)
980 992
981 993 def do_between(self, req):
982 994 if req.form.has_key('pairs'):
983 995 pairs = [map(bin, p.split("-"))
984 996 for p in req.form['pairs'][0].split(" ")]
985 997 resp = cStringIO.StringIO()
986 998 for b in self.repo.between(pairs):
987 999 resp.write(" ".join(map(hex, b)) + "\n")
988 1000 resp = resp.getvalue()
989 1001 req.httphdr("application/mercurial-0.1", length=len(resp))
990 1002 req.write(resp)
991 1003
992 1004 def do_changegroup(self, req):
993 1005 req.httphdr("application/mercurial-0.1")
994 1006 nodes = []
995 1007 if not self.allowpull:
996 1008 return
997 1009
998 1010 if req.form.has_key('roots'):
999 1011 nodes = map(bin, req.form['roots'][0].split(" "))
1000 1012
1001 1013 z = zlib.compressobj()
1002 1014 f = self.repo.changegroup(nodes, 'serve')
1003 1015 while 1:
1004 1016 chunk = f.read(4096)
1005 1017 if not chunk:
1006 1018 break
1007 1019 req.write(z.compress(chunk))
1008 1020
1009 1021 req.write(z.flush())
1010 1022
1011 1023 def do_changegroupsubset(self, req):
1012 1024 req.httphdr("application/mercurial-0.1")
1013 1025 bases = []
1014 1026 heads = []
1015 1027 if not self.allowpull:
1016 1028 return
1017 1029
1018 1030 if req.form.has_key('bases'):
1019 1031 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1020 1032 if req.form.has_key('heads'):
1021 1033 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1022 1034
1023 1035 z = zlib.compressobj()
1024 1036 f = self.repo.changegroupsubset(bases, heads, 'serve')
1025 1037 while 1:
1026 1038 chunk = f.read(4096)
1027 1039 if not chunk:
1028 1040 break
1029 1041 req.write(z.compress(chunk))
1030 1042
1031 1043 req.write(z.flush())
1032 1044
1033 1045 def do_archive(self, req):
1034 1046 type_ = req.form['type'][0]
1035 1047 allowed = self.configlist("web", "allow_archive")
1036 1048 if (type_ in self.archives and (type_ in allowed or
1037 1049 self.configbool("web", "allow" + type_, False))):
1038 1050 self.archive(req, req.form['node'][0], type_)
1039 1051 return
1040 1052
1041 1053 req.write(self.t("error"))
1042 1054
1043 1055 def do_static(self, req):
1044 1056 fname = req.form['file'][0]
1045 1057 # a repo owner may set web.static in .hg/hgrc to get any file
1046 1058 # readable by the user running the CGI script
1047 1059 static = self.config("web", "static",
1048 1060 os.path.join(self.templatepath, "static"),
1049 1061 untrusted=False)
1050 1062 req.write(staticfile(static, fname, req)
1051 1063 or self.t("error", error="%r not found" % fname))
1052 1064
1053 1065 def do_capabilities(self, req):
1054 1066 caps = ['lookup', 'changegroupsubset']
1055 1067 if self.configbool('server', 'uncompressed'):
1056 1068 caps.append('stream=%d' % self.repo.changelog.version)
1057 1069 # XXX: make configurable and/or share code with do_unbundle:
1058 1070 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1059 1071 if unbundleversions:
1060 1072 caps.append('unbundle=%s' % ','.join(unbundleversions))
1061 1073 resp = ' '.join(caps)
1062 1074 req.httphdr("application/mercurial-0.1", length=len(resp))
1063 1075 req.write(resp)
1064 1076
1065 1077 def check_perm(self, req, op, default):
1066 1078 '''check permission for operation based on user auth.
1067 1079 return true if op allowed, else false.
1068 1080 default is policy to use if no config given.'''
1069 1081
1070 1082 user = req.env.get('REMOTE_USER')
1071 1083
1072 1084 deny = self.configlist('web', 'deny_' + op)
1073 1085 if deny and (not user or deny == ['*'] or user in deny):
1074 1086 return False
1075 1087
1076 1088 allow = self.configlist('web', 'allow_' + op)
1077 1089 return (allow and (allow == ['*'] or user in allow)) or default
1078 1090
1079 1091 def do_unbundle(self, req):
1080 1092 def bail(response, headers={}):
1081 1093 length = int(req.env['CONTENT_LENGTH'])
1082 1094 for s in util.filechunkiter(req, limit=length):
1083 1095 # drain incoming bundle, else client will not see
1084 1096 # response when run outside cgi script
1085 1097 pass
1086 1098 req.httphdr("application/mercurial-0.1", headers=headers)
1087 1099 req.write('0\n')
1088 1100 req.write(response)
1089 1101
1090 1102 # require ssl by default, auth info cannot be sniffed and
1091 1103 # replayed
1092 1104 ssl_req = self.configbool('web', 'push_ssl', True)
1093 1105 if ssl_req:
1094 1106 if req.env.get('wsgi.url_scheme') != 'https':
1095 1107 bail(_('ssl required\n'))
1096 1108 return
1097 1109 proto = 'https'
1098 1110 else:
1099 1111 proto = 'http'
1100 1112
1101 1113 # do not allow push unless explicitly allowed
1102 1114 if not self.check_perm(req, 'push', False):
1103 1115 bail(_('push not authorized\n'),
1104 1116 headers={'status': '401 Unauthorized'})
1105 1117 return
1106 1118
1107 1119 their_heads = req.form['heads'][0].split(' ')
1108 1120
1109 1121 def check_heads():
1110 1122 heads = map(hex, self.repo.heads())
1111 1123 return their_heads == [hex('force')] or their_heads == heads
1112 1124
1113 1125 # fail early if possible
1114 1126 if not check_heads():
1115 1127 bail(_('unsynced changes\n'))
1116 1128 return
1117 1129
1118 1130 req.httphdr("application/mercurial-0.1")
1119 1131
1120 1132 # do not lock repo until all changegroup data is
1121 1133 # streamed. save to temporary file.
1122 1134
1123 1135 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1124 1136 fp = os.fdopen(fd, 'wb+')
1125 1137 try:
1126 1138 length = int(req.env['CONTENT_LENGTH'])
1127 1139 for s in util.filechunkiter(req, limit=length):
1128 1140 fp.write(s)
1129 1141
1130 1142 try:
1131 1143 lock = self.repo.lock()
1132 1144 try:
1133 1145 if not check_heads():
1134 1146 req.write('0\n')
1135 1147 req.write(_('unsynced changes\n'))
1136 1148 return
1137 1149
1138 1150 fp.seek(0)
1139 1151 header = fp.read(6)
1140 1152 if not header.startswith("HG"):
1141 1153 # old client with uncompressed bundle
1142 1154 def generator(f):
1143 1155 yield header
1144 1156 for chunk in f:
1145 1157 yield chunk
1146 1158 elif not header.startswith("HG10"):
1147 1159 req.write("0\n")
1148 1160 req.write(_("unknown bundle version\n"))
1149 1161 return
1150 1162 elif header == "HG10GZ":
1151 1163 def generator(f):
1152 1164 zd = zlib.decompressobj()
1153 1165 for chunk in f:
1154 1166 yield zd.decompress(chunk)
1155 1167 elif header == "HG10BZ":
1156 1168 def generator(f):
1157 1169 zd = bz2.BZ2Decompressor()
1158 1170 zd.decompress("BZ")
1159 1171 for chunk in f:
1160 1172 yield zd.decompress(chunk)
1161 1173 elif header == "HG10UN":
1162 1174 def generator(f):
1163 1175 for chunk in f:
1164 1176 yield chunk
1165 1177 else:
1166 1178 req.write("0\n")
1167 1179 req.write(_("unknown bundle compression type\n"))
1168 1180 return
1169 1181 gen = generator(util.filechunkiter(fp, 4096))
1170 1182
1171 1183 # send addchangegroup output to client
1172 1184
1173 1185 old_stdout = sys.stdout
1174 1186 sys.stdout = cStringIO.StringIO()
1175 1187
1176 1188 try:
1177 1189 url = 'remote:%s:%s' % (proto,
1178 1190 req.env.get('REMOTE_HOST', ''))
1179 1191 try:
1180 1192 ret = self.repo.addchangegroup(
1181 1193 util.chunkbuffer(gen), 'serve', url)
1182 1194 except util.Abort, inst:
1183 1195 sys.stdout.write("abort: %s\n" % inst)
1184 1196 ret = 0
1185 1197 finally:
1186 1198 val = sys.stdout.getvalue()
1187 1199 sys.stdout = old_stdout
1188 1200 req.write('%d\n' % ret)
1189 1201 req.write(val)
1190 1202 finally:
1191 1203 del lock
1192 1204 except (OSError, IOError), inst:
1193 1205 req.write('0\n')
1194 1206 filename = getattr(inst, 'filename', '')
1195 1207 # Don't send our filesystem layout to the client
1196 1208 if filename.startswith(self.repo.root):
1197 1209 filename = filename[len(self.repo.root)+1:]
1198 1210 else:
1199 1211 filename = ''
1200 1212 error = getattr(inst, 'strerror', 'Unknown error')
1201 1213 req.write('%s: %s\n' % (error, filename))
1202 1214 finally:
1203 1215 fp.close()
1204 1216 os.unlink(tempname)
1205 1217
1206 1218 def do_stream_out(self, req):
1207 1219 req.httphdr("application/mercurial-0.1")
1208 1220 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,58 +1,58 b''
1 1 default = 'summary'
2 2 header = header.tmpl
3 3 footer = footer.tmpl
4 4 search = search.tmpl
5 5 changelog = changelog.tmpl
6 6 summary = summary.tmpl
7 7 error = error.tmpl
8 8 notfound = notfound.tmpl
9 9 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
12 12 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 13 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 14 fileellipses = '...'
15 15 changelogentry = changelogentry.tmpl
16 16 searchentry = changelogentry.tmpl
17 17 changeset = changeset.tmpl
18 18 manifest = manifest.tmpl
19 19 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 20 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 21 filerevision = filerevision.tmpl
22 22 fileannotate = fileannotate.tmpl
23 23 filediff = filediff.tmpl
24 24 filelog = filelog.tmpl
25 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
26 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 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
28 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
29 difflineat = '<div style="color:#990099;">#line|escape#</div>'
30 diffline = '<div>#line|escape#</div>'
25 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</pre></div>'
26 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><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a></pre></td><td><pre>#line|escape#</pre></td></tr>'
27 difflineplus = '<div style="color:#008800;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
28 difflineminus = '<div style="color:#cc0000;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
29 difflineat = '<div style="color:#990099;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
30 diffline = '<div><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
31 31 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 32 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 33 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 34 filerename = '{file|escape}@'
35 35 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
36 36 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 37 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 38 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 39 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 40 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 41 tags = tags.tmpl
42 42 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 43 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></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 44 diffblock = '<pre>#lines#</pre>'
45 45 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 46 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 47 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 48 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 49 shortlog = shortlog.tmpl
50 50 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
51 51 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
52 52 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 53 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 54 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
55 55 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 56 index = index.tmpl
57 57 urlparameter = '#separator##name#=#value|urlescape#'
58 58 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,56 +1,56 b''
1 1 default = 'shortlog'
2 2 header = header.tmpl
3 3 footer = footer.tmpl
4 4 search = search.tmpl
5 5 changelog = changelog.tmpl
6 6 shortlog = shortlog.tmpl
7 7 shortlogentry = shortlogentry.tmpl
8 8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 13 fileellipses = '...'
14 14 changelogentry = changelogentry.tmpl
15 15 searchentry = changelogentry.tmpl
16 16 changeset = changeset.tmpl
17 17 manifest = manifest.tmpl
18 18 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 19 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 20 filerevision = filerevision.tmpl
21 21 fileannotate = fileannotate.tmpl
22 22 filediff = filediff.tmpl
23 23 filelog = filelog.tmpl
24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
24 fileline = '<div class="parity#parity#"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</div>'
25 25 filelogentry = filelogentry.tmpl
26 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 difflineplus = '<span class="plusline">#line|escape#</span>'
28 difflineminus = '<span class="minusline">#line|escape#</span>'
29 difflineat = '<span class="atline">#line|escape#</span>'
30 diffline = '#line|escape#'
26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 difflineplus = '<span class="plusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
28 difflineminus = '<span class="minusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
29 difflineat = '<span class="atline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
30 diffline = '<a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#'
31 31 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 32 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 33 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 34 filerename = '{file|escape}@'
35 35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
36 36 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 37 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 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>'
39 39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 41 tags = tags.tmpl
42 42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
43 43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
44 44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 46 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 47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 48 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 49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 50 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 51 index = index.tmpl
52 52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
53 53 notfound = notfound.tmpl
54 54 error = error.tmpl
55 55 urlparameter = '#separator##name#=#value|urlescape#'
56 56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
General Comments 0
You need to be logged in to leave comments. Login now