##// END OF EJS Templates
Merged WSGI fixes from http://hg.omnifarious.org/~hopper/webmerc/
Thomas Arendsen Hein -
r2539:8a8d9ada merge default
parent child Browse files
Show More
@@ -0,0 +1,16 b''
1 #!/usr/bin/env python
2
3 __doc__ = """This does HTTP get requests given a host:port and path and returns
4 a subset of the headers plus the body of the result."""
5
6 import httplib, sys
7 headers = [h.lower() for h in sys.argv[3:]]
8 conn = httplib.HTTPConnection(sys.argv[1])
9 conn.request("GET", sys.argv[2])
10 response = conn.getresponse()
11 print response.status, response.reason
12 for h in headers:
13 if response.getheader(h, None) is not None:
14 print "%s: %s" % (h, response.getheader(h))
15 print
16 sys.stdout.write(response.read())
@@ -0,0 +1,103 b''
1 #!/bin/sh
2
3 hg init test
4
5 cat >hgweb.cgi <<HGWEB
6 #!/usr/bin/env python
7 #
8 # An example CGI script to use hgweb, edit as necessary
9
10 import cgitb, os, sys
11 cgitb.enable()
12
13 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
14 from mercurial import hgweb
15
16 h = hgweb.hgweb("test", "Empty test repository")
17 h.run()
18 HGWEB
19 chmod 755 hgweb.cgi
20
21 cat >hgweb.config <<HGWEBDIRCONF
22 [paths]
23 test = test
24 HGWEBDIRCONF
25
26 cat >hgwebdir.cgi <<HGWEBDIR
27 #!/usr/bin/env python
28 #
29 # An example CGI script to export multiple hgweb repos, edit as necessary
30
31 import cgitb, sys
32 cgitb.enable()
33
34 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
35 from mercurial import hgweb
36
37 # The config file looks like this. You can have paths to individual
38 # repos, collections of repos in a directory tree, or both.
39 #
40 # [paths]
41 # virtual/path = /real/path
42 # virtual/path = /real/path
43 #
44 # [collections]
45 # /prefix/to/strip/off = /root/of/tree/full/of/repos
46 #
47 # collections example: say directory tree /foo contains repos /foo/bar,
48 # /foo/quux/baz. Give this config section:
49 # [collections]
50 # /foo = /foo
51 # Then repos will list as bar and quux/baz.
52
53 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
54 # or use a dictionary with entries like 'virtual/path': '/real/path'
55
56 h = hgweb.hgwebdir("hgweb.config")
57 h.run()
58 HGWEBDIR
59 chmod 755 hgwebdir.cgi
60
61 declare -x DOCUMENT_ROOT="/var/www/hg"
62 declare -x GATEWAY_INTERFACE="CGI/1.1"
63 declare -x HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
64 declare -x HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"
65 declare -x HTTP_ACCEPT_ENCODING="gzip,deflate"
66 declare -x HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"
67 declare -x HTTP_CACHE_CONTROL="max-age=0"
68 declare -x HTTP_CONNECTION="keep-alive"
69 declare -x HTTP_HOST="hg.omnifarious.org"
70 declare -x HTTP_KEEP_ALIVE="300"
71 declare -x HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"
72 declare -x OLDPWD
73 declare -x PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"
74 declare -x PATH_INFO="/"
75 declare -x PATH_TRANSLATED="/var/www/hg/index.html"
76 declare -x PWD="/home/hopper/hg_public"
77 declare -x QUERY_STRING=""
78 declare -x REMOTE_ADDR="127.0.0.2"
79 declare -x REMOTE_PORT="44703"
80 declare -x REQUEST_METHOD="GET"
81 declare -x REQUEST_URI="/test/"
82 declare -x SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"
83 declare -x SCRIPT_NAME="/test"
84 declare -x SCRIPT_URI="http://hg.omnifarious.org/test/"
85 declare -x SCRIPT_URL="/test/"
86 declare -x SERVER_ADDR="127.0.0.1"
87 declare -x SERVER_ADMIN="eric@localhost"
88 declare -x SERVER_NAME="hg.omnifarious.org"
89 declare -x SERVER_PORT="80"
90 declare -x SERVER_PROTOCOL="HTTP/1.1"
91 declare -x SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>\
92 "
93 declare -x SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"
94 ./hgweb.cgi >page1 2>&1 ; echo $?
95 ./hgwebdir.cgi >page2 2>&1 ; echo $?
96 PATH_INFO="/test/"
97 PATH_TRANSLATED="/var/something/test.cgi"
98 REQUEST_URI="/test/test/"
99 SCRIPT_URI="http://hg.omnifarious.org/test/test/"
100 SCRIPT_URL="/test/test/"
101 ./hgwebdir.cgi >page3 2>&1 ; echo $?
102 fgrep -i error page1 page2 page3 && exit 1
103 exit 0
@@ -0,0 +1,3 b''
1 0
2 0
3 0
@@ -0,0 +1,20 b''
1 #!/bin/sh
2
3 hg init test
4 cd test
5 cat >sometext.txt <<ENDSOME
6 This is just some random text
7 that will go inside the file and take a few lines.
8 It is very boring to read, but computers don't
9 care about things like that.
10 ENDSOME
11 hg add sometext.txt
12 hg commit -d "1 0" -m "Just some text"
13 hg serve -p 20059 -A access.log -E error.log -d --pid-file=hg.pid
14 ("$TESTDIR/get-with-headers.py" localhost:20059 '/?f=f165dc289438;file=sometext.txt;style=raw' content-type content-length content-disposition) >getoutput.txt &
15
16 sleep 5
17 kill `cat hg.pid`
18 sleep 1 # wait for server to scream and die
19 cat getoutput.txt
20 cat access.log error.log | sed 's/^\([^[]*\[\)[^]]*\(\].*\)$/\1date\2/g'
@@ -0,0 +1,10 b''
1 200 Script output follows
2 content-type: text/plain
3 content-length: 157
4 content-disposition: filename=sometext.txt
5
6 This is just some random text
7 that will go inside the file and take a few lines.
8 It is very boring to read, but computers don't
9 care about things like that.
10 localhost - - [date] "GET /?f=f165dc289438;file=sometext.txt;style=raw HTTP/1.1" 200 -
@@ -1,927 +1,943 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 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 16 from mercurial.node import *
17 17 from mercurial.i18n import gettext as _
18 18
19 19 def _up(p):
20 20 if p[0] != "/":
21 21 p = "/" + p
22 22 if p[-1] == "/":
23 23 p = p[:-1]
24 24 up = os.path.dirname(p)
25 25 if up == "/":
26 26 return "/"
27 27 return up + "/"
28 28
29 29 class hgweb(object):
30 30 def __init__(self, repo, name=None):
31 31 if type(repo) == type(""):
32 32 self.repo = hg.repository(ui.ui(), repo)
33 33 else:
34 34 self.repo = repo
35 35
36 36 self.mtime = -1
37 37 self.reponame = name
38 38 self.archives = 'zip', 'gz', 'bz2'
39 39 self.templatepath = self.repo.ui.config("web", "templates",
40 40 templater.templatepath())
41 41
42 42 def refresh(self):
43 43 mtime = get_mtime(self.repo.root)
44 44 if mtime != self.mtime:
45 45 self.mtime = mtime
46 46 self.repo = hg.repository(self.repo.ui, self.repo.root)
47 47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
48 48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
49 49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
50 50
51 51 def archivelist(self, nodeid):
52 52 allowed = self.repo.ui.configlist("web", "allow_archive")
53 53 for i in self.archives:
54 54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
55 55 yield {"type" : i, "node" : nodeid, "url": ""}
56 56
57 57 def listfiles(self, files, mf):
58 58 for f in files[:self.maxfiles]:
59 59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
60 60 if len(files) > self.maxfiles:
61 61 yield self.t("fileellipses")
62 62
63 63 def listfilediffs(self, files, changeset):
64 64 for f in files[:self.maxfiles]:
65 65 yield self.t("filedifflink", node=hex(changeset), file=f)
66 66 if len(files) > self.maxfiles:
67 67 yield self.t("fileellipses")
68 68
69 69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
70 70 if not rev:
71 71 rev = lambda x: ""
72 72 siblings = [s for s in siblings if s != nullid]
73 73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
74 74 return
75 75 for s in siblings:
76 76 yield dict(node=hex(s), rev=rev(s), **args)
77 77
78 78 def renamelink(self, fl, node):
79 79 r = fl.renamed(node)
80 80 if r:
81 81 return [dict(file=r[0], node=hex(r[1]))]
82 82 return []
83 83
84 84 def showtag(self, t1, node=nullid, **args):
85 85 for t in self.repo.nodetags(node):
86 86 yield self.t(t1, tag=t, **args)
87 87
88 88 def diff(self, node1, node2, files):
89 89 def filterfiles(filters, files):
90 90 l = [x for x in files if x in filters]
91 91
92 92 for t in filters:
93 93 if t and t[-1] != os.sep:
94 94 t += os.sep
95 95 l += [x for x in files if x.startswith(t)]
96 96 return l
97 97
98 98 parity = [0]
99 99 def diffblock(diff, f, fn):
100 100 yield self.t("diffblock",
101 101 lines=prettyprintlines(diff),
102 102 parity=parity[0],
103 103 file=f,
104 104 filenode=hex(fn or nullid))
105 105 parity[0] = 1 - parity[0]
106 106
107 107 def prettyprintlines(diff):
108 108 for l in diff.splitlines(1):
109 109 if l.startswith('+'):
110 110 yield self.t("difflineplus", line=l)
111 111 elif l.startswith('-'):
112 112 yield self.t("difflineminus", line=l)
113 113 elif l.startswith('@'):
114 114 yield self.t("difflineat", line=l)
115 115 else:
116 116 yield self.t("diffline", line=l)
117 117
118 118 r = self.repo
119 119 cl = r.changelog
120 120 mf = r.manifest
121 121 change1 = cl.read(node1)
122 122 change2 = cl.read(node2)
123 123 mmap1 = mf.read(change1[0])
124 124 mmap2 = mf.read(change2[0])
125 125 date1 = util.datestr(change1[2])
126 126 date2 = util.datestr(change2[2])
127 127
128 128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
129 129 if files:
130 130 modified, added, removed = map(lambda x: filterfiles(files, x),
131 131 (modified, added, removed))
132 132
133 133 diffopts = self.repo.ui.diffopts()
134 134 showfunc = diffopts['showfunc']
135 135 ignorews = diffopts['ignorews']
136 136 for f in modified:
137 137 to = r.file(f).read(mmap1[f])
138 138 tn = r.file(f).read(mmap2[f])
139 139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
140 140 showfunc=showfunc, ignorews=ignorews), f, tn)
141 141 for f in added:
142 142 to = None
143 143 tn = r.file(f).read(mmap2[f])
144 144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
145 145 showfunc=showfunc, ignorews=ignorews), f, tn)
146 146 for f in removed:
147 147 to = r.file(f).read(mmap1[f])
148 148 tn = None
149 149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 150 showfunc=showfunc, ignorews=ignorews), f, tn)
151 151
152 152 def changelog(self, pos):
153 153 def changenav(**map):
154 154 def seq(factor, maxchanges=None):
155 155 if maxchanges:
156 156 yield maxchanges
157 157 if maxchanges >= 20 and maxchanges <= 40:
158 158 yield 50
159 159 else:
160 160 yield 1 * factor
161 161 yield 3 * factor
162 162 for f in seq(factor * 10):
163 163 yield f
164 164
165 165 l = []
166 166 last = 0
167 167 for f in seq(1, self.maxchanges):
168 168 if f < self.maxchanges or f <= last:
169 169 continue
170 170 if f > count:
171 171 break
172 172 last = f
173 173 r = "%d" % f
174 174 if pos + f < count:
175 175 l.append(("+" + r, pos + f))
176 176 if pos - f >= 0:
177 177 l.insert(0, ("-" + r, pos - f))
178 178
179 179 yield {"rev": 0, "label": "(0)"}
180 180
181 181 for label, rev in l:
182 182 yield {"label": label, "rev": rev}
183 183
184 184 yield {"label": "tip", "rev": "tip"}
185 185
186 186 def changelist(**map):
187 187 parity = (start - end) & 1
188 188 cl = self.repo.changelog
189 189 l = [] # build a list in forward order for efficiency
190 190 for i in range(start, end):
191 191 n = cl.node(i)
192 192 changes = cl.read(n)
193 193 hn = hex(n)
194 194
195 195 l.insert(0, {"parity": parity,
196 196 "author": changes[1],
197 197 "parent": self.siblings(cl.parents(n), cl.rev,
198 198 cl.rev(n) - 1),
199 199 "child": self.siblings(cl.children(n), cl.rev,
200 200 cl.rev(n) + 1),
201 201 "changelogtag": self.showtag("changelogtag",n),
202 202 "manifest": hex(changes[0]),
203 203 "desc": changes[4],
204 204 "date": changes[2],
205 205 "files": self.listfilediffs(changes[3], n),
206 206 "rev": i,
207 207 "node": hn})
208 208 parity = 1 - parity
209 209
210 210 for e in l:
211 211 yield e
212 212
213 213 cl = self.repo.changelog
214 214 mf = cl.read(cl.tip())[0]
215 215 count = cl.count()
216 216 start = max(0, pos - self.maxchanges + 1)
217 217 end = min(count, start + self.maxchanges)
218 218 pos = end - 1
219 219
220 220 yield self.t('changelog',
221 221 changenav=changenav,
222 222 manifest=hex(mf),
223 223 rev=pos, changesets=count, entries=changelist,
224 224 archives=self.archivelist("tip"))
225 225
226 226 def search(self, query):
227 227
228 228 def changelist(**map):
229 229 cl = self.repo.changelog
230 230 count = 0
231 231 qw = query.lower().split()
232 232
233 233 def revgen():
234 234 for i in range(cl.count() - 1, 0, -100):
235 235 l = []
236 236 for j in range(max(0, i - 100), i):
237 237 n = cl.node(j)
238 238 changes = cl.read(n)
239 239 l.append((n, j, changes))
240 240 l.reverse()
241 241 for e in l:
242 242 yield e
243 243
244 244 for n, i, changes in revgen():
245 245 miss = 0
246 246 for q in qw:
247 247 if not (q in changes[1].lower() or
248 248 q in changes[4].lower() or
249 249 q in " ".join(changes[3][:20]).lower()):
250 250 miss = 1
251 251 break
252 252 if miss:
253 253 continue
254 254
255 255 count += 1
256 256 hn = hex(n)
257 257
258 258 yield self.t('searchentry',
259 259 parity=count & 1,
260 260 author=changes[1],
261 261 parent=self.siblings(cl.parents(n), cl.rev),
262 262 child=self.siblings(cl.children(n), cl.rev),
263 263 changelogtag=self.showtag("changelogtag",n),
264 264 manifest=hex(changes[0]),
265 265 desc=changes[4],
266 266 date=changes[2],
267 267 files=self.listfilediffs(changes[3], n),
268 268 rev=i,
269 269 node=hn)
270 270
271 271 if count >= self.maxchanges:
272 272 break
273 273
274 274 cl = self.repo.changelog
275 275 mf = cl.read(cl.tip())[0]
276 276
277 277 yield self.t('search',
278 278 query=query,
279 279 manifest=hex(mf),
280 280 entries=changelist)
281 281
282 282 def changeset(self, nodeid):
283 283 cl = self.repo.changelog
284 284 n = self.repo.lookup(nodeid)
285 285 nodeid = hex(n)
286 286 changes = cl.read(n)
287 287 p1 = cl.parents(n)[0]
288 288
289 289 files = []
290 290 mf = self.repo.manifest.read(changes[0])
291 291 for f in changes[3]:
292 292 files.append(self.t("filenodelink",
293 293 filenode=hex(mf.get(f, nullid)), file=f))
294 294
295 295 def diff(**map):
296 296 yield self.diff(p1, n, None)
297 297
298 298 yield self.t('changeset',
299 299 diff=diff,
300 300 rev=cl.rev(n),
301 301 node=nodeid,
302 302 parent=self.siblings(cl.parents(n), cl.rev),
303 303 child=self.siblings(cl.children(n), cl.rev),
304 304 changesettag=self.showtag("changesettag",n),
305 305 manifest=hex(changes[0]),
306 306 author=changes[1],
307 307 desc=changes[4],
308 308 date=changes[2],
309 309 files=files,
310 310 archives=self.archivelist(nodeid))
311 311
312 312 def filelog(self, f, filenode):
313 313 cl = self.repo.changelog
314 314 fl = self.repo.file(f)
315 315 filenode = hex(fl.lookup(filenode))
316 316 count = fl.count()
317 317
318 318 def entries(**map):
319 319 l = []
320 320 parity = (count - 1) & 1
321 321
322 322 for i in range(count):
323 323 n = fl.node(i)
324 324 lr = fl.linkrev(n)
325 325 cn = cl.node(lr)
326 326 cs = cl.read(cl.node(lr))
327 327
328 328 l.insert(0, {"parity": parity,
329 329 "filenode": hex(n),
330 330 "filerev": i,
331 331 "file": f,
332 332 "node": hex(cn),
333 333 "author": cs[1],
334 334 "date": cs[2],
335 335 "rename": self.renamelink(fl, n),
336 336 "parent": self.siblings(fl.parents(n),
337 337 fl.rev, file=f),
338 338 "child": self.siblings(fl.children(n),
339 339 fl.rev, file=f),
340 340 "desc": cs[4]})
341 341 parity = 1 - parity
342 342
343 343 for e in l:
344 344 yield e
345 345
346 346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
347 347
348 348 def filerevision(self, f, node):
349 349 fl = self.repo.file(f)
350 350 n = fl.lookup(node)
351 351 node = hex(n)
352 352 text = fl.read(n)
353 353 changerev = fl.linkrev(n)
354 354 cl = self.repo.changelog
355 355 cn = cl.node(changerev)
356 356 cs = cl.read(cn)
357 357 mfn = cs[0]
358 358
359 359 mt = mimetypes.guess_type(f)[0]
360 360 rawtext = text
361 361 if util.binary(text):
362 362 mt = mt or 'application/octet-stream'
363 363 text = "(binary:%s)" % mt
364 364 mt = mt or 'text/plain'
365 365
366 366 def lines():
367 367 for l, t in enumerate(text.splitlines(1)):
368 368 yield {"line": t,
369 369 "linenumber": "% 6d" % (l + 1),
370 370 "parity": l & 1}
371 371
372 372 yield self.t("filerevision",
373 373 file=f,
374 374 filenode=node,
375 375 path=_up(f),
376 376 text=lines(),
377 377 raw=rawtext,
378 378 mimetype=mt,
379 379 rev=changerev,
380 380 node=hex(cn),
381 381 manifest=hex(mfn),
382 382 author=cs[1],
383 383 date=cs[2],
384 384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
385 385 child=self.siblings(fl.children(n), fl.rev, file=f),
386 386 rename=self.renamelink(fl, n),
387 387 permissions=self.repo.manifest.readflags(mfn)[f])
388 388
389 389 def fileannotate(self, f, node):
390 390 bcache = {}
391 391 ncache = {}
392 392 fl = self.repo.file(f)
393 393 n = fl.lookup(node)
394 394 node = hex(n)
395 395 changerev = fl.linkrev(n)
396 396
397 397 cl = self.repo.changelog
398 398 cn = cl.node(changerev)
399 399 cs = cl.read(cn)
400 400 mfn = cs[0]
401 401
402 402 def annotate(**map):
403 403 parity = 1
404 404 last = None
405 405 for r, l in fl.annotate(n):
406 406 try:
407 407 cnode = ncache[r]
408 408 except KeyError:
409 409 cnode = ncache[r] = self.repo.changelog.node(r)
410 410
411 411 try:
412 412 name = bcache[r]
413 413 except KeyError:
414 414 cl = self.repo.changelog.read(cnode)
415 415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
416 416
417 417 if last != cnode:
418 418 parity = 1 - parity
419 419 last = cnode
420 420
421 421 yield {"parity": parity,
422 422 "node": hex(cnode),
423 423 "rev": r,
424 424 "author": name,
425 425 "file": f,
426 426 "line": l}
427 427
428 428 yield self.t("fileannotate",
429 429 file=f,
430 430 filenode=node,
431 431 annotate=annotate,
432 432 path=_up(f),
433 433 rev=changerev,
434 434 node=hex(cn),
435 435 manifest=hex(mfn),
436 436 author=cs[1],
437 437 date=cs[2],
438 438 rename=self.renamelink(fl, n),
439 439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
440 440 child=self.siblings(fl.children(n), fl.rev, file=f),
441 441 permissions=self.repo.manifest.readflags(mfn)[f])
442 442
443 443 def manifest(self, mnode, path):
444 444 man = self.repo.manifest
445 445 mn = man.lookup(mnode)
446 446 mnode = hex(mn)
447 447 mf = man.read(mn)
448 448 rev = man.rev(mn)
449 449 changerev = man.linkrev(mn)
450 450 node = self.repo.changelog.node(changerev)
451 451 mff = man.readflags(mn)
452 452
453 453 files = {}
454 454
455 455 p = path[1:]
456 456 if p and p[-1] != "/":
457 457 p += "/"
458 458 l = len(p)
459 459
460 460 for f,n in mf.items():
461 461 if f[:l] != p:
462 462 continue
463 463 remain = f[l:]
464 464 if "/" in remain:
465 465 short = remain[:remain.find("/") + 1] # bleah
466 466 files[short] = (f, None)
467 467 else:
468 468 short = os.path.basename(remain)
469 469 files[short] = (f, n)
470 470
471 471 def filelist(**map):
472 472 parity = 0
473 473 fl = files.keys()
474 474 fl.sort()
475 475 for f in fl:
476 476 full, fnode = files[f]
477 477 if not fnode:
478 478 continue
479 479
480 480 yield {"file": full,
481 481 "manifest": mnode,
482 482 "filenode": hex(fnode),
483 483 "parity": parity,
484 484 "basename": f,
485 485 "permissions": mff[full]}
486 486 parity = 1 - parity
487 487
488 488 def dirlist(**map):
489 489 parity = 0
490 490 fl = files.keys()
491 491 fl.sort()
492 492 for f in fl:
493 493 full, fnode = files[f]
494 494 if fnode:
495 495 continue
496 496
497 497 yield {"parity": parity,
498 498 "path": os.path.join(path, f),
499 499 "manifest": mnode,
500 500 "basename": f[:-1]}
501 501 parity = 1 - parity
502 502
503 503 yield self.t("manifest",
504 504 manifest=mnode,
505 505 rev=rev,
506 506 node=hex(node),
507 507 path=path,
508 508 up=_up(path),
509 509 fentries=filelist,
510 510 dentries=dirlist,
511 511 archives=self.archivelist(hex(node)))
512 512
513 513 def tags(self):
514 514 cl = self.repo.changelog
515 515 mf = cl.read(cl.tip())[0]
516 516
517 517 i = self.repo.tagslist()
518 518 i.reverse()
519 519
520 520 def entries(notip=False, **map):
521 521 parity = 0
522 522 for k,n in i:
523 523 if notip and k == "tip": continue
524 524 yield {"parity": parity,
525 525 "tag": k,
526 526 "tagmanifest": hex(cl.read(n)[0]),
527 527 "date": cl.read(n)[2],
528 528 "node": hex(n)}
529 529 parity = 1 - parity
530 530
531 531 yield self.t("tags",
532 532 manifest=hex(mf),
533 533 entries=lambda **x: entries(False, **x),
534 534 entriesnotip=lambda **x: entries(True, **x))
535 535
536 536 def summary(self):
537 537 cl = self.repo.changelog
538 538 mf = cl.read(cl.tip())[0]
539 539
540 540 i = self.repo.tagslist()
541 541 i.reverse()
542 542
543 543 def tagentries(**map):
544 544 parity = 0
545 545 count = 0
546 546 for k,n in i:
547 547 if k == "tip": # skip tip
548 548 continue;
549 549
550 550 count += 1
551 551 if count > 10: # limit to 10 tags
552 552 break;
553 553
554 554 c = cl.read(n)
555 555 m = c[0]
556 556 t = c[2]
557 557
558 558 yield self.t("tagentry",
559 559 parity = parity,
560 560 tag = k,
561 561 node = hex(n),
562 562 date = t,
563 563 tagmanifest = hex(m))
564 564 parity = 1 - parity
565 565
566 566 def changelist(**map):
567 567 parity = 0
568 568 cl = self.repo.changelog
569 569 l = [] # build a list in forward order for efficiency
570 570 for i in range(start, end):
571 571 n = cl.node(i)
572 572 changes = cl.read(n)
573 573 hn = hex(n)
574 574 t = changes[2]
575 575
576 576 l.insert(0, self.t(
577 577 'shortlogentry',
578 578 parity = parity,
579 579 author = changes[1],
580 580 manifest = hex(changes[0]),
581 581 desc = changes[4],
582 582 date = t,
583 583 rev = i,
584 584 node = hn))
585 585 parity = 1 - parity
586 586
587 587 yield l
588 588
589 589 cl = self.repo.changelog
590 590 mf = cl.read(cl.tip())[0]
591 591 count = cl.count()
592 592 start = max(0, count - self.maxchanges)
593 593 end = min(count, start + self.maxchanges)
594 594
595 595 yield self.t("summary",
596 596 desc = self.repo.ui.config("web", "description", "unknown"),
597 597 owner = (self.repo.ui.config("ui", "username") or # preferred
598 598 self.repo.ui.config("web", "contact") or # deprecated
599 599 self.repo.ui.config("web", "author", "unknown")), # also
600 600 lastchange = (0, 0), # FIXME
601 601 manifest = hex(mf),
602 602 tags = tagentries,
603 603 shortlog = changelist)
604 604
605 605 def filediff(self, file, changeset):
606 606 cl = self.repo.changelog
607 607 n = self.repo.lookup(changeset)
608 608 changeset = hex(n)
609 609 p1 = cl.parents(n)[0]
610 610 cs = cl.read(n)
611 611 mf = self.repo.manifest.read(cs[0])
612 612
613 613 def diff(**map):
614 614 yield self.diff(p1, n, [file])
615 615
616 616 yield self.t("filediff",
617 617 file=file,
618 618 filenode=hex(mf.get(file, nullid)),
619 619 node=changeset,
620 620 rev=self.repo.changelog.rev(n),
621 621 parent=self.siblings(cl.parents(n), cl.rev),
622 622 child=self.siblings(cl.children(n), cl.rev),
623 623 diff=diff)
624 624
625 625 archive_specs = {
626 626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
627 627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
628 628 'zip': ('application/zip', 'zip', '.zip', None),
629 629 }
630 630
631 631 def archive(self, req, cnode, type_):
632 632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
633 633 name = "%s-%s" % (reponame, short(cnode))
634 634 mimetype, artype, extension, encoding = self.archive_specs[type_]
635 635 headers = [('Content-type', mimetype),
636 636 ('Content-disposition', 'attachment; filename=%s%s' %
637 637 (name, extension))]
638 638 if encoding:
639 639 headers.append(('Content-encoding', encoding))
640 640 req.header(headers)
641 641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
642 642
643 643 # add tags to things
644 644 # tags -> list of changesets corresponding to tags
645 645 # find tag, changeset, file
646 646
647 647 def cleanpath(self, path):
648 648 p = util.normpath(path)
649 649 if p[:2] == "..":
650 650 raise Exception("suspicious path")
651 651 return p
652 652
653 def run(self, req):
653 def run(self):
654 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
655 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
656 import mercurial.hgweb.wsgicgi as wsgicgi
657 from request import wsgiapplication
658 def make_web_app():
659 return self
660 wsgicgi.launch(wsgiapplication(make_web_app))
661
662 def run_wsgi(self, req):
654 663 def header(**map):
655 664 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
656 665 msg = mimetools.Message(header_file, 0)
657 666 req.header(msg.items())
658 667 yield header_file.read()
659 668
669 def rawfileheader(**map):
670 req.header([('Content-type', map['mimetype']),
671 ('Content-disposition', 'filename=%s' % map['file']),
672 ('Content-length', str(len(map['raw'])))])
673 yield ''
674
660 675 def footer(**map):
661 676 yield self.t("footer",
662 677 motd=self.repo.ui.config("web", "motd", ""),
663 678 **map)
664 679
665 680 def expand_form(form):
666 681 shortcuts = {
667 682 'cl': [('cmd', ['changelog']), ('rev', None)],
668 683 'cs': [('cmd', ['changeset']), ('node', None)],
669 684 'f': [('cmd', ['file']), ('filenode', None)],
670 685 'fl': [('cmd', ['filelog']), ('filenode', None)],
671 686 'fd': [('cmd', ['filediff']), ('node', None)],
672 687 'fa': [('cmd', ['annotate']), ('filenode', None)],
673 688 'mf': [('cmd', ['manifest']), ('manifest', None)],
674 689 'ca': [('cmd', ['archive']), ('node', None)],
675 690 'tags': [('cmd', ['tags'])],
676 691 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
677 692 'static': [('cmd', ['static']), ('file', None)]
678 693 }
679 694
680 695 for k in shortcuts.iterkeys():
681 696 if form.has_key(k):
682 697 for name, value in shortcuts[k]:
683 698 if value is None:
684 699 value = form[k]
685 700 form[name] = value
686 701 del form[k]
687 702
688 703 self.refresh()
689 704
690 705 expand_form(req.form)
691 706
692 707 m = os.path.join(self.templatepath, "map")
693 708 style = self.repo.ui.config("web", "style", "")
694 709 if req.form.has_key('style'):
695 710 style = req.form['style'][0]
696 711 if style:
697 712 b = os.path.basename("map-" + style)
698 713 p = os.path.join(self.templatepath, b)
699 714 if os.path.isfile(p):
700 715 m = p
701 716
702 717 port = req.env["SERVER_PORT"]
703 718 port = port != "80" and (":" + port) or ""
704 719 uri = req.env["REQUEST_URI"]
705 720 if "?" in uri:
706 721 uri = uri.split("?")[0]
707 722 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
708 723 if not self.reponame:
709 724 self.reponame = (self.repo.ui.config("web", "name")
710 725 or uri.strip('/') or self.repo.root)
711 726
712 727 self.t = templater.templater(m, templater.common_filters,
713 728 defaults={"url": url,
714 729 "repo": self.reponame,
715 730 "header": header,
716 731 "footer": footer,
732 "rawfileheader": rawfileheader,
717 733 })
718 734
719 735 if not req.form.has_key('cmd'):
720 736 req.form['cmd'] = [self.t.cache['default'],]
721 737
722 738 cmd = req.form['cmd'][0]
723 739
724 740 method = getattr(self, 'do_' + cmd, None)
725 741 if method:
726 742 method(req)
727 743 else:
728 744 req.write(self.t("error"))
729 745
730 746 def do_changelog(self, req):
731 747 hi = self.repo.changelog.count() - 1
732 748 if req.form.has_key('rev'):
733 749 hi = req.form['rev'][0]
734 750 try:
735 751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
736 752 except hg.RepoError:
737 753 req.write(self.search(hi)) # XXX redirect to 404 page?
738 754 return
739 755
740 756 req.write(self.changelog(hi))
741 757
742 758 def do_changeset(self, req):
743 759 req.write(self.changeset(req.form['node'][0]))
744 760
745 761 def do_manifest(self, req):
746 762 req.write(self.manifest(req.form['manifest'][0],
747 763 self.cleanpath(req.form['path'][0])))
748 764
749 765 def do_tags(self, req):
750 766 req.write(self.tags())
751 767
752 768 def do_summary(self, req):
753 769 req.write(self.summary())
754 770
755 771 def do_filediff(self, req):
756 772 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
757 773 req.form['node'][0]))
758 774
759 775 def do_file(self, req):
760 776 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
761 777 req.form['filenode'][0]))
762 778
763 779 def do_annotate(self, req):
764 780 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
765 781 req.form['filenode'][0]))
766 782
767 783 def do_filelog(self, req):
768 784 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
769 785 req.form['filenode'][0]))
770 786
771 787 def do_heads(self, req):
772 788 resp = " ".join(map(hex, self.repo.heads())) + "\n"
773 789 req.httphdr("application/mercurial-0.1", length=len(resp))
774 790 req.write(resp)
775 791
776 792 def do_branches(self, req):
777 793 nodes = []
778 794 if req.form.has_key('nodes'):
779 795 nodes = map(bin, req.form['nodes'][0].split(" "))
780 796 resp = cStringIO.StringIO()
781 797 for b in self.repo.branches(nodes):
782 798 resp.write(" ".join(map(hex, b)) + "\n")
783 799 resp = resp.getvalue()
784 800 req.httphdr("application/mercurial-0.1", length=len(resp))
785 801 req.write(resp)
786 802
787 803 def do_between(self, req):
788 804 nodes = []
789 805 if req.form.has_key('pairs'):
790 806 pairs = [map(bin, p.split("-"))
791 807 for p in req.form['pairs'][0].split(" ")]
792 808 resp = cStringIO.StringIO()
793 809 for b in self.repo.between(pairs):
794 810 resp.write(" ".join(map(hex, b)) + "\n")
795 811 resp = resp.getvalue()
796 812 req.httphdr("application/mercurial-0.1", length=len(resp))
797 813 req.write(resp)
798 814
799 815 def do_changegroup(self, req):
800 816 req.httphdr("application/mercurial-0.1")
801 817 nodes = []
802 818 if not self.allowpull:
803 819 return
804 820
805 821 if req.form.has_key('roots'):
806 822 nodes = map(bin, req.form['roots'][0].split(" "))
807 823
808 824 z = zlib.compressobj()
809 825 f = self.repo.changegroup(nodes, 'serve')
810 826 while 1:
811 827 chunk = f.read(4096)
812 828 if not chunk:
813 829 break
814 830 req.write(z.compress(chunk))
815 831
816 832 req.write(z.flush())
817 833
818 834 def do_archive(self, req):
819 835 changeset = self.repo.lookup(req.form['node'][0])
820 836 type_ = req.form['type'][0]
821 837 allowed = self.repo.ui.configlist("web", "allow_archive")
822 838 if (type_ in self.archives and (type_ in allowed or
823 839 self.repo.ui.configbool("web", "allow" + type_, False))):
824 840 self.archive(req, changeset, type_)
825 841 return
826 842
827 843 req.write(self.t("error"))
828 844
829 845 def do_static(self, req):
830 846 fname = req.form['file'][0]
831 847 static = self.repo.ui.config("web", "static",
832 848 os.path.join(self.templatepath,
833 849 "static"))
834 850 req.write(staticfile(static, fname, req)
835 851 or self.t("error", error="%r not found" % fname))
836 852
837 853 def do_capabilities(self, req):
838 854 resp = 'unbundle'
839 855 req.httphdr("application/mercurial-0.1", length=len(resp))
840 856 req.write(resp)
841 857
842 858 def check_perm(self, req, op, default):
843 859 '''check permission for operation based on user auth.
844 860 return true if op allowed, else false.
845 861 default is policy to use if no config given.'''
846 862
847 863 user = req.env.get('REMOTE_USER')
848 864
849 865 deny = self.repo.ui.configlist('web', 'deny_' + op)
850 866 if deny and (not user or deny == ['*'] or user in deny):
851 867 return False
852 868
853 869 allow = self.repo.ui.configlist('web', 'allow_' + op)
854 870 return (allow and (allow == ['*'] or user in allow)) or default
855 871
856 872 def do_unbundle(self, req):
857 873 def bail(response, headers={}):
858 874 length = int(req.env['CONTENT_LENGTH'])
859 875 for s in util.filechunkiter(req, limit=length):
860 876 # drain incoming bundle, else client will not see
861 877 # response when run outside cgi script
862 878 pass
863 879 req.httphdr("application/mercurial-0.1", headers=headers)
864 880 req.write('0\n')
865 881 req.write(response)
866 882
867 883 # require ssl by default, auth info cannot be sniffed and
868 884 # replayed
869 885 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
870 886 if ssl_req and not req.env.get('HTTPS'):
871 887 bail(_('ssl required\n'))
872 888 return
873 889
874 890 # do not allow push unless explicitly allowed
875 891 if not self.check_perm(req, 'push', False):
876 892 bail(_('push not authorized\n'),
877 893 headers={'status': '401 Unauthorized'})
878 894 return
879 895
880 896 req.httphdr("application/mercurial-0.1")
881 897
882 898 their_heads = req.form['heads'][0].split(' ')
883 899
884 900 def check_heads():
885 901 heads = map(hex, self.repo.heads())
886 902 return their_heads == [hex('force')] or their_heads == heads
887 903
888 904 # fail early if possible
889 905 if not check_heads():
890 906 bail(_('unsynced changes\n'))
891 907 return
892 908
893 909 # do not lock repo until all changegroup data is
894 910 # streamed. save to temporary file.
895 911
896 912 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
897 913 fp = os.fdopen(fd, 'wb+')
898 914 try:
899 915 length = int(req.env['CONTENT_LENGTH'])
900 916 for s in util.filechunkiter(req, limit=length):
901 917 fp.write(s)
902 918
903 919 lock = self.repo.lock()
904 920 try:
905 921 if not check_heads():
906 922 req.write('0\n')
907 923 req.write(_('unsynced changes\n'))
908 924 return
909 925
910 926 fp.seek(0)
911 927
912 928 # send addchangegroup output to client
913 929
914 930 old_stdout = sys.stdout
915 931 sys.stdout = cStringIO.StringIO()
916 932
917 933 try:
918 934 ret = self.repo.addchangegroup(fp, 'serve')
919 935 req.write('%d\n' % ret)
920 936 req.write(sys.stdout.getvalue())
921 937 finally:
922 938 sys.stdout = old_stdout
923 939 finally:
924 940 lock.release()
925 941 finally:
926 942 fp.close()
927 943 os.unlink(tempname)
@@ -1,157 +1,166 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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
10 10 from mercurial.demandload import demandload
11 11 demandload(globals(), "ConfigParser mimetools cStringIO")
12 12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
14 14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
15 15 from mercurial.i18n import gettext as _
16 16
17 17 # This is a stopgap
18 18 class hgwebdir(object):
19 19 def __init__(self, config):
20 20 def cleannames(items):
21 21 return [(name.strip(os.sep), path) for name, path in items]
22 22
23 23 self.motd = ""
24 24 self.repos_sorted = ('name', False)
25 25 if isinstance(config, (list, tuple)):
26 26 self.repos = cleannames(config)
27 27 self.repos_sorted = ('', False)
28 28 elif isinstance(config, dict):
29 29 self.repos = cleannames(config.items())
30 30 self.repos.sort()
31 31 else:
32 32 cp = ConfigParser.SafeConfigParser()
33 33 cp.read(config)
34 34 self.repos = []
35 35 if cp.has_section('web') and cp.has_option('web', 'motd'):
36 36 self.motd = cp.get('web', 'motd')
37 37 if cp.has_section('paths'):
38 38 self.repos.extend(cleannames(cp.items('paths')))
39 39 if cp.has_section('collections'):
40 40 for prefix, root in cp.items('collections'):
41 41 for path in util.walkrepos(root):
42 42 repo = os.path.normpath(path)
43 43 name = repo
44 44 if name.startswith(prefix):
45 45 name = name[len(prefix):]
46 46 self.repos.append((name.lstrip(os.sep), repo))
47 47 self.repos.sort()
48 48
49 def run(self, req):
49 def run(self):
50 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
51 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
52 import mercurial.hgweb.wsgicgi as wsgicgi
53 from request import wsgiapplication
54 def make_web_app():
55 return self
56 wsgicgi.launch(wsgiapplication(make_web_app))
57
58 def run_wsgi(self, req):
50 59 def header(**map):
51 60 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
52 61 msg = mimetools.Message(header_file, 0)
53 62 req.header(msg.items())
54 63 yield header_file.read()
55 64
56 65 def footer(**map):
57 66 yield tmpl("footer", motd=self.motd, **map)
58 67
59 68 m = os.path.join(templater.templatepath(), "map")
60 69 tmpl = templater.templater(m, templater.common_filters,
61 70 defaults={"header": header,
62 71 "footer": footer})
63 72
64 73 def archivelist(ui, nodeid, url):
65 74 allowed = ui.configlist("web", "allow_archive")
66 75 for i in ['zip', 'gz', 'bz2']:
67 76 if i in allowed or ui.configbool("web", "allow" + i):
68 77 yield {"type" : i, "node": nodeid, "url": url}
69 78
70 79 def entries(sortcolumn="", descending=False, **map):
71 80 rows = []
72 81 parity = 0
73 82 for name, path in self.repos:
74 83 u = ui.ui()
75 84 try:
76 85 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
77 86 except IOError:
78 87 pass
79 88 get = u.config
80 89
81 90 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
82 91 .replace("//", "/"))
83 92
84 93 # update time with local timezone
85 94 try:
86 95 d = (get_mtime(path), util.makedate()[1])
87 96 except OSError:
88 97 continue
89 98
90 99 contact = (get("ui", "username") or # preferred
91 100 get("web", "contact") or # deprecated
92 101 get("web", "author", "")) # also
93 102 description = get("web", "description", "")
94 103 name = get("web", "name", name)
95 104 row = dict(contact=contact or "unknown",
96 105 contact_sort=contact.upper() or "unknown",
97 106 name=name,
98 107 name_sort=name,
99 108 url=url,
100 109 description=description or "unknown",
101 110 description_sort=description.upper() or "unknown",
102 111 lastchange=d,
103 112 lastchange_sort=d[1]-d[0],
104 113 archives=archivelist(u, "tip", url))
105 114 if (not sortcolumn
106 115 or (sortcolumn, descending) == self.repos_sorted):
107 116 # fast path for unsorted output
108 117 row['parity'] = parity
109 118 parity = 1 - parity
110 119 yield row
111 120 else:
112 121 rows.append((row["%s_sort" % sortcolumn], row))
113 122 if rows:
114 123 rows.sort()
115 124 if descending:
116 125 rows.reverse()
117 126 for key, row in rows:
118 127 row['parity'] = parity
119 128 parity = 1 - parity
120 129 yield row
121 130
122 131 virtual = req.env.get("PATH_INFO", "").strip('/')
123 132 if virtual:
124 133 real = dict(self.repos).get(virtual)
125 134 if real:
126 135 try:
127 hgweb(real).run(req)
136 hgweb(real).run_wsgi(req)
128 137 except IOError, inst:
129 138 req.write(tmpl("error", error=inst.strerror))
130 139 except hg.RepoError, inst:
131 140 req.write(tmpl("error", error=str(inst)))
132 141 else:
133 142 req.write(tmpl("notfound", repo=virtual))
134 143 else:
135 144 if req.form.has_key('static'):
136 145 static = os.path.join(templater.templatepath(), "static")
137 146 fname = req.form['static'][0]
138 147 req.write(staticfile(static, fname, req)
139 148 or tmpl("error", error="%r not found" % fname))
140 149 else:
141 150 sortable = ["name", "description", "contact", "lastchange"]
142 151 sortcolumn, descending = self.repos_sorted
143 152 if req.form.has_key('sort'):
144 153 sortcolumn = req.form['sort'][0]
145 154 descending = sortcolumn.startswith('-')
146 155 if descending:
147 156 sortcolumn = sortcolumn[1:]
148 157 if sortcolumn not in sortable:
149 158 sortcolumn = ""
150 159
151 160 sort = [("sort_%s" % column,
152 161 "%s%s" % ((not descending and column == sortcolumn)
153 162 and "-" or "", column))
154 163 for column in sortable]
155 164 req.write(tmpl("index", entries=entries,
156 165 sortcolumn=sortcolumn, descending=descending,
157 166 **dict(sort)))
@@ -1,90 +1,90 b''
1 1 # hgweb/request.py - An http request from either CGI or the standalone server.
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 from mercurial.demandload import demandload
10 10 demandload(globals(), "socket sys cgi os errno")
11 11 from mercurial.i18n import gettext as _
12 12
13 13 class wsgiapplication(object):
14 14 def __init__(self, destmaker):
15 15 self.destmaker = destmaker
16 16
17 17 def __call__(self, wsgienv, start_response):
18 18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19 19
20 20 class _wsgioutputfile(object):
21 21 def __init__(self, request):
22 22 self.request = request
23 23
24 24 def write(self, data):
25 25 self.request.write(data)
26 26 def writelines(self, lines):
27 27 for line in lines:
28 28 self.write(line)
29 29 def flush(self):
30 30 return None
31 31 def close(self):
32 32 return None
33 33
34 34 class _wsgirequest(object):
35 35 def __init__(self, destination, wsgienv, start_response):
36 36 version = wsgienv['wsgi.version']
37 37 if (version < (1,0)) or (version >= (2, 0)):
38 38 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
39 39 % version)
40 40 self.inp = wsgienv['wsgi.input']
41 41 self.out = _wsgioutputfile(self)
42 42 self.server_write = None
43 43 self.err = wsgienv['wsgi.errors']
44 44 self.threaded = wsgienv['wsgi.multithread']
45 45 self.multiprocess = wsgienv['wsgi.multiprocess']
46 46 self.run_once = wsgienv['wsgi.run_once']
47 47 self.env = wsgienv
48 48 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
49 49 self.start_response = start_response
50 50 self.headers = []
51 destination.run(self)
51 destination.run_wsgi(self)
52 52
53 53 def __iter__(self):
54 54 return iter([])
55 55
56 56 def read(self, count=-1):
57 57 return self.inp.read(count)
58 58
59 59 def write(self, *things):
60 60 for thing in things:
61 61 if hasattr(thing, "__iter__"):
62 62 for part in thing:
63 63 self.write(part)
64 64 else:
65 65 thing = str(thing)
66 66 if self.server_write is None:
67 67 if not self.headers:
68 68 raise RuntimeError("request.write called before headers sent (%s)." % thing)
69 69 self.server_write = self.start_response('200 Script output follows',
70 70 self.headers)
71 71 self.start_response = None
72 72 self.headers = None
73 73 try:
74 74 self.server_write(thing)
75 75 except socket.error, inst:
76 76 if inst[0] != errno.ECONNRESET:
77 77 raise
78 78
79 79 def header(self, headers=[('Content-type','text/html')]):
80 80 self.headers.extend(headers)
81 81
82 82 def httphdr(self, type, filename=None, length=0, headers={}):
83 83 headers = headers.items()
84 84 headers.append(('Content-type', type))
85 85 if filename:
86 86 headers.append(('Content-disposition', 'attachment; filename=%s' %
87 87 filename))
88 88 if length:
89 89 headers.append(('Content-length', str(length)))
90 90 self.header(headers)
@@ -1,524 +1,524 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12 12
13 13 esctable = {
14 14 '\\': '\\',
15 15 'r': '\r',
16 16 't': '\t',
17 17 'n': '\n',
18 18 'v': '\v',
19 19 }
20 20
21 21 def parsestring(s, quoted=True):
22 22 '''parse a string using simple c-like syntax.
23 23 string must be in quotes if quoted is True.'''
24 24 fp = cStringIO.StringIO()
25 25 if quoted:
26 26 first = s[0]
27 27 if len(s) < 2: raise SyntaxError(_('string too short'))
28 28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
29 29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
30 30 s = s[1:-1]
31 31 escape = False
32 32 for c in s:
33 33 if escape:
34 34 fp.write(esctable.get(c, c))
35 35 escape = False
36 36 elif c == '\\': escape = True
37 37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
38 38 else: fp.write(c)
39 39 if escape: raise SyntaxError(_('unterminated escape'))
40 40 return fp.getvalue()
41 41
42 42 class templater(object):
43 43 '''template expansion engine.
44 44
45 45 template expansion works like this. a map file contains key=value
46 46 pairs. if value is quoted, it is treated as string. otherwise, it
47 47 is treated as name of template file.
48 48
49 49 templater is asked to expand a key in map. it looks up key, and
50 50 looks for atrings like this: {foo}. it expands {foo} by looking up
51 51 foo in map, and substituting it. expansion is recursive: it stops
52 52 when there is no more {foo} to replace.
53 53
54 54 expansion also allows formatting and filtering.
55 55
56 56 format uses key to expand each item in list. syntax is
57 57 {key%format}.
58 58
59 59 filter uses function to transform value. syntax is
60 60 {key|filter1|filter2|...}.'''
61 61
62 62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
63 63 '''set up template engine.
64 64 mapfile is name of file to read map definitions from.
65 65 filters is dict of functions. each transforms a value into another.
66 66 defaults is dict of default map definitions.'''
67 67 self.mapfile = mapfile or 'template'
68 68 self.cache = cache.copy()
69 69 self.map = {}
70 70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
71 71 self.filters = filters
72 72 self.defaults = defaults
73 73
74 74 if not mapfile:
75 75 return
76 76 i = 0
77 77 for l in file(mapfile):
78 78 l = l.strip()
79 79 i += 1
80 80 if not l or l[0] in '#;': continue
81 81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
82 82 if m:
83 83 key, val = m.groups()
84 84 if val[0] in "'\"":
85 85 try:
86 86 self.cache[key] = parsestring(val)
87 87 except SyntaxError, inst:
88 88 raise SyntaxError('%s:%s: %s' %
89 89 (mapfile, i, inst.args[0]))
90 90 else:
91 91 self.map[key] = os.path.join(self.base, val)
92 92 else:
93 93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
94 94
95 95 def __contains__(self, key):
96 96 return key in self.cache
97 97
98 98 def __call__(self, t, **map):
99 99 '''perform expansion.
100 100 t is name of map element to expand.
101 101 map is added elements to use during expansion.'''
102 102 m = self.defaults.copy()
103 103 m.update(map)
104 104 try:
105 105 tmpl = self.cache[t]
106 106 except KeyError:
107 107 try:
108 108 tmpl = self.cache[t] = file(self.map[t]).read()
109 109 except IOError, inst:
110 110 raise IOError(inst.args[0], _('template file %s: %s') %
111 111 (self.map[t], inst.args[1]))
112 112 return self.template(tmpl, self.filters, **m)
113 113
114 114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
115 115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
116 116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
117 117
118 118 def template(self, tmpl, filters={}, **map):
119 119 lm = map.copy()
120 120 while tmpl:
121 121 m = self.template_re.search(tmpl)
122 122 if m:
123 123 start, end = m.span(0)
124 124 s, e = tmpl[start], tmpl[end - 1]
125 125 key = m.group(1)
126 126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
127 127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
128 128 (s, e, key))
129 129 if start:
130 130 yield tmpl[:start]
131 131 v = map.get(key, "")
132 132 v = callable(v) and v(**map) or v
133 133
134 134 format = m.group(2)
135 135 fl = m.group(4)
136 136
137 137 if format:
138 138 q = v.__iter__
139 139 for i in q():
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[end:]
151 151 else:
152 152 yield tmpl
153 153 break
154 154
155 155 agescales = [("second", 1),
156 156 ("minute", 60),
157 157 ("hour", 3600),
158 158 ("day", 3600 * 24),
159 159 ("week", 3600 * 24 * 7),
160 160 ("month", 3600 * 24 * 30),
161 161 ("year", 3600 * 24 * 365)]
162 162
163 163 agescales.reverse()
164 164
165 165 def age(date):
166 166 '''turn a (timestamp, tzoff) tuple into an age string.'''
167 167
168 168 def plural(t, c):
169 169 if c == 1:
170 170 return t
171 171 return t + "s"
172 172 def fmt(t, c):
173 173 return "%d %s" % (c, plural(t, c))
174 174
175 175 now = time.time()
176 176 then = date[0]
177 177 delta = max(1, int(now - then))
178 178
179 179 for t, s in agescales:
180 180 n = delta / s
181 181 if n >= 2 or s == 1:
182 182 return fmt(t, n)
183 183
184 184 def stringify(thing):
185 185 '''turn nested template iterator into string.'''
186 186 cs = cStringIO.StringIO()
187 187 def walk(things):
188 188 for t in things:
189 189 if hasattr(t, '__iter__'):
190 190 walk(t)
191 191 else:
192 192 cs.write(t)
193 193 walk(thing)
194 194 return cs.getvalue()
195 195
196 196 para_re = None
197 197 space_re = None
198 198
199 199 def fill(text, width):
200 200 '''fill many paragraphs.'''
201 201 global para_re, space_re
202 202 if para_re is None:
203 203 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
204 204 space_re = re.compile(r' +')
205
205
206 206 def findparas():
207 207 start = 0
208 208 while True:
209 209 m = para_re.search(text, start)
210 210 if not m:
211 211 w = len(text)
212 212 while w > start and text[w-1].isspace(): w -= 1
213 213 yield text[start:w], text[w:]
214 214 break
215 215 yield text[start:m.start(0)], m.group(1)
216 216 start = m.end(1)
217 217
218 218 fp = cStringIO.StringIO()
219 219 for para, rest in findparas():
220 220 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
221 221 fp.write(rest)
222 222 return fp.getvalue()
223 223
224 224 def isodate(date):
225 225 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
226 226 return util.datestr(date, format='%Y-%m-%d %H:%M')
227 227
228 228 def hgdate(date):
229 229 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
230 230 return "%d %d" % date
231 231
232 232 def nl2br(text):
233 233 '''replace raw newlines with xhtml line breaks.'''
234 234 return text.replace('\n', '<br/>\n')
235 235
236 236 def obfuscate(text):
237 237 return ''.join(['&#%d;' % ord(c) for c in text])
238 238
239 239 def domain(author):
240 240 '''get domain of author, or empty string if none.'''
241 241 f = author.find('@')
242 242 if f == -1: return ''
243 243 author = author[f+1:]
244 244 f = author.find('>')
245 245 if f >= 0: author = author[:f]
246 246 return author
247 247
248 248 def email(author):
249 249 '''get email of author.'''
250 250 r = author.find('>')
251 251 if r == -1: r = None
252 252 return author[author.find('<')+1:r]
253 253
254 254 def person(author):
255 255 '''get name of author, or else username.'''
256 256 f = author.find('<')
257 257 if f == -1: return util.shortuser(author)
258 258 return author[:f].rstrip()
259 259
260 260 def shortdate(date):
261 261 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
262 262 return util.datestr(date, format='%Y-%m-%d', timezone=False)
263 263
264 264 def indent(text, prefix):
265 265 '''indent each non-empty line of text after first with prefix.'''
266 266 fp = cStringIO.StringIO()
267 267 lines = text.splitlines()
268 268 num_lines = len(lines)
269 269 for i in xrange(num_lines):
270 270 l = lines[i]
271 271 if i and l.strip(): fp.write(prefix)
272 272 fp.write(l)
273 273 if i < num_lines - 1 or text.endswith('\n'):
274 274 fp.write('\n')
275 275 return fp.getvalue()
276 276
277 277 common_filters = {
278 278 "addbreaks": nl2br,
279 279 "basename": os.path.basename,
280 280 "age": age,
281 281 "date": lambda x: util.datestr(x),
282 282 "domain": domain,
283 283 "email": email,
284 284 "escape": lambda x: cgi.escape(x, True),
285 285 "fill68": lambda x: fill(x, width=68),
286 286 "fill76": lambda x: fill(x, width=76),
287 287 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
288 288 "tabindent": lambda x: indent(x, '\t'),
289 289 "hgdate": hgdate,
290 290 "isodate": isodate,
291 291 "obfuscate": obfuscate,
292 292 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
293 293 "person": person,
294 294 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
295 295 "short": lambda x: x[:12],
296 296 "shortdate": shortdate,
297 297 "stringify": stringify,
298 298 "strip": lambda x: x.strip(),
299 299 "urlescape": lambda x: urllib.quote(x),
300 300 "user": lambda x: util.shortuser(x),
301 301 }
302 302
303 303 def templatepath(name=None):
304 304 '''return location of template file or directory (if no name).
305 305 returns None if not found.'''
306 306
307 307 # executable version (py2exe) doesn't support __file__
308 308 if hasattr(sys, 'frozen'):
309 309 module = sys.executable
310 310 else:
311 311 module = __file__
312 312 for f in 'templates', '../templates':
313 313 fl = f.split('/')
314 314 if name: fl.append(name)
315 315 p = os.path.join(os.path.dirname(module), *fl)
316 316 if (name and os.path.exists(p)) or os.path.isdir(p):
317 317 return os.path.normpath(p)
318 318
319 319 class changeset_templater(object):
320 320 '''format changeset information.'''
321 321
322 322 def __init__(self, ui, repo, mapfile, dest=None):
323 323 self.t = templater(mapfile, common_filters,
324 324 cache={'parent': '{rev}:{node|short} ',
325 325 'manifest': '{rev}:{node|short}'})
326 326 self.ui = ui
327 327 self.dest = dest
328 328 self.repo = repo
329 329
330 330 def use_template(self, t):
331 331 '''set template string to use'''
332 332 self.t.cache['changeset'] = t
333 333
334 334 def write(self, thing, header=False):
335 335 '''write expanded template.
336 336 uses in-order recursive traverse of iterators.'''
337 337 dest = self.dest or self.ui
338 338 for t in thing:
339 339 if hasattr(t, '__iter__'):
340 340 self.write(t, header=header)
341 341 elif header:
342 342 dest.write_header(t)
343 343 else:
344 344 dest.write(t)
345 345
346 346 def write_header(self, thing):
347 347 self.write(thing, header=True)
348 348
349 349 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
350 350 **props):
351 351 '''show a single changeset or file revision'''
352 352 log = self.repo.changelog
353 353 if changenode is None:
354 354 changenode = log.node(rev)
355 355 elif not rev:
356 356 rev = log.rev(changenode)
357 357 if changes is None:
358 358 changes = log.read(changenode)
359 359
360 360 def showlist(name, values, plural=None, **args):
361 361 '''expand set of values.
362 362 name is name of key in template map.
363 363 values is list of strings or dicts.
364 364 plural is plural of name, if not simply name + 's'.
365 365
366 366 expansion works like this, given name 'foo'.
367 367
368 368 if values is empty, expand 'no_foos'.
369 369
370 370 if 'foo' not in template map, return values as a string,
371 371 joined by space.
372 372
373 373 expand 'start_foos'.
374 374
375 375 for each value, expand 'foo'. if 'last_foo' in template
376 376 map, expand it instead of 'foo' for last key.
377 377
378 378 expand 'end_foos'.
379 379 '''
380 380 if plural: names = plural
381 381 else: names = name + 's'
382 382 if not values:
383 383 noname = 'no_' + names
384 384 if noname in self.t:
385 385 yield self.t(noname, **args)
386 386 return
387 387 if name not in self.t:
388 388 if isinstance(values[0], str):
389 389 yield ' '.join(values)
390 390 else:
391 391 for v in values:
392 392 yield dict(v, **args)
393 393 return
394 394 startname = 'start_' + names
395 395 if startname in self.t:
396 396 yield self.t(startname, **args)
397 397 vargs = args.copy()
398 398 def one(v, tag=name):
399 399 try:
400 400 vargs.update(v)
401 401 except (AttributeError, ValueError):
402 402 try:
403 403 for a, b in v:
404 404 vargs[a] = b
405 405 except ValueError:
406 406 vargs[name] = v
407 407 return self.t(tag, **vargs)
408 408 lastname = 'last_' + name
409 409 if lastname in self.t:
410 410 last = values.pop()
411 411 else:
412 412 last = None
413 413 for v in values:
414 414 yield one(v)
415 415 if last is not None:
416 416 yield one(last, tag=lastname)
417 417 endname = 'end_' + names
418 418 if endname in self.t:
419 419 yield self.t(endname, **args)
420 420
421 421 if brinfo:
422 422 def showbranches(**args):
423 423 if changenode in brinfo:
424 424 for x in showlist('branch', brinfo[changenode],
425 425 plural='branches', **args):
426 426 yield x
427 427 else:
428 428 showbranches = ''
429 429
430 430 if self.ui.debugflag:
431 431 def showmanifest(**args):
432 432 args = args.copy()
433 433 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
434 434 node=hex(changes[0])))
435 435 yield self.t('manifest', **args)
436 436 else:
437 437 showmanifest = ''
438 438
439 439 def showparents(**args):
440 440 parents = [[('rev', log.rev(p)), ('node', hex(p))]
441 441 for p in log.parents(changenode)
442 442 if self.ui.debugflag or p != nullid]
443 443 if (not self.ui.debugflag and len(parents) == 1 and
444 444 parents[0][0][1] == rev - 1):
445 445 return
446 446 for x in showlist('parent', parents, **args):
447 447 yield x
448 448
449 449 def showtags(**args):
450 450 for x in showlist('tag', self.repo.nodetags(changenode), **args):
451 451 yield x
452 452
453 453 if self.ui.debugflag:
454 454 files = self.repo.changes(log.parents(changenode)[0], changenode)
455 455 def showfiles(**args):
456 456 for x in showlist('file', files[0], **args): yield x
457 457 def showadds(**args):
458 458 for x in showlist('file_add', files[1], **args): yield x
459 459 def showdels(**args):
460 460 for x in showlist('file_del', files[2], **args): yield x
461 461 else:
462 462 def showfiles(**args):
463 463 for x in showlist('file', changes[3], **args): yield x
464 464 showadds = ''
465 465 showdels = ''
466 466
467 467 defprops = {
468 468 'author': changes[1],
469 469 'branches': showbranches,
470 470 'date': changes[2],
471 471 'desc': changes[4],
472 472 'file_adds': showadds,
473 473 'file_dels': showdels,
474 474 'files': showfiles,
475 475 'manifest': showmanifest,
476 476 'node': hex(changenode),
477 477 'parents': showparents,
478 478 'rev': rev,
479 479 'tags': showtags,
480 480 }
481 481 props = props.copy()
482 482 props.update(defprops)
483 483
484 484 try:
485 485 if self.ui.debugflag and 'header_debug' in self.t:
486 486 key = 'header_debug'
487 487 elif self.ui.quiet and 'header_quiet' in self.t:
488 488 key = 'header_quiet'
489 489 elif self.ui.verbose and 'header_verbose' in self.t:
490 490 key = 'header_verbose'
491 491 elif 'header' in self.t:
492 492 key = 'header'
493 493 else:
494 494 key = ''
495 495 if key:
496 496 self.write_header(self.t(key, **props))
497 497 if self.ui.debugflag and 'changeset_debug' in self.t:
498 498 key = 'changeset_debug'
499 499 elif self.ui.quiet and 'changeset_quiet' in self.t:
500 500 key = 'changeset_quiet'
501 501 elif self.ui.verbose and 'changeset_verbose' in self.t:
502 502 key = 'changeset_verbose'
503 503 else:
504 504 key = 'changeset'
505 505 self.write(self.t(key, **props))
506 506 except KeyError, inst:
507 507 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
508 508 inst.args[0]))
509 509 except SyntaxError, inst:
510 510 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
511 511
512 512 class stringio(object):
513 513 '''wrap cStringIO for use by changeset_templater.'''
514 514 def __init__(self):
515 515 self.fp = cStringIO.StringIO()
516 516
517 517 def write(self, *args):
518 518 for a in args:
519 519 self.fp.write(a)
520 520
521 521 write_header = write
522 522
523 523 def __getattr__(self, key):
524 524 return getattr(self.fp, key)
@@ -1,16 +1,16 b''
1 1 header = header-raw.tmpl
2 2 footer = ''
3 3 changeset = changeset-raw.tmpl
4 4 difflineplus = '#line#'
5 5 difflineminus = '#line#'
6 6 difflineat = '#line#'
7 7 diffline = '#line#'
8 8 changesetparent = '# Parent #node#'
9 9 changesetchild = '# Child #node#'
10 10 filenodelink = ''
11 filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#'
11 filerevision = '#rawfileheader##raw#'
12 12 fileline = '#line#'
13 13 diffblock = '#lines#'
14 14 filediff = filediff-raw.tmpl
15 15 fileannotate = fileannotate-raw.tmpl
16 16 annotateline = '#author#@#rev#: #line#'
General Comments 0
You need to be logged in to leave comments. Login now