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