##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r6857:e8c2dae4 merge default
parent child Browse files
Show More
@@ -1,219 +1,225
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''add color output to the status and qseries commands
20 20
21 21 This extension modifies the status command to add color to its output to
22 22 reflect file status, and the qseries command to add color to reflect patch
23 23 status (applied, unapplied, missing). Other effects in addition to color,
24 24 like bold and underlined text, are also available. Effects are rendered
25 25 with the ECMA-48 SGR control function (aka ANSI escape codes). This module
26 26 also provides the render_text function, which can be used to add effects to
27 27 any text.
28 28
29 29 To enable this extension, add this to your .hgrc file:
30 30 [extensions]
31 31 color =
32 32
33 33 Default effects my be overriden from the .hgrc file:
34 34
35 35 [color]
36 36 status.modified = blue bold underline red_background
37 37 status.added = green bold
38 38 status.removed = red bold blue_background
39 39 status.deleted = cyan bold underline
40 40 status.unknown = magenta bold underline
41 41 status.ignored = black bold
42 42
43 'none' turns off all effects
43 # 'none' turns off all effects
44 44 status.clean = none
45 45 status.copied = none
46 46
47 47 qseries.applied = blue bold underline
48 48 qseries.unapplied = black bold
49 49 qseries.missing = red bold
50 50 '''
51 51
52 52 import re, sys
53 53
54 54 from mercurial import commands, cmdutil
55 55 from mercurial.i18n import _
56 56
57 57 # start and stop parameters for effects
58 58 _effect_params = { 'none': (0, 0),
59 59 'black': (30, 39),
60 60 'red': (31, 39),
61 61 'green': (32, 39),
62 62 'yellow': (33, 39),
63 63 'blue': (34, 39),
64 64 'magenta': (35, 39),
65 65 'cyan': (36, 39),
66 66 'white': (37, 39),
67 67 'bold': (1, 22),
68 68 'italic': (3, 23),
69 69 'underline': (4, 24),
70 70 'inverse': (7, 27),
71 71 'black_background': (40, 49),
72 72 'red_background': (41, 49),
73 73 'green_background': (42, 49),
74 74 'yellow_background': (43, 49),
75 75 'blue_background': (44, 49),
76 76 'purple_background': (45, 49),
77 77 'cyan_background': (46, 49),
78 78 'white_background': (47, 49), }
79 79
80 80 def render_effects(text, *effects):
81 81 'Wrap text in commands to turn on each effect.'
82 start = []
82 start = [ str(_effect_params['none'][0]) ]
83 83 stop = []
84 84 for effect in effects:
85 85 start.append(str(_effect_params[effect][0]))
86 86 stop.append(str(_effect_params[effect][1]))
87 stop.append(str(_effect_params['none'][1]))
87 88 start = '\033[' + ';'.join(start) + 'm'
88 89 stop = '\033[' + ';'.join(stop) + 'm'
89 90 return start + text + stop
90 91
91 92 def colorstatus(statusfunc, ui, repo, *pats, **opts):
92 93 '''run the status command with colored output'''
93 94
94 95 delimiter = opts['print0'] and '\0' or '\n'
95 96
96 97 # run status and capture it's output
97 98 ui.pushbuffer()
98 99 retval = statusfunc(ui, repo, *pats, **opts)
99 100 # filter out empty strings
100 101 lines = [ line for line in ui.popbuffer().split(delimiter) if line ]
101 102
102 103 if opts['no_status']:
103 104 # if --no-status, run the command again without that option to get
104 105 # output with status abbreviations
105 106 opts['no_status'] = False
106 107 ui.pushbuffer()
107 108 statusfunc(ui, repo, *pats, **opts)
108 109 # filter out empty strings
109 110 lines_with_status = [ line for
110 111 line in ui.popbuffer().split(delimiter) if line ]
111 112 else:
112 113 lines_with_status = lines
113 114
114 115 # apply color to output and display it
115 116 for i in xrange(0, len(lines)):
116 117 status = _status_abbreviations[lines_with_status[i][0]]
117 118 effects = _status_effects[status]
118 119 if effects:
119 120 lines[i] = render_effects(lines[i], *effects)
120 121 sys.stdout.write(lines[i] + delimiter)
121 122 return retval
122 123
123 124 _status_abbreviations = { 'M': 'modified',
124 125 'A': 'added',
125 126 'R': 'removed',
126 127 '!': 'deleted',
127 128 '?': 'unknown',
128 129 'I': 'ignored',
129 130 'C': 'clean',
130 131 ' ': 'copied', }
131 132
132 133 _status_effects = { 'modified': ('blue', 'bold'),
133 134 'added': ('green', 'bold'),
134 135 'removed': ('red', 'bold'),
135 136 'deleted': ('cyan', 'bold', 'underline'),
136 137 'unknown': ('magenta', 'bold', 'underline'),
137 138 'ignored': ('black', 'bold'),
138 139 'clean': ('none', ),
139 140 'copied': ('none', ), }
140 141
141 142 def colorqseries(qseriesfunc, ui, repo, *dummy, **opts):
142 143 '''run the qseries command with colored output'''
143 144 ui.pushbuffer()
144 145 retval = qseriesfunc(ui, repo, **opts)
145 146 patches = ui.popbuffer().splitlines()
146 147 for patch in patches:
148 patchname = patch
149 if opts['summary']:
150 patchname = patchname.split(': ')[0]
151 if ui.verbose:
152 patchname = patchname.split(' ', 2)[-1]
153
147 154 if opts['missing']:
148 155 effects = _patch_effects['missing']
149 # Determine if patch is applied. Search for beginning of output
150 # line in the applied patch list, in case --summary has been used
151 # and output line isn't just the patch name.
156 # Determine if patch is applied.
152 157 elif [ applied for applied in repo.mq.applied
153 if patch.startswith(applied.name) ]:
158 if patchname == applied.name ]:
154 159 effects = _patch_effects['applied']
155 160 else:
156 161 effects = _patch_effects['unapplied']
157 162 sys.stdout.write(render_effects(patch, *effects) + '\n')
158 163 return retval
159 164
160 165 _patch_effects = { 'applied': ('blue', 'bold', 'underline'),
161 166 'missing': ('red', 'bold'),
162 167 'unapplied': ('black', 'bold'), }
163 168
164 169 def uisetup(ui):
165 170 '''Initialize the extension.'''
166 171 nocoloropt = ('', 'no-color', None, _("don't colorize output"))
167 172 _decoratecmd(ui, 'status', commands.table, colorstatus, nocoloropt)
168 173 _configcmdeffects(ui, 'status', _status_effects);
169 if ui.config('extensions', 'hgext.mq', default=None) is not None:
174 if ui.config('extensions', 'hgext.mq') is not None or \
175 ui.config('extensions', 'mq') is not None:
170 176 from hgext import mq
171 177 _decoratecmd(ui, 'qseries', mq.cmdtable, colorqseries, nocoloropt)
172 178 _configcmdeffects(ui, 'qseries', _patch_effects);
173 179
174 180 def _decoratecmd(ui, cmd, table, delegate, *delegateoptions):
175 181 '''Replace the function that implements cmd in table with a decorator.
176 182
177 183 The decorator that becomes the new implementation of cmd calls
178 184 delegate. The delegate's first argument is the replaced function,
179 185 followed by the normal Mercurial command arguments (ui, repo, ...). If
180 186 the delegate adds command options, supply them as delegateoptions.
181 187 '''
182 188 cmdkey, cmdentry = _cmdtableitem(ui, cmd, table)
183 189 decorator = lambda ui, repo, *args, **opts: \
184 190 _colordecorator(delegate, cmdentry[0],
185 191 ui, repo, *args, **opts)
186 192 # make sure 'hg help cmd' still works
187 193 decorator.__doc__ = cmdentry[0].__doc__
188 194 decoratorentry = (decorator,) + cmdentry[1:]
189 195 for option in delegateoptions:
190 196 decoratorentry[1].append(option)
191 197 table[cmdkey] = decoratorentry
192 198
193 199 def _cmdtableitem(ui, cmd, table):
194 200 '''Return key, value from table for cmd, or None if not found.'''
195 201 aliases, entry = cmdutil.findcmd(ui, cmd, table)
196 202 for candidatekey, candidateentry in table.iteritems():
197 203 if candidateentry is entry:
198 204 return candidatekey, entry
199 205
200 206 def _colordecorator(colorfunc, nocolorfunc, ui, repo, *args, **opts):
201 207 '''Delegate to colorfunc or nocolorfunc, depending on conditions.
202 208
203 209 Delegate to colorfunc unless --no-color option is set or output is not
204 210 to a tty.
205 211 '''
206 212 if opts['no_color'] or not sys.stdout.isatty():
207 213 return nocolorfunc(ui, repo, *args, **opts)
208 214 return colorfunc(nocolorfunc, ui, repo, *args, **opts)
209 215
210 216 def _configcmdeffects(ui, cmdname, effectsmap):
211 217 '''Override default effects for cmdname with those from .hgrc file.
212 218
213 219 Entries in the .hgrc file are in the [color] section, and look like
214 220 'cmdname'.'status' (for instance, 'status.modified = blue bold inverse').
215 221 '''
216 222 for status in effectsmap:
217 223 effects = ui.config('color', cmdname + '.' + status)
218 224 if effects:
219 225 effectsmap[status] = re.split('\W+', effects)
@@ -1,606 +1,605
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, mimetypes, re, cgi
9 9 import webutil
10 10 from mercurial import revlog, archival, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 12 from mercurial.util import binary, datestr
13 13 from mercurial.repo import RepoError
14 14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 15 from common import HTTP_OK, HTTP_NOT_FOUND
16 16 from mercurial import graphmod, util
17 17
18 18 # __all__ is populated with the allowed commands. Be sure to add to it if
19 19 # you're adding a new command, or the new command won't work.
20 20
21 21 __all__ = [
22 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
24 24 'archive', 'static', 'graph',
25 25 ]
26 26
27 27 def log(web, req, tmpl):
28 28 if 'file' in req.form and req.form['file'][0]:
29 29 return filelog(web, req, tmpl)
30 30 else:
31 31 return changelog(web, req, tmpl)
32 32
33 33 def rawfile(web, req, tmpl):
34 34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
35 35 if not path:
36 36 content = manifest(web, req, tmpl)
37 37 req.respond(HTTP_OK, web.ctype)
38 38 return content
39 39
40 40 try:
41 41 fctx = webutil.filectx(web.repo, req)
42 42 except revlog.LookupError, inst:
43 43 try:
44 44 content = manifest(web, req, tmpl)
45 45 req.respond(HTTP_OK, web.ctype)
46 46 return content
47 47 except ErrorResponse:
48 48 raise inst
49 49
50 50 path = fctx.path()
51 51 text = fctx.data()
52 52 mt = mimetypes.guess_type(path)[0]
53 53 if mt is None or binary(text):
54 54 mt = mt or 'application/octet-stream'
55 55
56 56 req.respond(HTTP_OK, mt, path, len(text))
57 57 return [text]
58 58
59 59 def _filerevision(web, tmpl, fctx):
60 60 f = fctx.path()
61 61 text = fctx.data()
62 62 fl = fctx.filelog()
63 63 n = fctx.filenode()
64 64 parity = paritygen(web.stripecount)
65 65
66 66 if binary(text):
67 67 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
68 68 text = '(binary:%s)' % mt
69 69
70 70 def lines():
71 71 for lineno, t in enumerate(text.splitlines(1)):
72 72 yield {"line": t,
73 73 "lineid": "l%d" % (lineno + 1),
74 74 "linenumber": "% 6d" % (lineno + 1),
75 75 "parity": parity.next()}
76 76
77 77 return tmpl("filerevision",
78 78 file=f,
79 79 path=webutil.up(f),
80 80 text=lines(),
81 81 rev=fctx.rev(),
82 82 node=hex(fctx.node()),
83 83 author=fctx.user(),
84 84 date=fctx.date(),
85 85 desc=fctx.description(),
86 86 branch=webutil.nodebranchnodefault(fctx),
87 87 parent=webutil.siblings(fctx.parents()),
88 88 child=webutil.siblings(fctx.children()),
89 89 rename=webutil.renamelink(fctx),
90 90 permissions=fctx.manifest().flags(f))
91 91
92 92 def file(web, req, tmpl):
93 93 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
94 if path:
94 if not path:
95 return manifest(web, req, tmpl)
96 try:
97 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
98 except revlog.LookupError, inst:
95 99 try:
96 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
97 except revlog.LookupError, inst:
98 pass
99
100 try:
101 return manifest(web, req, tmpl)
102 except ErrorResponse:
103 raise inst
100 return manifest(web, req, tmpl)
101 except ErrorResponse:
102 raise inst
104 103
105 104 def _search(web, tmpl, query):
106 105
107 106 def changelist(**map):
108 107 cl = web.repo.changelog
109 108 count = 0
110 109 qw = query.lower().split()
111 110
112 111 def revgen():
113 112 for i in xrange(len(cl) - 1, 0, -100):
114 113 l = []
115 114 for j in xrange(max(0, i - 100), i + 1):
116 115 ctx = web.repo[j]
117 116 l.append(ctx)
118 117 l.reverse()
119 118 for e in l:
120 119 yield e
121 120
122 121 for ctx in revgen():
123 122 miss = 0
124 123 for q in qw:
125 124 if not (q in ctx.user().lower() or
126 125 q in ctx.description().lower() or
127 126 q in " ".join(ctx.files()).lower()):
128 127 miss = 1
129 128 break
130 129 if miss:
131 130 continue
132 131
133 132 count += 1
134 133 n = ctx.node()
135 134 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
136 135
137 136 yield tmpl('searchentry',
138 137 parity=parity.next(),
139 138 author=ctx.user(),
140 139 parent=webutil.siblings(ctx.parents()),
141 140 child=webutil.siblings(ctx.children()),
142 141 changelogtag=showtags,
143 142 desc=ctx.description(),
144 143 date=ctx.date(),
145 144 files=web.listfilediffs(tmpl, ctx.files(), n),
146 145 rev=ctx.rev(),
147 146 node=hex(n),
148 147 tags=webutil.nodetagsdict(web.repo, n),
149 148 inbranch=webutil.nodeinbranch(web.repo, ctx),
150 149 branches=webutil.nodebranchdict(web.repo, ctx))
151 150
152 151 if count >= web.maxchanges:
153 152 break
154 153
155 154 cl = web.repo.changelog
156 155 parity = paritygen(web.stripecount)
157 156
158 157 return tmpl('search',
159 158 query=query,
160 159 node=hex(cl.tip()),
161 160 entries=changelist,
162 161 archives=web.archivelist("tip"))
163 162
164 163 def changelog(web, req, tmpl, shortlog = False):
165 164 if 'node' in req.form:
166 165 ctx = webutil.changectx(web.repo, req)
167 166 else:
168 167 if 'rev' in req.form:
169 168 hi = req.form['rev'][0]
170 169 else:
171 170 hi = len(web.repo) - 1
172 171 try:
173 172 ctx = web.repo[hi]
174 173 except RepoError:
175 174 return _search(web, tmpl, hi) # XXX redirect to 404 page?
176 175
177 176 def changelist(limit=0, **map):
178 177 cl = web.repo.changelog
179 178 l = [] # build a list in forward order for efficiency
180 179 for i in xrange(start, end):
181 180 ctx = web.repo[i]
182 181 n = ctx.node()
183 182 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
184 183
185 184 l.insert(0, {"parity": parity.next(),
186 185 "author": ctx.user(),
187 186 "parent": webutil.siblings(ctx.parents(), i - 1),
188 187 "child": webutil.siblings(ctx.children(), i + 1),
189 188 "changelogtag": showtags,
190 189 "desc": ctx.description(),
191 190 "date": ctx.date(),
192 191 "files": web.listfilediffs(tmpl, ctx.files(), n),
193 192 "rev": i,
194 193 "node": hex(n),
195 194 "tags": webutil.nodetagsdict(web.repo, n),
196 195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
197 196 "branches": webutil.nodebranchdict(web.repo, ctx)
198 197 })
199 198
200 199 if limit > 0:
201 200 l = l[:limit]
202 201
203 202 for e in l:
204 203 yield e
205 204
206 205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
207 206 cl = web.repo.changelog
208 207 count = len(cl)
209 208 pos = ctx.rev()
210 209 start = max(0, pos - maxchanges + 1)
211 210 end = min(count, start + maxchanges)
212 211 pos = end - 1
213 212 parity = paritygen(web.stripecount, offset=start-end)
214 213
215 214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
216 215
217 216 return tmpl(shortlog and 'shortlog' or 'changelog',
218 217 changenav=changenav,
219 218 node=hex(ctx.node()),
220 219 rev=pos, changesets=count,
221 220 entries=lambda **x: changelist(limit=0,**x),
222 221 latestentry=lambda **x: changelist(limit=1,**x),
223 222 archives=web.archivelist("tip"))
224 223
225 224 def shortlog(web, req, tmpl):
226 225 return changelog(web, req, tmpl, shortlog = True)
227 226
228 227 def changeset(web, req, tmpl):
229 228 ctx = webutil.changectx(web.repo, req)
230 229 n = ctx.node()
231 230 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
232 231 parents = ctx.parents()
233 232 p1 = parents[0].node()
234 233
235 234 files = []
236 235 parity = paritygen(web.stripecount)
237 236 for f in ctx.files():
238 237 files.append(tmpl("filenodelink",
239 238 node=hex(n), file=f,
240 239 parity=parity.next()))
241 240
242 241 diffs = web.diff(tmpl, p1, n, None)
243 242 return tmpl('changeset',
244 243 diff=diffs,
245 244 rev=ctx.rev(),
246 245 node=hex(n),
247 246 parent=webutil.siblings(parents),
248 247 child=webutil.siblings(ctx.children()),
249 248 changesettag=showtags,
250 249 author=ctx.user(),
251 250 desc=ctx.description(),
252 251 date=ctx.date(),
253 252 files=files,
254 253 archives=web.archivelist(hex(n)),
255 254 tags=webutil.nodetagsdict(web.repo, n),
256 255 branch=webutil.nodebranchnodefault(ctx),
257 256 inbranch=webutil.nodeinbranch(web.repo, ctx),
258 257 branches=webutil.nodebranchdict(web.repo, ctx))
259 258
260 259 rev = changeset
261 260
262 261 def manifest(web, req, tmpl):
263 262 ctx = webutil.changectx(web.repo, req)
264 263 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
265 264 mf = ctx.manifest()
266 265 node = ctx.node()
267 266
268 267 files = {}
269 268 parity = paritygen(web.stripecount)
270 269
271 270 if path and path[-1] != "/":
272 271 path += "/"
273 272 l = len(path)
274 273 abspath = "/" + path
275 274
276 275 for f, n in mf.items():
277 276 if f[:l] != path:
278 277 continue
279 278 remain = f[l:]
280 279 if "/" in remain:
281 280 short = remain[:remain.index("/") + 1] # bleah
282 281 files[short] = (f, None)
283 282 else:
284 283 short = os.path.basename(remain)
285 284 files[short] = (f, n)
286 285
287 286 if not files:
288 287 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
289 288
290 289 def filelist(**map):
291 290 for f in util.sort(files):
292 291 full, fnode = files[f]
293 292 if not fnode:
294 293 continue
295 294
296 295 fctx = ctx.filectx(full)
297 296 yield {"file": full,
298 297 "parity": parity.next(),
299 298 "basename": f,
300 299 "date": fctx.date(),
301 300 "size": fctx.size(),
302 301 "permissions": mf.flags(full)}
303 302
304 303 def dirlist(**map):
305 304 for f in util.sort(files):
306 305 full, fnode = files[f]
307 306 if fnode:
308 307 continue
309 308
310 309 yield {"parity": parity.next(),
311 310 "path": "%s%s" % (abspath, f),
312 311 "basename": f[:-1]}
313 312
314 313 return tmpl("manifest",
315 314 rev=ctx.rev(),
316 315 node=hex(node),
317 316 path=abspath,
318 317 up=webutil.up(abspath),
319 318 upparity=parity.next(),
320 319 fentries=filelist,
321 320 dentries=dirlist,
322 321 archives=web.archivelist(hex(node)),
323 322 tags=webutil.nodetagsdict(web.repo, node),
324 323 inbranch=webutil.nodeinbranch(web.repo, ctx),
325 324 branches=webutil.nodebranchdict(web.repo, ctx))
326 325
327 326 def tags(web, req, tmpl):
328 327 i = web.repo.tagslist()
329 328 i.reverse()
330 329 parity = paritygen(web.stripecount)
331 330
332 331 def entries(notip=False,limit=0, **map):
333 332 count = 0
334 333 for k, n in i:
335 334 if notip and k == "tip":
336 335 continue
337 336 if limit > 0 and count >= limit:
338 337 continue
339 338 count = count + 1
340 339 yield {"parity": parity.next(),
341 340 "tag": k,
342 341 "date": web.repo[n].date(),
343 342 "node": hex(n)}
344 343
345 344 return tmpl("tags",
346 345 node=hex(web.repo.changelog.tip()),
347 346 entries=lambda **x: entries(False,0, **x),
348 347 entriesnotip=lambda **x: entries(True,0, **x),
349 348 latestentry=lambda **x: entries(True,1, **x))
350 349
351 350 def summary(web, req, tmpl):
352 351 i = web.repo.tagslist()
353 352 i.reverse()
354 353
355 354 def tagentries(**map):
356 355 parity = paritygen(web.stripecount)
357 356 count = 0
358 357 for k, n in i:
359 358 if k == "tip": # skip tip
360 359 continue
361 360
362 361 count += 1
363 362 if count > 10: # limit to 10 tags
364 363 break
365 364
366 365 yield tmpl("tagentry",
367 366 parity=parity.next(),
368 367 tag=k,
369 368 node=hex(n),
370 369 date=web.repo[n].date())
371 370
372 371 def branches(**map):
373 372 parity = paritygen(web.stripecount)
374 373
375 374 b = web.repo.branchtags()
376 375 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
377 376 for r,n,t in util.sort(l):
378 377 yield {'parity': parity.next(),
379 378 'branch': t,
380 379 'node': hex(n),
381 380 'date': web.repo[n].date()}
382 381
383 382 def changelist(**map):
384 383 parity = paritygen(web.stripecount, offset=start-end)
385 384 l = [] # build a list in forward order for efficiency
386 385 for i in xrange(start, end):
387 386 ctx = web.repo[i]
388 387 n = ctx.node()
389 388 hn = hex(n)
390 389
391 390 l.insert(0, tmpl(
392 391 'shortlogentry',
393 392 parity=parity.next(),
394 393 author=ctx.user(),
395 394 desc=ctx.description(),
396 395 date=ctx.date(),
397 396 rev=i,
398 397 node=hn,
399 398 tags=webutil.nodetagsdict(web.repo, n),
400 399 inbranch=webutil.nodeinbranch(web.repo, ctx),
401 400 branches=webutil.nodebranchdict(web.repo, ctx)))
402 401
403 402 yield l
404 403
405 404 cl = web.repo.changelog
406 405 count = len(cl)
407 406 start = max(0, count - web.maxchanges)
408 407 end = min(count, start + web.maxchanges)
409 408
410 409 return tmpl("summary",
411 410 desc=web.config("web", "description", "unknown"),
412 411 owner=get_contact(web.config) or "unknown",
413 412 lastchange=cl.read(cl.tip())[2],
414 413 tags=tagentries,
415 414 branches=branches,
416 415 shortlog=changelist,
417 416 node=hex(cl.tip()),
418 417 archives=web.archivelist("tip"))
419 418
420 419 def filediff(web, req, tmpl):
421 420 fctx = webutil.filectx(web.repo, req)
422 421 n = fctx.node()
423 422 path = fctx.path()
424 423 parents = fctx.parents()
425 424 p1 = parents and parents[0].node() or nullid
426 425
427 426 diffs = web.diff(tmpl, p1, n, [path])
428 427 return tmpl("filediff",
429 428 file=path,
430 429 node=hex(n),
431 430 rev=fctx.rev(),
432 431 date=fctx.date(),
433 432 desc=fctx.description(),
434 433 author=fctx.user(),
435 434 rename=webutil.renamelink(fctx),
436 435 branch=webutil.nodebranchnodefault(fctx),
437 436 parent=webutil.siblings(parents),
438 437 child=webutil.siblings(fctx.children()),
439 438 diff=diffs)
440 439
441 440 diff = filediff
442 441
443 442 def annotate(web, req, tmpl):
444 443 fctx = webutil.filectx(web.repo, req)
445 444 f = fctx.path()
446 445 n = fctx.filenode()
447 446 fl = fctx.filelog()
448 447 parity = paritygen(web.stripecount)
449 448
450 449 def annotate(**map):
451 450 last = None
452 451 if binary(fctx.data()):
453 452 mt = (mimetypes.guess_type(fctx.path())[0]
454 453 or 'application/octet-stream')
455 454 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
456 455 '(binary:%s)' % mt)])
457 456 else:
458 457 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
459 458 for lineno, ((f, targetline), l) in lines:
460 459 fnode = f.filenode()
461 460
462 461 if last != fnode:
463 462 last = fnode
464 463
465 464 yield {"parity": parity.next(),
466 465 "node": hex(f.node()),
467 466 "rev": f.rev(),
468 467 "author": f.user(),
469 468 "desc": f.description(),
470 469 "file": f.path(),
471 470 "targetline": targetline,
472 471 "line": l,
473 472 "lineid": "l%d" % (lineno + 1),
474 473 "linenumber": "% 6d" % (lineno + 1)}
475 474
476 475 return tmpl("fileannotate",
477 476 file=f,
478 477 annotate=annotate,
479 478 path=webutil.up(f),
480 479 rev=fctx.rev(),
481 480 node=hex(fctx.node()),
482 481 author=fctx.user(),
483 482 date=fctx.date(),
484 483 desc=fctx.description(),
485 484 rename=webutil.renamelink(fctx),
486 485 branch=webutil.nodebranchnodefault(fctx),
487 486 parent=webutil.siblings(fctx.parents()),
488 487 child=webutil.siblings(fctx.children()),
489 488 permissions=fctx.manifest().flags(f))
490 489
491 490 def filelog(web, req, tmpl):
492 491 fctx = webutil.filectx(web.repo, req)
493 492 f = fctx.path()
494 493 fl = fctx.filelog()
495 494 count = len(fl)
496 495 pagelen = web.maxshortchanges
497 496 pos = fctx.filerev()
498 497 start = max(0, pos - pagelen + 1)
499 498 end = min(count, start + pagelen)
500 499 pos = end - 1
501 500 parity = paritygen(web.stripecount, offset=start-end)
502 501
503 502 def entries(limit=0, **map):
504 503 l = []
505 504
506 505 for i in xrange(start, end):
507 506 ctx = fctx.filectx(i)
508 507 n = fl.node(i)
509 508
510 509 l.insert(0, {"parity": parity.next(),
511 510 "filerev": i,
512 511 "file": f,
513 512 "node": hex(ctx.node()),
514 513 "author": ctx.user(),
515 514 "date": ctx.date(),
516 515 "rename": webutil.renamelink(fctx),
517 516 "parent": webutil.siblings(fctx.parents()),
518 517 "child": webutil.siblings(fctx.children()),
519 518 "desc": ctx.description()})
520 519
521 520 if limit > 0:
522 521 l = l[:limit]
523 522
524 523 for e in l:
525 524 yield e
526 525
527 526 nodefunc = lambda x: fctx.filectx(fileid=x)
528 527 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
529 528 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
530 529 entries=lambda **x: entries(limit=0, **x),
531 530 latestentry=lambda **x: entries(limit=1, **x))
532 531
533 532
534 533 def archive(web, req, tmpl):
535 534 type_ = req.form.get('type', [None])[0]
536 535 allowed = web.configlist("web", "allow_archive")
537 536 key = req.form['node'][0]
538 537
539 538 if not (type_ in web.archives and (type_ in allowed or
540 539 web.configbool("web", "allow" + type_, False))):
541 540 msg = 'Unsupported archive type: %s' % type_
542 541 raise ErrorResponse(HTTP_NOT_FOUND, msg)
543 542
544 543 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
545 544 cnode = web.repo.lookup(key)
546 545 arch_version = key
547 546 if cnode == key or key == 'tip':
548 547 arch_version = short(cnode)
549 548 name = "%s-%s" % (reponame, arch_version)
550 549 mimetype, artype, extension, encoding = web.archive_specs[type_]
551 550 headers = [
552 551 ('Content-Type', mimetype),
553 552 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
554 553 ]
555 554 if encoding:
556 555 headers.append(('Content-Encoding', encoding))
557 556 req.header(headers)
558 557 req.respond(HTTP_OK)
559 558 archival.archive(web.repo, req, cnode, artype, prefix=name)
560 559 return []
561 560
562 561
563 562 def static(web, req, tmpl):
564 563 fname = req.form['file'][0]
565 564 # a repo owner may set web.static in .hg/hgrc to get any file
566 565 # readable by the user running the CGI script
567 566 static = web.config("web", "static",
568 567 os.path.join(web.templatepath, "static"),
569 568 untrusted=False)
570 569 return [staticfile(static, fname, req)]
571 570
572 571 def graph(web, req, tmpl):
573 572 rev = webutil.changectx(web.repo, req).rev()
574 573 bg_height = 39
575 574
576 575 max_rev = len(web.repo) - 1
577 576 revcount = min(max_rev, int(req.form.get('revcount', [25])[0]))
578 577 revnode = web.repo.changelog.node(rev)
579 578 revnode_hex = hex(revnode)
580 579 uprev = min(max_rev, rev + revcount)
581 580 downrev = max(0, rev - revcount)
582 581 lessrev = max(0, rev - revcount / 2)
583 582
584 583 maxchanges = web.maxshortchanges or web.maxchanges
585 584 count = len(web.repo)
586 585 changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx)
587 586
588 587 tree = list(graphmod.graph(web.repo, rev, rev - revcount))
589 588 canvasheight = (len(tree) + 1) * bg_height - 27;
590 589
591 590 data = []
592 591 for i, (ctx, vtx, edges) in enumerate(tree):
593 592 node = short(ctx.node())
594 593 age = templatefilters.age(ctx.date())
595 594 desc = templatefilters.firstline(ctx.description())
596 595 desc = cgi.escape(desc)
597 596 user = cgi.escape(templatefilters.person(ctx.user()))
598 597 branch = ctx.branch()
599 598 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
600 599 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
601 600
602 601 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
603 602 lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1,
604 603 revcountless=revcount / 2, downrev=downrev,
605 604 canvasheight=canvasheight, bg_height=bg_height,
606 605 jsdata=data, node=revnode_hex, changenav=changenav)
General Comments 0
You need to be logged in to leave comments. Login now