##// END OF EJS Templates
hgweb: provide symrev (symbolic revision) property to the templates...
av6 -
r25602:85fb416f default
parent child Browse files
Show More
@@ -1,68 +1,68 b''
1 # highlight - syntax highlighting in hgweb, based on Pygments
1 # highlight - syntax highlighting in hgweb, based on Pygments
2 #
2 #
3 # Copyright 2008, 2009 Patrick Mezard <pmezard@gmail.com> and others
3 # Copyright 2008, 2009 Patrick Mezard <pmezard@gmail.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # The original module was split in an interface and an implementation
8 # The original module was split in an interface and an implementation
9 # file to defer pygments loading and speedup extension setup.
9 # file to defer pygments loading and speedup extension setup.
10
10
11 """syntax highlighting for hgweb (requires Pygments)
11 """syntax highlighting for hgweb (requires Pygments)
12
12
13 It depends on the Pygments syntax highlighting library:
13 It depends on the Pygments syntax highlighting library:
14 http://pygments.org/
14 http://pygments.org/
15
15
16 There is a single configuration option::
16 There is a single configuration option::
17
17
18 [web]
18 [web]
19 pygments_style = <style>
19 pygments_style = <style>
20
20
21 The default is 'colorful'.
21 The default is 'colorful'.
22 """
22 """
23
23
24 import highlight
24 import highlight
25 from mercurial.hgweb import webcommands, webutil, common
25 from mercurial.hgweb import webcommands, webutil, common
26 from mercurial import extensions, encoding
26 from mercurial import extensions, encoding
27 # Note for extension authors: ONLY specify testedwith = 'internal' for
27 # Note for extension authors: ONLY specify testedwith = 'internal' for
28 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
28 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
29 # be specifying the version(s) of Mercurial they are tested with, or
29 # be specifying the version(s) of Mercurial they are tested with, or
30 # leave the attribute unspecified.
30 # leave the attribute unspecified.
31 testedwith = 'internal'
31 testedwith = 'internal'
32
32
33 def filerevision_highlight(orig, web, tmpl, fctx):
33 def filerevision_highlight(orig, web, req, tmpl, fctx):
34 mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
34 mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
35 # only pygmentize for mimetype containing 'html' so we both match
35 # only pygmentize for mimetype containing 'html' so we both match
36 # 'text/html' and possibly 'application/xhtml+xml' in the future
36 # 'text/html' and possibly 'application/xhtml+xml' in the future
37 # so that we don't have to touch the extension when the mimetype
37 # so that we don't have to touch the extension when the mimetype
38 # for a template changes; also hgweb optimizes the case that a
38 # for a template changes; also hgweb optimizes the case that a
39 # raw file is sent using rawfile() and doesn't call us, so we
39 # raw file is sent using rawfile() and doesn't call us, so we
40 # can't clash with the file's content-type here in case we
40 # can't clash with the file's content-type here in case we
41 # pygmentize a html file
41 # pygmentize a html file
42 if 'html' in mt:
42 if 'html' in mt:
43 style = web.config('web', 'pygments_style', 'colorful')
43 style = web.config('web', 'pygments_style', 'colorful')
44 highlight.pygmentize('fileline', fctx, style, tmpl)
44 highlight.pygmentize('fileline', fctx, style, tmpl)
45 return orig(web, tmpl, fctx)
45 return orig(web, req, tmpl, fctx)
46
46
47 def annotate_highlight(orig, web, req, tmpl):
47 def annotate_highlight(orig, web, req, tmpl):
48 mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
48 mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
49 if 'html' in mt:
49 if 'html' in mt:
50 fctx = webutil.filectx(web.repo, req)
50 fctx = webutil.filectx(web.repo, req)
51 style = web.config('web', 'pygments_style', 'colorful')
51 style = web.config('web', 'pygments_style', 'colorful')
52 highlight.pygmentize('annotateline', fctx, style, tmpl)
52 highlight.pygmentize('annotateline', fctx, style, tmpl)
53 return orig(web, req, tmpl)
53 return orig(web, req, tmpl)
54
54
55 def generate_css(web, req, tmpl):
55 def generate_css(web, req, tmpl):
56 pg_style = web.config('web', 'pygments_style', 'colorful')
56 pg_style = web.config('web', 'pygments_style', 'colorful')
57 fmter = highlight.HtmlFormatter(style=pg_style)
57 fmter = highlight.HtmlFormatter(style=pg_style)
58 req.respond(common.HTTP_OK, 'text/css')
58 req.respond(common.HTTP_OK, 'text/css')
59 return ['/* pygments_style = %s */\n\n' % pg_style,
59 return ['/* pygments_style = %s */\n\n' % pg_style,
60 fmter.get_style_defs('')]
60 fmter.get_style_defs('')]
61
61
62 def extsetup():
62 def extsetup():
63 # monkeypatch in the new version
63 # monkeypatch in the new version
64 extensions.wrapfunction(webcommands, '_filerevision',
64 extensions.wrapfunction(webcommands, '_filerevision',
65 filerevision_highlight)
65 filerevision_highlight)
66 extensions.wrapfunction(webcommands, 'annotate', annotate_highlight)
66 extensions.wrapfunction(webcommands, 'annotate', annotate_highlight)
67 webcommands.highlightcss = generate_css
67 webcommands.highlightcss = generate_css
68 webcommands.__all__.append('highlightcss')
68 webcommands.__all__.append('highlightcss')
@@ -1,1332 +1,1352 b''
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
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 error, encoding, archival, templater, templatefilters
10 from mercurial import error, encoding, archival, templater, templatefilters
11 from mercurial.node import short, hex
11 from mercurial.node import short, hex
12 from mercurial import util
12 from mercurial import util
13 from common import paritygen, staticfile, get_contact, ErrorResponse
13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 from mercurial import graphmod, patch
15 from mercurial import graphmod, patch
16 from mercurial import scmutil
16 from mercurial import scmutil
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 from mercurial.error import ParseError, RepoLookupError, Abort
18 from mercurial.error import ParseError, RepoLookupError, Abort
19 from mercurial import revset
19 from mercurial import revset
20
20
21 __all__ = []
21 __all__ = []
22 commands = {}
22 commands = {}
23
23
24 class webcommand(object):
24 class webcommand(object):
25 """Decorator used to register a web command handler.
25 """Decorator used to register a web command handler.
26
26
27 The decorator takes as its positional arguments the name/path the
27 The decorator takes as its positional arguments the name/path the
28 command should be accessible under.
28 command should be accessible under.
29
29
30 Usage:
30 Usage:
31
31
32 @webcommand('mycommand')
32 @webcommand('mycommand')
33 def mycommand(web, req, tmpl):
33 def mycommand(web, req, tmpl):
34 pass
34 pass
35 """
35 """
36
36
37 def __init__(self, name):
37 def __init__(self, name):
38 self.name = name
38 self.name = name
39
39
40 def __call__(self, func):
40 def __call__(self, func):
41 __all__.append(self.name)
41 __all__.append(self.name)
42 commands[self.name] = func
42 commands[self.name] = func
43 return func
43 return func
44
44
45 @webcommand('log')
45 @webcommand('log')
46 def log(web, req, tmpl):
46 def log(web, req, tmpl):
47 """
47 """
48 /log[/{revision}[/{path}]]
48 /log[/{revision}[/{path}]]
49 --------------------------
49 --------------------------
50
50
51 Show repository or file history.
51 Show repository or file history.
52
52
53 For URLs of the form ``/log/{revision}``, a list of changesets starting at
53 For URLs of the form ``/log/{revision}``, a list of changesets starting at
54 the specified changeset identifier is shown. If ``{revision}`` is not
54 the specified changeset identifier is shown. If ``{revision}`` is not
55 defined, the default is ``tip``. This form is equivalent to the
55 defined, the default is ``tip``. This form is equivalent to the
56 ``changelog`` handler.
56 ``changelog`` handler.
57
57
58 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
58 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
59 file will be shown. This form is equivalent to the ``filelog`` handler.
59 file will be shown. This form is equivalent to the ``filelog`` handler.
60 """
60 """
61
61
62 if 'file' in req.form and req.form['file'][0]:
62 if 'file' in req.form and req.form['file'][0]:
63 return filelog(web, req, tmpl)
63 return filelog(web, req, tmpl)
64 else:
64 else:
65 return changelog(web, req, tmpl)
65 return changelog(web, req, tmpl)
66
66
67 @webcommand('rawfile')
67 @webcommand('rawfile')
68 def rawfile(web, req, tmpl):
68 def rawfile(web, req, tmpl):
69 guessmime = web.configbool('web', 'guessmime', False)
69 guessmime = web.configbool('web', 'guessmime', False)
70
70
71 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
71 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
72 if not path:
72 if not path:
73 content = manifest(web, req, tmpl)
73 content = manifest(web, req, tmpl)
74 req.respond(HTTP_OK, web.ctype)
74 req.respond(HTTP_OK, web.ctype)
75 return content
75 return content
76
76
77 try:
77 try:
78 fctx = webutil.filectx(web.repo, req)
78 fctx = webutil.filectx(web.repo, req)
79 except error.LookupError, inst:
79 except error.LookupError, inst:
80 try:
80 try:
81 content = manifest(web, req, tmpl)
81 content = manifest(web, req, tmpl)
82 req.respond(HTTP_OK, web.ctype)
82 req.respond(HTTP_OK, web.ctype)
83 return content
83 return content
84 except ErrorResponse:
84 except ErrorResponse:
85 raise inst
85 raise inst
86
86
87 path = fctx.path()
87 path = fctx.path()
88 text = fctx.data()
88 text = fctx.data()
89 mt = 'application/binary'
89 mt = 'application/binary'
90 if guessmime:
90 if guessmime:
91 mt = mimetypes.guess_type(path)[0]
91 mt = mimetypes.guess_type(path)[0]
92 if mt is None:
92 if mt is None:
93 if util.binary(text):
93 if util.binary(text):
94 mt = 'application/binary'
94 mt = 'application/binary'
95 else:
95 else:
96 mt = 'text/plain'
96 mt = 'text/plain'
97 if mt.startswith('text/'):
97 if mt.startswith('text/'):
98 mt += '; charset="%s"' % encoding.encoding
98 mt += '; charset="%s"' % encoding.encoding
99
99
100 req.respond(HTTP_OK, mt, path, body=text)
100 req.respond(HTTP_OK, mt, path, body=text)
101 return []
101 return []
102
102
103 def _filerevision(web, tmpl, fctx):
103 def _filerevision(web, req, tmpl, fctx):
104 f = fctx.path()
104 f = fctx.path()
105 text = fctx.data()
105 text = fctx.data()
106 parity = paritygen(web.stripecount)
106 parity = paritygen(web.stripecount)
107
107
108 if util.binary(text):
108 if util.binary(text):
109 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
109 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
110 text = '(binary:%s)' % mt
110 text = '(binary:%s)' % mt
111
111
112 def lines():
112 def lines():
113 for lineno, t in enumerate(text.splitlines(True)):
113 for lineno, t in enumerate(text.splitlines(True)):
114 yield {"line": t,
114 yield {"line": t,
115 "lineid": "l%d" % (lineno + 1),
115 "lineid": "l%d" % (lineno + 1),
116 "linenumber": "% 6d" % (lineno + 1),
116 "linenumber": "% 6d" % (lineno + 1),
117 "parity": parity.next()}
117 "parity": parity.next()}
118
118
119 return tmpl("filerevision",
119 return tmpl("filerevision",
120 file=f,
120 file=f,
121 path=webutil.up(f),
121 path=webutil.up(f),
122 text=lines(),
122 text=lines(),
123 rev=fctx.rev(),
123 rev=fctx.rev(),
124 symrev=webutil.symrevorshortnode(req, fctx),
124 node=fctx.hex(),
125 node=fctx.hex(),
125 author=fctx.user(),
126 author=fctx.user(),
126 date=fctx.date(),
127 date=fctx.date(),
127 desc=fctx.description(),
128 desc=fctx.description(),
128 extra=fctx.extra(),
129 extra=fctx.extra(),
129 branch=webutil.nodebranchnodefault(fctx),
130 branch=webutil.nodebranchnodefault(fctx),
130 parent=webutil.parents(fctx),
131 parent=webutil.parents(fctx),
131 child=webutil.children(fctx),
132 child=webutil.children(fctx),
132 rename=webutil.renamelink(fctx),
133 rename=webutil.renamelink(fctx),
133 tags=webutil.nodetagsdict(web.repo, fctx.node()),
134 tags=webutil.nodetagsdict(web.repo, fctx.node()),
134 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
135 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
135 permissions=fctx.manifest().flags(f))
136 permissions=fctx.manifest().flags(f))
136
137
137 @webcommand('file')
138 @webcommand('file')
138 def file(web, req, tmpl):
139 def file(web, req, tmpl):
139 """
140 """
140 /file/{revision}[/{path}]
141 /file/{revision}[/{path}]
141 -------------------------
142 -------------------------
142
143
143 Show information about a directory or file in the repository.
144 Show information about a directory or file in the repository.
144
145
145 Info about the ``path`` given as a URL parameter will be rendered.
146 Info about the ``path`` given as a URL parameter will be rendered.
146
147
147 If ``path`` is a directory, information about the entries in that
148 If ``path`` is a directory, information about the entries in that
148 directory will be rendered. This form is equivalent to the ``manifest``
149 directory will be rendered. This form is equivalent to the ``manifest``
149 handler.
150 handler.
150
151
151 If ``path`` is a file, information about that file will be shown via
152 If ``path`` is a file, information about that file will be shown via
152 the ``filerevision`` template.
153 the ``filerevision`` template.
153
154
154 If ``path`` is not defined, information about the root directory will
155 If ``path`` is not defined, information about the root directory will
155 be rendered.
156 be rendered.
156 """
157 """
157 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
158 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
158 if not path:
159 if not path:
159 return manifest(web, req, tmpl)
160 return manifest(web, req, tmpl)
160 try:
161 try:
161 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
162 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
162 except error.LookupError, inst:
163 except error.LookupError, inst:
163 try:
164 try:
164 return manifest(web, req, tmpl)
165 return manifest(web, req, tmpl)
165 except ErrorResponse:
166 except ErrorResponse:
166 raise inst
167 raise inst
167
168
168 def _search(web, req, tmpl):
169 def _search(web, req, tmpl):
169 MODE_REVISION = 'rev'
170 MODE_REVISION = 'rev'
170 MODE_KEYWORD = 'keyword'
171 MODE_KEYWORD = 'keyword'
171 MODE_REVSET = 'revset'
172 MODE_REVSET = 'revset'
172
173
173 def revsearch(ctx):
174 def revsearch(ctx):
174 yield ctx
175 yield ctx
175
176
176 def keywordsearch(query):
177 def keywordsearch(query):
177 lower = encoding.lower
178 lower = encoding.lower
178 qw = lower(query).split()
179 qw = lower(query).split()
179
180
180 def revgen():
181 def revgen():
181 cl = web.repo.changelog
182 cl = web.repo.changelog
182 for i in xrange(len(web.repo) - 1, 0, -100):
183 for i in xrange(len(web.repo) - 1, 0, -100):
183 l = []
184 l = []
184 for j in cl.revs(max(0, i - 99), i):
185 for j in cl.revs(max(0, i - 99), i):
185 ctx = web.repo[j]
186 ctx = web.repo[j]
186 l.append(ctx)
187 l.append(ctx)
187 l.reverse()
188 l.reverse()
188 for e in l:
189 for e in l:
189 yield e
190 yield e
190
191
191 for ctx in revgen():
192 for ctx in revgen():
192 miss = 0
193 miss = 0
193 for q in qw:
194 for q in qw:
194 if not (q in lower(ctx.user()) or
195 if not (q in lower(ctx.user()) or
195 q in lower(ctx.description()) or
196 q in lower(ctx.description()) or
196 q in lower(" ".join(ctx.files()))):
197 q in lower(" ".join(ctx.files()))):
197 miss = 1
198 miss = 1
198 break
199 break
199 if miss:
200 if miss:
200 continue
201 continue
201
202
202 yield ctx
203 yield ctx
203
204
204 def revsetsearch(revs):
205 def revsetsearch(revs):
205 for r in revs:
206 for r in revs:
206 yield web.repo[r]
207 yield web.repo[r]
207
208
208 searchfuncs = {
209 searchfuncs = {
209 MODE_REVISION: (revsearch, 'exact revision search'),
210 MODE_REVISION: (revsearch, 'exact revision search'),
210 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
211 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
211 MODE_REVSET: (revsetsearch, 'revset expression search'),
212 MODE_REVSET: (revsetsearch, 'revset expression search'),
212 }
213 }
213
214
214 def getsearchmode(query):
215 def getsearchmode(query):
215 try:
216 try:
216 ctx = web.repo[query]
217 ctx = web.repo[query]
217 except (error.RepoError, error.LookupError):
218 except (error.RepoError, error.LookupError):
218 # query is not an exact revision pointer, need to
219 # query is not an exact revision pointer, need to
219 # decide if it's a revset expression or keywords
220 # decide if it's a revset expression or keywords
220 pass
221 pass
221 else:
222 else:
222 return MODE_REVISION, ctx
223 return MODE_REVISION, ctx
223
224
224 revdef = 'reverse(%s)' % query
225 revdef = 'reverse(%s)' % query
225 try:
226 try:
226 tree = revset.parse(revdef)
227 tree = revset.parse(revdef)
227 except ParseError:
228 except ParseError:
228 # can't parse to a revset tree
229 # can't parse to a revset tree
229 return MODE_KEYWORD, query
230 return MODE_KEYWORD, query
230
231
231 if revset.depth(tree) <= 2:
232 if revset.depth(tree) <= 2:
232 # no revset syntax used
233 # no revset syntax used
233 return MODE_KEYWORD, query
234 return MODE_KEYWORD, query
234
235
235 if any((token, (value or '')[:3]) == ('string', 're:')
236 if any((token, (value or '')[:3]) == ('string', 're:')
236 for token, value, pos in revset.tokenize(revdef)):
237 for token, value, pos in revset.tokenize(revdef)):
237 return MODE_KEYWORD, query
238 return MODE_KEYWORD, query
238
239
239 funcsused = revset.funcsused(tree)
240 funcsused = revset.funcsused(tree)
240 if not funcsused.issubset(revset.safesymbols):
241 if not funcsused.issubset(revset.safesymbols):
241 return MODE_KEYWORD, query
242 return MODE_KEYWORD, query
242
243
243 mfunc = revset.match(web.repo.ui, revdef)
244 mfunc = revset.match(web.repo.ui, revdef)
244 try:
245 try:
245 revs = mfunc(web.repo)
246 revs = mfunc(web.repo)
246 return MODE_REVSET, revs
247 return MODE_REVSET, revs
247 # ParseError: wrongly placed tokens, wrongs arguments, etc
248 # ParseError: wrongly placed tokens, wrongs arguments, etc
248 # RepoLookupError: no such revision, e.g. in 'revision:'
249 # RepoLookupError: no such revision, e.g. in 'revision:'
249 # Abort: bookmark/tag not exists
250 # Abort: bookmark/tag not exists
250 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
251 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
251 except (ParseError, RepoLookupError, Abort, LookupError):
252 except (ParseError, RepoLookupError, Abort, LookupError):
252 return MODE_KEYWORD, query
253 return MODE_KEYWORD, query
253
254
254 def changelist(**map):
255 def changelist(**map):
255 count = 0
256 count = 0
256
257
257 for ctx in searchfunc[0](funcarg):
258 for ctx in searchfunc[0](funcarg):
258 count += 1
259 count += 1
259 n = ctx.node()
260 n = ctx.node()
260 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
261 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
261 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
262 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
262
263
263 yield tmpl('searchentry',
264 yield tmpl('searchentry',
264 parity=parity.next(),
265 parity=parity.next(),
265 author=ctx.user(),
266 author=ctx.user(),
266 parent=webutil.parents(ctx),
267 parent=webutil.parents(ctx),
267 child=webutil.children(ctx),
268 child=webutil.children(ctx),
268 changelogtag=showtags,
269 changelogtag=showtags,
269 desc=ctx.description(),
270 desc=ctx.description(),
270 extra=ctx.extra(),
271 extra=ctx.extra(),
271 date=ctx.date(),
272 date=ctx.date(),
272 files=files,
273 files=files,
273 rev=ctx.rev(),
274 rev=ctx.rev(),
274 node=hex(n),
275 node=hex(n),
275 tags=webutil.nodetagsdict(web.repo, n),
276 tags=webutil.nodetagsdict(web.repo, n),
276 bookmarks=webutil.nodebookmarksdict(web.repo, n),
277 bookmarks=webutil.nodebookmarksdict(web.repo, n),
277 inbranch=webutil.nodeinbranch(web.repo, ctx),
278 inbranch=webutil.nodeinbranch(web.repo, ctx),
278 branches=webutil.nodebranchdict(web.repo, ctx))
279 branches=webutil.nodebranchdict(web.repo, ctx))
279
280
280 if count >= revcount:
281 if count >= revcount:
281 break
282 break
282
283
283 query = req.form['rev'][0]
284 query = req.form['rev'][0]
284 revcount = web.maxchanges
285 revcount = web.maxchanges
285 if 'revcount' in req.form:
286 if 'revcount' in req.form:
286 try:
287 try:
287 revcount = int(req.form.get('revcount', [revcount])[0])
288 revcount = int(req.form.get('revcount', [revcount])[0])
288 revcount = max(revcount, 1)
289 revcount = max(revcount, 1)
289 tmpl.defaults['sessionvars']['revcount'] = revcount
290 tmpl.defaults['sessionvars']['revcount'] = revcount
290 except ValueError:
291 except ValueError:
291 pass
292 pass
292
293
293 lessvars = copy.copy(tmpl.defaults['sessionvars'])
294 lessvars = copy.copy(tmpl.defaults['sessionvars'])
294 lessvars['revcount'] = max(revcount / 2, 1)
295 lessvars['revcount'] = max(revcount / 2, 1)
295 lessvars['rev'] = query
296 lessvars['rev'] = query
296 morevars = copy.copy(tmpl.defaults['sessionvars'])
297 morevars = copy.copy(tmpl.defaults['sessionvars'])
297 morevars['revcount'] = revcount * 2
298 morevars['revcount'] = revcount * 2
298 morevars['rev'] = query
299 morevars['rev'] = query
299
300
300 mode, funcarg = getsearchmode(query)
301 mode, funcarg = getsearchmode(query)
301
302
302 if 'forcekw' in req.form:
303 if 'forcekw' in req.form:
303 showforcekw = ''
304 showforcekw = ''
304 showunforcekw = searchfuncs[mode][1]
305 showunforcekw = searchfuncs[mode][1]
305 mode = MODE_KEYWORD
306 mode = MODE_KEYWORD
306 funcarg = query
307 funcarg = query
307 else:
308 else:
308 if mode != MODE_KEYWORD:
309 if mode != MODE_KEYWORD:
309 showforcekw = searchfuncs[MODE_KEYWORD][1]
310 showforcekw = searchfuncs[MODE_KEYWORD][1]
310 else:
311 else:
311 showforcekw = ''
312 showforcekw = ''
312 showunforcekw = ''
313 showunforcekw = ''
313
314
314 searchfunc = searchfuncs[mode]
315 searchfunc = searchfuncs[mode]
315
316
316 tip = web.repo['tip']
317 tip = web.repo['tip']
317 parity = paritygen(web.stripecount)
318 parity = paritygen(web.stripecount)
318
319
319 return tmpl('search', query=query, node=tip.hex(),
320 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
320 entries=changelist, archives=web.archivelist("tip"),
321 entries=changelist, archives=web.archivelist("tip"),
321 morevars=morevars, lessvars=lessvars,
322 morevars=morevars, lessvars=lessvars,
322 modedesc=searchfunc[1],
323 modedesc=searchfunc[1],
323 showforcekw=showforcekw, showunforcekw=showunforcekw)
324 showforcekw=showforcekw, showunforcekw=showunforcekw)
324
325
325 @webcommand('changelog')
326 @webcommand('changelog')
326 def changelog(web, req, tmpl, shortlog=False):
327 def changelog(web, req, tmpl, shortlog=False):
327 """
328 """
328 /changelog[/{revision}]
329 /changelog[/{revision}]
329 -----------------------
330 -----------------------
330
331
331 Show information about multiple changesets.
332 Show information about multiple changesets.
332
333
333 If the optional ``revision`` URL argument is absent, information about
334 If the optional ``revision`` URL argument is absent, information about
334 all changesets starting at ``tip`` will be rendered. If the ``revision``
335 all changesets starting at ``tip`` will be rendered. If the ``revision``
335 argument is present, changesets will be shown starting from the specified
336 argument is present, changesets will be shown starting from the specified
336 revision.
337 revision.
337
338
338 If ``revision`` is absent, the ``rev`` query string argument may be
339 If ``revision`` is absent, the ``rev`` query string argument may be
339 defined. This will perform a search for changesets.
340 defined. This will perform a search for changesets.
340
341
341 The argument for ``rev`` can be a single revision, a revision set,
342 The argument for ``rev`` can be a single revision, a revision set,
342 or a literal keyword to search for in changeset data (equivalent to
343 or a literal keyword to search for in changeset data (equivalent to
343 :hg:`log -k`).
344 :hg:`log -k`).
344
345
345 The ``revcount`` query string argument defines the maximum numbers of
346 The ``revcount`` query string argument defines the maximum numbers of
346 changesets to render.
347 changesets to render.
347
348
348 For non-searches, the ``changelog`` template will be rendered.
349 For non-searches, the ``changelog`` template will be rendered.
349 """
350 """
350
351
351 query = ''
352 query = ''
352 if 'node' in req.form:
353 if 'node' in req.form:
353 ctx = webutil.changectx(web.repo, req)
354 ctx = webutil.changectx(web.repo, req)
355 symrev = webutil.symrevorshortnode(req, ctx)
354 elif 'rev' in req.form:
356 elif 'rev' in req.form:
355 return _search(web, req, tmpl)
357 return _search(web, req, tmpl)
356 else:
358 else:
357 ctx = web.repo['tip']
359 ctx = web.repo['tip']
360 symrev = 'tip'
358
361
359 def changelist():
362 def changelist():
360 revs = []
363 revs = []
361 if pos != -1:
364 if pos != -1:
362 revs = web.repo.changelog.revs(pos, 0)
365 revs = web.repo.changelog.revs(pos, 0)
363 curcount = 0
366 curcount = 0
364 for rev in revs:
367 for rev in revs:
365 curcount += 1
368 curcount += 1
366 if curcount > revcount + 1:
369 if curcount > revcount + 1:
367 break
370 break
368
371
369 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
372 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
370 entry['parity'] = parity.next()
373 entry['parity'] = parity.next()
371 yield entry
374 yield entry
372
375
373 if shortlog:
376 if shortlog:
374 revcount = web.maxshortchanges
377 revcount = web.maxshortchanges
375 else:
378 else:
376 revcount = web.maxchanges
379 revcount = web.maxchanges
377
380
378 if 'revcount' in req.form:
381 if 'revcount' in req.form:
379 try:
382 try:
380 revcount = int(req.form.get('revcount', [revcount])[0])
383 revcount = int(req.form.get('revcount', [revcount])[0])
381 revcount = max(revcount, 1)
384 revcount = max(revcount, 1)
382 tmpl.defaults['sessionvars']['revcount'] = revcount
385 tmpl.defaults['sessionvars']['revcount'] = revcount
383 except ValueError:
386 except ValueError:
384 pass
387 pass
385
388
386 lessvars = copy.copy(tmpl.defaults['sessionvars'])
389 lessvars = copy.copy(tmpl.defaults['sessionvars'])
387 lessvars['revcount'] = max(revcount / 2, 1)
390 lessvars['revcount'] = max(revcount / 2, 1)
388 morevars = copy.copy(tmpl.defaults['sessionvars'])
391 morevars = copy.copy(tmpl.defaults['sessionvars'])
389 morevars['revcount'] = revcount * 2
392 morevars['revcount'] = revcount * 2
390
393
391 count = len(web.repo)
394 count = len(web.repo)
392 pos = ctx.rev()
395 pos = ctx.rev()
393 parity = paritygen(web.stripecount)
396 parity = paritygen(web.stripecount)
394
397
395 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
398 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
396
399
397 entries = list(changelist())
400 entries = list(changelist())
398 latestentry = entries[:1]
401 latestentry = entries[:1]
399 if len(entries) > revcount:
402 if len(entries) > revcount:
400 nextentry = entries[-1:]
403 nextentry = entries[-1:]
401 entries = entries[:-1]
404 entries = entries[:-1]
402 else:
405 else:
403 nextentry = []
406 nextentry = []
404
407
405 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
408 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
406 node=ctx.hex(), rev=pos, changesets=count,
409 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
407 entries=entries,
410 entries=entries,
408 latestentry=latestentry, nextentry=nextentry,
411 latestentry=latestentry, nextentry=nextentry,
409 archives=web.archivelist("tip"), revcount=revcount,
412 archives=web.archivelist("tip"), revcount=revcount,
410 morevars=morevars, lessvars=lessvars, query=query)
413 morevars=morevars, lessvars=lessvars, query=query)
411
414
412 @webcommand('shortlog')
415 @webcommand('shortlog')
413 def shortlog(web, req, tmpl):
416 def shortlog(web, req, tmpl):
414 """
417 """
415 /shortlog
418 /shortlog
416 ---------
419 ---------
417
420
418 Show basic information about a set of changesets.
421 Show basic information about a set of changesets.
419
422
420 This accepts the same parameters as the ``changelog`` handler. The only
423 This accepts the same parameters as the ``changelog`` handler. The only
421 difference is the ``shortlog`` template will be rendered instead of the
424 difference is the ``shortlog`` template will be rendered instead of the
422 ``changelog`` template.
425 ``changelog`` template.
423 """
426 """
424 return changelog(web, req, tmpl, shortlog=True)
427 return changelog(web, req, tmpl, shortlog=True)
425
428
426 @webcommand('changeset')
429 @webcommand('changeset')
427 def changeset(web, req, tmpl):
430 def changeset(web, req, tmpl):
428 """
431 """
429 /changeset[/{revision}]
432 /changeset[/{revision}]
430 -----------------------
433 -----------------------
431
434
432 Show information about a single changeset.
435 Show information about a single changeset.
433
436
434 A URL path argument is the changeset identifier to show. See ``hg help
437 A URL path argument is the changeset identifier to show. See ``hg help
435 revisions`` for possible values. If not defined, the ``tip`` changeset
438 revisions`` for possible values. If not defined, the ``tip`` changeset
436 will be shown.
439 will be shown.
437
440
438 The ``changeset`` template is rendered. Contents of the ``changesettag``,
441 The ``changeset`` template is rendered. Contents of the ``changesettag``,
439 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
442 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
440 templates related to diffs may all be used to produce the output.
443 templates related to diffs may all be used to produce the output.
441 """
444 """
442 ctx = webutil.changectx(web.repo, req)
445 ctx = webutil.changectx(web.repo, req)
443
446
444 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
447 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
445
448
446 rev = webcommand('rev')(changeset)
449 rev = webcommand('rev')(changeset)
447
450
448 def decodepath(path):
451 def decodepath(path):
449 """Hook for mapping a path in the repository to a path in the
452 """Hook for mapping a path in the repository to a path in the
450 working copy.
453 working copy.
451
454
452 Extensions (e.g., largefiles) can override this to remap files in
455 Extensions (e.g., largefiles) can override this to remap files in
453 the virtual file system presented by the manifest command below."""
456 the virtual file system presented by the manifest command below."""
454 return path
457 return path
455
458
456 @webcommand('manifest')
459 @webcommand('manifest')
457 def manifest(web, req, tmpl):
460 def manifest(web, req, tmpl):
458 """
461 """
459 /manifest[/{revision}[/{path}]]
462 /manifest[/{revision}[/{path}]]
460 -------------------------------
463 -------------------------------
461
464
462 Show information about a directory.
465 Show information about a directory.
463
466
464 If the URL path arguments are omitted, information about the root
467 If the URL path arguments are omitted, information about the root
465 directory for the ``tip`` changeset will be shown.
468 directory for the ``tip`` changeset will be shown.
466
469
467 Because this handler can only show information for directories, it
470 Because this handler can only show information for directories, it
468 is recommended to use the ``file`` handler instead, as it can handle both
471 is recommended to use the ``file`` handler instead, as it can handle both
469 directories and files.
472 directories and files.
470
473
471 The ``manifest`` template will be rendered for this handler.
474 The ``manifest`` template will be rendered for this handler.
472 """
475 """
473 ctx = webutil.changectx(web.repo, req)
476 if 'node' in req.form:
477 ctx = webutil.changectx(web.repo, req)
478 symrev = webutil.symrevorshortnode(req, ctx)
479 else:
480 ctx = web.repo['tip']
481 symrev = 'tip'
474 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
482 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
475 mf = ctx.manifest()
483 mf = ctx.manifest()
476 node = ctx.node()
484 node = ctx.node()
477
485
478 files = {}
486 files = {}
479 dirs = {}
487 dirs = {}
480 parity = paritygen(web.stripecount)
488 parity = paritygen(web.stripecount)
481
489
482 if path and path[-1] != "/":
490 if path and path[-1] != "/":
483 path += "/"
491 path += "/"
484 l = len(path)
492 l = len(path)
485 abspath = "/" + path
493 abspath = "/" + path
486
494
487 for full, n in mf.iteritems():
495 for full, n in mf.iteritems():
488 # the virtual path (working copy path) used for the full
496 # the virtual path (working copy path) used for the full
489 # (repository) path
497 # (repository) path
490 f = decodepath(full)
498 f = decodepath(full)
491
499
492 if f[:l] != path:
500 if f[:l] != path:
493 continue
501 continue
494 remain = f[l:]
502 remain = f[l:]
495 elements = remain.split('/')
503 elements = remain.split('/')
496 if len(elements) == 1:
504 if len(elements) == 1:
497 files[remain] = full
505 files[remain] = full
498 else:
506 else:
499 h = dirs # need to retain ref to dirs (root)
507 h = dirs # need to retain ref to dirs (root)
500 for elem in elements[0:-1]:
508 for elem in elements[0:-1]:
501 if elem not in h:
509 if elem not in h:
502 h[elem] = {}
510 h[elem] = {}
503 h = h[elem]
511 h = h[elem]
504 if len(h) > 1:
512 if len(h) > 1:
505 break
513 break
506 h[None] = None # denotes files present
514 h[None] = None # denotes files present
507
515
508 if mf and not files and not dirs:
516 if mf and not files and not dirs:
509 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
517 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
510
518
511 def filelist(**map):
519 def filelist(**map):
512 for f in sorted(files):
520 for f in sorted(files):
513 full = files[f]
521 full = files[f]
514
522
515 fctx = ctx.filectx(full)
523 fctx = ctx.filectx(full)
516 yield {"file": full,
524 yield {"file": full,
517 "parity": parity.next(),
525 "parity": parity.next(),
518 "basename": f,
526 "basename": f,
519 "date": fctx.date(),
527 "date": fctx.date(),
520 "size": fctx.size(),
528 "size": fctx.size(),
521 "permissions": mf.flags(full)}
529 "permissions": mf.flags(full)}
522
530
523 def dirlist(**map):
531 def dirlist(**map):
524 for d in sorted(dirs):
532 for d in sorted(dirs):
525
533
526 emptydirs = []
534 emptydirs = []
527 h = dirs[d]
535 h = dirs[d]
528 while isinstance(h, dict) and len(h) == 1:
536 while isinstance(h, dict) and len(h) == 1:
529 k, v = h.items()[0]
537 k, v = h.items()[0]
530 if v:
538 if v:
531 emptydirs.append(k)
539 emptydirs.append(k)
532 h = v
540 h = v
533
541
534 path = "%s%s" % (abspath, d)
542 path = "%s%s" % (abspath, d)
535 yield {"parity": parity.next(),
543 yield {"parity": parity.next(),
536 "path": path,
544 "path": path,
537 "emptydirs": "/".join(emptydirs),
545 "emptydirs": "/".join(emptydirs),
538 "basename": d}
546 "basename": d}
539
547
540 return tmpl("manifest",
548 return tmpl("manifest",
541 rev=ctx.rev(),
549 rev=ctx.rev(),
550 symrev=symrev,
542 node=hex(node),
551 node=hex(node),
543 path=abspath,
552 path=abspath,
544 up=webutil.up(abspath),
553 up=webutil.up(abspath),
545 upparity=parity.next(),
554 upparity=parity.next(),
546 fentries=filelist,
555 fentries=filelist,
547 dentries=dirlist,
556 dentries=dirlist,
548 archives=web.archivelist(hex(node)),
557 archives=web.archivelist(hex(node)),
549 tags=webutil.nodetagsdict(web.repo, node),
558 tags=webutil.nodetagsdict(web.repo, node),
550 bookmarks=webutil.nodebookmarksdict(web.repo, node),
559 bookmarks=webutil.nodebookmarksdict(web.repo, node),
551 branch=webutil.nodebranchnodefault(ctx),
560 branch=webutil.nodebranchnodefault(ctx),
552 inbranch=webutil.nodeinbranch(web.repo, ctx),
561 inbranch=webutil.nodeinbranch(web.repo, ctx),
553 branches=webutil.nodebranchdict(web.repo, ctx))
562 branches=webutil.nodebranchdict(web.repo, ctx))
554
563
555 @webcommand('tags')
564 @webcommand('tags')
556 def tags(web, req, tmpl):
565 def tags(web, req, tmpl):
557 """
566 """
558 /tags
567 /tags
559 -----
568 -----
560
569
561 Show information about tags.
570 Show information about tags.
562
571
563 No arguments are accepted.
572 No arguments are accepted.
564
573
565 The ``tags`` template is rendered.
574 The ``tags`` template is rendered.
566 """
575 """
567 i = list(reversed(web.repo.tagslist()))
576 i = list(reversed(web.repo.tagslist()))
568 parity = paritygen(web.stripecount)
577 parity = paritygen(web.stripecount)
569
578
570 def entries(notip, latestonly, **map):
579 def entries(notip, latestonly, **map):
571 t = i
580 t = i
572 if notip:
581 if notip:
573 t = [(k, n) for k, n in i if k != "tip"]
582 t = [(k, n) for k, n in i if k != "tip"]
574 if latestonly:
583 if latestonly:
575 t = t[:1]
584 t = t[:1]
576 for k, n in t:
585 for k, n in t:
577 yield {"parity": parity.next(),
586 yield {"parity": parity.next(),
578 "tag": k,
587 "tag": k,
579 "date": web.repo[n].date(),
588 "date": web.repo[n].date(),
580 "node": hex(n)}
589 "node": hex(n)}
581
590
582 return tmpl("tags",
591 return tmpl("tags",
583 node=hex(web.repo.changelog.tip()),
592 node=hex(web.repo.changelog.tip()),
584 entries=lambda **x: entries(False, False, **x),
593 entries=lambda **x: entries(False, False, **x),
585 entriesnotip=lambda **x: entries(True, False, **x),
594 entriesnotip=lambda **x: entries(True, False, **x),
586 latestentry=lambda **x: entries(True, True, **x))
595 latestentry=lambda **x: entries(True, True, **x))
587
596
588 @webcommand('bookmarks')
597 @webcommand('bookmarks')
589 def bookmarks(web, req, tmpl):
598 def bookmarks(web, req, tmpl):
590 """
599 """
591 /bookmarks
600 /bookmarks
592 ----------
601 ----------
593
602
594 Show information about bookmarks.
603 Show information about bookmarks.
595
604
596 No arguments are accepted.
605 No arguments are accepted.
597
606
598 The ``bookmarks`` template is rendered.
607 The ``bookmarks`` template is rendered.
599 """
608 """
600 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
609 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
601 parity = paritygen(web.stripecount)
610 parity = paritygen(web.stripecount)
602
611
603 def entries(latestonly, **map):
612 def entries(latestonly, **map):
604 if latestonly:
613 if latestonly:
605 t = [min(i)]
614 t = [min(i)]
606 else:
615 else:
607 t = sorted(i)
616 t = sorted(i)
608 for k, n in t:
617 for k, n in t:
609 yield {"parity": parity.next(),
618 yield {"parity": parity.next(),
610 "bookmark": k,
619 "bookmark": k,
611 "date": web.repo[n].date(),
620 "date": web.repo[n].date(),
612 "node": hex(n)}
621 "node": hex(n)}
613
622
614 return tmpl("bookmarks",
623 return tmpl("bookmarks",
615 node=hex(web.repo.changelog.tip()),
624 node=hex(web.repo.changelog.tip()),
616 entries=lambda **x: entries(latestonly=False, **x),
625 entries=lambda **x: entries(latestonly=False, **x),
617 latestentry=lambda **x: entries(latestonly=True, **x))
626 latestentry=lambda **x: entries(latestonly=True, **x))
618
627
619 @webcommand('branches')
628 @webcommand('branches')
620 def branches(web, req, tmpl):
629 def branches(web, req, tmpl):
621 """
630 """
622 /branches
631 /branches
623 ---------
632 ---------
624
633
625 Show information about branches.
634 Show information about branches.
626
635
627 All known branches are contained in the output, even closed branches.
636 All known branches are contained in the output, even closed branches.
628
637
629 No arguments are accepted.
638 No arguments are accepted.
630
639
631 The ``branches`` template is rendered.
640 The ``branches`` template is rendered.
632 """
641 """
633 tips = []
642 tips = []
634 heads = web.repo.heads()
643 heads = web.repo.heads()
635 parity = paritygen(web.stripecount)
644 parity = paritygen(web.stripecount)
636 sortkey = lambda item: (not item[1], item[0].rev())
645 sortkey = lambda item: (not item[1], item[0].rev())
637
646
638 def entries(limit, **map):
647 def entries(limit, **map):
639 count = 0
648 count = 0
640 if not tips:
649 if not tips:
641 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
650 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
642 tips.append((web.repo[tip], closed))
651 tips.append((web.repo[tip], closed))
643 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
652 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
644 if limit > 0 and count >= limit:
653 if limit > 0 and count >= limit:
645 return
654 return
646 count += 1
655 count += 1
647 if closed:
656 if closed:
648 status = 'closed'
657 status = 'closed'
649 elif ctx.node() not in heads:
658 elif ctx.node() not in heads:
650 status = 'inactive'
659 status = 'inactive'
651 else:
660 else:
652 status = 'open'
661 status = 'open'
653 yield {'parity': parity.next(),
662 yield {'parity': parity.next(),
654 'branch': ctx.branch(),
663 'branch': ctx.branch(),
655 'status': status,
664 'status': status,
656 'node': ctx.hex(),
665 'node': ctx.hex(),
657 'date': ctx.date()}
666 'date': ctx.date()}
658
667
659 return tmpl('branches', node=hex(web.repo.changelog.tip()),
668 return tmpl('branches', node=hex(web.repo.changelog.tip()),
660 entries=lambda **x: entries(0, **x),
669 entries=lambda **x: entries(0, **x),
661 latestentry=lambda **x: entries(1, **x))
670 latestentry=lambda **x: entries(1, **x))
662
671
663 @webcommand('summary')
672 @webcommand('summary')
664 def summary(web, req, tmpl):
673 def summary(web, req, tmpl):
665 """
674 """
666 /summary
675 /summary
667 --------
676 --------
668
677
669 Show a summary of repository state.
678 Show a summary of repository state.
670
679
671 Information about the latest changesets, bookmarks, tags, and branches
680 Information about the latest changesets, bookmarks, tags, and branches
672 is captured by this handler.
681 is captured by this handler.
673
682
674 The ``summary`` template is rendered.
683 The ``summary`` template is rendered.
675 """
684 """
676 i = reversed(web.repo.tagslist())
685 i = reversed(web.repo.tagslist())
677
686
678 def tagentries(**map):
687 def tagentries(**map):
679 parity = paritygen(web.stripecount)
688 parity = paritygen(web.stripecount)
680 count = 0
689 count = 0
681 for k, n in i:
690 for k, n in i:
682 if k == "tip": # skip tip
691 if k == "tip": # skip tip
683 continue
692 continue
684
693
685 count += 1
694 count += 1
686 if count > 10: # limit to 10 tags
695 if count > 10: # limit to 10 tags
687 break
696 break
688
697
689 yield tmpl("tagentry",
698 yield tmpl("tagentry",
690 parity=parity.next(),
699 parity=parity.next(),
691 tag=k,
700 tag=k,
692 node=hex(n),
701 node=hex(n),
693 date=web.repo[n].date())
702 date=web.repo[n].date())
694
703
695 def bookmarks(**map):
704 def bookmarks(**map):
696 parity = paritygen(web.stripecount)
705 parity = paritygen(web.stripecount)
697 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
706 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
698 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
707 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
699 yield {'parity': parity.next(),
708 yield {'parity': parity.next(),
700 'bookmark': k,
709 'bookmark': k,
701 'date': web.repo[n].date(),
710 'date': web.repo[n].date(),
702 'node': hex(n)}
711 'node': hex(n)}
703
712
704 def branches(**map):
713 def branches(**map):
705 parity = paritygen(web.stripecount)
714 parity = paritygen(web.stripecount)
706
715
707 b = web.repo.branchmap()
716 b = web.repo.branchmap()
708 l = [(-web.repo.changelog.rev(tip), tip, tag)
717 l = [(-web.repo.changelog.rev(tip), tip, tag)
709 for tag, heads, tip, closed in b.iterbranches()]
718 for tag, heads, tip, closed in b.iterbranches()]
710 for r, n, t in sorted(l):
719 for r, n, t in sorted(l):
711 yield {'parity': parity.next(),
720 yield {'parity': parity.next(),
712 'branch': t,
721 'branch': t,
713 'node': hex(n),
722 'node': hex(n),
714 'date': web.repo[n].date()}
723 'date': web.repo[n].date()}
715
724
716 def changelist(**map):
725 def changelist(**map):
717 parity = paritygen(web.stripecount, offset=start - end)
726 parity = paritygen(web.stripecount, offset=start - end)
718 l = [] # build a list in forward order for efficiency
727 l = [] # build a list in forward order for efficiency
719 revs = []
728 revs = []
720 if start < end:
729 if start < end:
721 revs = web.repo.changelog.revs(start, end - 1)
730 revs = web.repo.changelog.revs(start, end - 1)
722 for i in revs:
731 for i in revs:
723 ctx = web.repo[i]
732 ctx = web.repo[i]
724 n = ctx.node()
733 n = ctx.node()
725 hn = hex(n)
734 hn = hex(n)
726
735
727 l.append(tmpl(
736 l.append(tmpl(
728 'shortlogentry',
737 'shortlogentry',
729 parity=parity.next(),
738 parity=parity.next(),
730 author=ctx.user(),
739 author=ctx.user(),
731 desc=ctx.description(),
740 desc=ctx.description(),
732 extra=ctx.extra(),
741 extra=ctx.extra(),
733 date=ctx.date(),
742 date=ctx.date(),
734 rev=i,
743 rev=i,
735 node=hn,
744 node=hn,
736 tags=webutil.nodetagsdict(web.repo, n),
745 tags=webutil.nodetagsdict(web.repo, n),
737 bookmarks=webutil.nodebookmarksdict(web.repo, n),
746 bookmarks=webutil.nodebookmarksdict(web.repo, n),
738 inbranch=webutil.nodeinbranch(web.repo, ctx),
747 inbranch=webutil.nodeinbranch(web.repo, ctx),
739 branches=webutil.nodebranchdict(web.repo, ctx)))
748 branches=webutil.nodebranchdict(web.repo, ctx)))
740
749
741 l.reverse()
750 l.reverse()
742 yield l
751 yield l
743
752
744 tip = web.repo['tip']
753 tip = web.repo['tip']
745 count = len(web.repo)
754 count = len(web.repo)
746 start = max(0, count - web.maxchanges)
755 start = max(0, count - web.maxchanges)
747 end = min(count, start + web.maxchanges)
756 end = min(count, start + web.maxchanges)
748
757
749 return tmpl("summary",
758 return tmpl("summary",
750 desc=web.config("web", "description", "unknown"),
759 desc=web.config("web", "description", "unknown"),
751 owner=get_contact(web.config) or "unknown",
760 owner=get_contact(web.config) or "unknown",
752 lastchange=tip.date(),
761 lastchange=tip.date(),
753 tags=tagentries,
762 tags=tagentries,
754 bookmarks=bookmarks,
763 bookmarks=bookmarks,
755 branches=branches,
764 branches=branches,
756 shortlog=changelist,
765 shortlog=changelist,
757 node=tip.hex(),
766 node=tip.hex(),
767 symrev='tip',
758 archives=web.archivelist("tip"))
768 archives=web.archivelist("tip"))
759
769
760 @webcommand('filediff')
770 @webcommand('filediff')
761 def filediff(web, req, tmpl):
771 def filediff(web, req, tmpl):
762 """
772 """
763 /diff/{revision}/{path}
773 /diff/{revision}/{path}
764 -----------------------
774 -----------------------
765
775
766 Show how a file changed in a particular commit.
776 Show how a file changed in a particular commit.
767
777
768 The ``filediff`` template is rendered.
778 The ``filediff`` template is rendered.
769
779
770 This hander is registered under both the ``/diff`` and ``/filediff``
780 This hander is registered under both the ``/diff`` and ``/filediff``
771 paths. ``/diff`` is used in modern code.
781 paths. ``/diff`` is used in modern code.
772 """
782 """
773 fctx, ctx = None, None
783 fctx, ctx = None, None
774 try:
784 try:
775 fctx = webutil.filectx(web.repo, req)
785 fctx = webutil.filectx(web.repo, req)
776 except LookupError:
786 except LookupError:
777 ctx = webutil.changectx(web.repo, req)
787 ctx = webutil.changectx(web.repo, req)
778 path = webutil.cleanpath(web.repo, req.form['file'][0])
788 path = webutil.cleanpath(web.repo, req.form['file'][0])
779 if path not in ctx.files():
789 if path not in ctx.files():
780 raise
790 raise
781
791
782 if fctx is not None:
792 if fctx is not None:
783 n = fctx.node()
793 n = fctx.node()
784 path = fctx.path()
794 path = fctx.path()
785 ctx = fctx.changectx()
795 ctx = fctx.changectx()
786 else:
796 else:
787 n = ctx.node()
797 n = ctx.node()
788 # path already defined in except clause
798 # path already defined in except clause
789
799
790 parity = paritygen(web.stripecount)
800 parity = paritygen(web.stripecount)
791 style = web.config('web', 'style', 'paper')
801 style = web.config('web', 'style', 'paper')
792 if 'style' in req.form:
802 if 'style' in req.form:
793 style = req.form['style'][0]
803 style = req.form['style'][0]
794
804
795 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
805 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
796 if fctx:
806 if fctx:
797 rename = webutil.renamelink(fctx)
807 rename = webutil.renamelink(fctx)
798 ctx = fctx
808 ctx = fctx
799 else:
809 else:
800 rename = []
810 rename = []
801 ctx = ctx
811 ctx = ctx
802 return tmpl("filediff",
812 return tmpl("filediff",
803 file=path,
813 file=path,
804 node=hex(n),
814 node=hex(n),
805 rev=ctx.rev(),
815 rev=ctx.rev(),
816 symrev=webutil.symrevorshortnode(req, ctx),
806 date=ctx.date(),
817 date=ctx.date(),
807 desc=ctx.description(),
818 desc=ctx.description(),
808 extra=ctx.extra(),
819 extra=ctx.extra(),
809 author=ctx.user(),
820 author=ctx.user(),
810 rename=rename,
821 rename=rename,
811 branch=webutil.nodebranchnodefault(ctx),
822 branch=webutil.nodebranchnodefault(ctx),
812 parent=webutil.parents(ctx),
823 parent=webutil.parents(ctx),
813 child=webutil.children(ctx),
824 child=webutil.children(ctx),
814 tags=webutil.nodetagsdict(web.repo, n),
825 tags=webutil.nodetagsdict(web.repo, n),
815 bookmarks=webutil.nodebookmarksdict(web.repo, n),
826 bookmarks=webutil.nodebookmarksdict(web.repo, n),
816 diff=diffs)
827 diff=diffs)
817
828
818 diff = webcommand('diff')(filediff)
829 diff = webcommand('diff')(filediff)
819
830
820 @webcommand('comparison')
831 @webcommand('comparison')
821 def comparison(web, req, tmpl):
832 def comparison(web, req, tmpl):
822 """
833 """
823 /comparison/{revision}/{path}
834 /comparison/{revision}/{path}
824 -----------------------------
835 -----------------------------
825
836
826 Show a comparison between the old and new versions of a file from changes
837 Show a comparison between the old and new versions of a file from changes
827 made on a particular revision.
838 made on a particular revision.
828
839
829 This is similar to the ``diff`` handler. However, this form features
840 This is similar to the ``diff`` handler. However, this form features
830 a split or side-by-side diff rather than a unified diff.
841 a split or side-by-side diff rather than a unified diff.
831
842
832 The ``context`` query string argument can be used to control the lines of
843 The ``context`` query string argument can be used to control the lines of
833 context in the diff.
844 context in the diff.
834
845
835 The ``filecomparison`` template is rendered.
846 The ``filecomparison`` template is rendered.
836 """
847 """
837 ctx = webutil.changectx(web.repo, req)
848 ctx = webutil.changectx(web.repo, req)
838 if 'file' not in req.form:
849 if 'file' not in req.form:
839 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
850 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
840 path = webutil.cleanpath(web.repo, req.form['file'][0])
851 path = webutil.cleanpath(web.repo, req.form['file'][0])
841 rename = path in ctx and webutil.renamelink(ctx[path]) or []
852 rename = path in ctx and webutil.renamelink(ctx[path]) or []
842
853
843 parsecontext = lambda v: v == 'full' and -1 or int(v)
854 parsecontext = lambda v: v == 'full' and -1 or int(v)
844 if 'context' in req.form:
855 if 'context' in req.form:
845 context = parsecontext(req.form['context'][0])
856 context = parsecontext(req.form['context'][0])
846 else:
857 else:
847 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
858 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
848
859
849 def filelines(f):
860 def filelines(f):
850 if util.binary(f.data()):
861 if util.binary(f.data()):
851 mt = mimetypes.guess_type(f.path())[0]
862 mt = mimetypes.guess_type(f.path())[0]
852 if not mt:
863 if not mt:
853 mt = 'application/octet-stream'
864 mt = 'application/octet-stream'
854 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
865 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
855 return f.data().splitlines()
866 return f.data().splitlines()
856
867
857 parent = ctx.p1()
868 parent = ctx.p1()
858 leftrev = parent.rev()
869 leftrev = parent.rev()
859 leftnode = parent.node()
870 leftnode = parent.node()
860 rightrev = ctx.rev()
871 rightrev = ctx.rev()
861 rightnode = ctx.node()
872 rightnode = ctx.node()
862 if path in ctx:
873 if path in ctx:
863 fctx = ctx[path]
874 fctx = ctx[path]
864 rightlines = filelines(fctx)
875 rightlines = filelines(fctx)
865 if path not in parent:
876 if path not in parent:
866 leftlines = ()
877 leftlines = ()
867 else:
878 else:
868 pfctx = parent[path]
879 pfctx = parent[path]
869 leftlines = filelines(pfctx)
880 leftlines = filelines(pfctx)
870 else:
881 else:
871 rightlines = ()
882 rightlines = ()
872 fctx = ctx.parents()[0][path]
883 fctx = ctx.parents()[0][path]
873 leftlines = filelines(fctx)
884 leftlines = filelines(fctx)
874
885
875 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
886 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
876 return tmpl('filecomparison',
887 return tmpl('filecomparison',
877 file=path,
888 file=path,
878 node=hex(ctx.node()),
889 node=hex(ctx.node()),
879 rev=ctx.rev(),
890 rev=ctx.rev(),
891 symrev=webutil.symrevorshortnode(req, ctx),
880 date=ctx.date(),
892 date=ctx.date(),
881 desc=ctx.description(),
893 desc=ctx.description(),
882 extra=ctx.extra(),
894 extra=ctx.extra(),
883 author=ctx.user(),
895 author=ctx.user(),
884 rename=rename,
896 rename=rename,
885 branch=webutil.nodebranchnodefault(ctx),
897 branch=webutil.nodebranchnodefault(ctx),
886 parent=webutil.parents(fctx),
898 parent=webutil.parents(fctx),
887 child=webutil.children(fctx),
899 child=webutil.children(fctx),
888 tags=webutil.nodetagsdict(web.repo, ctx.node()),
900 tags=webutil.nodetagsdict(web.repo, ctx.node()),
889 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
901 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
890 leftrev=leftrev,
902 leftrev=leftrev,
891 leftnode=hex(leftnode),
903 leftnode=hex(leftnode),
892 rightrev=rightrev,
904 rightrev=rightrev,
893 rightnode=hex(rightnode),
905 rightnode=hex(rightnode),
894 comparison=comparison)
906 comparison=comparison)
895
907
896 @webcommand('annotate')
908 @webcommand('annotate')
897 def annotate(web, req, tmpl):
909 def annotate(web, req, tmpl):
898 """
910 """
899 /annotate/{revision}/{path}
911 /annotate/{revision}/{path}
900 ---------------------------
912 ---------------------------
901
913
902 Show changeset information for each line in a file.
914 Show changeset information for each line in a file.
903
915
904 The ``fileannotate`` template is rendered.
916 The ``fileannotate`` template is rendered.
905 """
917 """
906 fctx = webutil.filectx(web.repo, req)
918 fctx = webutil.filectx(web.repo, req)
907 f = fctx.path()
919 f = fctx.path()
908 parity = paritygen(web.stripecount)
920 parity = paritygen(web.stripecount)
909 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
921 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
910 section='annotate', whitespace=True)
922 section='annotate', whitespace=True)
911
923
912 def annotate(**map):
924 def annotate(**map):
913 last = None
925 last = None
914 if util.binary(fctx.data()):
926 if util.binary(fctx.data()):
915 mt = (mimetypes.guess_type(fctx.path())[0]
927 mt = (mimetypes.guess_type(fctx.path())[0]
916 or 'application/octet-stream')
928 or 'application/octet-stream')
917 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
929 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
918 '(binary:%s)' % mt)])
930 '(binary:%s)' % mt)])
919 else:
931 else:
920 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
932 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
921 diffopts=diffopts))
933 diffopts=diffopts))
922 for lineno, ((f, targetline), l) in lines:
934 for lineno, ((f, targetline), l) in lines:
923 fnode = f.filenode()
935 fnode = f.filenode()
924
936
925 if last != fnode:
937 if last != fnode:
926 last = fnode
938 last = fnode
927
939
928 yield {"parity": parity.next(),
940 yield {"parity": parity.next(),
929 "node": f.hex(),
941 "node": f.hex(),
930 "rev": f.rev(),
942 "rev": f.rev(),
931 "author": f.user(),
943 "author": f.user(),
932 "desc": f.description(),
944 "desc": f.description(),
933 "extra": f.extra(),
945 "extra": f.extra(),
934 "file": f.path(),
946 "file": f.path(),
935 "targetline": targetline,
947 "targetline": targetline,
936 "line": l,
948 "line": l,
937 "lineno": lineno + 1,
949 "lineno": lineno + 1,
938 "lineid": "l%d" % (lineno + 1),
950 "lineid": "l%d" % (lineno + 1),
939 "linenumber": "% 6d" % (lineno + 1),
951 "linenumber": "% 6d" % (lineno + 1),
940 "revdate": f.date()}
952 "revdate": f.date()}
941
953
942 return tmpl("fileannotate",
954 return tmpl("fileannotate",
943 file=f,
955 file=f,
944 annotate=annotate,
956 annotate=annotate,
945 path=webutil.up(f),
957 path=webutil.up(f),
946 rev=fctx.rev(),
958 rev=fctx.rev(),
959 symrev=webutil.symrevorshortnode(req, fctx),
947 node=fctx.hex(),
960 node=fctx.hex(),
948 author=fctx.user(),
961 author=fctx.user(),
949 date=fctx.date(),
962 date=fctx.date(),
950 desc=fctx.description(),
963 desc=fctx.description(),
951 extra=fctx.extra(),
964 extra=fctx.extra(),
952 rename=webutil.renamelink(fctx),
965 rename=webutil.renamelink(fctx),
953 branch=webutil.nodebranchnodefault(fctx),
966 branch=webutil.nodebranchnodefault(fctx),
954 parent=webutil.parents(fctx),
967 parent=webutil.parents(fctx),
955 child=webutil.children(fctx),
968 child=webutil.children(fctx),
956 tags=webutil.nodetagsdict(web.repo, fctx.node()),
969 tags=webutil.nodetagsdict(web.repo, fctx.node()),
957 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
970 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
958 permissions=fctx.manifest().flags(f))
971 permissions=fctx.manifest().flags(f))
959
972
960 @webcommand('filelog')
973 @webcommand('filelog')
961 def filelog(web, req, tmpl):
974 def filelog(web, req, tmpl):
962 """
975 """
963 /filelog/{revision}/{path}
976 /filelog/{revision}/{path}
964 --------------------------
977 --------------------------
965
978
966 Show information about the history of a file in the repository.
979 Show information about the history of a file in the repository.
967
980
968 The ``revcount`` query string argument can be defined to control the
981 The ``revcount`` query string argument can be defined to control the
969 maximum number of entries to show.
982 maximum number of entries to show.
970
983
971 The ``filelog`` template will be rendered.
984 The ``filelog`` template will be rendered.
972 """
985 """
973
986
974 try:
987 try:
975 fctx = webutil.filectx(web.repo, req)
988 fctx = webutil.filectx(web.repo, req)
976 f = fctx.path()
989 f = fctx.path()
977 fl = fctx.filelog()
990 fl = fctx.filelog()
978 except error.LookupError:
991 except error.LookupError:
979 f = webutil.cleanpath(web.repo, req.form['file'][0])
992 f = webutil.cleanpath(web.repo, req.form['file'][0])
980 fl = web.repo.file(f)
993 fl = web.repo.file(f)
981 numrevs = len(fl)
994 numrevs = len(fl)
982 if not numrevs: # file doesn't exist at all
995 if not numrevs: # file doesn't exist at all
983 raise
996 raise
984 rev = webutil.changectx(web.repo, req).rev()
997 rev = webutil.changectx(web.repo, req).rev()
985 first = fl.linkrev(0)
998 first = fl.linkrev(0)
986 if rev < first: # current rev is from before file existed
999 if rev < first: # current rev is from before file existed
987 raise
1000 raise
988 frev = numrevs - 1
1001 frev = numrevs - 1
989 while fl.linkrev(frev) > rev:
1002 while fl.linkrev(frev) > rev:
990 frev -= 1
1003 frev -= 1
991 fctx = web.repo.filectx(f, fl.linkrev(frev))
1004 fctx = web.repo.filectx(f, fl.linkrev(frev))
992
1005
993 revcount = web.maxshortchanges
1006 revcount = web.maxshortchanges
994 if 'revcount' in req.form:
1007 if 'revcount' in req.form:
995 try:
1008 try:
996 revcount = int(req.form.get('revcount', [revcount])[0])
1009 revcount = int(req.form.get('revcount', [revcount])[0])
997 revcount = max(revcount, 1)
1010 revcount = max(revcount, 1)
998 tmpl.defaults['sessionvars']['revcount'] = revcount
1011 tmpl.defaults['sessionvars']['revcount'] = revcount
999 except ValueError:
1012 except ValueError:
1000 pass
1013 pass
1001
1014
1002 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1015 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1003 lessvars['revcount'] = max(revcount / 2, 1)
1016 lessvars['revcount'] = max(revcount / 2, 1)
1004 morevars = copy.copy(tmpl.defaults['sessionvars'])
1017 morevars = copy.copy(tmpl.defaults['sessionvars'])
1005 morevars['revcount'] = revcount * 2
1018 morevars['revcount'] = revcount * 2
1006
1019
1007 count = fctx.filerev() + 1
1020 count = fctx.filerev() + 1
1008 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
1021 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
1009 end = min(count, start + revcount) # last rev on this page
1022 end = min(count, start + revcount) # last rev on this page
1010 parity = paritygen(web.stripecount, offset=start - end)
1023 parity = paritygen(web.stripecount, offset=start - end)
1011
1024
1012 def entries():
1025 def entries():
1013 l = []
1026 l = []
1014
1027
1015 repo = web.repo
1028 repo = web.repo
1016 revs = fctx.filelog().revs(start, end - 1)
1029 revs = fctx.filelog().revs(start, end - 1)
1017 for i in revs:
1030 for i in revs:
1018 iterfctx = fctx.filectx(i)
1031 iterfctx = fctx.filectx(i)
1019
1032
1020 l.append({"parity": parity.next(),
1033 l.append({"parity": parity.next(),
1021 "filerev": i,
1034 "filerev": i,
1022 "file": f,
1035 "file": f,
1023 "node": iterfctx.hex(),
1036 "node": iterfctx.hex(),
1024 "author": iterfctx.user(),
1037 "author": iterfctx.user(),
1025 "date": iterfctx.date(),
1038 "date": iterfctx.date(),
1026 "rename": webutil.renamelink(iterfctx),
1039 "rename": webutil.renamelink(iterfctx),
1027 "parent": webutil.parents(iterfctx),
1040 "parent": webutil.parents(iterfctx),
1028 "child": webutil.children(iterfctx),
1041 "child": webutil.children(iterfctx),
1029 "desc": iterfctx.description(),
1042 "desc": iterfctx.description(),
1030 "extra": iterfctx.extra(),
1043 "extra": iterfctx.extra(),
1031 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
1044 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
1032 "bookmarks": webutil.nodebookmarksdict(
1045 "bookmarks": webutil.nodebookmarksdict(
1033 repo, iterfctx.node()),
1046 repo, iterfctx.node()),
1034 "branch": webutil.nodebranchnodefault(iterfctx),
1047 "branch": webutil.nodebranchnodefault(iterfctx),
1035 "inbranch": webutil.nodeinbranch(repo, iterfctx),
1048 "inbranch": webutil.nodeinbranch(repo, iterfctx),
1036 "branches": webutil.nodebranchdict(repo, iterfctx)})
1049 "branches": webutil.nodebranchdict(repo, iterfctx)})
1037 for e in reversed(l):
1050 for e in reversed(l):
1038 yield e
1051 yield e
1039
1052
1040 entries = list(entries())
1053 entries = list(entries())
1041 latestentry = entries[:1]
1054 latestentry = entries[:1]
1042
1055
1043 revnav = webutil.filerevnav(web.repo, fctx.path())
1056 revnav = webutil.filerevnav(web.repo, fctx.path())
1044 nav = revnav.gen(end - 1, revcount, count)
1057 nav = revnav.gen(end - 1, revcount, count)
1045 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
1058 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
1059 symrev=webutil.symrevorshortnode(req, fctx),
1046 entries=entries,
1060 entries=entries,
1047 latestentry=latestentry,
1061 latestentry=latestentry,
1048 revcount=revcount, morevars=morevars, lessvars=lessvars)
1062 revcount=revcount, morevars=morevars, lessvars=lessvars)
1049
1063
1050 @webcommand('archive')
1064 @webcommand('archive')
1051 def archive(web, req, tmpl):
1065 def archive(web, req, tmpl):
1052 """
1066 """
1053 /archive/{revision}.{format}[/{path}]
1067 /archive/{revision}.{format}[/{path}]
1054 -------------------------------------
1068 -------------------------------------
1055
1069
1056 Obtain an archive of repository content.
1070 Obtain an archive of repository content.
1057
1071
1058 The content and type of the archive is defined by a URL path parameter.
1072 The content and type of the archive is defined by a URL path parameter.
1059 ``format`` is the file extension of the archive type to be generated. e.g.
1073 ``format`` is the file extension of the archive type to be generated. e.g.
1060 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1074 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1061 server configuration.
1075 server configuration.
1062
1076
1063 The optional ``path`` URL parameter controls content to include in the
1077 The optional ``path`` URL parameter controls content to include in the
1064 archive. If omitted, every file in the specified revision is present in the
1078 archive. If omitted, every file in the specified revision is present in the
1065 archive. If included, only the specified file or contents of the specified
1079 archive. If included, only the specified file or contents of the specified
1066 directory will be included in the archive.
1080 directory will be included in the archive.
1067
1081
1068 No template is used for this handler. Raw, binary content is generated.
1082 No template is used for this handler. Raw, binary content is generated.
1069 """
1083 """
1070
1084
1071 type_ = req.form.get('type', [None])[0]
1085 type_ = req.form.get('type', [None])[0]
1072 allowed = web.configlist("web", "allow_archive")
1086 allowed = web.configlist("web", "allow_archive")
1073 key = req.form['node'][0]
1087 key = req.form['node'][0]
1074
1088
1075 if type_ not in web.archives:
1089 if type_ not in web.archives:
1076 msg = 'Unsupported archive type: %s' % type_
1090 msg = 'Unsupported archive type: %s' % type_
1077 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1091 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1078
1092
1079 if not ((type_ in allowed or
1093 if not ((type_ in allowed or
1080 web.configbool("web", "allow" + type_, False))):
1094 web.configbool("web", "allow" + type_, False))):
1081 msg = 'Archive type not allowed: %s' % type_
1095 msg = 'Archive type not allowed: %s' % type_
1082 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1096 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1083
1097
1084 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1098 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1085 cnode = web.repo.lookup(key)
1099 cnode = web.repo.lookup(key)
1086 arch_version = key
1100 arch_version = key
1087 if cnode == key or key == 'tip':
1101 if cnode == key or key == 'tip':
1088 arch_version = short(cnode)
1102 arch_version = short(cnode)
1089 name = "%s-%s" % (reponame, arch_version)
1103 name = "%s-%s" % (reponame, arch_version)
1090
1104
1091 ctx = webutil.changectx(web.repo, req)
1105 ctx = webutil.changectx(web.repo, req)
1092 pats = []
1106 pats = []
1093 matchfn = scmutil.match(ctx, [])
1107 matchfn = scmutil.match(ctx, [])
1094 file = req.form.get('file', None)
1108 file = req.form.get('file', None)
1095 if file:
1109 if file:
1096 pats = ['path:' + file[0]]
1110 pats = ['path:' + file[0]]
1097 matchfn = scmutil.match(ctx, pats, default='path')
1111 matchfn = scmutil.match(ctx, pats, default='path')
1098 if pats:
1112 if pats:
1099 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1113 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1100 if not files:
1114 if not files:
1101 raise ErrorResponse(HTTP_NOT_FOUND,
1115 raise ErrorResponse(HTTP_NOT_FOUND,
1102 'file(s) not found: %s' % file[0])
1116 'file(s) not found: %s' % file[0])
1103
1117
1104 mimetype, artype, extension, encoding = web.archive_specs[type_]
1118 mimetype, artype, extension, encoding = web.archive_specs[type_]
1105 headers = [
1119 headers = [
1106 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1120 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1107 ]
1121 ]
1108 if encoding:
1122 if encoding:
1109 headers.append(('Content-Encoding', encoding))
1123 headers.append(('Content-Encoding', encoding))
1110 req.headers.extend(headers)
1124 req.headers.extend(headers)
1111 req.respond(HTTP_OK, mimetype)
1125 req.respond(HTTP_OK, mimetype)
1112
1126
1113 archival.archive(web.repo, req, cnode, artype, prefix=name,
1127 archival.archive(web.repo, req, cnode, artype, prefix=name,
1114 matchfn=matchfn,
1128 matchfn=matchfn,
1115 subrepos=web.configbool("web", "archivesubrepos"))
1129 subrepos=web.configbool("web", "archivesubrepos"))
1116 return []
1130 return []
1117
1131
1118
1132
1119 @webcommand('static')
1133 @webcommand('static')
1120 def static(web, req, tmpl):
1134 def static(web, req, tmpl):
1121 fname = req.form['file'][0]
1135 fname = req.form['file'][0]
1122 # a repo owner may set web.static in .hg/hgrc to get any file
1136 # a repo owner may set web.static in .hg/hgrc to get any file
1123 # readable by the user running the CGI script
1137 # readable by the user running the CGI script
1124 static = web.config("web", "static", None, untrusted=False)
1138 static = web.config("web", "static", None, untrusted=False)
1125 if not static:
1139 if not static:
1126 tp = web.templatepath or templater.templatepaths()
1140 tp = web.templatepath or templater.templatepaths()
1127 if isinstance(tp, str):
1141 if isinstance(tp, str):
1128 tp = [tp]
1142 tp = [tp]
1129 static = [os.path.join(p, 'static') for p in tp]
1143 static = [os.path.join(p, 'static') for p in tp]
1130 staticfile(static, fname, req)
1144 staticfile(static, fname, req)
1131 return []
1145 return []
1132
1146
1133 @webcommand('graph')
1147 @webcommand('graph')
1134 def graph(web, req, tmpl):
1148 def graph(web, req, tmpl):
1135 """
1149 """
1136 /graph[/{revision}]
1150 /graph[/{revision}]
1137 -------------------
1151 -------------------
1138
1152
1139 Show information about the graphical topology of the repository.
1153 Show information about the graphical topology of the repository.
1140
1154
1141 Information rendered by this handler can be used to create visual
1155 Information rendered by this handler can be used to create visual
1142 representations of repository topology.
1156 representations of repository topology.
1143
1157
1144 The ``revision`` URL parameter controls the starting changeset.
1158 The ``revision`` URL parameter controls the starting changeset.
1145
1159
1146 The ``revcount`` query string argument can define the number of changesets
1160 The ``revcount`` query string argument can define the number of changesets
1147 to show information for.
1161 to show information for.
1148
1162
1149 This handler will render the ``graph`` template.
1163 This handler will render the ``graph`` template.
1150 """
1164 """
1151
1165
1152 ctx = webutil.changectx(web.repo, req)
1166 if 'node' in req.form:
1167 ctx = webutil.changectx(web.repo, req)
1168 symrev = webutil.symrevorshortnode(req, ctx)
1169 else:
1170 ctx = web.repo['tip']
1171 symrev = 'tip'
1153 rev = ctx.rev()
1172 rev = ctx.rev()
1154
1173
1155 bg_height = 39
1174 bg_height = 39
1156 revcount = web.maxshortchanges
1175 revcount = web.maxshortchanges
1157 if 'revcount' in req.form:
1176 if 'revcount' in req.form:
1158 try:
1177 try:
1159 revcount = int(req.form.get('revcount', [revcount])[0])
1178 revcount = int(req.form.get('revcount', [revcount])[0])
1160 revcount = max(revcount, 1)
1179 revcount = max(revcount, 1)
1161 tmpl.defaults['sessionvars']['revcount'] = revcount
1180 tmpl.defaults['sessionvars']['revcount'] = revcount
1162 except ValueError:
1181 except ValueError:
1163 pass
1182 pass
1164
1183
1165 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1184 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1166 lessvars['revcount'] = max(revcount / 2, 1)
1185 lessvars['revcount'] = max(revcount / 2, 1)
1167 morevars = copy.copy(tmpl.defaults['sessionvars'])
1186 morevars = copy.copy(tmpl.defaults['sessionvars'])
1168 morevars['revcount'] = revcount * 2
1187 morevars['revcount'] = revcount * 2
1169
1188
1170 count = len(web.repo)
1189 count = len(web.repo)
1171 pos = rev
1190 pos = rev
1172
1191
1173 uprev = min(max(0, count - 1), rev + revcount)
1192 uprev = min(max(0, count - 1), rev + revcount)
1174 downrev = max(0, rev - revcount)
1193 downrev = max(0, rev - revcount)
1175 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1194 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1176
1195
1177 tree = []
1196 tree = []
1178 if pos != -1:
1197 if pos != -1:
1179 allrevs = web.repo.changelog.revs(pos, 0)
1198 allrevs = web.repo.changelog.revs(pos, 0)
1180 revs = []
1199 revs = []
1181 for i in allrevs:
1200 for i in allrevs:
1182 revs.append(i)
1201 revs.append(i)
1183 if len(revs) >= revcount:
1202 if len(revs) >= revcount:
1184 break
1203 break
1185
1204
1186 # We have to feed a baseset to dagwalker as it is expecting smartset
1205 # We have to feed a baseset to dagwalker as it is expecting smartset
1187 # object. This does not have a big impact on hgweb performance itself
1206 # object. This does not have a big impact on hgweb performance itself
1188 # since hgweb graphing code is not itself lazy yet.
1207 # since hgweb graphing code is not itself lazy yet.
1189 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1208 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1190 # As we said one line above... not lazy.
1209 # As we said one line above... not lazy.
1191 tree = list(graphmod.colored(dag, web.repo))
1210 tree = list(graphmod.colored(dag, web.repo))
1192
1211
1193 def getcolumns(tree):
1212 def getcolumns(tree):
1194 cols = 0
1213 cols = 0
1195 for (id, type, ctx, vtx, edges) in tree:
1214 for (id, type, ctx, vtx, edges) in tree:
1196 if type != graphmod.CHANGESET:
1215 if type != graphmod.CHANGESET:
1197 continue
1216 continue
1198 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1217 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1199 max([edge[1] for edge in edges] or [0]))
1218 max([edge[1] for edge in edges] or [0]))
1200 return cols
1219 return cols
1201
1220
1202 def graphdata(usetuples, **map):
1221 def graphdata(usetuples, **map):
1203 data = []
1222 data = []
1204
1223
1205 row = 0
1224 row = 0
1206 for (id, type, ctx, vtx, edges) in tree:
1225 for (id, type, ctx, vtx, edges) in tree:
1207 if type != graphmod.CHANGESET:
1226 if type != graphmod.CHANGESET:
1208 continue
1227 continue
1209 node = str(ctx)
1228 node = str(ctx)
1210 age = templatefilters.age(ctx.date())
1229 age = templatefilters.age(ctx.date())
1211 desc = templatefilters.firstline(ctx.description())
1230 desc = templatefilters.firstline(ctx.description())
1212 desc = cgi.escape(templatefilters.nonempty(desc))
1231 desc = cgi.escape(templatefilters.nonempty(desc))
1213 user = cgi.escape(templatefilters.person(ctx.user()))
1232 user = cgi.escape(templatefilters.person(ctx.user()))
1214 branch = cgi.escape(ctx.branch())
1233 branch = cgi.escape(ctx.branch())
1215 try:
1234 try:
1216 branchnode = web.repo.branchtip(branch)
1235 branchnode = web.repo.branchtip(branch)
1217 except error.RepoLookupError:
1236 except error.RepoLookupError:
1218 branchnode = None
1237 branchnode = None
1219 branch = branch, branchnode == ctx.node()
1238 branch = branch, branchnode == ctx.node()
1220
1239
1221 if usetuples:
1240 if usetuples:
1222 data.append((node, vtx, edges, desc, user, age, branch,
1241 data.append((node, vtx, edges, desc, user, age, branch,
1223 [cgi.escape(x) for x in ctx.tags()],
1242 [cgi.escape(x) for x in ctx.tags()],
1224 [cgi.escape(x) for x in ctx.bookmarks()]))
1243 [cgi.escape(x) for x in ctx.bookmarks()]))
1225 else:
1244 else:
1226 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1245 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1227 'color': (edge[2] - 1) % 6 + 1,
1246 'color': (edge[2] - 1) % 6 + 1,
1228 'width': edge[3], 'bcolor': edge[4]}
1247 'width': edge[3], 'bcolor': edge[4]}
1229 for edge in edges]
1248 for edge in edges]
1230
1249
1231 data.append(
1250 data.append(
1232 {'node': node,
1251 {'node': node,
1233 'col': vtx[0],
1252 'col': vtx[0],
1234 'color': (vtx[1] - 1) % 6 + 1,
1253 'color': (vtx[1] - 1) % 6 + 1,
1235 'edges': edgedata,
1254 'edges': edgedata,
1236 'row': row,
1255 'row': row,
1237 'nextrow': row + 1,
1256 'nextrow': row + 1,
1238 'desc': desc,
1257 'desc': desc,
1239 'user': user,
1258 'user': user,
1240 'age': age,
1259 'age': age,
1241 'bookmarks': webutil.nodebookmarksdict(
1260 'bookmarks': webutil.nodebookmarksdict(
1242 web.repo, ctx.node()),
1261 web.repo, ctx.node()),
1243 'branches': webutil.nodebranchdict(web.repo, ctx),
1262 'branches': webutil.nodebranchdict(web.repo, ctx),
1244 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1263 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1245 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1264 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1246
1265
1247 row += 1
1266 row += 1
1248
1267
1249 return data
1268 return data
1250
1269
1251 cols = getcolumns(tree)
1270 cols = getcolumns(tree)
1252 rows = len(tree)
1271 rows = len(tree)
1253 canvasheight = (rows + 1) * bg_height - 27
1272 canvasheight = (rows + 1) * bg_height - 27
1254
1273
1255 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1274 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1275 uprev=uprev,
1256 lessvars=lessvars, morevars=morevars, downrev=downrev,
1276 lessvars=lessvars, morevars=morevars, downrev=downrev,
1257 cols=cols, rows=rows,
1277 cols=cols, rows=rows,
1258 canvaswidth=(cols + 1) * bg_height,
1278 canvaswidth=(cols + 1) * bg_height,
1259 truecanvasheight=rows * bg_height,
1279 truecanvasheight=rows * bg_height,
1260 canvasheight=canvasheight, bg_height=bg_height,
1280 canvasheight=canvasheight, bg_height=bg_height,
1261 jsdata=lambda **x: graphdata(True, **x),
1281 jsdata=lambda **x: graphdata(True, **x),
1262 nodes=lambda **x: graphdata(False, **x),
1282 nodes=lambda **x: graphdata(False, **x),
1263 node=ctx.hex(), changenav=changenav)
1283 node=ctx.hex(), changenav=changenav)
1264
1284
1265 def _getdoc(e):
1285 def _getdoc(e):
1266 doc = e[0].__doc__
1286 doc = e[0].__doc__
1267 if doc:
1287 if doc:
1268 doc = _(doc).split('\n')[0]
1288 doc = _(doc).split('\n')[0]
1269 else:
1289 else:
1270 doc = _('(no help text available)')
1290 doc = _('(no help text available)')
1271 return doc
1291 return doc
1272
1292
1273 @webcommand('help')
1293 @webcommand('help')
1274 def help(web, req, tmpl):
1294 def help(web, req, tmpl):
1275 """
1295 """
1276 /help[/{topic}]
1296 /help[/{topic}]
1277 ---------------
1297 ---------------
1278
1298
1279 Render help documentation.
1299 Render help documentation.
1280
1300
1281 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1301 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1282 is defined, that help topic will be rendered. If not, an index of
1302 is defined, that help topic will be rendered. If not, an index of
1283 available help topics will be rendered.
1303 available help topics will be rendered.
1284
1304
1285 The ``help`` template will be rendered when requesting help for a topic.
1305 The ``help`` template will be rendered when requesting help for a topic.
1286 ``helptopics`` will be rendered for the index of help topics.
1306 ``helptopics`` will be rendered for the index of help topics.
1287 """
1307 """
1288 from mercurial import commands # avoid cycle
1308 from mercurial import commands # avoid cycle
1289 from mercurial import help as helpmod # avoid cycle
1309 from mercurial import help as helpmod # avoid cycle
1290
1310
1291 topicname = req.form.get('node', [None])[0]
1311 topicname = req.form.get('node', [None])[0]
1292 if not topicname:
1312 if not topicname:
1293 def topics(**map):
1313 def topics(**map):
1294 for entries, summary, _doc in helpmod.helptable:
1314 for entries, summary, _doc in helpmod.helptable:
1295 yield {'topic': entries[0], 'summary': summary}
1315 yield {'topic': entries[0], 'summary': summary}
1296
1316
1297 early, other = [], []
1317 early, other = [], []
1298 primary = lambda s: s.split('|')[0]
1318 primary = lambda s: s.split('|')[0]
1299 for c, e in commands.table.iteritems():
1319 for c, e in commands.table.iteritems():
1300 doc = _getdoc(e)
1320 doc = _getdoc(e)
1301 if 'DEPRECATED' in doc or c.startswith('debug'):
1321 if 'DEPRECATED' in doc or c.startswith('debug'):
1302 continue
1322 continue
1303 cmd = primary(c)
1323 cmd = primary(c)
1304 if cmd.startswith('^'):
1324 if cmd.startswith('^'):
1305 early.append((cmd[1:], doc))
1325 early.append((cmd[1:], doc))
1306 else:
1326 else:
1307 other.append((cmd, doc))
1327 other.append((cmd, doc))
1308
1328
1309 early.sort()
1329 early.sort()
1310 other.sort()
1330 other.sort()
1311
1331
1312 def earlycommands(**map):
1332 def earlycommands(**map):
1313 for c, doc in early:
1333 for c, doc in early:
1314 yield {'topic': c, 'summary': doc}
1334 yield {'topic': c, 'summary': doc}
1315
1335
1316 def othercommands(**map):
1336 def othercommands(**map):
1317 for c, doc in other:
1337 for c, doc in other:
1318 yield {'topic': c, 'summary': doc}
1338 yield {'topic': c, 'summary': doc}
1319
1339
1320 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1340 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1321 othercommands=othercommands, title='Index')
1341 othercommands=othercommands, title='Index')
1322
1342
1323 u = webutil.wsgiui()
1343 u = webutil.wsgiui()
1324 u.verbose = True
1344 u.verbose = True
1325 try:
1345 try:
1326 doc = helpmod.help_(u, topicname)
1346 doc = helpmod.help_(u, topicname)
1327 except error.UnknownCommand:
1347 except error.UnknownCommand:
1328 raise ErrorResponse(HTTP_NOT_FOUND)
1348 raise ErrorResponse(HTTP_NOT_FOUND)
1329 return tmpl('help', topic=topicname, doc=doc)
1349 return tmpl('help', topic=topicname, doc=doc)
1330
1350
1331 # tell hggettext to extract docstrings from these functions:
1351 # tell hggettext to extract docstrings from these functions:
1332 i18nfunctions = commands.values()
1352 i18nfunctions = commands.values()
@@ -1,503 +1,510 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, copy
9 import os, copy, urllib
10 from mercurial import match, patch, error, ui, util, pathutil, context
10 from mercurial import match, patch, error, ui, util, pathutil, context
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import hex, nullid
12 from mercurial.node import hex, nullid, short
13 from common import ErrorResponse, paritygen
13 from common import ErrorResponse, paritygen
14 from common import HTTP_NOT_FOUND
14 from common import HTTP_NOT_FOUND
15 import difflib
15 import difflib
16
16
17 def up(p):
17 def up(p):
18 if p[0] != "/":
18 if p[0] != "/":
19 p = "/" + p
19 p = "/" + p
20 if p[-1] == "/":
20 if p[-1] == "/":
21 p = p[:-1]
21 p = p[:-1]
22 up = os.path.dirname(p)
22 up = os.path.dirname(p)
23 if up == "/":
23 if up == "/":
24 return "/"
24 return "/"
25 return up + "/"
25 return up + "/"
26
26
27 def _navseq(step, firststep=None):
27 def _navseq(step, firststep=None):
28 if firststep:
28 if firststep:
29 yield firststep
29 yield firststep
30 if firststep >= 20 and firststep <= 40:
30 if firststep >= 20 and firststep <= 40:
31 firststep = 50
31 firststep = 50
32 yield firststep
32 yield firststep
33 assert step > 0
33 assert step > 0
34 assert firststep > 0
34 assert firststep > 0
35 while step <= firststep:
35 while step <= firststep:
36 step *= 10
36 step *= 10
37 while True:
37 while True:
38 yield 1 * step
38 yield 1 * step
39 yield 3 * step
39 yield 3 * step
40 step *= 10
40 step *= 10
41
41
42 class revnav(object):
42 class revnav(object):
43
43
44 def __init__(self, repo):
44 def __init__(self, repo):
45 """Navigation generation object
45 """Navigation generation object
46
46
47 :repo: repo object we generate nav for
47 :repo: repo object we generate nav for
48 """
48 """
49 # used for hex generation
49 # used for hex generation
50 self._revlog = repo.changelog
50 self._revlog = repo.changelog
51
51
52 def __nonzero__(self):
52 def __nonzero__(self):
53 """return True if any revision to navigate over"""
53 """return True if any revision to navigate over"""
54 return self._first() is not None
54 return self._first() is not None
55
55
56 def _first(self):
56 def _first(self):
57 """return the minimum non-filtered changeset or None"""
57 """return the minimum non-filtered changeset or None"""
58 try:
58 try:
59 return iter(self._revlog).next()
59 return iter(self._revlog).next()
60 except StopIteration:
60 except StopIteration:
61 return None
61 return None
62
62
63 def hex(self, rev):
63 def hex(self, rev):
64 return hex(self._revlog.node(rev))
64 return hex(self._revlog.node(rev))
65
65
66 def gen(self, pos, pagelen, limit):
66 def gen(self, pos, pagelen, limit):
67 """computes label and revision id for navigation link
67 """computes label and revision id for navigation link
68
68
69 :pos: is the revision relative to which we generate navigation.
69 :pos: is the revision relative to which we generate navigation.
70 :pagelen: the size of each navigation page
70 :pagelen: the size of each navigation page
71 :limit: how far shall we link
71 :limit: how far shall we link
72
72
73 The return is:
73 The return is:
74 - a single element tuple
74 - a single element tuple
75 - containing a dictionary with a `before` and `after` key
75 - containing a dictionary with a `before` and `after` key
76 - values are generator functions taking arbitrary number of kwargs
76 - values are generator functions taking arbitrary number of kwargs
77 - yield items are dictionaries with `label` and `node` keys
77 - yield items are dictionaries with `label` and `node` keys
78 """
78 """
79 if not self:
79 if not self:
80 # empty repo
80 # empty repo
81 return ({'before': (), 'after': ()},)
81 return ({'before': (), 'after': ()},)
82
82
83 targets = []
83 targets = []
84 for f in _navseq(1, pagelen):
84 for f in _navseq(1, pagelen):
85 if f > limit:
85 if f > limit:
86 break
86 break
87 targets.append(pos + f)
87 targets.append(pos + f)
88 targets.append(pos - f)
88 targets.append(pos - f)
89 targets.sort()
89 targets.sort()
90
90
91 first = self._first()
91 first = self._first()
92 navbefore = [("(%i)" % first, self.hex(first))]
92 navbefore = [("(%i)" % first, self.hex(first))]
93 navafter = []
93 navafter = []
94 for rev in targets:
94 for rev in targets:
95 if rev not in self._revlog:
95 if rev not in self._revlog:
96 continue
96 continue
97 if pos < rev < limit:
97 if pos < rev < limit:
98 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
98 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
99 if 0 < rev < pos:
99 if 0 < rev < pos:
100 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
100 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
101
101
102
102
103 navafter.append(("tip", "tip"))
103 navafter.append(("tip", "tip"))
104
104
105 data = lambda i: {"label": i[0], "node": i[1]}
105 data = lambda i: {"label": i[0], "node": i[1]}
106 return ({'before': lambda **map: (data(i) for i in navbefore),
106 return ({'before': lambda **map: (data(i) for i in navbefore),
107 'after': lambda **map: (data(i) for i in navafter)},)
107 'after': lambda **map: (data(i) for i in navafter)},)
108
108
109 class filerevnav(revnav):
109 class filerevnav(revnav):
110
110
111 def __init__(self, repo, path):
111 def __init__(self, repo, path):
112 """Navigation generation object
112 """Navigation generation object
113
113
114 :repo: repo object we generate nav for
114 :repo: repo object we generate nav for
115 :path: path of the file we generate nav for
115 :path: path of the file we generate nav for
116 """
116 """
117 # used for iteration
117 # used for iteration
118 self._changelog = repo.unfiltered().changelog
118 self._changelog = repo.unfiltered().changelog
119 # used for hex generation
119 # used for hex generation
120 self._revlog = repo.file(path)
120 self._revlog = repo.file(path)
121
121
122 def hex(self, rev):
122 def hex(self, rev):
123 return hex(self._changelog.node(self._revlog.linkrev(rev)))
123 return hex(self._changelog.node(self._revlog.linkrev(rev)))
124
124
125
125
126 def _siblings(siblings=[], hiderev=None):
126 def _siblings(siblings=[], hiderev=None):
127 siblings = [s for s in siblings if s.node() != nullid]
127 siblings = [s for s in siblings if s.node() != nullid]
128 if len(siblings) == 1 and siblings[0].rev() == hiderev:
128 if len(siblings) == 1 and siblings[0].rev() == hiderev:
129 return
129 return
130 for s in siblings:
130 for s in siblings:
131 d = {'node': s.hex(), 'rev': s.rev()}
131 d = {'node': s.hex(), 'rev': s.rev()}
132 d['user'] = s.user()
132 d['user'] = s.user()
133 d['date'] = s.date()
133 d['date'] = s.date()
134 d['description'] = s.description()
134 d['description'] = s.description()
135 d['branch'] = s.branch()
135 d['branch'] = s.branch()
136 if util.safehasattr(s, 'path'):
136 if util.safehasattr(s, 'path'):
137 d['file'] = s.path()
137 d['file'] = s.path()
138 yield d
138 yield d
139
139
140 def parents(ctx, hide=None):
140 def parents(ctx, hide=None):
141 if isinstance(ctx, context.basefilectx):
141 if isinstance(ctx, context.basefilectx):
142 introrev = ctx.introrev()
142 introrev = ctx.introrev()
143 if ctx.changectx().rev() != introrev:
143 if ctx.changectx().rev() != introrev:
144 return _siblings([ctx.repo()[introrev]], hide)
144 return _siblings([ctx.repo()[introrev]], hide)
145 return _siblings(ctx.parents(), hide)
145 return _siblings(ctx.parents(), hide)
146
146
147 def children(ctx, hide=None):
147 def children(ctx, hide=None):
148 return _siblings(ctx.children(), hide)
148 return _siblings(ctx.children(), hide)
149
149
150 def renamelink(fctx):
150 def renamelink(fctx):
151 r = fctx.renamed()
151 r = fctx.renamed()
152 if r:
152 if r:
153 return [{'file': r[0], 'node': hex(r[1])}]
153 return [{'file': r[0], 'node': hex(r[1])}]
154 return []
154 return []
155
155
156 def nodetagsdict(repo, node):
156 def nodetagsdict(repo, node):
157 return [{"name": i} for i in repo.nodetags(node)]
157 return [{"name": i} for i in repo.nodetags(node)]
158
158
159 def nodebookmarksdict(repo, node):
159 def nodebookmarksdict(repo, node):
160 return [{"name": i} for i in repo.nodebookmarks(node)]
160 return [{"name": i} for i in repo.nodebookmarks(node)]
161
161
162 def nodebranchdict(repo, ctx):
162 def nodebranchdict(repo, ctx):
163 branches = []
163 branches = []
164 branch = ctx.branch()
164 branch = ctx.branch()
165 # If this is an empty repo, ctx.node() == nullid,
165 # If this is an empty repo, ctx.node() == nullid,
166 # ctx.branch() == 'default'.
166 # ctx.branch() == 'default'.
167 try:
167 try:
168 branchnode = repo.branchtip(branch)
168 branchnode = repo.branchtip(branch)
169 except error.RepoLookupError:
169 except error.RepoLookupError:
170 branchnode = None
170 branchnode = None
171 if branchnode == ctx.node():
171 if branchnode == ctx.node():
172 branches.append({"name": branch})
172 branches.append({"name": branch})
173 return branches
173 return branches
174
174
175 def nodeinbranch(repo, ctx):
175 def nodeinbranch(repo, ctx):
176 branches = []
176 branches = []
177 branch = ctx.branch()
177 branch = ctx.branch()
178 try:
178 try:
179 branchnode = repo.branchtip(branch)
179 branchnode = repo.branchtip(branch)
180 except error.RepoLookupError:
180 except error.RepoLookupError:
181 branchnode = None
181 branchnode = None
182 if branch != 'default' and branchnode != ctx.node():
182 if branch != 'default' and branchnode != ctx.node():
183 branches.append({"name": branch})
183 branches.append({"name": branch})
184 return branches
184 return branches
185
185
186 def nodebranchnodefault(ctx):
186 def nodebranchnodefault(ctx):
187 branches = []
187 branches = []
188 branch = ctx.branch()
188 branch = ctx.branch()
189 if branch != 'default':
189 if branch != 'default':
190 branches.append({"name": branch})
190 branches.append({"name": branch})
191 return branches
191 return branches
192
192
193 def showtag(repo, tmpl, t1, node=nullid, **args):
193 def showtag(repo, tmpl, t1, node=nullid, **args):
194 for t in repo.nodetags(node):
194 for t in repo.nodetags(node):
195 yield tmpl(t1, tag=t, **args)
195 yield tmpl(t1, tag=t, **args)
196
196
197 def showbookmark(repo, tmpl, t1, node=nullid, **args):
197 def showbookmark(repo, tmpl, t1, node=nullid, **args):
198 for t in repo.nodebookmarks(node):
198 for t in repo.nodebookmarks(node):
199 yield tmpl(t1, bookmark=t, **args)
199 yield tmpl(t1, bookmark=t, **args)
200
200
201 def cleanpath(repo, path):
201 def cleanpath(repo, path):
202 path = path.lstrip('/')
202 path = path.lstrip('/')
203 return pathutil.canonpath(repo.root, '', path)
203 return pathutil.canonpath(repo.root, '', path)
204
204
205 def changeidctx (repo, changeid):
205 def changeidctx (repo, changeid):
206 try:
206 try:
207 ctx = repo[changeid]
207 ctx = repo[changeid]
208 except error.RepoError:
208 except error.RepoError:
209 man = repo.manifest
209 man = repo.manifest
210 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
210 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
211
211
212 return ctx
212 return ctx
213
213
214 def changectx (repo, req):
214 def changectx (repo, req):
215 changeid = "tip"
215 changeid = "tip"
216 if 'node' in req.form:
216 if 'node' in req.form:
217 changeid = req.form['node'][0]
217 changeid = req.form['node'][0]
218 ipos=changeid.find(':')
218 ipos=changeid.find(':')
219 if ipos != -1:
219 if ipos != -1:
220 changeid = changeid[(ipos + 1):]
220 changeid = changeid[(ipos + 1):]
221 elif 'manifest' in req.form:
221 elif 'manifest' in req.form:
222 changeid = req.form['manifest'][0]
222 changeid = req.form['manifest'][0]
223
223
224 return changeidctx(repo, changeid)
224 return changeidctx(repo, changeid)
225
225
226 def basechangectx(repo, req):
226 def basechangectx(repo, req):
227 if 'node' in req.form:
227 if 'node' in req.form:
228 changeid = req.form['node'][0]
228 changeid = req.form['node'][0]
229 ipos=changeid.find(':')
229 ipos=changeid.find(':')
230 if ipos != -1:
230 if ipos != -1:
231 changeid = changeid[:ipos]
231 changeid = changeid[:ipos]
232 return changeidctx(repo, changeid)
232 return changeidctx(repo, changeid)
233
233
234 return None
234 return None
235
235
236 def filectx(repo, req):
236 def filectx(repo, req):
237 if 'file' not in req.form:
237 if 'file' not in req.form:
238 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
238 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
239 path = cleanpath(repo, req.form['file'][0])
239 path = cleanpath(repo, req.form['file'][0])
240 if 'node' in req.form:
240 if 'node' in req.form:
241 changeid = req.form['node'][0]
241 changeid = req.form['node'][0]
242 elif 'filenode' in req.form:
242 elif 'filenode' in req.form:
243 changeid = req.form['filenode'][0]
243 changeid = req.form['filenode'][0]
244 else:
244 else:
245 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
245 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
246 try:
246 try:
247 fctx = repo[changeid][path]
247 fctx = repo[changeid][path]
248 except error.RepoError:
248 except error.RepoError:
249 fctx = repo.filectx(path, fileid=changeid)
249 fctx = repo.filectx(path, fileid=changeid)
250
250
251 return fctx
251 return fctx
252
252
253 def changelistentry(web, ctx, tmpl):
253 def changelistentry(web, ctx, tmpl):
254 '''Obtain a dictionary to be used for entries in a changelist.
254 '''Obtain a dictionary to be used for entries in a changelist.
255
255
256 This function is called when producing items for the "entries" list passed
256 This function is called when producing items for the "entries" list passed
257 to the "shortlog" and "changelog" templates.
257 to the "shortlog" and "changelog" templates.
258 '''
258 '''
259 repo = web.repo
259 repo = web.repo
260 rev = ctx.rev()
260 rev = ctx.rev()
261 n = ctx.node()
261 n = ctx.node()
262 showtags = showtag(repo, tmpl, 'changelogtag', n)
262 showtags = showtag(repo, tmpl, 'changelogtag', n)
263 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
263 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
264
264
265 return {
265 return {
266 "author": ctx.user(),
266 "author": ctx.user(),
267 "parent": parents(ctx, rev - 1),
267 "parent": parents(ctx, rev - 1),
268 "child": children(ctx, rev + 1),
268 "child": children(ctx, rev + 1),
269 "changelogtag": showtags,
269 "changelogtag": showtags,
270 "desc": ctx.description(),
270 "desc": ctx.description(),
271 "extra": ctx.extra(),
271 "extra": ctx.extra(),
272 "date": ctx.date(),
272 "date": ctx.date(),
273 "files": files,
273 "files": files,
274 "rev": rev,
274 "rev": rev,
275 "node": hex(n),
275 "node": hex(n),
276 "tags": nodetagsdict(repo, n),
276 "tags": nodetagsdict(repo, n),
277 "bookmarks": nodebookmarksdict(repo, n),
277 "bookmarks": nodebookmarksdict(repo, n),
278 "inbranch": nodeinbranch(repo, ctx),
278 "inbranch": nodeinbranch(repo, ctx),
279 "branches": nodebranchdict(repo, ctx)
279 "branches": nodebranchdict(repo, ctx)
280 }
280 }
281
281
282 def symrevorshortnode(req, ctx):
283 if 'node' in req.form:
284 return urllib.quote(req.form['node'][0])
285 else:
286 return short(ctx.node())
287
282 def changesetentry(web, req, tmpl, ctx):
288 def changesetentry(web, req, tmpl, ctx):
283 '''Obtain a dictionary to be used to render the "changeset" template.'''
289 '''Obtain a dictionary to be used to render the "changeset" template.'''
284
290
285 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
291 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
286 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
292 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
287 ctx.node())
293 ctx.node())
288 showbranch = nodebranchnodefault(ctx)
294 showbranch = nodebranchnodefault(ctx)
289
295
290 files = []
296 files = []
291 parity = paritygen(web.stripecount)
297 parity = paritygen(web.stripecount)
292 for blockno, f in enumerate(ctx.files()):
298 for blockno, f in enumerate(ctx.files()):
293 template = f in ctx and 'filenodelink' or 'filenolink'
299 template = f in ctx and 'filenodelink' or 'filenolink'
294 files.append(tmpl(template,
300 files.append(tmpl(template,
295 node=ctx.hex(), file=f, blockno=blockno + 1,
301 node=ctx.hex(), file=f, blockno=blockno + 1,
296 parity=parity.next()))
302 parity=parity.next()))
297
303
298 basectx = basechangectx(web.repo, req)
304 basectx = basechangectx(web.repo, req)
299 if basectx is None:
305 if basectx is None:
300 basectx = ctx.p1()
306 basectx = ctx.p1()
301
307
302 style = web.config('web', 'style', 'paper')
308 style = web.config('web', 'style', 'paper')
303 if 'style' in req.form:
309 if 'style' in req.form:
304 style = req.form['style'][0]
310 style = req.form['style'][0]
305
311
306 parity = paritygen(web.stripecount)
312 parity = paritygen(web.stripecount)
307 diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
313 diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
308
314
309 parity = paritygen(web.stripecount)
315 parity = paritygen(web.stripecount)
310 diffstatsgen = diffstatgen(ctx, basectx)
316 diffstatsgen = diffstatgen(ctx, basectx)
311 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
317 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
312
318
313 return dict(
319 return dict(
314 diff=diff,
320 diff=diff,
315 rev=ctx.rev(),
321 rev=ctx.rev(),
316 node=ctx.hex(),
322 node=ctx.hex(),
323 symrev=symrevorshortnode(req, ctx),
317 parent=tuple(parents(ctx)),
324 parent=tuple(parents(ctx)),
318 child=children(ctx),
325 child=children(ctx),
319 basenode=basectx.hex(),
326 basenode=basectx.hex(),
320 changesettag=showtags,
327 changesettag=showtags,
321 changesetbookmark=showbookmarks,
328 changesetbookmark=showbookmarks,
322 changesetbranch=showbranch,
329 changesetbranch=showbranch,
323 author=ctx.user(),
330 author=ctx.user(),
324 desc=ctx.description(),
331 desc=ctx.description(),
325 extra=ctx.extra(),
332 extra=ctx.extra(),
326 date=ctx.date(),
333 date=ctx.date(),
327 phase=ctx.phasestr(),
334 phase=ctx.phasestr(),
328 files=files,
335 files=files,
329 diffsummary=lambda **x: diffsummary(diffstatsgen),
336 diffsummary=lambda **x: diffsummary(diffstatsgen),
330 diffstat=diffstats,
337 diffstat=diffstats,
331 archives=web.archivelist(ctx.hex()),
338 archives=web.archivelist(ctx.hex()),
332 tags=nodetagsdict(web.repo, ctx.node()),
339 tags=nodetagsdict(web.repo, ctx.node()),
333 bookmarks=nodebookmarksdict(web.repo, ctx.node()),
340 bookmarks=nodebookmarksdict(web.repo, ctx.node()),
334 branch=showbranch,
341 branch=showbranch,
335 inbranch=nodeinbranch(web.repo, ctx),
342 inbranch=nodeinbranch(web.repo, ctx),
336 branches=nodebranchdict(web.repo, ctx))
343 branches=nodebranchdict(web.repo, ctx))
337
344
338 def listfilediffs(tmpl, files, node, max):
345 def listfilediffs(tmpl, files, node, max):
339 for f in files[:max]:
346 for f in files[:max]:
340 yield tmpl('filedifflink', node=hex(node), file=f)
347 yield tmpl('filedifflink', node=hex(node), file=f)
341 if len(files) > max:
348 if len(files) > max:
342 yield tmpl('fileellipses')
349 yield tmpl('fileellipses')
343
350
344 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
351 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
345
352
346 def countgen():
353 def countgen():
347 start = 1
354 start = 1
348 while True:
355 while True:
349 yield start
356 yield start
350 start += 1
357 start += 1
351
358
352 blockcount = countgen()
359 blockcount = countgen()
353 def prettyprintlines(diff, blockno):
360 def prettyprintlines(diff, blockno):
354 for lineno, l in enumerate(diff.splitlines(True)):
361 for lineno, l in enumerate(diff.splitlines(True)):
355 difflineno = "%d.%d" % (blockno, lineno + 1)
362 difflineno = "%d.%d" % (blockno, lineno + 1)
356 if l.startswith('+'):
363 if l.startswith('+'):
357 ltype = "difflineplus"
364 ltype = "difflineplus"
358 elif l.startswith('-'):
365 elif l.startswith('-'):
359 ltype = "difflineminus"
366 ltype = "difflineminus"
360 elif l.startswith('@'):
367 elif l.startswith('@'):
361 ltype = "difflineat"
368 ltype = "difflineat"
362 else:
369 else:
363 ltype = "diffline"
370 ltype = "diffline"
364 yield tmpl(ltype,
371 yield tmpl(ltype,
365 line=l,
372 line=l,
366 lineno=lineno + 1,
373 lineno=lineno + 1,
367 lineid="l%s" % difflineno,
374 lineid="l%s" % difflineno,
368 linenumber="% 8s" % difflineno)
375 linenumber="% 8s" % difflineno)
369
376
370 if files:
377 if files:
371 m = match.exact(repo.root, repo.getcwd(), files)
378 m = match.exact(repo.root, repo.getcwd(), files)
372 else:
379 else:
373 m = match.always(repo.root, repo.getcwd())
380 m = match.always(repo.root, repo.getcwd())
374
381
375 diffopts = patch.diffopts(repo.ui, untrusted=True)
382 diffopts = patch.diffopts(repo.ui, untrusted=True)
376 if basectx is None:
383 if basectx is None:
377 parents = ctx.parents()
384 parents = ctx.parents()
378 if parents:
385 if parents:
379 node1 = parents[0].node()
386 node1 = parents[0].node()
380 else:
387 else:
381 node1 = nullid
388 node1 = nullid
382 else:
389 else:
383 node1 = basectx.node()
390 node1 = basectx.node()
384 node2 = ctx.node()
391 node2 = ctx.node()
385
392
386 block = []
393 block = []
387 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
394 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
388 if chunk.startswith('diff') and block:
395 if chunk.startswith('diff') and block:
389 blockno = blockcount.next()
396 blockno = blockcount.next()
390 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
397 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
391 lines=prettyprintlines(''.join(block), blockno))
398 lines=prettyprintlines(''.join(block), blockno))
392 block = []
399 block = []
393 if chunk.startswith('diff') and style != 'raw':
400 if chunk.startswith('diff') and style != 'raw':
394 chunk = ''.join(chunk.splitlines(True)[1:])
401 chunk = ''.join(chunk.splitlines(True)[1:])
395 block.append(chunk)
402 block.append(chunk)
396 blockno = blockcount.next()
403 blockno = blockcount.next()
397 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
404 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
398 lines=prettyprintlines(''.join(block), blockno))
405 lines=prettyprintlines(''.join(block), blockno))
399
406
400 def compare(tmpl, context, leftlines, rightlines):
407 def compare(tmpl, context, leftlines, rightlines):
401 '''Generator function that provides side-by-side comparison data.'''
408 '''Generator function that provides side-by-side comparison data.'''
402
409
403 def compline(type, leftlineno, leftline, rightlineno, rightline):
410 def compline(type, leftlineno, leftline, rightlineno, rightline):
404 lineid = leftlineno and ("l%s" % leftlineno) or ''
411 lineid = leftlineno and ("l%s" % leftlineno) or ''
405 lineid += rightlineno and ("r%s" % rightlineno) or ''
412 lineid += rightlineno and ("r%s" % rightlineno) or ''
406 return tmpl('comparisonline',
413 return tmpl('comparisonline',
407 type=type,
414 type=type,
408 lineid=lineid,
415 lineid=lineid,
409 leftlineno=leftlineno,
416 leftlineno=leftlineno,
410 leftlinenumber="% 6s" % (leftlineno or ''),
417 leftlinenumber="% 6s" % (leftlineno or ''),
411 leftline=leftline or '',
418 leftline=leftline or '',
412 rightlineno=rightlineno,
419 rightlineno=rightlineno,
413 rightlinenumber="% 6s" % (rightlineno or ''),
420 rightlinenumber="% 6s" % (rightlineno or ''),
414 rightline=rightline or '')
421 rightline=rightline or '')
415
422
416 def getblock(opcodes):
423 def getblock(opcodes):
417 for type, llo, lhi, rlo, rhi in opcodes:
424 for type, llo, lhi, rlo, rhi in opcodes:
418 len1 = lhi - llo
425 len1 = lhi - llo
419 len2 = rhi - rlo
426 len2 = rhi - rlo
420 count = min(len1, len2)
427 count = min(len1, len2)
421 for i in xrange(count):
428 for i in xrange(count):
422 yield compline(type=type,
429 yield compline(type=type,
423 leftlineno=llo + i + 1,
430 leftlineno=llo + i + 1,
424 leftline=leftlines[llo + i],
431 leftline=leftlines[llo + i],
425 rightlineno=rlo + i + 1,
432 rightlineno=rlo + i + 1,
426 rightline=rightlines[rlo + i])
433 rightline=rightlines[rlo + i])
427 if len1 > len2:
434 if len1 > len2:
428 for i in xrange(llo + count, lhi):
435 for i in xrange(llo + count, lhi):
429 yield compline(type=type,
436 yield compline(type=type,
430 leftlineno=i + 1,
437 leftlineno=i + 1,
431 leftline=leftlines[i],
438 leftline=leftlines[i],
432 rightlineno=None,
439 rightlineno=None,
433 rightline=None)
440 rightline=None)
434 elif len2 > len1:
441 elif len2 > len1:
435 for i in xrange(rlo + count, rhi):
442 for i in xrange(rlo + count, rhi):
436 yield compline(type=type,
443 yield compline(type=type,
437 leftlineno=None,
444 leftlineno=None,
438 leftline=None,
445 leftline=None,
439 rightlineno=i + 1,
446 rightlineno=i + 1,
440 rightline=rightlines[i])
447 rightline=rightlines[i])
441
448
442 s = difflib.SequenceMatcher(None, leftlines, rightlines)
449 s = difflib.SequenceMatcher(None, leftlines, rightlines)
443 if context < 0:
450 if context < 0:
444 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
451 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
445 else:
452 else:
446 for oc in s.get_grouped_opcodes(n=context):
453 for oc in s.get_grouped_opcodes(n=context):
447 yield tmpl('comparisonblock', lines=getblock(oc))
454 yield tmpl('comparisonblock', lines=getblock(oc))
448
455
449 def diffstatgen(ctx, basectx):
456 def diffstatgen(ctx, basectx):
450 '''Generator function that provides the diffstat data.'''
457 '''Generator function that provides the diffstat data.'''
451
458
452 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
459 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
453 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
460 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
454 while True:
461 while True:
455 yield stats, maxname, maxtotal, addtotal, removetotal, binary
462 yield stats, maxname, maxtotal, addtotal, removetotal, binary
456
463
457 def diffsummary(statgen):
464 def diffsummary(statgen):
458 '''Return a short summary of the diff.'''
465 '''Return a short summary of the diff.'''
459
466
460 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
467 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
461 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
468 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
462 len(stats), addtotal, removetotal)
469 len(stats), addtotal, removetotal)
463
470
464 def diffstat(tmpl, ctx, statgen, parity):
471 def diffstat(tmpl, ctx, statgen, parity):
465 '''Return a diffstat template for each file in the diff.'''
472 '''Return a diffstat template for each file in the diff.'''
466
473
467 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
474 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
468 files = ctx.files()
475 files = ctx.files()
469
476
470 def pct(i):
477 def pct(i):
471 if maxtotal == 0:
478 if maxtotal == 0:
472 return 0
479 return 0
473 return (float(i) / maxtotal) * 100
480 return (float(i) / maxtotal) * 100
474
481
475 fileno = 0
482 fileno = 0
476 for filename, adds, removes, isbinary in stats:
483 for filename, adds, removes, isbinary in stats:
477 template = filename in files and 'diffstatlink' or 'diffstatnolink'
484 template = filename in files and 'diffstatlink' or 'diffstatnolink'
478 total = adds + removes
485 total = adds + removes
479 fileno += 1
486 fileno += 1
480 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
487 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
481 total=total, addpct=pct(adds), removepct=pct(removes),
488 total=total, addpct=pct(adds), removepct=pct(removes),
482 parity=parity.next())
489 parity=parity.next())
483
490
484 class sessionvars(object):
491 class sessionvars(object):
485 def __init__(self, vars, start='?'):
492 def __init__(self, vars, start='?'):
486 self.start = start
493 self.start = start
487 self.vars = vars
494 self.vars = vars
488 def __getitem__(self, key):
495 def __getitem__(self, key):
489 return self.vars[key]
496 return self.vars[key]
490 def __setitem__(self, key, value):
497 def __setitem__(self, key, value):
491 self.vars[key] = value
498 self.vars[key] = value
492 def __copy__(self):
499 def __copy__(self):
493 return sessionvars(copy.copy(self.vars), self.start)
500 return sessionvars(copy.copy(self.vars), self.start)
494 def __iter__(self):
501 def __iter__(self):
495 separator = self.start
502 separator = self.start
496 for key, value in sorted(self.vars.iteritems()):
503 for key, value in sorted(self.vars.iteritems()):
497 yield {'name': key, 'value': str(value), 'separator': separator}
504 yield {'name': key, 'value': str(value), 'separator': separator}
498 separator = '&'
505 separator = '&'
499
506
500 class wsgiui(ui.ui):
507 class wsgiui(ui.ui):
501 # default termwidth breaks under mod_wsgi
508 # default termwidth breaks under mod_wsgi
502 def termwidth(self):
509 def termwidth(self):
503 return 80
510 return 80
General Comments 0
You need to be logged in to leave comments. Login now