##// END OF EJS Templates
hgweb: add changeset description to file revision page
Brendan Cully -
r3395:3c8f0dc9 default
parent child Browse files
Show More
@@ -1,1043 +1,1044 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, 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
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), 'urllib')
15 15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
16 16 demandload(globals(), "mercurial:revlog,templater")
17 17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
18 18 from mercurial.node import *
19 19 from mercurial.i18n import gettext as _
20 20
21 21 def _up(p):
22 22 if p[0] != "/":
23 23 p = "/" + p
24 24 if p[-1] == "/":
25 25 p = p[:-1]
26 26 up = os.path.dirname(p)
27 27 if up == "/":
28 28 return "/"
29 29 return up + "/"
30 30
31 31 class hgweb(object):
32 32 def __init__(self, repo, name=None):
33 33 if type(repo) == type(""):
34 34 self.repo = hg.repository(ui.ui(), repo)
35 35 else:
36 36 self.repo = repo
37 37
38 38 self.mtime = -1
39 39 self.reponame = name
40 40 self.archives = 'zip', 'gz', 'bz2'
41 41 self.stripecount = 1
42 42 self.templatepath = self.repo.ui.config("web", "templates",
43 43 templater.templatepath())
44 44
45 45 def refresh(self):
46 46 mtime = get_mtime(self.repo.root)
47 47 if mtime != self.mtime:
48 48 self.mtime = mtime
49 49 self.repo = hg.repository(self.repo.ui, self.repo.root)
50 50 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
51 51 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
52 52 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
53 53 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
54 54 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
55 55
56 56 def archivelist(self, nodeid):
57 57 allowed = self.repo.ui.configlist("web", "allow_archive")
58 58 for i, spec in self.archive_specs.iteritems():
59 59 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
60 60 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
61 61
62 62 def listfilediffs(self, files, changeset):
63 63 for f in files[:self.maxfiles]:
64 64 yield self.t("filedifflink", node=hex(changeset), file=f)
65 65 if len(files) > self.maxfiles:
66 66 yield self.t("fileellipses")
67 67
68 68 def siblings(self, siblings=[], hiderev=None, **args):
69 69 siblings = [s for s in siblings if s.node() != nullid]
70 70 if len(siblings) == 1 and siblings[0].rev() == hiderev:
71 71 return
72 72 for s in siblings:
73 73 d = {'node': hex(s.node()), 'rev': s.rev()}
74 74 if hasattr(s, 'path'):
75 75 d['file'] = s.path()
76 76 d.update(args)
77 77 yield d
78 78
79 79 def renamelink(self, fl, node):
80 80 r = fl.renamed(node)
81 81 if r:
82 82 return [dict(file=r[0], node=hex(r[1]))]
83 83 return []
84 84
85 85 def showtag(self, t1, node=nullid, **args):
86 86 for t in self.repo.nodetags(node):
87 87 yield self.t(t1, tag=t, **args)
88 88
89 89 def diff(self, node1, node2, files):
90 90 def filterfiles(filters, files):
91 91 l = [x for x in files if x in filters]
92 92
93 93 for t in filters:
94 94 if t and t[-1] != os.sep:
95 95 t += os.sep
96 96 l += [x for x in files if x.startswith(t)]
97 97 return l
98 98
99 99 parity = [0]
100 100 def diffblock(diff, f, fn):
101 101 yield self.t("diffblock",
102 102 lines=prettyprintlines(diff),
103 103 parity=parity[0],
104 104 file=f,
105 105 filenode=hex(fn or nullid))
106 106 parity[0] = 1 - parity[0]
107 107
108 108 def prettyprintlines(diff):
109 109 for l in diff.splitlines(1):
110 110 if l.startswith('+'):
111 111 yield self.t("difflineplus", line=l)
112 112 elif l.startswith('-'):
113 113 yield self.t("difflineminus", line=l)
114 114 elif l.startswith('@'):
115 115 yield self.t("difflineat", line=l)
116 116 else:
117 117 yield self.t("diffline", line=l)
118 118
119 119 r = self.repo
120 120 cl = r.changelog
121 121 mf = r.manifest
122 122 change1 = cl.read(node1)
123 123 change2 = cl.read(node2)
124 124 mmap1 = mf.read(change1[0])
125 125 mmap2 = mf.read(change2[0])
126 126 date1 = util.datestr(change1[2])
127 127 date2 = util.datestr(change2[2])
128 128
129 129 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
130 130 if files:
131 131 modified, added, removed = map(lambda x: filterfiles(files, x),
132 132 (modified, added, removed))
133 133
134 134 diffopts = patch.diffopts(self.repo.ui)
135 135 for f in modified:
136 136 to = r.file(f).read(mmap1[f])
137 137 tn = r.file(f).read(mmap2[f])
138 138 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
139 139 opts=diffopts), f, tn)
140 140 for f in added:
141 141 to = None
142 142 tn = r.file(f).read(mmap2[f])
143 143 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
144 144 opts=diffopts), f, tn)
145 145 for f in removed:
146 146 to = r.file(f).read(mmap1[f])
147 147 tn = None
148 148 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
149 149 opts=diffopts), f, tn)
150 150
151 151 def changelog(self, ctx, shortlog=False):
152 152 pos = ctx.rev()
153 153 def changenav(**map):
154 154 def seq(factor, maxchanges=None):
155 155 if maxchanges:
156 156 yield maxchanges
157 157 if maxchanges >= 20 and maxchanges <= 40:
158 158 yield 50
159 159 else:
160 160 yield 1 * factor
161 161 yield 3 * factor
162 162 for f in seq(factor * 10):
163 163 yield f
164 164
165 165 l = []
166 166 last = 0
167 167 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
168 168 for f in seq(1, maxchanges):
169 169 if f < maxchanges or f <= last:
170 170 continue
171 171 if f > count:
172 172 break
173 173 last = f
174 174 r = "%d" % f
175 175 if pos + f < count:
176 176 l.append(("+" + r, pos + f))
177 177 if pos - f >= 0:
178 178 l.insert(0, ("-" + r, pos - f))
179 179
180 180 yield {"rev": 0, "label": "(0)"}
181 181
182 182 for label, rev in l:
183 183 yield {"label": label, "rev": rev}
184 184
185 185 yield {"label": "tip", "rev": "tip"}
186 186
187 187 def changelist(**map):
188 188 parity = (start - end) & 1
189 189 cl = self.repo.changelog
190 190 l = [] # build a list in forward order for efficiency
191 191 for i in range(start, end):
192 192 ctx = self.repo.changectx(i)
193 193 n = ctx.node()
194 194
195 195 l.insert(0, {"parity": parity,
196 196 "author": ctx.user(),
197 197 "parent": self.siblings(ctx.parents(), i - 1),
198 198 "child": self.siblings(ctx.children(), i + 1),
199 199 "changelogtag": self.showtag("changelogtag",n),
200 200 "desc": ctx.description(),
201 201 "date": ctx.date(),
202 202 "files": self.listfilediffs(ctx.files(), n),
203 203 "rev": i,
204 204 "node": hex(n)})
205 205 parity = 1 - parity
206 206
207 207 for e in l:
208 208 yield e
209 209
210 210 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
211 211 cl = self.repo.changelog
212 212 count = cl.count()
213 213 start = max(0, pos - maxchanges + 1)
214 214 end = min(count, start + maxchanges)
215 215 pos = end - 1
216 216
217 217 yield self.t(shortlog and 'shortlog' or 'changelog',
218 218 changenav=changenav,
219 219 node=hex(cl.tip()),
220 220 rev=pos, changesets=count, entries=changelist,
221 221 archives=self.archivelist("tip"))
222 222
223 223 def search(self, query):
224 224
225 225 def changelist(**map):
226 226 cl = self.repo.changelog
227 227 count = 0
228 228 qw = query.lower().split()
229 229
230 230 def revgen():
231 231 for i in range(cl.count() - 1, 0, -100):
232 232 l = []
233 233 for j in range(max(0, i - 100), i):
234 234 ctx = self.repo.changectx(j)
235 235 l.append(ctx)
236 236 l.reverse()
237 237 for e in l:
238 238 yield e
239 239
240 240 for ctx in revgen():
241 241 miss = 0
242 242 for q in qw:
243 243 if not (q in ctx.user().lower() or
244 244 q in ctx.description().lower() or
245 245 q in " ".join(ctx.files()[:20]).lower()):
246 246 miss = 1
247 247 break
248 248 if miss:
249 249 continue
250 250
251 251 count += 1
252 252 n = ctx.node()
253 253
254 254 yield self.t('searchentry',
255 255 parity=self.stripes(count),
256 256 author=ctx.user(),
257 257 parent=self.siblings(ctx.parents()),
258 258 child=self.siblings(ctx.children()),
259 259 changelogtag=self.showtag("changelogtag",n),
260 260 desc=ctx.description(),
261 261 date=ctx.date(),
262 262 files=self.listfilediffs(ctx.files(), n),
263 263 rev=ctx.rev(),
264 264 node=hex(n))
265 265
266 266 if count >= self.maxchanges:
267 267 break
268 268
269 269 cl = self.repo.changelog
270 270
271 271 yield self.t('search',
272 272 query=query,
273 273 node=hex(cl.tip()),
274 274 entries=changelist)
275 275
276 276 def changeset(self, ctx):
277 277 n = ctx.node()
278 278 parents = ctx.parents()
279 279 p1 = parents[0].node()
280 280
281 281 files = []
282 282 parity = 0
283 283 for f in ctx.files():
284 284 files.append(self.t("filenodelink",
285 285 node=hex(n), file=f,
286 286 parity=parity))
287 287 parity = 1 - parity
288 288
289 289 def diff(**map):
290 290 yield self.diff(p1, n, None)
291 291
292 292 yield self.t('changeset',
293 293 diff=diff,
294 294 rev=ctx.rev(),
295 295 node=hex(n),
296 296 parent=self.siblings(parents),
297 297 child=self.siblings(ctx.children()),
298 298 changesettag=self.showtag("changesettag",n),
299 299 author=ctx.user(),
300 300 desc=ctx.description(),
301 301 date=ctx.date(),
302 302 files=files,
303 303 archives=self.archivelist(hex(n)))
304 304
305 305 def filelog(self, fctx):
306 306 f = fctx.path()
307 307 fl = fctx.filelog()
308 308 count = fl.count()
309 309
310 310 def entries(**map):
311 311 l = []
312 312 parity = (count - 1) & 1
313 313
314 314 for i in range(count):
315 315 ctx = fctx.filectx(i)
316 316 n = fl.node(i)
317 317
318 318 l.insert(0, {"parity": parity,
319 319 "filerev": i,
320 320 "file": f,
321 321 "node": hex(ctx.node()),
322 322 "author": ctx.user(),
323 323 "date": ctx.date(),
324 324 "rename": self.renamelink(fl, n),
325 325 "parent": self.siblings(fctx.parents()),
326 326 "child": self.siblings(fctx.children()),
327 327 "desc": ctx.description()})
328 328 parity = 1 - parity
329 329
330 330 for e in l:
331 331 yield e
332 332
333 333 yield self.t("filelog", file=f, node=hex(fctx.node()), entries=entries)
334 334
335 335 def filerevision(self, fctx):
336 336 f = fctx.path()
337 337 text = fctx.data()
338 338 fl = fctx.filelog()
339 339 n = fctx.filenode()
340 340
341 341 mt = mimetypes.guess_type(f)[0]
342 342 rawtext = text
343 343 if util.binary(text):
344 344 mt = mt or 'application/octet-stream'
345 345 text = "(binary:%s)" % mt
346 346 mt = mt or 'text/plain'
347 347
348 348 def lines():
349 349 for l, t in enumerate(text.splitlines(1)):
350 350 yield {"line": t,
351 351 "linenumber": "% 6d" % (l + 1),
352 352 "parity": self.stripes(l)}
353 353
354 354 yield self.t("filerevision",
355 355 file=f,
356 356 path=_up(f),
357 357 text=lines(),
358 358 raw=rawtext,
359 359 mimetype=mt,
360 360 rev=fctx.rev(),
361 361 node=hex(fctx.node()),
362 362 author=fctx.user(),
363 363 date=fctx.date(),
364 desc=fctx.description(),
364 365 parent=self.siblings(fctx.parents()),
365 366 child=self.siblings(fctx.children()),
366 367 rename=self.renamelink(fl, n),
367 368 permissions=fctx.manifest().execf(f))
368 369
369 370 def fileannotate(self, fctx):
370 371 f = fctx.path()
371 372 n = fctx.filenode()
372 373 fl = fctx.filelog()
373 374
374 375 def annotate(**map):
375 376 parity = 0
376 377 last = None
377 378 for f, l in fctx.annotate(follow=True):
378 379 fnode = f.filenode()
379 380 name = self.repo.ui.shortuser(f.user())
380 381
381 382 if last != fnode:
382 383 parity = 1 - parity
383 384 last = fnode
384 385
385 386 yield {"parity": parity,
386 387 "node": hex(f.node()),
387 388 "rev": f.rev(),
388 389 "author": name,
389 390 "file": f.path(),
390 391 "line": l}
391 392
392 393 yield self.t("fileannotate",
393 394 file=f,
394 395 annotate=annotate,
395 396 path=_up(f),
396 397 rev=fctx.rev(),
397 398 node=hex(fctx.node()),
398 399 author=fctx.user(),
399 400 date=fctx.date(),
400 401 desc=fctx.description(),
401 402 rename=self.renamelink(fl, n),
402 403 parent=self.siblings(fctx.parents()),
403 404 child=self.siblings(fctx.children()),
404 405 permissions=fctx.manifest().execf(f))
405 406
406 407 def manifest(self, ctx, path):
407 408 mf = ctx.manifest()
408 409 node = ctx.node()
409 410
410 411 files = {}
411 412
412 413 p = path[1:]
413 414 if p and p[-1] != "/":
414 415 p += "/"
415 416 l = len(p)
416 417
417 418 for f,n in mf.items():
418 419 if f[:l] != p:
419 420 continue
420 421 remain = f[l:]
421 422 if "/" in remain:
422 423 short = remain[:remain.index("/") + 1] # bleah
423 424 files[short] = (f, None)
424 425 else:
425 426 short = os.path.basename(remain)
426 427 files[short] = (f, n)
427 428
428 429 def filelist(**map):
429 430 parity = 0
430 431 fl = files.keys()
431 432 fl.sort()
432 433 for f in fl:
433 434 full, fnode = files[f]
434 435 if not fnode:
435 436 continue
436 437
437 438 yield {"file": full,
438 439 "parity": self.stripes(parity),
439 440 "basename": f,
440 441 "size": ctx.filectx(full).size(),
441 442 "permissions": mf.execf(full)}
442 443 parity += 1
443 444
444 445 def dirlist(**map):
445 446 parity = 0
446 447 fl = files.keys()
447 448 fl.sort()
448 449 for f in fl:
449 450 full, fnode = files[f]
450 451 if fnode:
451 452 continue
452 453
453 454 yield {"parity": self.stripes(parity),
454 455 "path": os.path.join(path, f),
455 456 "basename": f[:-1]}
456 457 parity += 1
457 458
458 459 yield self.t("manifest",
459 460 rev=ctx.rev(),
460 461 node=hex(node),
461 462 path=path,
462 463 up=_up(path),
463 464 fentries=filelist,
464 465 dentries=dirlist,
465 466 archives=self.archivelist(hex(node)))
466 467
467 468 def tags(self):
468 469 cl = self.repo.changelog
469 470
470 471 i = self.repo.tagslist()
471 472 i.reverse()
472 473
473 474 def entries(notip=False, **map):
474 475 parity = 0
475 476 for k,n in i:
476 477 if notip and k == "tip": continue
477 478 yield {"parity": self.stripes(parity),
478 479 "tag": k,
479 480 "date": cl.read(n)[2],
480 481 "node": hex(n)}
481 482 parity += 1
482 483
483 484 yield self.t("tags",
484 485 node=hex(self.repo.changelog.tip()),
485 486 entries=lambda **x: entries(False, **x),
486 487 entriesnotip=lambda **x: entries(True, **x))
487 488
488 489 def summary(self):
489 490 cl = self.repo.changelog
490 491
491 492 i = self.repo.tagslist()
492 493 i.reverse()
493 494
494 495 def tagentries(**map):
495 496 parity = 0
496 497 count = 0
497 498 for k,n in i:
498 499 if k == "tip": # skip tip
499 500 continue;
500 501
501 502 count += 1
502 503 if count > 10: # limit to 10 tags
503 504 break;
504 505
505 506 c = cl.read(n)
506 507 t = c[2]
507 508
508 509 yield self.t("tagentry",
509 510 parity = self.stripes(parity),
510 511 tag = k,
511 512 node = hex(n),
512 513 date = t)
513 514 parity += 1
514 515
515 516 def changelist(**map):
516 517 parity = 0
517 518 cl = self.repo.changelog
518 519 l = [] # build a list in forward order for efficiency
519 520 for i in range(start, end):
520 521 n = cl.node(i)
521 522 changes = cl.read(n)
522 523 hn = hex(n)
523 524 t = changes[2]
524 525
525 526 l.insert(0, self.t(
526 527 'shortlogentry',
527 528 parity = parity,
528 529 author = changes[1],
529 530 desc = changes[4],
530 531 date = t,
531 532 rev = i,
532 533 node = hn))
533 534 parity = 1 - parity
534 535
535 536 yield l
536 537
537 538 count = cl.count()
538 539 start = max(0, count - self.maxchanges)
539 540 end = min(count, start + self.maxchanges)
540 541
541 542 yield self.t("summary",
542 543 desc = self.repo.ui.config("web", "description", "unknown"),
543 544 owner = (self.repo.ui.config("ui", "username") or # preferred
544 545 self.repo.ui.config("web", "contact") or # deprecated
545 546 self.repo.ui.config("web", "author", "unknown")), # also
546 547 lastchange = cl.read(cl.tip())[2],
547 548 tags = tagentries,
548 549 shortlog = changelist,
549 550 node = hex(cl.tip()),
550 551 archives=self.archivelist("tip"))
551 552
552 553 def filediff(self, fctx):
553 554 n = fctx.node()
554 555 path = fctx.path()
555 556 parents = fctx.changectx().parents()
556 557 p1 = parents[0].node()
557 558
558 559 def diff(**map):
559 560 yield self.diff(p1, n, [path])
560 561
561 562 yield self.t("filediff",
562 563 file=path,
563 564 node=hex(n),
564 565 rev=fctx.rev(),
565 566 parent=self.siblings(parents),
566 567 child=self.siblings(fctx.children()),
567 568 diff=diff)
568 569
569 570 archive_specs = {
570 571 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
571 572 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
572 573 'zip': ('application/zip', 'zip', '.zip', None),
573 574 }
574 575
575 576 def archive(self, req, cnode, type_):
576 577 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
577 578 name = "%s-%s" % (reponame, short(cnode))
578 579 mimetype, artype, extension, encoding = self.archive_specs[type_]
579 580 headers = [('Content-type', mimetype),
580 581 ('Content-disposition', 'attachment; filename=%s%s' %
581 582 (name, extension))]
582 583 if encoding:
583 584 headers.append(('Content-encoding', encoding))
584 585 req.header(headers)
585 586 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
586 587
587 588 # add tags to things
588 589 # tags -> list of changesets corresponding to tags
589 590 # find tag, changeset, file
590 591
591 592 def cleanpath(self, path):
592 593 return util.canonpath(self.repo.root, '', path)
593 594
594 595 def run(self):
595 596 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
596 597 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
597 598 import mercurial.hgweb.wsgicgi as wsgicgi
598 599 from request import wsgiapplication
599 600 def make_web_app():
600 601 return self
601 602 wsgicgi.launch(wsgiapplication(make_web_app))
602 603
603 604 def run_wsgi(self, req):
604 605 def header(**map):
605 606 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
606 607 msg = mimetools.Message(header_file, 0)
607 608 req.header(msg.items())
608 609 yield header_file.read()
609 610
610 611 def rawfileheader(**map):
611 612 req.header([('Content-type', map['mimetype']),
612 613 ('Content-disposition', 'filename=%s' % map['file']),
613 614 ('Content-length', str(len(map['raw'])))])
614 615 yield ''
615 616
616 617 def footer(**map):
617 618 yield self.t("footer",
618 619 motd=self.repo.ui.config("web", "motd", ""),
619 620 **map)
620 621
621 622 def expand_form(form):
622 623 shortcuts = {
623 624 'cl': [('cmd', ['changelog']), ('rev', None)],
624 625 'sl': [('cmd', ['shortlog']), ('rev', None)],
625 626 'cs': [('cmd', ['changeset']), ('node', None)],
626 627 'f': [('cmd', ['file']), ('filenode', None)],
627 628 'fl': [('cmd', ['filelog']), ('filenode', None)],
628 629 'fd': [('cmd', ['filediff']), ('node', None)],
629 630 'fa': [('cmd', ['annotate']), ('filenode', None)],
630 631 'mf': [('cmd', ['manifest']), ('manifest', None)],
631 632 'ca': [('cmd', ['archive']), ('node', None)],
632 633 'tags': [('cmd', ['tags'])],
633 634 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
634 635 'static': [('cmd', ['static']), ('file', None)]
635 636 }
636 637
637 638 for k in shortcuts.iterkeys():
638 639 if form.has_key(k):
639 640 for name, value in shortcuts[k]:
640 641 if value is None:
641 642 value = form[k]
642 643 form[name] = value
643 644 del form[k]
644 645
645 646 def rewrite_request(req):
646 647 '''translate new web interface to traditional format'''
647 648
648 649 def spliturl(req):
649 650 def firstitem(query):
650 651 return query.split('&', 1)[0].split(';', 1)[0]
651 652
652 653 def normurl(url):
653 654 inner = '/'.join([x for x in url.split('/') if x])
654 655 tl = len(url) > 1 and url.endswith('/') and '/' or ''
655 656
656 657 return '%s%s%s' % (url.startswith('/') and '/' or '',
657 658 inner, tl)
658 659
659 660 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
660 661 pi = normurl(req.env.get('PATH_INFO', ''))
661 662 if pi:
662 663 # strip leading /
663 664 pi = pi[1:]
664 665 if pi:
665 666 root = root[:-len(pi)]
666 667 if req.env.has_key('REPO_NAME'):
667 668 rn = req.env['REPO_NAME'] + '/'
668 669 root += rn
669 670 query = pi[len(rn):]
670 671 else:
671 672 query = pi
672 673 else:
673 674 root += '?'
674 675 query = firstitem(req.env['QUERY_STRING'])
675 676
676 677 return (root, query)
677 678
678 679 req.url, query = spliturl(req)
679 680
680 681 if req.form.has_key('cmd'):
681 682 # old style
682 683 return
683 684
684 685 args = query.split('/', 2)
685 686 if not args or not args[0]:
686 687 return
687 688
688 689 cmd = args.pop(0)
689 690 style = cmd.rfind('-')
690 691 if style != -1:
691 692 req.form['style'] = [cmd[:style]]
692 693 cmd = cmd[style+1:]
693 694 # avoid accepting e.g. style parameter as command
694 695 if hasattr(self, 'do_' + cmd):
695 696 req.form['cmd'] = [cmd]
696 697
697 698 if args and args[0]:
698 699 node = args.pop(0)
699 700 req.form['node'] = [node]
700 701 if args:
701 702 req.form['file'] = args
702 703
703 704 if cmd == 'static':
704 705 req.form['file'] = req.form['node']
705 706 elif cmd == 'archive':
706 707 fn = req.form['node'][0]
707 708 for type_, spec in self.archive_specs.iteritems():
708 709 ext = spec[2]
709 710 if fn.endswith(ext):
710 711 req.form['node'] = [fn[:-len(ext)]]
711 712 req.form['type'] = [type_]
712 713
713 714 def sessionvars(**map):
714 715 fields = []
715 716 if req.form.has_key('style'):
716 717 style = req.form['style'][0]
717 718 if style != self.repo.ui.config('web', 'style', ''):
718 719 fields.append(('style', style))
719 720
720 721 separator = req.url[-1] == '?' and ';' or '?'
721 722 for name, value in fields:
722 723 yield dict(name=name, value=value, separator=separator)
723 724 separator = ';'
724 725
725 726 self.refresh()
726 727
727 728 expand_form(req.form)
728 729 rewrite_request(req)
729 730
730 731 style = self.repo.ui.config("web", "style", "")
731 732 if req.form.has_key('style'):
732 733 style = req.form['style'][0]
733 734 mapfile = style_map(self.templatepath, style)
734 735
735 736 if not req.url:
736 737 port = req.env["SERVER_PORT"]
737 738 port = port != "80" and (":" + port) or ""
738 739 uri = req.env["REQUEST_URI"]
739 740 if "?" in uri:
740 741 uri = uri.split("?")[0]
741 742 req.url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
742 743
743 744 if not self.reponame:
744 745 self.reponame = (self.repo.ui.config("web", "name")
745 746 or req.env.get('REPO_NAME')
746 747 or req.url.strip('/') or self.repo.root)
747 748
748 749 self.t = templater.templater(mapfile, templater.common_filters,
749 750 defaults={"url": req.url,
750 751 "repo": self.reponame,
751 752 "header": header,
752 753 "footer": footer,
753 754 "rawfileheader": rawfileheader,
754 755 "sessionvars": sessionvars
755 756 })
756 757
757 758 if not req.form.has_key('cmd'):
758 759 req.form['cmd'] = [self.t.cache['default'],]
759 760
760 761 cmd = req.form['cmd'][0]
761 762
762 763 method = getattr(self, 'do_' + cmd, None)
763 764 if method:
764 765 try:
765 766 method(req)
766 767 except (hg.RepoError, revlog.RevlogError), inst:
767 768 req.write(self.t("error", error=str(inst)))
768 769 else:
769 770 req.write(self.t("error", error='No such method: ' + cmd))
770 771
771 772 def changectx(self, req):
772 773 if req.form.has_key('node'):
773 774 changeid = req.form['node'][0]
774 775 elif req.form.has_key('manifest'):
775 776 changeid = req.form['manifest'][0]
776 777 else:
777 778 changeid = self.repo.changelog.count() - 1
778 779
779 780 try:
780 781 ctx = self.repo.changectx(changeid)
781 782 except hg.RepoError:
782 783 man = self.repo.manifest
783 784 mn = man.lookup(changeid)
784 785 ctx = self.repo.changectx(man.linkrev(mn))
785 786
786 787 return ctx
787 788
788 789 def filectx(self, req):
789 790 path = self.cleanpath(req.form['file'][0])
790 791 if req.form.has_key('node'):
791 792 changeid = req.form['node'][0]
792 793 else:
793 794 changeid = req.form['filenode'][0]
794 795 try:
795 796 ctx = self.repo.changectx(changeid)
796 797 fctx = ctx.filectx(path)
797 798 except hg.RepoError:
798 799 fctx = self.repo.filectx(path, fileid=changeid)
799 800
800 801 return fctx
801 802
802 803 def stripes(self, parity):
803 804 "make horizontal stripes for easier reading"
804 805 if self.stripecount:
805 806 return (1 + parity / self.stripecount) & 1
806 807 else:
807 808 return 0
808 809
809 810 def do_log(self, req):
810 811 if req.form.has_key('file') and req.form['file'][0]:
811 812 self.do_filelog(req)
812 813 else:
813 814 self.do_changelog(req)
814 815
815 816 def do_rev(self, req):
816 817 self.do_changeset(req)
817 818
818 819 def do_file(self, req):
819 820 path = req.form.get('file', [''])[0]
820 821 if path:
821 822 try:
822 823 req.write(self.filerevision(self.filectx(req)))
823 824 return
824 825 except hg.RepoError:
825 826 pass
826 827 path = self.cleanpath(path)
827 828
828 829 req.write(self.manifest(self.changectx(req), '/' + path))
829 830
830 831 def do_diff(self, req):
831 832 self.do_filediff(req)
832 833
833 834 def do_changelog(self, req, shortlog = False):
834 835 if req.form.has_key('node'):
835 836 ctx = self.changectx(req)
836 837 else:
837 838 if req.form.has_key('rev'):
838 839 hi = req.form['rev'][0]
839 840 else:
840 841 hi = self.repo.changelog.count() - 1
841 842 try:
842 843 ctx = self.repo.changectx(hi)
843 844 except hg.RepoError:
844 845 req.write(self.search(hi)) # XXX redirect to 404 page?
845 846 return
846 847
847 848 req.write(self.changelog(ctx, shortlog = shortlog))
848 849
849 850 def do_shortlog(self, req):
850 851 self.do_changelog(req, shortlog = True)
851 852
852 853 def do_changeset(self, req):
853 854 req.write(self.changeset(self.changectx(req)))
854 855
855 856 def do_manifest(self, req):
856 857 req.write(self.manifest(self.changectx(req),
857 858 self.cleanpath(req.form['path'][0])))
858 859
859 860 def do_tags(self, req):
860 861 req.write(self.tags())
861 862
862 863 def do_summary(self, req):
863 864 req.write(self.summary())
864 865
865 866 def do_filediff(self, req):
866 867 req.write(self.filediff(self.filectx(req)))
867 868
868 869 def do_annotate(self, req):
869 870 req.write(self.fileannotate(self.filectx(req)))
870 871
871 872 def do_filelog(self, req):
872 873 req.write(self.filelog(self.filectx(req)))
873 874
874 875 def do_heads(self, req):
875 876 resp = " ".join(map(hex, self.repo.heads())) + "\n"
876 877 req.httphdr("application/mercurial-0.1", length=len(resp))
877 878 req.write(resp)
878 879
879 880 def do_branches(self, req):
880 881 nodes = []
881 882 if req.form.has_key('nodes'):
882 883 nodes = map(bin, req.form['nodes'][0].split(" "))
883 884 resp = cStringIO.StringIO()
884 885 for b in self.repo.branches(nodes):
885 886 resp.write(" ".join(map(hex, b)) + "\n")
886 887 resp = resp.getvalue()
887 888 req.httphdr("application/mercurial-0.1", length=len(resp))
888 889 req.write(resp)
889 890
890 891 def do_between(self, req):
891 892 if req.form.has_key('pairs'):
892 893 pairs = [map(bin, p.split("-"))
893 894 for p in req.form['pairs'][0].split(" ")]
894 895 resp = cStringIO.StringIO()
895 896 for b in self.repo.between(pairs):
896 897 resp.write(" ".join(map(hex, b)) + "\n")
897 898 resp = resp.getvalue()
898 899 req.httphdr("application/mercurial-0.1", length=len(resp))
899 900 req.write(resp)
900 901
901 902 def do_changegroup(self, req):
902 903 req.httphdr("application/mercurial-0.1")
903 904 nodes = []
904 905 if not self.allowpull:
905 906 return
906 907
907 908 if req.form.has_key('roots'):
908 909 nodes = map(bin, req.form['roots'][0].split(" "))
909 910
910 911 z = zlib.compressobj()
911 912 f = self.repo.changegroup(nodes, 'serve')
912 913 while 1:
913 914 chunk = f.read(4096)
914 915 if not chunk:
915 916 break
916 917 req.write(z.compress(chunk))
917 918
918 919 req.write(z.flush())
919 920
920 921 def do_archive(self, req):
921 922 changeset = self.repo.lookup(req.form['node'][0])
922 923 type_ = req.form['type'][0]
923 924 allowed = self.repo.ui.configlist("web", "allow_archive")
924 925 if (type_ in self.archives and (type_ in allowed or
925 926 self.repo.ui.configbool("web", "allow" + type_, False))):
926 927 self.archive(req, changeset, type_)
927 928 return
928 929
929 930 req.write(self.t("error"))
930 931
931 932 def do_static(self, req):
932 933 fname = req.form['file'][0]
933 934 static = self.repo.ui.config("web", "static",
934 935 os.path.join(self.templatepath,
935 936 "static"))
936 937 req.write(staticfile(static, fname, req)
937 938 or self.t("error", error="%r not found" % fname))
938 939
939 940 def do_capabilities(self, req):
940 941 caps = ['unbundle']
941 942 if self.repo.ui.configbool('server', 'uncompressed'):
942 943 caps.append('stream=%d' % self.repo.revlogversion)
943 944 resp = ' '.join(caps)
944 945 req.httphdr("application/mercurial-0.1", length=len(resp))
945 946 req.write(resp)
946 947
947 948 def check_perm(self, req, op, default):
948 949 '''check permission for operation based on user auth.
949 950 return true if op allowed, else false.
950 951 default is policy to use if no config given.'''
951 952
952 953 user = req.env.get('REMOTE_USER')
953 954
954 955 deny = self.repo.ui.configlist('web', 'deny_' + op)
955 956 if deny and (not user or deny == ['*'] or user in deny):
956 957 return False
957 958
958 959 allow = self.repo.ui.configlist('web', 'allow_' + op)
959 960 return (allow and (allow == ['*'] or user in allow)) or default
960 961
961 962 def do_unbundle(self, req):
962 963 def bail(response, headers={}):
963 964 length = int(req.env['CONTENT_LENGTH'])
964 965 for s in util.filechunkiter(req, limit=length):
965 966 # drain incoming bundle, else client will not see
966 967 # response when run outside cgi script
967 968 pass
968 969 req.httphdr("application/mercurial-0.1", headers=headers)
969 970 req.write('0\n')
970 971 req.write(response)
971 972
972 973 # require ssl by default, auth info cannot be sniffed and
973 974 # replayed
974 975 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
975 976 if ssl_req:
976 977 if not req.env.get('HTTPS'):
977 978 bail(_('ssl required\n'))
978 979 return
979 980 proto = 'https'
980 981 else:
981 982 proto = 'http'
982 983
983 984 # do not allow push unless explicitly allowed
984 985 if not self.check_perm(req, 'push', False):
985 986 bail(_('push not authorized\n'),
986 987 headers={'status': '401 Unauthorized'})
987 988 return
988 989
989 990 req.httphdr("application/mercurial-0.1")
990 991
991 992 their_heads = req.form['heads'][0].split(' ')
992 993
993 994 def check_heads():
994 995 heads = map(hex, self.repo.heads())
995 996 return their_heads == [hex('force')] or their_heads == heads
996 997
997 998 # fail early if possible
998 999 if not check_heads():
999 1000 bail(_('unsynced changes\n'))
1000 1001 return
1001 1002
1002 1003 # do not lock repo until all changegroup data is
1003 1004 # streamed. save to temporary file.
1004 1005
1005 1006 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1006 1007 fp = os.fdopen(fd, 'wb+')
1007 1008 try:
1008 1009 length = int(req.env['CONTENT_LENGTH'])
1009 1010 for s in util.filechunkiter(req, limit=length):
1010 1011 fp.write(s)
1011 1012
1012 1013 lock = self.repo.lock()
1013 1014 try:
1014 1015 if not check_heads():
1015 1016 req.write('0\n')
1016 1017 req.write(_('unsynced changes\n'))
1017 1018 return
1018 1019
1019 1020 fp.seek(0)
1020 1021
1021 1022 # send addchangegroup output to client
1022 1023
1023 1024 old_stdout = sys.stdout
1024 1025 sys.stdout = cStringIO.StringIO()
1025 1026
1026 1027 try:
1027 1028 url = 'remote:%s:%s' % (proto,
1028 1029 req.env.get('REMOTE_HOST', ''))
1029 1030 ret = self.repo.addchangegroup(fp, 'serve', url)
1030 1031 finally:
1031 1032 val = sys.stdout.getvalue()
1032 1033 sys.stdout = old_stdout
1033 1034 req.write('%d\n' % ret)
1034 1035 req.write(val)
1035 1036 finally:
1036 1037 lock.release()
1037 1038 finally:
1038 1039 fp.close()
1039 1040 os.unlink(tempname)
1040 1041
1041 1042 def do_stream_out(self, req):
1042 1043 req.httphdr("application/mercurial-0.1")
1043 1044 streamclone.stream_out(self.repo, req)
@@ -1,40 +1,44 b''
1 1 #header#
2 2 <title>#repo|escape#:#file|escape#</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="buttons">
7 7 <a href="#url#log/#rev#{sessionvars%urlparameter}">changelog</a>
8 8 <a href="#url#shortlog/#rev#{sessionvars%urlparameter}">shortlog</a>
9 9 <a href="#url#tags{sessionvars%urlparameter}">tags</a>
10 10 <a href="#url#rev/#node|short#{sessionvars%urlparameter}">changeset</a>
11 11 <a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a>
12 12 <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a>
13 13 <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a>
14 14 <a href="#url#raw-file/#node|short#/#file|urlescape#">raw</a>
15 15 </div>
16 16
17 17 <h2>#file|escape#</h2>
18 18
19 19 <table>
20 20 <tr>
21 21 <td class="metatag">changeset #rev#:</td>
22 22 <td><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>
23 23 #parent%filerevparent#
24 24 #child%filerevchild#
25 25 <tr>
26 26 <td class="metatag">author:</td>
27 27 <td>#author|obfuscate#</td></tr>
28 28 <tr>
29 29 <td class="metatag">date:</td>
30 30 <td>#date|date# (#date|age# ago)</td></tr>
31 31 <tr>
32 32 <td class="metatag">permissions:</td>
33 33 <td>#permissions|permissions#</td></tr>
34 <tr>
35 <td class="metatag">description:</td>
36 <td>{desc|strip|escape|addbreaks}</td>
37 </tr>
34 38 </table>
35 39
36 40 <pre>
37 41 #text%fileline#
38 42 </pre>
39 43
40 44 #footer#
@@ -1,52 +1,58 b''
1 1 #header#
2 2 <title>#repo|escape#: File revision</title>
3 3 <link rel="alternate" type="application/rss+xml"
4 4 href="{url}rss-log" title="RSS feed for #repo|escape#">
5 5 </head>
6 6 <body>
7 7
8 8 <div class="page_header">
9 9 <a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / file revision
10 10 </div>
11 11
12 12 <div class="page_nav">
13 13 <a href="{url}summary{sessionvars%urlparameter}">summary</a> |
14 14 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
15 15 <a href="{url}log{sessionvars%urlparameter}">changelog</a> |
16 16 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
17 17 <a href="{url}file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a> |
18 18 <a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> |
19 19 file |
20 20 <a href="{url}log/{node|short}/#file|urlescape#{sessionvars%urlparameter}">revisions</a> |
21 21 <a href="{url}annotate/{node|short}/#file|urlescape#{sessionvars%urlparameter}">annotate</a> |
22 22 <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
23 23 <a href="{url}raw-file/{node|short}/#file|urlescape#">raw</a><br/>
24 24 </div>
25 25
26 26 <div class="title">#file|escape#</div>
27 27
28 <div class="title_text">
28 29 <table>
29 30 <tr>
30 31 <td class="metatag">changeset #rev#:</td>
31 32 <td><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>
32 33 #parent%filerevparent#
33 34 #child%filerevchild#
34 35 <tr>
35 36 <td class="metatag">manifest:</td>
36 37 <td><a href="{url}file/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>
37 38 <tr>
38 39 <td class="metatag">author:</td>
39 40 <td>#author|obfuscate#</td></tr>
40 41 <tr>
41 42 <td class="metatag">date:</td>
42 43 <td>#date|date# (#date|age# ago)</td></tr>
43 44 <tr>
44 45 <td class="metatag">permissions:</td>
45 46 <td>#permissions|permissions#</td></tr>
46 47 </table>
48 </div>
49
50 <div class="page_path">
51 {desc|strip|escape|addbreaks}
52 </div>
47 53
48 54 <div class="page_body">
49 55 #text%fileline#
50 56 </div>
51 57
52 58 #footer#
General Comments 0
You need to be logged in to leave comments. Login now