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