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