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