##// END OF EJS Templates
kill some trailing spaces
Dirkjan Ochtman -
r7434:cf7741aa default
parent child Browse files
Show More
@@ -1,125 +1,125
1 1 # __init__.py - inotify-based status acceleration for Linux
2 2 #
3 3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.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 '''inotify-based status acceleration for Linux systems
10 10 '''
11 11
12 12 # todo: socket permissions
13 13
14 14 from mercurial.i18n import _
15 15 from mercurial import cmdutil, util
16 16 import client, errno, os, server, socket
17 17 from weakref import proxy
18 18
19 19 def serve(ui, repo, **opts):
20 20 '''start an inotify server for this repository'''
21 21 timeout = opts.get('timeout')
22 22 if timeout:
23 23 timeout = float(timeout) * 1e3
24 24
25 25 class service:
26 26 def init(self):
27 27 try:
28 28 self.master = server.Master(ui, repo, timeout)
29 29 except server.AlreadyStartedException, inst:
30 30 raise util.Abort(str(inst))
31 31
32 32 def run(self):
33 33 try:
34 34 self.master.run()
35 35 finally:
36 36 self.master.shutdown()
37 37
38 38 service = service()
39 39 cmdutil.service(opts, initfn=service.init, runfn=service.run)
40 40
41 41 def reposetup(ui, repo):
42 42 if not repo.local():
43 43 return
44 44
45 45 # XXX: weakref until hg stops relying on __del__
46 46 repo = proxy(repo)
47 47
48 48 class inotifydirstate(repo.dirstate.__class__):
49 49 # Set to True if we're the inotify server, so we don't attempt
50 50 # to recurse.
51 51 inotifyserver = False
52 52
53 53 def status(self, match, ignored, clean, unknown=True):
54 54 files = match.files()
55 55 if '.' in files:
56 files = []
56 files = []
57 57 try:
58 58 if not ignored and not self.inotifyserver:
59 59 result = client.query(ui, repo, files, match, False,
60 60 clean, unknown)
61 61 if ui.config('inotify', 'debug'):
62 62 r2 = super(inotifydirstate, self).status(
63 63 match, False, clean, unknown)
64 64 for c,a,b in zip('LMARDUIC', result, r2):
65 65 for f in a:
66 66 if f not in b:
67 67 ui.warn('*** inotify: %s +%s\n' % (c, f))
68 68 for f in b:
69 69 if f not in a:
70 70 ui.warn('*** inotify: %s -%s\n' % (c, f))
71 71 result = r2
72 72
73 73 if result is not None:
74 74 return result
75 75 except (OSError, socket.error), err:
76 76 if err[0] == errno.ECONNREFUSED:
77 77 ui.warn(_('(found dead inotify server socket; '
78 78 'removing it)\n'))
79 79 os.unlink(repo.join('inotify.sock'))
80 80 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and \
81 81 ui.configbool('inotify', 'autostart', True):
82 82 query = None
83 83 ui.debug(_('(starting inotify server)\n'))
84 84 try:
85 85 try:
86 86 server.start(ui, repo)
87 87 query = client.query
88 88 except server.AlreadyStartedException, inst:
89 89 # another process may have started its own
90 90 # inotify server while this one was starting.
91 91 ui.debug(str(inst))
92 92 query = client.query
93 93 except Exception, inst:
94 94 ui.warn(_('could not start inotify server: '
95 95 '%s\n') % inst)
96 96 if query:
97 97 try:
98 98 return query(ui, repo, files or [], match,
99 99 ignored, clean, unknown)
100 100 except socket.error, err:
101 101 ui.warn(_('could not talk to new inotify '
102 102 'server: %s\n') % err[-1])
103 103 elif err[0] == errno.ENOENT:
104 104 ui.warn(_('(inotify server not running)\n'))
105 105 else:
106 106 ui.warn(_('failed to contact inotify server: %s\n')
107 107 % err[-1])
108 108 ui.print_exc()
109 109 # replace by old status function
110 110 self.status = super(inotifydirstate, self).status
111 111
112 112 return super(inotifydirstate, self).status(
113 113 match, ignored, clean, unknown)
114 114
115 115 repo.dirstate.__class__ = inotifydirstate
116 116
117 117 cmdtable = {
118 118 '^inserve':
119 119 (serve,
120 120 [('d', 'daemon', None, _('run server in background')),
121 121 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
122 122 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
123 123 ('', 'pid-file', '', _('name of file to write process ID to'))],
124 124 _('hg inserve [OPT]...')),
125 125 }
@@ -1,663 +1,663
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 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 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import revlog, archival, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 12 from mercurial.util import binary, datestr
13 13 from mercurial.repo import RepoError
14 14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 15 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
16 16 from mercurial import graphmod, util
17 17
18 18 # __all__ is populated with the allowed commands. Be sure to add to it if
19 19 # you're adding a new command, or the new command won't work.
20 20
21 21 __all__ = [
22 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
24 24 'archive', 'static', 'graph',
25 25 ]
26 26
27 27 def log(web, req, tmpl):
28 28 if 'file' in req.form and req.form['file'][0]:
29 29 return filelog(web, req, tmpl)
30 30 else:
31 31 return changelog(web, req, tmpl)
32 32
33 33 def rawfile(web, req, tmpl):
34 34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
35 35 if not path:
36 36 content = manifest(web, req, tmpl)
37 37 req.respond(HTTP_OK, web.ctype)
38 38 return content
39 39
40 40 try:
41 41 fctx = webutil.filectx(web.repo, req)
42 42 except revlog.LookupError, inst:
43 43 try:
44 44 content = manifest(web, req, tmpl)
45 45 req.respond(HTTP_OK, web.ctype)
46 46 return content
47 47 except ErrorResponse:
48 48 raise inst
49 49
50 50 path = fctx.path()
51 51 text = fctx.data()
52 52 mt = mimetypes.guess_type(path)[0]
53 53 if mt is None:
54 54 mt = binary(text) and 'application/octet-stream' or 'text/plain'
55 55
56 56 req.respond(HTTP_OK, mt, path, len(text))
57 57 return [text]
58 58
59 59 def _filerevision(web, tmpl, fctx):
60 60 f = fctx.path()
61 61 text = fctx.data()
62 62 parity = paritygen(web.stripecount)
63 63
64 64 if binary(text):
65 65 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
66 66 text = '(binary:%s)' % mt
67 67
68 68 def lines():
69 69 for lineno, t in enumerate(text.splitlines(1)):
70 70 yield {"line": t,
71 71 "lineid": "l%d" % (lineno + 1),
72 72 "linenumber": "% 6d" % (lineno + 1),
73 73 "parity": parity.next()}
74 74
75 75 return tmpl("filerevision",
76 76 file=f,
77 77 path=webutil.up(f),
78 78 text=lines(),
79 79 rev=fctx.rev(),
80 80 node=hex(fctx.node()),
81 81 author=fctx.user(),
82 82 date=fctx.date(),
83 83 desc=fctx.description(),
84 84 branch=webutil.nodebranchnodefault(fctx),
85 85 parent=webutil.siblings(fctx.parents()),
86 86 child=webutil.siblings(fctx.children()),
87 87 rename=webutil.renamelink(fctx),
88 88 permissions=fctx.manifest().flags(f))
89 89
90 90 def file(web, req, tmpl):
91 91 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
92 92 if not path:
93 93 return manifest(web, req, tmpl)
94 94 try:
95 95 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
96 96 except revlog.LookupError, inst:
97 97 try:
98 98 return manifest(web, req, tmpl)
99 99 except ErrorResponse:
100 100 raise inst
101 101
102 102 def _search(web, tmpl, query):
103 103
104 104 def changelist(**map):
105 105 cl = web.repo.changelog
106 106 count = 0
107 107 qw = query.lower().split()
108 108
109 109 def revgen():
110 110 for i in xrange(len(cl) - 1, 0, -100):
111 111 l = []
112 112 for j in xrange(max(0, i - 100), i + 1):
113 113 ctx = web.repo[j]
114 114 l.append(ctx)
115 115 l.reverse()
116 116 for e in l:
117 117 yield e
118 118
119 119 for ctx in revgen():
120 120 miss = 0
121 121 for q in qw:
122 122 if not (q in ctx.user().lower() or
123 123 q in ctx.description().lower() or
124 124 q in " ".join(ctx.files()).lower()):
125 125 miss = 1
126 126 break
127 127 if miss:
128 128 continue
129 129
130 130 count += 1
131 131 n = ctx.node()
132 132 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
133 133 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
134 134
135 135 yield tmpl('searchentry',
136 136 parity=parity.next(),
137 137 author=ctx.user(),
138 138 parent=webutil.siblings(ctx.parents()),
139 139 child=webutil.siblings(ctx.children()),
140 140 changelogtag=showtags,
141 141 desc=ctx.description(),
142 142 date=ctx.date(),
143 143 files=files,
144 144 rev=ctx.rev(),
145 145 node=hex(n),
146 146 tags=webutil.nodetagsdict(web.repo, n),
147 147 inbranch=webutil.nodeinbranch(web.repo, ctx),
148 148 branches=webutil.nodebranchdict(web.repo, ctx))
149 149
150 150 if count >= web.maxchanges:
151 151 break
152 152
153 153 cl = web.repo.changelog
154 154 parity = paritygen(web.stripecount)
155 155
156 156 return tmpl('search',
157 157 query=query,
158 158 node=hex(cl.tip()),
159 159 entries=changelist,
160 160 archives=web.archivelist("tip"))
161 161
162 162 def changelog(web, req, tmpl, shortlog = False):
163 163 if 'node' in req.form:
164 164 ctx = webutil.changectx(web.repo, req)
165 165 else:
166 166 if 'rev' in req.form:
167 167 hi = req.form['rev'][0]
168 168 else:
169 169 hi = len(web.repo) - 1
170 170 try:
171 171 ctx = web.repo[hi]
172 172 except RepoError:
173 173 return _search(web, tmpl, hi) # XXX redirect to 404 page?
174 174
175 175 def changelist(limit=0, **map):
176 176 cl = web.repo.changelog
177 177 l = [] # build a list in forward order for efficiency
178 178 for i in xrange(start, end):
179 179 ctx = web.repo[i]
180 180 n = ctx.node()
181 181 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
182 182 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
183 183
184 184 l.insert(0, {"parity": parity.next(),
185 185 "author": ctx.user(),
186 186 "parent": webutil.siblings(ctx.parents(), i - 1),
187 187 "child": webutil.siblings(ctx.children(), i + 1),
188 188 "changelogtag": showtags,
189 189 "desc": ctx.description(),
190 190 "date": ctx.date(),
191 191 "files": files,
192 192 "rev": i,
193 193 "node": hex(n),
194 194 "tags": webutil.nodetagsdict(web.repo, n),
195 195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
196 196 "branches": webutil.nodebranchdict(web.repo, ctx)
197 197 })
198 198
199 199 if limit > 0:
200 200 l = l[:limit]
201 201
202 202 for e in l:
203 203 yield e
204 204
205 205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
206 206 cl = web.repo.changelog
207 207 count = len(cl)
208 208 pos = ctx.rev()
209 209 start = max(0, pos - maxchanges + 1)
210 210 end = min(count, start + maxchanges)
211 211 pos = end - 1
212 212 parity = paritygen(web.stripecount, offset=start-end)
213 213
214 214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
215 215
216 216 return tmpl(shortlog and 'shortlog' or 'changelog',
217 217 changenav=changenav,
218 218 node=hex(ctx.node()),
219 219 rev=pos, changesets=count,
220 220 entries=lambda **x: changelist(limit=0,**x),
221 221 latestentry=lambda **x: changelist(limit=1,**x),
222 222 archives=web.archivelist("tip"))
223 223
224 224 def shortlog(web, req, tmpl):
225 225 return changelog(web, req, tmpl, shortlog = True)
226 226
227 227 def changeset(web, req, tmpl):
228 228 ctx = webutil.changectx(web.repo, req)
229 229 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
230 230 showbranch = webutil.nodebranchnodefault(ctx)
231 231 parents = ctx.parents()
232 232
233 233 files = []
234 234 parity = paritygen(web.stripecount)
235 235 for f in ctx.files():
236 236 template = f in ctx and 'filenodelink' or 'filenolink'
237 237 files.append(tmpl(template,
238 238 node=ctx.hex(), file=f,
239 239 parity=parity.next()))
240 240
241 241 parity = paritygen(web.stripecount)
242 242 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity)
243 243 return tmpl('changeset',
244 244 diff=diffs,
245 245 rev=ctx.rev(),
246 246 node=ctx.hex(),
247 247 parent=webutil.siblings(parents),
248 248 child=webutil.siblings(ctx.children()),
249 249 changesettag=showtags,
250 250 changesetbranch=showbranch,
251 251 author=ctx.user(),
252 252 desc=ctx.description(),
253 253 date=ctx.date(),
254 254 files=files,
255 255 archives=web.archivelist(ctx.hex()),
256 256 tags=webutil.nodetagsdict(web.repo, ctx.node()),
257 257 branch=webutil.nodebranchnodefault(ctx),
258 258 inbranch=webutil.nodeinbranch(web.repo, ctx),
259 259 branches=webutil.nodebranchdict(web.repo, ctx))
260 260
261 261 rev = changeset
262 262
263 263 def manifest(web, req, tmpl):
264 264 ctx = webutil.changectx(web.repo, req)
265 265 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
266 266 mf = ctx.manifest()
267 267 node = ctx.node()
268 268
269 269 files = {}
270 270 dirs = {}
271 271 parity = paritygen(web.stripecount)
272 272
273 273 if path and path[-1] != "/":
274 274 path += "/"
275 275 l = len(path)
276 276 abspath = "/" + path
277 277
278 278 for f, n in mf.items():
279 279 if f[:l] != path:
280 280 continue
281 281 remain = f[l:]
282 282 elements = remain.split('/')
283 283 if len(elements) == 1:
284 284 files[remain] = f
285 285 else:
286 286 h = dirs # need to retain ref to dirs (root)
287 287 for elem in elements[0:-1]:
288 288 if elem not in h:
289 289 h[elem] = {}
290 290 h = h[elem]
291 291 if len(h) > 1:
292 292 break
293 293 h[None] = None # denotes files present
294 294
295 295 if not files and not dirs:
296 296 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
297 297
298 298 def filelist(**map):
299 299 for f in util.sort(files):
300 300 full = files[f]
301 301
302 302 fctx = ctx.filectx(full)
303 303 yield {"file": full,
304 304 "parity": parity.next(),
305 305 "basename": f,
306 306 "date": fctx.date(),
307 307 "size": fctx.size(),
308 308 "permissions": mf.flags(full)}
309 309
310 310 def dirlist(**map):
311 311 for d in util.sort(dirs):
312 312
313 313 emptydirs = []
314 314 h = dirs[d]
315 315 while isinstance(h, dict) and len(h) == 1:
316 316 k,v = h.items()[0]
317 317 if v:
318 318 emptydirs.append(k)
319 319 h = v
320 320
321 321 path = "%s%s" % (abspath, d)
322 322 yield {"parity": parity.next(),
323 323 "path": path,
324 324 "emptydirs": "/".join(emptydirs),
325 325 "basename": d}
326 326
327 327 return tmpl("manifest",
328 328 rev=ctx.rev(),
329 329 node=hex(node),
330 330 path=abspath,
331 331 up=webutil.up(abspath),
332 332 upparity=parity.next(),
333 333 fentries=filelist,
334 334 dentries=dirlist,
335 335 archives=web.archivelist(hex(node)),
336 336 tags=webutil.nodetagsdict(web.repo, node),
337 337 inbranch=webutil.nodeinbranch(web.repo, ctx),
338 338 branches=webutil.nodebranchdict(web.repo, ctx))
339 339
340 340 def tags(web, req, tmpl):
341 341 i = web.repo.tagslist()
342 342 i.reverse()
343 343 parity = paritygen(web.stripecount)
344 344
345 345 def entries(notip=False,limit=0, **map):
346 346 count = 0
347 347 for k, n in i:
348 348 if notip and k == "tip":
349 349 continue
350 350 if limit > 0 and count >= limit:
351 351 continue
352 352 count = count + 1
353 353 yield {"parity": parity.next(),
354 354 "tag": k,
355 355 "date": web.repo[n].date(),
356 356 "node": hex(n)}
357 357
358 358 return tmpl("tags",
359 359 node=hex(web.repo.changelog.tip()),
360 360 entries=lambda **x: entries(False,0, **x),
361 361 entriesnotip=lambda **x: entries(True,0, **x),
362 362 latestentry=lambda **x: entries(True,1, **x))
363 363
364 364 def summary(web, req, tmpl):
365 365 i = web.repo.tagslist()
366 366 i.reverse()
367 367
368 368 def tagentries(**map):
369 369 parity = paritygen(web.stripecount)
370 370 count = 0
371 371 for k, n in i:
372 372 if k == "tip": # skip tip
373 373 continue
374 374
375 375 count += 1
376 376 if count > 10: # limit to 10 tags
377 377 break
378 378
379 379 yield tmpl("tagentry",
380 380 parity=parity.next(),
381 381 tag=k,
382 382 node=hex(n),
383 383 date=web.repo[n].date())
384 384
385 385 def branches(**map):
386 386 parity = paritygen(web.stripecount)
387 387
388 388 b = web.repo.branchtags()
389 389 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
390 390 for r,n,t in util.sort(l):
391 391 yield {'parity': parity.next(),
392 392 'branch': t,
393 393 'node': hex(n),
394 394 'date': web.repo[n].date()}
395 395
396 396 def changelist(**map):
397 397 parity = paritygen(web.stripecount, offset=start-end)
398 398 l = [] # build a list in forward order for efficiency
399 399 for i in xrange(start, end):
400 400 ctx = web.repo[i]
401 401 n = ctx.node()
402 402 hn = hex(n)
403 403
404 404 l.insert(0, tmpl(
405 405 'shortlogentry',
406 406 parity=parity.next(),
407 407 author=ctx.user(),
408 408 desc=ctx.description(),
409 409 date=ctx.date(),
410 410 rev=i,
411 411 node=hn,
412 412 tags=webutil.nodetagsdict(web.repo, n),
413 413 inbranch=webutil.nodeinbranch(web.repo, ctx),
414 414 branches=webutil.nodebranchdict(web.repo, ctx)))
415 415
416 416 yield l
417 417
418 418 cl = web.repo.changelog
419 419 count = len(cl)
420 420 start = max(0, count - web.maxchanges)
421 421 end = min(count, start + web.maxchanges)
422 422
423 423 return tmpl("summary",
424 424 desc=web.config("web", "description", "unknown"),
425 425 owner=get_contact(web.config) or "unknown",
426 426 lastchange=cl.read(cl.tip())[2],
427 427 tags=tagentries,
428 428 branches=branches,
429 429 shortlog=changelist,
430 430 node=hex(cl.tip()),
431 431 archives=web.archivelist("tip"))
432 432
433 433 def filediff(web, req, tmpl):
434 434 fctx, ctx = None, None
435 435 try:
436 436 fctx = webutil.filectx(web.repo, req)
437 437 except LookupError:
438 438 ctx = webutil.changectx(web.repo, req)
439 439 path = webutil.cleanpath(web.repo, req.form['file'][0])
440 440 if path not in ctx.files():
441 441 raise
442 442
443 443 if fctx is not None:
444 444 n = fctx.node()
445 445 path = fctx.path()
446 446 parents = fctx.parents()
447 447 p1 = parents and parents[0].node() or nullid
448 448 else:
449 449 n = ctx.node()
450 450 # path already defined in except clause
451 451 parents = ctx.parents()
452 452
453 453 parity = paritygen(web.stripecount)
454 454 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity)
455 455 rename = fctx and webutil.renamelink(fctx) or []
456 456 ctx = fctx and fctx or ctx
457 457 return tmpl("filediff",
458 458 file=path,
459 459 node=hex(n),
460 460 rev=ctx.rev(),
461 461 date=ctx.date(),
462 462 desc=ctx.description(),
463 463 author=ctx.user(),
464 464 rename=rename,
465 465 branch=webutil.nodebranchnodefault(ctx),
466 466 parent=webutil.siblings(parents),
467 467 child=webutil.siblings(ctx.children()),
468 468 diff=diffs)
469 469
470 470 diff = filediff
471 471
472 472 def annotate(web, req, tmpl):
473 473 fctx = webutil.filectx(web.repo, req)
474 474 f = fctx.path()
475 475 parity = paritygen(web.stripecount)
476 476
477 477 def annotate(**map):
478 478 last = None
479 479 if binary(fctx.data()):
480 480 mt = (mimetypes.guess_type(fctx.path())[0]
481 481 or 'application/octet-stream')
482 482 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
483 483 '(binary:%s)' % mt)])
484 484 else:
485 485 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
486 486 for lineno, ((f, targetline), l) in lines:
487 487 fnode = f.filenode()
488 488
489 489 if last != fnode:
490 490 last = fnode
491 491
492 492 yield {"parity": parity.next(),
493 493 "node": hex(f.node()),
494 494 "rev": f.rev(),
495 495 "author": f.user(),
496 496 "desc": f.description(),
497 497 "file": f.path(),
498 498 "targetline": targetline,
499 499 "line": l,
500 500 "lineid": "l%d" % (lineno + 1),
501 501 "linenumber": "% 6d" % (lineno + 1)}
502 502
503 503 return tmpl("fileannotate",
504 504 file=f,
505 505 annotate=annotate,
506 506 path=webutil.up(f),
507 507 rev=fctx.rev(),
508 508 node=hex(fctx.node()),
509 509 author=fctx.user(),
510 510 date=fctx.date(),
511 511 desc=fctx.description(),
512 512 rename=webutil.renamelink(fctx),
513 513 branch=webutil.nodebranchnodefault(fctx),
514 514 parent=webutil.siblings(fctx.parents()),
515 515 child=webutil.siblings(fctx.children()),
516 516 permissions=fctx.manifest().flags(f))
517 517
518 518 def filelog(web, req, tmpl):
519 519
520 520 try:
521 521 fctx = webutil.filectx(web.repo, req)
522 522 f = fctx.path()
523 523 fl = fctx.filelog()
524 524 except revlog.LookupError:
525 525 f = webutil.cleanpath(web.repo, req.form['file'][0])
526 526 fl = web.repo.file(f)
527 527 numrevs = len(fl)
528 528 if not numrevs: # file doesn't exist at all
529 529 raise
530 530 rev = webutil.changectx(web.repo, req).rev()
531 531 first = fl.linkrev(0)
532 532 if rev < first: # current rev is from before file existed
533 533 raise
534 534 frev = numrevs - 1
535 535 while fl.linkrev(frev) > rev:
536 536 frev -= 1
537 537 fctx = web.repo.filectx(f, fl.linkrev(frev))
538 538
539 539 count = fctx.filerev() + 1
540 540 pagelen = web.maxshortchanges
541 541 start = max(0, fctx.filerev() - pagelen + 1) # first rev on this page
542 542 end = min(count, start + pagelen) # last rev on this page
543 543 parity = paritygen(web.stripecount, offset=start-end)
544 544
545 545 def entries(limit=0, **map):
546 546 l = []
547 547
548 548 for i in xrange(start, end):
549 549 ctx = fctx.filectx(i)
550 550
551 551 l.insert(0, {"parity": parity.next(),
552 552 "filerev": i,
553 553 "file": f,
554 554 "node": hex(ctx.node()),
555 555 "author": ctx.user(),
556 556 "date": ctx.date(),
557 557 "rename": webutil.renamelink(fctx),
558 558 "parent": webutil.siblings(fctx.parents()),
559 559 "child": webutil.siblings(fctx.children()),
560 560 "desc": ctx.description(),
561 561 "tags": webutil.nodetagsdict(web.repo, ctx.node()),
562 562 "branch": webutil.nodebranchnodefault(ctx),
563 563 "inbranch": webutil.nodeinbranch(web.repo, ctx),
564 564 "branches": webutil.nodebranchdict(web.repo, ctx)})
565
565
566 566 if limit > 0:
567 567 l = l[:limit]
568 568
569 569 for e in l:
570 570 yield e
571 571
572 572 nodefunc = lambda x: fctx.filectx(fileid=x)
573 573 nav = webutil.revnavgen(end - 1, pagelen, count, nodefunc)
574 574 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
575 575 entries=lambda **x: entries(limit=0, **x),
576 576 latestentry=lambda **x: entries(limit=1, **x))
577 577
578 578
579 579 def archive(web, req, tmpl):
580 580 type_ = req.form.get('type', [None])[0]
581 581 allowed = web.configlist("web", "allow_archive")
582 582 key = req.form['node'][0]
583 583
584 584 if type_ not in web.archives:
585 585 msg = 'Unsupported archive type: %s' % type_
586 586 raise ErrorResponse(HTTP_NOT_FOUND, msg)
587 587
588 588 if not ((type_ in allowed or
589 589 web.configbool("web", "allow" + type_, False))):
590 590 msg = 'Archive type not allowed: %s' % type_
591 591 raise ErrorResponse(HTTP_FORBIDDEN, msg)
592 592
593 593 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
594 594 cnode = web.repo.lookup(key)
595 595 arch_version = key
596 596 if cnode == key or key == 'tip':
597 597 arch_version = short(cnode)
598 598 name = "%s-%s" % (reponame, arch_version)
599 599 mimetype, artype, extension, encoding = web.archive_specs[type_]
600 600 headers = [
601 601 ('Content-Type', mimetype),
602 602 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
603 603 ]
604 604 if encoding:
605 605 headers.append(('Content-Encoding', encoding))
606 606 req.header(headers)
607 607 req.respond(HTTP_OK)
608 608 archival.archive(web.repo, req, cnode, artype, prefix=name)
609 609 return []
610 610
611 611
612 612 def static(web, req, tmpl):
613 613 fname = req.form['file'][0]
614 614 # a repo owner may set web.static in .hg/hgrc to get any file
615 615 # readable by the user running the CGI script
616 616 static = web.config("web", "static", None, untrusted=False)
617 617 if not static:
618 618 tp = web.templatepath
619 619 if isinstance(tp, str):
620 620 tp = [tp]
621 621 static = [os.path.join(p, 'static') for p in tp]
622 622 return [staticfile(static, fname, req)]
623 623
624 624 def graph(web, req, tmpl):
625 625 rev = webutil.changectx(web.repo, req).rev()
626 626 bg_height = 39
627 627
628 628 revcount = 25
629 629 if 'revcount' in req.form:
630 630 revcount = int(req.form.get('revcount', [revcount])[0])
631 631 tmpl.defaults['sessionvars']['revcount'] = revcount
632 632
633 633 lessvars = copy.copy(tmpl.defaults['sessionvars'])
634 634 lessvars['revcount'] = revcount / 2
635 635 morevars = copy.copy(tmpl.defaults['sessionvars'])
636 636 morevars['revcount'] = revcount * 2
637 637
638 638 max_rev = len(web.repo) - 1
639 639 revcount = min(max_rev, revcount)
640 640 revnode = web.repo.changelog.node(rev)
641 641 revnode_hex = hex(revnode)
642 642 uprev = min(max_rev, rev + revcount)
643 643 downrev = max(0, rev - revcount)
644 644 count = len(web.repo)
645 645 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
646 646
647 647 tree = list(graphmod.graph(web.repo, rev, downrev))
648 648 canvasheight = (len(tree) + 1) * bg_height - 27;
649 649 data = []
650 650 for i, (ctx, vtx, edges) in enumerate(tree):
651 651 node = short(ctx.node())
652 652 age = templatefilters.age(ctx.date())
653 653 desc = templatefilters.firstline(ctx.description())
654 654 desc = cgi.escape(desc)
655 655 user = cgi.escape(templatefilters.person(ctx.user()))
656 656 branch = ctx.branch()
657 657 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
658 658 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
659 659
660 660 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
661 661 lessvars=lessvars, morevars=morevars, downrev=downrev,
662 662 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
663 663 node=revnode_hex, changenav=changenav)
@@ -1,191 +1,191
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 i18n import _
9 9 import re, sys, os
10 10 from mercurial import util
11 11
12 12 path = ['templates', '../templates']
13 13
14 14 def parsestring(s, quoted=True):
15 15 '''parse a string using simple c-like syntax.
16 16 string must be in quotes if quoted is True.'''
17 17 if quoted:
18 18 if len(s) < 2 or s[0] != s[-1]:
19 19 raise SyntaxError(_('unmatched quotes'))
20 20 return s[1:-1].decode('string_escape')
21 21
22 22 return s.decode('string_escape')
23 23
24 24 class templater(object):
25 25 '''template expansion engine.
26 26
27 27 template expansion works like this. a map file contains key=value
28 28 pairs. if value is quoted, it is treated as string. otherwise, it
29 29 is treated as name of template file.
30 30
31 31 templater is asked to expand a key in map. it looks up key, and
32 32 looks for strings like this: {foo}. it expands {foo} by looking up
33 33 foo in map, and substituting it. expansion is recursive: it stops
34 34 when there is no more {foo} to replace.
35 35
36 36 expansion also allows formatting and filtering.
37 37
38 38 format uses key to expand each item in list. syntax is
39 39 {key%format}.
40 40
41 41 filter uses function to transform value. syntax is
42 42 {key|filter1|filter2|...}.'''
43 43
44 44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
45 45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
46 46
47 47 def __init__(self, mapfile, filters={}, defaults={}, cache={},
48 48 minchunk=1024, maxchunk=65536):
49 49 '''set up template engine.
50 50 mapfile is name of file to read map definitions from.
51 51 filters is dict of functions. each transforms a value into another.
52 52 defaults is dict of default map definitions.'''
53 53 self.mapfile = mapfile or 'template'
54 54 self.cache = cache.copy()
55 55 self.map = {}
56 56 self.base = (mapfile and os.path.dirname(mapfile)) or ''
57 57 self.filters = filters
58 58 self.defaults = defaults
59 59 self.minchunk, self.maxchunk = minchunk, maxchunk
60 60
61 61 if not mapfile:
62 62 return
63 63 if not os.path.exists(mapfile):
64 64 raise util.Abort(_('style not found: %s') % mapfile)
65 65
66 66 i = 0
67 67 for l in file(mapfile):
68 68 l = l.strip()
69 69 i += 1
70 70 if not l or l[0] in '#;': continue
71 71 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
72 72 if m:
73 73 key, val = m.groups()
74 74 if val[0] in "'\"":
75 75 try:
76 76 self.cache[key] = parsestring(val)
77 77 except SyntaxError, inst:
78 78 raise SyntaxError('%s:%s: %s' %
79 79 (mapfile, i, inst.args[0]))
80 80 else:
81 81 self.map[key] = os.path.join(self.base, val)
82 82 else:
83 83 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
84 84
85 85 def __contains__(self, key):
86 86 return key in self.cache or key in self.map
87 87
88 88 def _template(self, t):
89 89 '''Get the template for the given template name. Use a local cache.'''
90 90 if not t in self.cache:
91 91 try:
92 92 self.cache[t] = file(self.map[t]).read()
93 93 except IOError, inst:
94 94 raise IOError(inst.args[0], _('template file %s: %s') %
95 95 (self.map[t], inst.args[1]))
96 96 return self.cache[t]
97 97
98 98 def _process(self, tmpl, map):
99 99 '''Render a template. Returns a generator.'''
100 100 while tmpl:
101 101 m = self.template_re.search(tmpl)
102 102 if not m:
103 103 yield tmpl
104 104 break
105 105
106 106 start, end = m.span(0)
107 107 key, format, fl = m.groups()
108 108
109 109 if start:
110 110 yield tmpl[:start]
111 111 tmpl = tmpl[end:]
112 112
113 113 if key in map:
114 114 v = map[key]
115 115 else:
116 116 v = self.defaults.get(key, "")
117 117 if callable(v):
118 118 v = v(**map)
119 119 if format:
120 120 if not hasattr(v, '__iter__'):
121 121 raise SyntaxError(_("Error expanding '%s%%%s'")
122 122 % (key, format))
123 123 lm = map.copy()
124 124 for i in v:
125 125 lm.update(i)
126 126 t = self._template(format)
127 127 yield self._process(t, lm)
128 128 else:
129 129 if fl:
130 130 for f in fl.split("|")[1:]:
131 131 v = self.filters[f](v)
132 132 yield v
133 133
134 134 def __call__(self, t, **map):
135 135 stream = self.expand(t, **map)
136 136 if self.minchunk:
137 137 stream = util.increasingchunks(stream, min=self.minchunk,
138 138 max=self.maxchunk)
139 139 return stream
140
140
141 141 def expand(self, t, **map):
142 142 '''Perform expansion. t is name of map element to expand. map contains
143 143 added elements for use during expansion. Is a generator.'''
144 144 tmpl = self._template(t)
145 145 iters = [self._process(tmpl, map)]
146 146 while iters:
147 147 try:
148 148 item = iters[0].next()
149 149 except StopIteration:
150 150 iters.pop(0)
151 151 continue
152 152 if isinstance(item, str):
153 153 yield item
154 154 elif item is None:
155 155 yield ''
156 156 elif hasattr(item, '__iter__'):
157 157 iters.insert(0, iter(item))
158 158 else:
159 159 yield str(item)
160 160
161 161 def templatepath(name=None):
162 162 '''return location of template file or directory (if no name).
163 163 returns None if not found.'''
164 164 normpaths = []
165 165
166 166 # executable version (py2exe) doesn't support __file__
167 167 if hasattr(sys, 'frozen'):
168 168 module = sys.executable
169 169 else:
170 170 module = __file__
171 171 for f in path:
172 172 if f.startswith('/'):
173 173 p = f
174 174 else:
175 175 fl = f.split('/')
176 176 p = os.path.join(os.path.dirname(module), *fl)
177 177 if name:
178 178 p = os.path.join(p, name)
179 179 if name and os.path.exists(p):
180 180 return os.path.normpath(p)
181 181 elif os.path.isdir(p):
182 182 normpaths.append(os.path.normpath(p))
183 183
184 184 return normpaths
185 185
186 186 def stringify(thing):
187 187 '''turn nested template iterator into string.'''
188 188 if hasattr(thing, '__iter__'):
189 189 return "".join([stringify(t) for t in thing if t is not None])
190 190 return str(thing)
191 191
General Comments 0
You need to be logged in to leave comments. Login now