##// END OF EJS Templates
Allows abbreviated hashes in hgweb
Matt Mackall -
r1369:b6d4ebeb default
parent child Browse files
Show More
@@ -1,978 +1,986 b''
1 1 # hgweb.py - web interface to a mercurial repository
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 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, cgi, sys
10 10 from demandload import demandload
11 11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 13 from node import *
14 14
15 15 def templatepath():
16 16 for f in "templates", "../templates":
17 17 p = os.path.join(os.path.dirname(__file__), f)
18 18 if os.path.isdir(p):
19 19 return p
20 20
21 21 def age(x):
22 22 def plural(t, c):
23 23 if c == 1:
24 24 return t
25 25 return t + "s"
26 26 def fmt(t, c):
27 27 return "%d %s" % (c, plural(t, c))
28 28
29 29 now = time.time()
30 30 then = x[0]
31 31 delta = max(1, int(now - then))
32 32
33 33 scales = [["second", 1],
34 34 ["minute", 60],
35 35 ["hour", 3600],
36 36 ["day", 3600 * 24],
37 37 ["week", 3600 * 24 * 7],
38 38 ["month", 3600 * 24 * 30],
39 39 ["year", 3600 * 24 * 365]]
40 40
41 41 scales.reverse()
42 42
43 43 for t, s in scales:
44 44 n = delta / s
45 45 if n >= 2 or s == 1:
46 46 return fmt(t, n)
47 47
48 48 def nl2br(text):
49 49 return text.replace('\n', '<br/>\n')
50 50
51 51 def obfuscate(text):
52 52 return ''.join(['&#%d;' % ord(c) for c in text])
53 53
54 54 def up(p):
55 55 if p[0] != "/":
56 56 p = "/" + p
57 57 if p[-1] == "/":
58 58 p = p[:-1]
59 59 up = os.path.dirname(p)
60 60 if up == "/":
61 61 return "/"
62 62 return up + "/"
63 63
64 64 class hgrequest:
65 65 def __init__(self, inp=None, out=None, env=None):
66 66 self.inp = inp or sys.stdin
67 67 self.out = out or sys.stdout
68 68 self.env = env or os.environ
69 69 self.form = cgi.parse(self.inp, self.env)
70 70
71 71 def write(self, *things):
72 72 for thing in things:
73 73 if hasattr(thing, "__iter__"):
74 74 for part in thing:
75 75 self.write(part)
76 76 else:
77 77 try:
78 78 self.out.write(str(thing))
79 79 except socket.error, inst:
80 80 if inst[0] != errno.ECONNRESET:
81 81 raise
82 82
83 83 def header(self, headers=[('Content-type','text/html')]):
84 84 for header in headers:
85 85 self.out.write("%s: %s\r\n" % header)
86 86 self.out.write("\r\n")
87 87
88 88 def httphdr(self, type, file="", size=0):
89 89
90 90 headers = [('Content-type', type)]
91 91 if file:
92 92 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
93 93 if size > 0:
94 94 headers.append(('Content-length', str(size)))
95 95 self.header(headers)
96 96
97 97 class templater:
98 98 def __init__(self, mapfile, filters={}, defaults={}):
99 99 self.cache = {}
100 100 self.map = {}
101 101 self.base = os.path.dirname(mapfile)
102 102 self.filters = filters
103 103 self.defaults = defaults
104 104
105 105 for l in file(mapfile):
106 106 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
107 107 if m:
108 108 self.cache[m.group(1)] = m.group(2)
109 109 else:
110 110 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
111 111 if m:
112 112 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
113 113 else:
114 114 raise LookupError("unknown map entry '%s'" % l)
115 115
116 116 def __call__(self, t, **map):
117 117 m = self.defaults.copy()
118 118 m.update(map)
119 119 try:
120 120 tmpl = self.cache[t]
121 121 except KeyError:
122 122 tmpl = self.cache[t] = file(self.map[t]).read()
123 123 return self.template(tmpl, self.filters, **m)
124 124
125 125 def template(self, tmpl, filters={}, **map):
126 126 while tmpl:
127 127 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
128 128 if m:
129 129 yield tmpl[:m.start(0)]
130 130 v = map.get(m.group(1), "")
131 131 v = callable(v) and v(**map) or v
132 132
133 133 format = m.group(2)
134 134 fl = m.group(4)
135 135
136 136 if format:
137 137 q = v.__iter__
138 138 for i in q():
139 139 lm = map.copy()
140 140 lm.update(i)
141 141 yield self(format[1:], **lm)
142 142
143 143 v = ""
144 144
145 145 elif fl:
146 146 for f in fl.split("|")[1:]:
147 147 v = filters[f](v)
148 148
149 149 yield v
150 150 tmpl = tmpl[m.end(0):]
151 151 else:
152 152 yield tmpl
153 153 return
154 154
155 155 common_filters = {
156 156 "escape": cgi.escape,
157 157 "age": age,
158 158 "date": lambda x: util.datestr(x),
159 159 "addbreaks": nl2br,
160 160 "obfuscate": obfuscate,
161 161 "short": (lambda x: x[:12]),
162 162 "firstline": (lambda x: x.splitlines(1)[0]),
163 163 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
164 164 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
165 165 }
166 166
167 167 class hgweb:
168 168 def __init__(self, repo, name=None):
169 169 if type(repo) == type(""):
170 170 self.repo = hg.repository(ui.ui(), repo)
171 171 else:
172 172 self.repo = repo
173 173
174 174 self.mtime = -1
175 175 self.reponame = name
176 176 self.archives = 'zip', 'gz', 'bz2'
177 177
178 178 def refresh(self):
179 179 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
180 180 if s.st_mtime != self.mtime:
181 181 self.mtime = s.st_mtime
182 182 self.repo = hg.repository(self.repo.ui, self.repo.root)
183 183 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
184 184 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
185 185 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
186 186
187 187 def listfiles(self, files, mf):
188 188 for f in files[:self.maxfiles]:
189 189 yield self.t("filenodelink", node=hex(mf[f]), file=f)
190 190 if len(files) > self.maxfiles:
191 191 yield self.t("fileellipses")
192 192
193 193 def listfilediffs(self, files, changeset):
194 194 for f in files[:self.maxfiles]:
195 195 yield self.t("filedifflink", node=hex(changeset), file=f)
196 196 if len(files) > self.maxfiles:
197 197 yield self.t("fileellipses")
198 198
199 199 def parents(self, t1, nodes=[], rev=None,**args):
200 200 if not rev:
201 201 rev = lambda x: ""
202 202 for node in nodes:
203 203 if node != nullid:
204 204 yield self.t(t1, node=hex(node), rev=rev(node), **args)
205 205
206 206 def showtag(self, t1, node=nullid, **args):
207 207 for t in self.repo.nodetags(node):
208 208 yield self.t(t1, tag=t, **args)
209 209
210 210 def diff(self, node1, node2, files):
211 211 def filterfiles(list, files):
212 212 l = [x for x in list if x in files]
213 213
214 214 for f in files:
215 215 if f[-1] != os.sep:
216 216 f += os.sep
217 217 l += [x for x in list if x.startswith(f)]
218 218 return l
219 219
220 220 parity = [0]
221 221 def diffblock(diff, f, fn):
222 222 yield self.t("diffblock",
223 223 lines=prettyprintlines(diff),
224 224 parity=parity[0],
225 225 file=f,
226 226 filenode=hex(fn or nullid))
227 227 parity[0] = 1 - parity[0]
228 228
229 229 def prettyprintlines(diff):
230 230 for l in diff.splitlines(1):
231 231 if l.startswith('+'):
232 232 yield self.t("difflineplus", line=l)
233 233 elif l.startswith('-'):
234 234 yield self.t("difflineminus", line=l)
235 235 elif l.startswith('@'):
236 236 yield self.t("difflineat", line=l)
237 237 else:
238 238 yield self.t("diffline", line=l)
239 239
240 240 r = self.repo
241 241 cl = r.changelog
242 242 mf = r.manifest
243 243 change1 = cl.read(node1)
244 244 change2 = cl.read(node2)
245 245 mmap1 = mf.read(change1[0])
246 246 mmap2 = mf.read(change2[0])
247 247 date1 = util.datestr(change1[2])
248 248 date2 = util.datestr(change2[2])
249 249
250 250 c, a, d, u = r.changes(node1, node2)
251 251 if files:
252 252 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
253 253
254 254 for f in c:
255 255 to = r.file(f).read(mmap1[f])
256 256 tn = r.file(f).read(mmap2[f])
257 257 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
258 258 for f in a:
259 259 to = None
260 260 tn = r.file(f).read(mmap2[f])
261 261 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
262 262 for f in d:
263 263 to = r.file(f).read(mmap1[f])
264 264 tn = None
265 265 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
266 266
267 267 def changelog(self, pos):
268 268 def changenav(**map):
269 269 def seq(factor=1):
270 270 yield 1 * factor
271 271 yield 3 * factor
272 272 #yield 5 * factor
273 273 for f in seq(factor * 10):
274 274 yield f
275 275
276 276 l = []
277 277 for f in seq():
278 278 if f < self.maxchanges / 2:
279 279 continue
280 280 if f > count:
281 281 break
282 282 r = "%d" % f
283 283 if pos + f < count:
284 284 l.append(("+" + r, pos + f))
285 285 if pos - f >= 0:
286 286 l.insert(0, ("-" + r, pos - f))
287 287
288 288 yield {"rev": 0, "label": "(0)"}
289 289
290 290 for label, rev in l:
291 291 yield {"label": label, "rev": rev}
292 292
293 293 yield {"label": "tip", "rev": ""}
294 294
295 295 def changelist(**map):
296 296 parity = (start - end) & 1
297 297 cl = self.repo.changelog
298 298 l = [] # build a list in forward order for efficiency
299 299 for i in range(start, end):
300 300 n = cl.node(i)
301 301 changes = cl.read(n)
302 302 hn = hex(n)
303 303
304 304 l.insert(0, {"parity": parity,
305 305 "author": changes[1],
306 306 "parent": self.parents("changelogparent",
307 307 cl.parents(n), cl.rev),
308 308 "changelogtag": self.showtag("changelogtag",n),
309 309 "manifest": hex(changes[0]),
310 310 "desc": changes[4],
311 311 "date": changes[2],
312 312 "files": self.listfilediffs(changes[3], n),
313 313 "rev": i,
314 314 "node": hn})
315 315 parity = 1 - parity
316 316
317 317 for e in l:
318 318 yield e
319 319
320 320 cl = self.repo.changelog
321 321 mf = cl.read(cl.tip())[0]
322 322 count = cl.count()
323 323 start = max(0, pos - self.maxchanges + 1)
324 324 end = min(count, start + self.maxchanges)
325 325 pos = end - 1
326 326
327 327 yield self.t('changelog',
328 328 changenav=changenav,
329 329 manifest=hex(mf),
330 330 rev=pos, changesets=count, entries=changelist)
331 331
332 332 def search(self, query):
333 333
334 334 def changelist(**map):
335 335 cl = self.repo.changelog
336 336 count = 0
337 337 qw = query.lower().split()
338 338
339 339 def revgen():
340 340 for i in range(cl.count() - 1, 0, -100):
341 341 l = []
342 342 for j in range(max(0, i - 100), i):
343 343 n = cl.node(j)
344 344 changes = cl.read(n)
345 345 l.append((n, j, changes))
346 346 l.reverse()
347 347 for e in l:
348 348 yield e
349 349
350 350 for n, i, changes in revgen():
351 351 miss = 0
352 352 for q in qw:
353 353 if not (q in changes[1].lower() or
354 354 q in changes[4].lower() or
355 355 q in " ".join(changes[3][:20]).lower()):
356 356 miss = 1
357 357 break
358 358 if miss:
359 359 continue
360 360
361 361 count += 1
362 362 hn = hex(n)
363 363
364 364 yield self.t('searchentry',
365 365 parity=count & 1,
366 366 author=changes[1],
367 367 parent=self.parents("changelogparent",
368 368 cl.parents(n), cl.rev),
369 369 changelogtag=self.showtag("changelogtag",n),
370 370 manifest=hex(changes[0]),
371 371 desc=changes[4],
372 372 date=changes[2],
373 373 files=self.listfilediffs(changes[3], n),
374 374 rev=i,
375 375 node=hn)
376 376
377 377 if count >= self.maxchanges:
378 378 break
379 379
380 380 cl = self.repo.changelog
381 381 mf = cl.read(cl.tip())[0]
382 382
383 383 yield self.t('search',
384 384 query=query,
385 385 manifest=hex(mf),
386 386 entries=changelist)
387 387
388 388 def changeset(self, nodeid):
389 n = bin(nodeid)
390 389 cl = self.repo.changelog
390 n = self.repo.lookup(nodeid)
391 nodeid = hex(n)
391 392 changes = cl.read(n)
392 393 p1 = cl.parents(n)[0]
393 394
394 395 files = []
395 396 mf = self.repo.manifest.read(changes[0])
396 397 for f in changes[3]:
397 398 files.append(self.t("filenodelink",
398 399 filenode=hex(mf.get(f, nullid)), file=f))
399 400
400 401 def diff(**map):
401 402 yield self.diff(p1, n, None)
402 403
403 404 def archivelist():
404 405 for i in self.archives:
405 406 if self.repo.ui.configbool("web", "allow" + i, False):
406 407 yield {"type" : i, "node" : nodeid}
407 408
408 409 yield self.t('changeset',
409 410 diff=diff,
410 411 rev=cl.rev(n),
411 412 node=nodeid,
412 413 parent=self.parents("changesetparent",
413 414 cl.parents(n), cl.rev),
414 415 changesettag=self.showtag("changesettag",n),
415 416 manifest=hex(changes[0]),
416 417 author=changes[1],
417 418 desc=changes[4],
418 419 date=changes[2],
419 420 files=files,
420 421 archives=archivelist())
421 422
422 423 def filelog(self, f, filenode):
423 424 cl = self.repo.changelog
424 425 fl = self.repo.file(f)
426 filenode = hex(fl.lookup(filenode))
425 427 count = fl.count()
426 428
427 429 def entries(**map):
428 430 l = []
429 431 parity = (count - 1) & 1
430 432
431 433 for i in range(count):
432 434 n = fl.node(i)
433 435 lr = fl.linkrev(n)
434 436 cn = cl.node(lr)
435 437 cs = cl.read(cl.node(lr))
436 438
437 439 l.insert(0, {"parity": parity,
438 440 "filenode": hex(n),
439 441 "filerev": i,
440 442 "file": f,
441 443 "node": hex(cn),
442 444 "author": cs[1],
443 445 "date": cs[2],
444 446 "parent": self.parents("filelogparent",
445 447 fl.parents(n),
446 448 fl.rev, file=f),
447 449 "desc": cs[4]})
448 450 parity = 1 - parity
449 451
450 452 for e in l:
451 453 yield e
452 454
453 455 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
454 456
455 457 def filerevision(self, f, node):
456 458 fl = self.repo.file(f)
457 n = bin(node)
459 n = fl.lookup(node)
460 node = hex(n)
458 461 text = fl.read(n)
459 462 changerev = fl.linkrev(n)
460 463 cl = self.repo.changelog
461 464 cn = cl.node(changerev)
462 465 cs = cl.read(cn)
463 466 mfn = cs[0]
464 467
465 468 def lines():
466 469 for l, t in enumerate(text.splitlines(1)):
467 470 yield {"line": t,
468 471 "linenumber": "% 6d" % (l + 1),
469 472 "parity": l & 1}
470 473
471 474 yield self.t("filerevision",
472 475 file=f,
473 476 filenode=node,
474 477 path=up(f),
475 478 text=lines(),
476 479 rev=changerev,
477 480 node=hex(cn),
478 481 manifest=hex(mfn),
479 482 author=cs[1],
480 483 date=cs[2],
481 484 parent=self.parents("filerevparent",
482 485 fl.parents(n), fl.rev, file=f),
483 486 permissions=self.repo.manifest.readflags(mfn)[f])
484 487
485 488 def fileannotate(self, f, node):
486 489 bcache = {}
487 490 ncache = {}
488 491 fl = self.repo.file(f)
489 n = bin(node)
492 n = fl.lookup(node)
493 node = hex(n)
490 494 changerev = fl.linkrev(n)
491 495
492 496 cl = self.repo.changelog
493 497 cn = cl.node(changerev)
494 498 cs = cl.read(cn)
495 499 mfn = cs[0]
496 500
497 501 def annotate(**map):
498 502 parity = 1
499 503 last = None
500 504 for r, l in fl.annotate(n):
501 505 try:
502 506 cnode = ncache[r]
503 507 except KeyError:
504 508 cnode = ncache[r] = self.repo.changelog.node(r)
505 509
506 510 try:
507 511 name = bcache[r]
508 512 except KeyError:
509 513 cl = self.repo.changelog.read(cnode)
510 514 bcache[r] = name = self.repo.ui.shortuser(cl[1])
511 515
512 516 if last != cnode:
513 517 parity = 1 - parity
514 518 last = cnode
515 519
516 520 yield {"parity": parity,
517 521 "node": hex(cnode),
518 522 "rev": r,
519 523 "author": name,
520 524 "file": f,
521 525 "line": l}
522 526
523 527 yield self.t("fileannotate",
524 528 file=f,
525 529 filenode=node,
526 530 annotate=annotate,
527 531 path=up(f),
528 532 rev=changerev,
529 533 node=hex(cn),
530 534 manifest=hex(mfn),
531 535 author=cs[1],
532 536 date=cs[2],
533 537 parent=self.parents("fileannotateparent",
534 538 fl.parents(n), fl.rev, file=f),
535 539 permissions=self.repo.manifest.readflags(mfn)[f])
536 540
537 541 def manifest(self, mnode, path):
538 mf = self.repo.manifest.read(bin(mnode))
539 rev = self.repo.manifest.rev(bin(mnode))
542 man = self.repo.manifest
543 mn = man.lookup(mnode)
544 mnode = hex(mn)
545 mf = man.read(mn)
546 rev = man.rev(mn)
540 547 node = self.repo.changelog.node(rev)
541 mff=self.repo.manifest.readflags(bin(mnode))
548 mff = man.readflags(mn)
542 549
543 550 files = {}
544 551
545 552 p = path[1:]
546 553 l = len(p)
547 554
548 555 for f,n in mf.items():
549 556 if f[:l] != p:
550 557 continue
551 558 remain = f[l:]
552 559 if "/" in remain:
553 560 short = remain[:remain.find("/") + 1] # bleah
554 561 files[short] = (f, None)
555 562 else:
556 563 short = os.path.basename(remain)
557 564 files[short] = (f, n)
558 565
559 566 def filelist(**map):
560 567 parity = 0
561 568 fl = files.keys()
562 569 fl.sort()
563 570 for f in fl:
564 571 full, fnode = files[f]
565 572 if not fnode:
566 573 continue
567 574
568 575 yield {"file": full,
569 576 "manifest": mnode,
570 577 "filenode": hex(fnode),
571 578 "parity": parity,
572 579 "basename": f,
573 580 "permissions": mff[full]}
574 581 parity = 1 - parity
575 582
576 583 def dirlist(**map):
577 584 parity = 0
578 585 fl = files.keys()
579 586 fl.sort()
580 587 for f in fl:
581 588 full, fnode = files[f]
582 589 if fnode:
583 590 continue
584 591
585 592 yield {"parity": parity,
586 593 "path": os.path.join(path, f),
587 594 "manifest": mnode,
588 595 "basename": f[:-1]}
589 596 parity = 1 - parity
590 597
591 598 yield self.t("manifest",
592 599 manifest=mnode,
593 600 rev=rev,
594 601 node=hex(node),
595 602 path=path,
596 603 up=up(path),
597 604 fentries=filelist,
598 605 dentries=dirlist)
599 606
600 607 def tags(self):
601 608 cl = self.repo.changelog
602 609 mf = cl.read(cl.tip())[0]
603 610
604 611 i = self.repo.tagslist()
605 612 i.reverse()
606 613
607 614 def entries(**map):
608 615 parity = 0
609 616 for k,n in i:
610 617 yield {"parity": parity,
611 618 "tag": k,
612 619 "node": hex(n)}
613 620 parity = 1 - parity
614 621
615 622 yield self.t("tags",
616 623 manifest=hex(mf),
617 624 entries=entries)
618 625
619 626 def filediff(self, file, changeset):
620 n = bin(changeset)
621 627 cl = self.repo.changelog
628 n = self.repo.lookup(changeset)
629 changeset = hex(n)
622 630 p1 = cl.parents(n)[0]
623 631 cs = cl.read(n)
624 632 mf = self.repo.manifest.read(cs[0])
625 633
626 634 def diff(**map):
627 635 yield self.diff(p1, n, file)
628 636
629 637 yield self.t("filediff",
630 638 file=file,
631 639 filenode=hex(mf.get(file, nullid)),
632 640 node=changeset,
633 641 rev=self.repo.changelog.rev(n),
634 642 parent=self.parents("filediffparent",
635 643 cl.parents(n), cl.rev),
636 644 diff=diff)
637 645
638 646 def archive(self, req, cnode, type):
639 647 cs = self.repo.changelog.read(cnode)
640 648 mnode = cs[0]
641 649 mf = self.repo.manifest.read(mnode)
642 650 rev = self.repo.manifest.rev(mnode)
643 651 reponame = re.sub(r"\W+", "-", self.reponame)
644 652 name = "%s-%s/" % (reponame, short(cnode))
645 653
646 654 files = mf.keys()
647 655 files.sort()
648 656
649 657 if type == 'zip':
650 658 tmp = tempfile.mkstemp()[1]
651 659 try:
652 660 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
653 661
654 662 for f in files:
655 663 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
656 664 zf.close()
657 665
658 666 f = open(tmp, 'r')
659 667 req.httphdr('application/zip', name[:-1] + '.zip',
660 668 os.path.getsize(tmp))
661 669 req.write(f.read())
662 670 f.close()
663 671 finally:
664 672 os.unlink(tmp)
665 673
666 674 else:
667 675 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
668 676 mff = self.repo.manifest.readflags(mnode)
669 677 mtime = int(time.time())
670 678
671 679 if type == "gz":
672 680 encoding = "gzip"
673 681 else:
674 682 encoding = "x-bzip2"
675 683 req.header([('Content-type', 'application/x-tar'),
676 684 ('Content-disposition', 'attachment; filename=%s%s%s' %
677 685 (name[:-1], '.tar.', type)),
678 686 ('Content-encoding', encoding)])
679 687 for fname in files:
680 688 rcont = self.repo.file(fname).read(mf[fname])
681 689 finfo = tarfile.TarInfo(name + fname)
682 690 finfo.mtime = mtime
683 691 finfo.size = len(rcont)
684 692 finfo.mode = mff[fname] and 0755 or 0644
685 693 tf.addfile(finfo, StringIO.StringIO(rcont))
686 694 tf.close()
687 695
688 696 # add tags to things
689 697 # tags -> list of changesets corresponding to tags
690 698 # find tag, changeset, file
691 699
692 700 def run(self, req=hgrequest()):
693 701 def header(**map):
694 702 yield self.t("header", **map)
695 703
696 704 def footer(**map):
697 705 yield self.t("footer", **map)
698 706
699 707 self.refresh()
700 708
701 709 t = self.repo.ui.config("web", "templates", templatepath())
702 710 m = os.path.join(t, "map")
703 711 style = self.repo.ui.config("web", "style", "")
704 712 if req.form.has_key('style'):
705 713 style = req.form['style'][0]
706 714 if style:
707 715 b = os.path.basename("map-" + style)
708 716 p = os.path.join(t, b)
709 717 if os.path.isfile(p):
710 718 m = p
711 719
712 720 port = req.env["SERVER_PORT"]
713 721 port = port != "80" and (":" + port) or ""
714 722 uri = req.env["REQUEST_URI"]
715 723 if "?" in uri:
716 724 uri = uri.split("?")[0]
717 725 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
718 726 if not self.reponame:
719 727 self.reponame = (self.repo.ui.config("web", "name")
720 728 or uri.strip('/') or self.repo.root)
721 729
722 730 self.t = templater(m, common_filters,
723 731 {"url": url,
724 732 "repo": self.reponame,
725 733 "header": header,
726 734 "footer": footer,
727 735 })
728 736
729 737 if not req.form.has_key('cmd'):
730 738 req.form['cmd'] = [self.t.cache['default'],]
731 739
732 740 if req.form['cmd'][0] == 'changelog':
733 741 c = self.repo.changelog.count() - 1
734 742 hi = c
735 743 if req.form.has_key('rev'):
736 744 hi = req.form['rev'][0]
737 745 try:
738 746 hi = self.repo.changelog.rev(self.repo.lookup(hi))
739 747 except hg.RepoError:
740 748 req.write(self.search(hi))
741 749 return
742 750
743 751 req.write(self.changelog(hi))
744 752
745 753 elif req.form['cmd'][0] == 'changeset':
746 754 req.write(self.changeset(req.form['node'][0]))
747 755
748 756 elif req.form['cmd'][0] == 'manifest':
749 757 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
750 758
751 759 elif req.form['cmd'][0] == 'tags':
752 760 req.write(self.tags())
753 761
754 762 elif req.form['cmd'][0] == 'filediff':
755 763 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
756 764
757 765 elif req.form['cmd'][0] == 'file':
758 766 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
759 767
760 768 elif req.form['cmd'][0] == 'annotate':
761 769 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
762 770
763 771 elif req.form['cmd'][0] == 'filelog':
764 772 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
765 773
766 774 elif req.form['cmd'][0] == 'heads':
767 775 req.httphdr("application/mercurial-0.1")
768 776 h = self.repo.heads()
769 777 req.write(" ".join(map(hex, h)) + "\n")
770 778
771 779 elif req.form['cmd'][0] == 'branches':
772 780 req.httphdr("application/mercurial-0.1")
773 781 nodes = []
774 782 if req.form.has_key('nodes'):
775 783 nodes = map(bin, req.form['nodes'][0].split(" "))
776 784 for b in self.repo.branches(nodes):
777 785 req.write(" ".join(map(hex, b)) + "\n")
778 786
779 787 elif req.form['cmd'][0] == 'between':
780 788 req.httphdr("application/mercurial-0.1")
781 789 nodes = []
782 790 if req.form.has_key('pairs'):
783 791 pairs = [map(bin, p.split("-"))
784 792 for p in req.form['pairs'][0].split(" ")]
785 793 for b in self.repo.between(pairs):
786 794 req.write(" ".join(map(hex, b)) + "\n")
787 795
788 796 elif req.form['cmd'][0] == 'changegroup':
789 797 req.httphdr("application/mercurial-0.1")
790 798 nodes = []
791 799 if not self.allowpull:
792 800 return
793 801
794 802 if req.form.has_key('roots'):
795 803 nodes = map(bin, req.form['roots'][0].split(" "))
796 804
797 805 z = zlib.compressobj()
798 806 f = self.repo.changegroup(nodes)
799 807 while 1:
800 808 chunk = f.read(4096)
801 809 if not chunk:
802 810 break
803 811 req.write(z.compress(chunk))
804 812
805 813 req.write(z.flush())
806 814
807 815 elif req.form['cmd'][0] == 'archive':
808 changeset = bin(req.form['node'][0])
816 changeset = self.repo.lookup(req.form['node'][0])
809 817 type = req.form['type'][0]
810 818 if (type in self.archives and
811 819 self.repo.ui.configbool("web", "allow" + type, False)):
812 820 self.archive(req, changeset, type)
813 821 return
814 822
815 823 req.write(self.t("error"))
816 824
817 825 else:
818 826 req.write(self.t("error"))
819 827
820 828 def create_server(repo):
821 829
822 830 def openlog(opt, default):
823 831 if opt and opt != '-':
824 832 return open(opt, 'w')
825 833 return default
826 834
827 835 address = repo.ui.config("web", "address", "")
828 836 port = int(repo.ui.config("web", "port", 8000))
829 837 use_ipv6 = repo.ui.configbool("web", "ipv6")
830 838 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
831 839 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
832 840
833 841 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
834 842 address_family = getattr(socket, 'AF_INET6', None)
835 843
836 844 def __init__(self, *args, **kwargs):
837 845 if self.address_family is None:
838 846 raise hg.RepoError('IPv6 not available on this system')
839 847 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
840 848
841 849 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
842 850 def log_error(self, format, *args):
843 851 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
844 852 self.log_date_time_string(),
845 853 format % args))
846 854
847 855 def log_message(self, format, *args):
848 856 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
849 857 self.log_date_time_string(),
850 858 format % args))
851 859
852 860 def do_POST(self):
853 861 try:
854 862 self.do_hgweb()
855 863 except socket.error, inst:
856 864 if inst[0] != errno.EPIPE:
857 865 raise
858 866
859 867 def do_GET(self):
860 868 self.do_POST()
861 869
862 870 def do_hgweb(self):
863 871 query = ""
864 872 p = self.path.find("?")
865 873 if p:
866 874 query = self.path[p + 1:]
867 875 query = query.replace('+', ' ')
868 876
869 877 env = {}
870 878 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
871 879 env['REQUEST_METHOD'] = self.command
872 880 env['SERVER_NAME'] = self.server.server_name
873 881 env['SERVER_PORT'] = str(self.server.server_port)
874 882 env['REQUEST_URI'] = "/"
875 883 if query:
876 884 env['QUERY_STRING'] = query
877 885 host = self.address_string()
878 886 if host != self.client_address[0]:
879 887 env['REMOTE_HOST'] = host
880 888 env['REMOTE_ADDR'] = self.client_address[0]
881 889
882 890 if self.headers.typeheader is None:
883 891 env['CONTENT_TYPE'] = self.headers.type
884 892 else:
885 893 env['CONTENT_TYPE'] = self.headers.typeheader
886 894 length = self.headers.getheader('content-length')
887 895 if length:
888 896 env['CONTENT_LENGTH'] = length
889 897 accept = []
890 898 for line in self.headers.getallmatchingheaders('accept'):
891 899 if line[:1] in "\t\n\r ":
892 900 accept.append(line.strip())
893 901 else:
894 902 accept = accept + line[7:].split(',')
895 903 env['HTTP_ACCEPT'] = ','.join(accept)
896 904
897 905 req = hgrequest(self.rfile, self.wfile, env)
898 906 self.send_response(200, "Script output follows")
899 907 hg.run(req)
900 908
901 909 hg = hgweb(repo)
902 910 if use_ipv6:
903 911 return IPv6HTTPServer((address, port), hgwebhandler)
904 912 else:
905 913 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
906 914
907 915 def server(path, name, templates, address, port, use_ipv6=False,
908 916 accesslog=sys.stdout, errorlog=sys.stderr):
909 917 httpd = create_server(path, name, templates, address, port, use_ipv6,
910 918 accesslog, errorlog)
911 919 httpd.serve_forever()
912 920
913 921 # This is a stopgap
914 922 class hgwebdir:
915 923 def __init__(self, config):
916 924 def cleannames(items):
917 925 return [(name.strip('/'), path) for name, path in items]
918 926
919 927 if type(config) == type([]):
920 928 self.repos = cleannames(config)
921 929 elif type(config) == type({}):
922 930 self.repos = cleannames(config.items())
923 931 self.repos.sort()
924 932 else:
925 933 cp = ConfigParser.SafeConfigParser()
926 934 cp.read(config)
927 935 self.repos = cleannames(cp.items("paths"))
928 936 self.repos.sort()
929 937
930 938 def run(self, req=hgrequest()):
931 939 def header(**map):
932 940 yield tmpl("header", **map)
933 941
934 942 def footer(**map):
935 943 yield tmpl("footer", **map)
936 944
937 945 m = os.path.join(templatepath(), "map")
938 946 tmpl = templater(m, common_filters,
939 947 {"header": header, "footer": footer})
940 948
941 949 def entries(**map):
942 950 parity = 0
943 951 for name, path in self.repos:
944 952 u = ui.ui()
945 953 try:
946 954 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
947 955 except IOError:
948 956 pass
949 957 get = u.config
950 958
951 959 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
952 960 .replace("//", "/"))
953 961
954 962 # update time with local timezone
955 963 d = (os.stat(os.path.join(path,
956 964 ".hg", "00changelog.d")).st_mtime,
957 965 util.makedate()[1])
958 966
959 967 yield dict(contact=(get("ui", "username") or # preferred
960 968 get("web", "contact") or # deprecated
961 969 get("web", "author", "unknown")), # also
962 970 name=get("web", "name", name),
963 971 url=url,
964 972 parity=parity,
965 973 shortdesc=get("web", "description", "unknown"),
966 974 lastupdate=d)
967 975
968 976 parity = 1 - parity
969 977
970 978 virtual = req.env.get("PATH_INFO", "").strip('/')
971 979 if virtual:
972 980 real = dict(self.repos).get(virtual)
973 981 if real:
974 982 hgweb(real).run(req)
975 983 else:
976 984 req.write(tmpl("notfound", repo=virtual))
977 985 else:
978 986 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now