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