##// END OF EJS Templates
hgweb: separate out utility functions
Dirkjan Ochtman -
r6392:2540521d default
parent child Browse files
Show More
@@ -0,0 +1,94 b''
1 # hgweb/webutil.py - utility library for the web interface.
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
8
9 from mercurial.node import hex, nullid
10 from mercurial.repo import RepoError
11 from mercurial import util
12
13 def siblings(siblings=[], hiderev=None, **args):
14 siblings = [s for s in siblings if s.node() != nullid]
15 if len(siblings) == 1 and siblings[0].rev() == hiderev:
16 return
17 for s in siblings:
18 d = {'node': hex(s.node()), 'rev': s.rev()}
19 if hasattr(s, 'path'):
20 d['file'] = s.path()
21 d.update(args)
22 yield d
23
24 def renamelink(fl, node):
25 r = fl.renamed(node)
26 if r:
27 return [dict(file=r[0], node=hex(r[1]))]
28 return []
29
30 def nodetagsdict(repo, node):
31 return [{"name": i} for i in repo.nodetags(node)]
32
33 def nodebranchdict(repo, ctx):
34 branches = []
35 branch = ctx.branch()
36 # If this is an empty repo, ctx.node() == nullid,
37 # ctx.branch() == 'default', but branchtags() is
38 # an empty dict. Using dict.get avoids a traceback.
39 if repo.branchtags().get(branch) == ctx.node():
40 branches.append({"name": branch})
41 return branches
42
43 def nodeinbranch(repo, ctx):
44 branches = []
45 branch = ctx.branch()
46 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
47 branches.append({"name": branch})
48 return branches
49
50 def nodebranchnodefault(ctx):
51 branches = []
52 branch = ctx.branch()
53 if branch != 'default':
54 branches.append({"name": branch})
55 return branches
56
57 def showtag(repo, tmpl, t1, node=nullid, **args):
58 for t in repo.nodetags(node):
59 yield tmpl(t1, tag=t, **args)
60
61 def cleanpath(repo, path):
62 path = path.lstrip('/')
63 return util.canonpath(repo.root, '', path)
64
65 def changectx(repo, req):
66 if 'node' in req.form:
67 changeid = req.form['node'][0]
68 elif 'manifest' in req.form:
69 changeid = req.form['manifest'][0]
70 else:
71 changeid = self.repo.changelog.count() - 1
72
73 try:
74 ctx = repo.changectx(changeid)
75 except RepoError:
76 man = repo.manifest
77 mn = man.lookup(changeid)
78 ctx = repo.changectx(man.linkrev(mn))
79
80 return ctx
81
82 def filectx(repo, req):
83 path = cleanpath(repo, req.form['file'][0])
84 if 'node' in req.form:
85 changeid = req.form['node'][0]
86 else:
87 changeid = req.form['filenode'][0]
88 try:
89 ctx = repo.changectx(changeid)
90 fctx = ctx.filectx(path)
91 except RepoError:
92 fctx = repo.filectx(path, fileid=changeid)
93
94 return fctx
@@ -1,978 +1,892 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re
10 10 from mercurial.node import hex, nullid, short
11 11 from mercurial.repo import RepoError
12 12 from mercurial import mdiff, ui, hg, util, archival, patch, hook
13 13 from mercurial import revlog, templater, templatefilters, changegroup
14 14 from common import get_mtime, style_map, paritygen, countgen, get_contact
15 15 from common import ErrorResponse
16 16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
17 17 from request import wsgirequest
18 import webcommands, protocol
18 import webcommands, protocol, webutil
19 19
20 20 shortcuts = {
21 21 'cl': [('cmd', ['changelog']), ('rev', None)],
22 22 'sl': [('cmd', ['shortlog']), ('rev', None)],
23 23 'cs': [('cmd', ['changeset']), ('node', None)],
24 24 'f': [('cmd', ['file']), ('filenode', None)],
25 25 'fl': [('cmd', ['filelog']), ('filenode', None)],
26 26 'fd': [('cmd', ['filediff']), ('node', None)],
27 27 'fa': [('cmd', ['annotate']), ('filenode', None)],
28 28 'mf': [('cmd', ['manifest']), ('manifest', None)],
29 29 'ca': [('cmd', ['archive']), ('node', None)],
30 30 'tags': [('cmd', ['tags'])],
31 31 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
32 32 'static': [('cmd', ['static']), ('file', None)]
33 33 }
34 34
35 35 def _up(p):
36 36 if p[0] != "/":
37 37 p = "/" + p
38 38 if p[-1] == "/":
39 39 p = p[:-1]
40 40 up = os.path.dirname(p)
41 41 if up == "/":
42 42 return "/"
43 43 return up + "/"
44 44
45 45 def revnavgen(pos, pagelen, limit, nodefunc):
46 46 def seq(factor, limit=None):
47 47 if limit:
48 48 yield limit
49 49 if limit >= 20 and limit <= 40:
50 50 yield 50
51 51 else:
52 52 yield 1 * factor
53 53 yield 3 * factor
54 54 for f in seq(factor * 10):
55 55 yield f
56 56
57 57 def nav(**map):
58 58 l = []
59 59 last = 0
60 60 for f in seq(1, pagelen):
61 61 if f < pagelen or f <= last:
62 62 continue
63 63 if f > limit:
64 64 break
65 65 last = f
66 66 if pos + f < limit:
67 67 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
68 68 if pos - f >= 0:
69 69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
70 70
71 71 try:
72 72 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
73 73
74 74 for label, node in l:
75 75 yield {"label": label, "node": node}
76 76
77 77 yield {"label": "tip", "node": "tip"}
78 78 except RepoError:
79 79 pass
80 80
81 81 return nav
82 82
83 83 class hgweb(object):
84 84 def __init__(self, repo, name=None):
85 85 if isinstance(repo, str):
86 86 parentui = ui.ui(report_untrusted=False, interactive=False)
87 87 self.repo = hg.repository(parentui, repo)
88 88 else:
89 89 self.repo = repo
90 90
91 91 hook.redirect(True)
92 92 self.mtime = -1
93 93 self.reponame = name
94 94 self.archives = 'zip', 'gz', 'bz2'
95 95 self.stripecount = 1
96 96 self._capabilities = None
97 97 # a repo owner may set web.templates in .hg/hgrc to get any file
98 98 # readable by the user running the CGI script
99 99 self.templatepath = self.config("web", "templates",
100 100 templater.templatepath(),
101 101 untrusted=False)
102 102
103 103 # The CGI scripts are often run by a user different from the repo owner.
104 104 # Trust the settings from the .hg/hgrc files by default.
105 105 def config(self, section, name, default=None, untrusted=True):
106 106 return self.repo.ui.config(section, name, default,
107 107 untrusted=untrusted)
108 108
109 109 def configbool(self, section, name, default=False, untrusted=True):
110 110 return self.repo.ui.configbool(section, name, default,
111 111 untrusted=untrusted)
112 112
113 113 def configlist(self, section, name, default=None, untrusted=True):
114 114 return self.repo.ui.configlist(section, name, default,
115 115 untrusted=untrusted)
116 116
117 117 def refresh(self):
118 118 mtime = get_mtime(self.repo.root)
119 119 if mtime != self.mtime:
120 120 self.mtime = mtime
121 121 self.repo = hg.repository(self.repo.ui, self.repo.root)
122 122 self.maxchanges = int(self.config("web", "maxchanges", 10))
123 123 self.stripecount = int(self.config("web", "stripes", 1))
124 124 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
125 125 self.maxfiles = int(self.config("web", "maxfiles", 10))
126 126 self.allowpull = self.configbool("web", "allowpull", True)
127 127 self.encoding = self.config("web", "encoding", util._encoding)
128 128 self._capabilities = None
129 129
130 130 def capabilities(self):
131 131 if self._capabilities is not None:
132 132 return self._capabilities
133 133 caps = ['lookup', 'changegroupsubset']
134 134 if self.configbool('server', 'uncompressed'):
135 135 caps.append('stream=%d' % self.repo.changelog.version)
136 136 if changegroup.bundlepriority:
137 137 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
138 138 self._capabilities = caps
139 139 return caps
140 140
141 141 def run(self):
142 142 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
143 143 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
144 144 import mercurial.hgweb.wsgicgi as wsgicgi
145 145 wsgicgi.launch(self)
146 146
147 147 def __call__(self, env, respond):
148 148 req = wsgirequest(env, respond)
149 149 self.run_wsgi(req)
150 150 return req
151 151
152 152 def run_wsgi(self, req):
153 153
154 154 self.refresh()
155 155
156 156 # expand form shortcuts
157 157
158 158 for k in shortcuts.iterkeys():
159 159 if k in req.form:
160 160 for name, value in shortcuts[k]:
161 161 if value is None:
162 162 value = req.form[k]
163 163 req.form[name] = value
164 164 del req.form[k]
165 165
166 166 # work with CGI variables to create coherent structure
167 167 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
168 168
169 169 req.url = req.env['SCRIPT_NAME']
170 170 if not req.url.endswith('/'):
171 171 req.url += '/'
172 172 if 'REPO_NAME' in req.env:
173 173 req.url += req.env['REPO_NAME'] + '/'
174 174
175 175 if req.env.get('PATH_INFO'):
176 176 parts = req.env.get('PATH_INFO').strip('/').split('/')
177 177 repo_parts = req.env.get('REPO_NAME', '').split('/')
178 178 if parts[:len(repo_parts)] == repo_parts:
179 179 parts = parts[len(repo_parts):]
180 180 query = '/'.join(parts)
181 181 else:
182 182 query = req.env['QUERY_STRING'].split('&', 1)[0]
183 183 query = query.split(';', 1)[0]
184 184
185 185 # translate user-visible url structure to internal structure
186 186
187 187 args = query.split('/', 2)
188 188 if 'cmd' not in req.form and args and args[0]:
189 189
190 190 cmd = args.pop(0)
191 191 style = cmd.rfind('-')
192 192 if style != -1:
193 193 req.form['style'] = [cmd[:style]]
194 194 cmd = cmd[style+1:]
195 195
196 196 # avoid accepting e.g. style parameter as command
197 197 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
198 198 req.form['cmd'] = [cmd]
199 199
200 200 if args and args[0]:
201 201 node = args.pop(0)
202 202 req.form['node'] = [node]
203 203 if args:
204 204 req.form['file'] = args
205 205
206 206 if cmd == 'static':
207 207 req.form['file'] = req.form['node']
208 208 elif cmd == 'archive':
209 209 fn = req.form['node'][0]
210 210 for type_, spec in self.archive_specs.iteritems():
211 211 ext = spec[2]
212 212 if fn.endswith(ext):
213 213 req.form['node'] = [fn[:-len(ext)]]
214 214 req.form['type'] = [type_]
215 215
216 216 # process this if it's a protocol request
217 217
218 218 cmd = req.form.get('cmd', [''])[0]
219 219 if cmd in protocol.__all__:
220 220 method = getattr(protocol, cmd)
221 221 method(self, req)
222 222 return
223 223
224 224 # process the web interface request
225 225
226 226 try:
227 227
228 228 tmpl = self.templater(req)
229 229 ctype = tmpl('mimetype', encoding=self.encoding)
230 230 ctype = templater.stringify(ctype)
231 231
232 232 if cmd == '':
233 233 req.form['cmd'] = [tmpl.cache['default']]
234 234 cmd = req.form['cmd'][0]
235 235
236 236 if cmd not in webcommands.__all__:
237 237 msg = 'no such method: %s' % cmd
238 238 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
239 239 elif cmd == 'file' and 'raw' in req.form.get('style', []):
240 240 self.ctype = ctype
241 241 content = webcommands.rawfile(self, req, tmpl)
242 242 else:
243 243 content = getattr(webcommands, cmd)(self, req, tmpl)
244 244 req.respond(HTTP_OK, ctype)
245 245
246 246 req.write(content)
247 247 del tmpl
248 248
249 249 except revlog.LookupError, err:
250 250 req.respond(HTTP_NOT_FOUND, ctype)
251 251 msg = str(err)
252 252 if 'manifest' not in msg:
253 253 msg = 'revision not found: %s' % err.name
254 254 req.write(tmpl('error', error=msg))
255 255 except (RepoError, revlog.RevlogError), inst:
256 256 req.respond(HTTP_SERVER_ERROR, ctype)
257 257 req.write(tmpl('error', error=str(inst)))
258 258 except ErrorResponse, inst:
259 259 req.respond(inst.code, ctype)
260 260 req.write(tmpl('error', error=inst.message))
261 261
262 262 def templater(self, req):
263 263
264 264 # determine scheme, port and server name
265 265 # this is needed to create absolute urls
266 266
267 267 proto = req.env.get('wsgi.url_scheme')
268 268 if proto == 'https':
269 269 proto = 'https'
270 270 default_port = "443"
271 271 else:
272 272 proto = 'http'
273 273 default_port = "80"
274 274
275 275 port = req.env["SERVER_PORT"]
276 276 port = port != default_port and (":" + port) or ""
277 277 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
278 278 staticurl = self.config("web", "staticurl") or req.url + 'static/'
279 279 if not staticurl.endswith('/'):
280 280 staticurl += '/'
281 281
282 282 # some functions for the templater
283 283
284 284 def header(**map):
285 285 yield tmpl('header', encoding=self.encoding, **map)
286 286
287 287 def footer(**map):
288 288 yield tmpl("footer", **map)
289 289
290 290 def motd(**map):
291 291 yield self.config("web", "motd", "")
292 292
293 293 def sessionvars(**map):
294 294 fields = []
295 295 if 'style' in req.form:
296 296 style = req.form['style'][0]
297 297 if style != self.config('web', 'style', ''):
298 298 fields.append(('style', style))
299 299
300 300 separator = req.url[-1] == '?' and ';' or '?'
301 301 for name, value in fields:
302 302 yield dict(name=name, value=value, separator=separator)
303 303 separator = ';'
304 304
305 305 # figure out which style to use
306 306
307 307 style = self.config("web", "style", "")
308 308 if 'style' in req.form:
309 309 style = req.form['style'][0]
310 310 mapfile = style_map(self.templatepath, style)
311 311
312 312 if not self.reponame:
313 313 self.reponame = (self.config("web", "name")
314 314 or req.env.get('REPO_NAME')
315 315 or req.url.strip('/') or self.repo.root)
316 316
317 317 # create the templater
318 318
319 319 tmpl = templater.templater(mapfile, templatefilters.filters,
320 320 defaults={"url": req.url,
321 321 "staticurl": staticurl,
322 322 "urlbase": urlbase,
323 323 "repo": self.reponame,
324 324 "header": header,
325 325 "footer": footer,
326 326 "motd": motd,
327 327 "sessionvars": sessionvars
328 328 })
329 329 return tmpl
330 330
331 331 def archivelist(self, nodeid):
332 332 allowed = self.configlist("web", "allow_archive")
333 333 for i, spec in self.archive_specs.iteritems():
334 334 if i in allowed or self.configbool("web", "allow" + i):
335 335 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
336 336
337 337 def listfilediffs(self, tmpl, files, changeset):
338 338 for f in files[:self.maxfiles]:
339 339 yield tmpl("filedifflink", node=hex(changeset), file=f)
340 340 if len(files) > self.maxfiles:
341 341 yield tmpl("fileellipses")
342 342
343 def siblings(self, siblings=[], hiderev=None, **args):
344 siblings = [s for s in siblings if s.node() != nullid]
345 if len(siblings) == 1 and siblings[0].rev() == hiderev:
346 return
347 for s in siblings:
348 d = {'node': hex(s.node()), 'rev': s.rev()}
349 if hasattr(s, 'path'):
350 d['file'] = s.path()
351 d.update(args)
352 yield d
353
354 def renamelink(self, fl, node):
355 r = fl.renamed(node)
356 if r:
357 return [dict(file=r[0], node=hex(r[1]))]
358 return []
359
360 def nodetagsdict(self, node):
361 return [{"name": i} for i in self.repo.nodetags(node)]
362
363 def nodebranchdict(self, ctx):
364 branches = []
365 branch = ctx.branch()
366 # If this is an empty repo, ctx.node() == nullid,
367 # ctx.branch() == 'default', but branchtags() is
368 # an empty dict. Using dict.get avoids a traceback.
369 if self.repo.branchtags().get(branch) == ctx.node():
370 branches.append({"name": branch})
371 return branches
372
373 def nodeinbranch(self, ctx):
374 branches = []
375 branch = ctx.branch()
376 if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
377 branches.append({"name": branch})
378 return branches
379
380 def nodebranchnodefault(self, ctx):
381 branches = []
382 branch = ctx.branch()
383 if branch != 'default':
384 branches.append({"name": branch})
385 return branches
386
387 def showtag(self, tmpl, t1, node=nullid, **args):
388 for t in self.repo.nodetags(node):
389 yield tmpl(t1, tag=t, **args)
390
391 343 def diff(self, tmpl, node1, node2, files):
392 344 def filterfiles(filters, files):
393 345 l = [x for x in files if x in filters]
394 346
395 347 for t in filters:
396 348 if t and t[-1] != os.sep:
397 349 t += os.sep
398 350 l += [x for x in files if x.startswith(t)]
399 351 return l
400 352
401 353 parity = paritygen(self.stripecount)
402 354 def diffblock(diff, f, fn):
403 355 yield tmpl("diffblock",
404 356 lines=prettyprintlines(diff),
405 357 parity=parity.next(),
406 358 file=f,
407 359 filenode=hex(fn or nullid))
408 360
409 361 blockcount = countgen()
410 362 def prettyprintlines(diff):
411 363 blockno = blockcount.next()
412 364 for lineno, l in enumerate(diff.splitlines(1)):
413 365 if blockno == 0:
414 366 lineno = lineno + 1
415 367 else:
416 368 lineno = "%d.%d" % (blockno, lineno + 1)
417 369 if l.startswith('+'):
418 370 ltype = "difflineplus"
419 371 elif l.startswith('-'):
420 372 ltype = "difflineminus"
421 373 elif l.startswith('@'):
422 374 ltype = "difflineat"
423 375 else:
424 376 ltype = "diffline"
425 377 yield tmpl(ltype,
426 378 line=l,
427 379 lineid="l%s" % lineno,
428 380 linenumber="% 8s" % lineno)
429 381
430 382 r = self.repo
431 383 c1 = r.changectx(node1)
432 384 c2 = r.changectx(node2)
433 385 date1 = util.datestr(c1.date())
434 386 date2 = util.datestr(c2.date())
435 387
436 388 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
437 389 if files:
438 390 modified, added, removed = map(lambda x: filterfiles(files, x),
439 391 (modified, added, removed))
440 392
441 393 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
442 394 for f in modified:
443 395 to = c1.filectx(f).data()
444 396 tn = c2.filectx(f).data()
445 397 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
446 398 opts=diffopts), f, tn)
447 399 for f in added:
448 400 to = None
449 401 tn = c2.filectx(f).data()
450 402 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
451 403 opts=diffopts), f, tn)
452 404 for f in removed:
453 405 to = c1.filectx(f).data()
454 406 tn = None
455 407 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
456 408 opts=diffopts), f, tn)
457 409
458 410 def changelog(self, tmpl, ctx, shortlog=False):
459 411 def changelist(limit=0,**map):
460 412 cl = self.repo.changelog
461 413 l = [] # build a list in forward order for efficiency
462 414 for i in xrange(start, end):
463 415 ctx = self.repo.changectx(i)
464 416 n = ctx.node()
465 showtags = self.showtag(tmpl, 'changelogtag', n)
417 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n)
466 418
467 419 l.insert(0, {"parity": parity.next(),
468 420 "author": ctx.user(),
469 "parent": self.siblings(ctx.parents(), i - 1),
470 "child": self.siblings(ctx.children(), i + 1),
421 "parent": webutil.siblings(ctx.parents(), i - 1),
422 "child": webutil.siblings(ctx.children(), i + 1),
471 423 "changelogtag": showtags,
472 424 "desc": ctx.description(),
473 425 "date": ctx.date(),
474 426 "files": self.listfilediffs(tmpl, ctx.files(), n),
475 427 "rev": i,
476 428 "node": hex(n),
477 "tags": self.nodetagsdict(n),
478 "inbranch": self.nodeinbranch(ctx),
479 "branches": self.nodebranchdict(ctx)})
429 "tags": webutil.nodetagsdict(self.repo, n),
430 "inbranch": webutil.nodeinbranch(self.repo, ctx),
431 "branches": webutil.nodebranchdict(self.repo, ctx)
432 })
480 433
481 434 if limit > 0:
482 435 l = l[:limit]
483 436
484 437 for e in l:
485 438 yield e
486 439
487 440 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
488 441 cl = self.repo.changelog
489 442 count = cl.count()
490 443 pos = ctx.rev()
491 444 start = max(0, pos - maxchanges + 1)
492 445 end = min(count, start + maxchanges)
493 446 pos = end - 1
494 447 parity = paritygen(self.stripecount, offset=start-end)
495 448
496 449 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
497 450
498 451 return tmpl(shortlog and 'shortlog' or 'changelog',
499 452 changenav=changenav,
500 453 node=hex(cl.tip()),
501 454 rev=pos, changesets=count,
502 455 entries=lambda **x: changelist(limit=0,**x),
503 456 latestentry=lambda **x: changelist(limit=1,**x),
504 457 archives=self.archivelist("tip"))
505 458
506 459 def search(self, tmpl, query):
507 460
508 461 def changelist(**map):
509 462 cl = self.repo.changelog
510 463 count = 0
511 464 qw = query.lower().split()
512 465
513 466 def revgen():
514 467 for i in xrange(cl.count() - 1, 0, -100):
515 468 l = []
516 469 for j in xrange(max(0, i - 100), i + 1):
517 470 ctx = self.repo.changectx(j)
518 471 l.append(ctx)
519 472 l.reverse()
520 473 for e in l:
521 474 yield e
522 475
523 476 for ctx in revgen():
524 477 miss = 0
525 478 for q in qw:
526 479 if not (q in ctx.user().lower() or
527 480 q in ctx.description().lower() or
528 481 q in " ".join(ctx.files()).lower()):
529 482 miss = 1
530 483 break
531 484 if miss:
532 485 continue
533 486
534 487 count += 1
535 488 n = ctx.node()
536 showtags = self.showtag(tmpl, 'changelogtag', n)
489 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n)
537 490
538 491 yield tmpl('searchentry',
539 492 parity=parity.next(),
540 493 author=ctx.user(),
541 parent=self.siblings(ctx.parents()),
542 child=self.siblings(ctx.children()),
494 parent=webutil.siblings(ctx.parents()),
495 child=webutil.siblings(ctx.children()),
543 496 changelogtag=showtags,
544 497 desc=ctx.description(),
545 498 date=ctx.date(),
546 499 files=self.listfilediffs(tmpl, ctx.files(), n),
547 500 rev=ctx.rev(),
548 501 node=hex(n),
549 tags=self.nodetagsdict(n),
550 inbranch=self.nodeinbranch(ctx),
551 branches=self.nodebranchdict(ctx))
502 tags=webutil.nodetagsdict(self.repo, n),
503 inbranch=webutil.nodeinbranch(self.repo, ctx),
504 branches=webutil.nodebranchdict(self.repo, ctx))
552 505
553 506 if count >= self.maxchanges:
554 507 break
555 508
556 509 cl = self.repo.changelog
557 510 parity = paritygen(self.stripecount)
558 511
559 512 return tmpl('search',
560 513 query=query,
561 514 node=hex(cl.tip()),
562 515 entries=changelist,
563 516 archives=self.archivelist("tip"))
564 517
565 518 def changeset(self, tmpl, ctx):
566 519 n = ctx.node()
567 showtags = self.showtag(tmpl, 'changesettag', n)
520 showtags = webutil.showtag(self.repo, tmpl, 'changesettag', n)
568 521 parents = ctx.parents()
569 522 p1 = parents[0].node()
570 523
571 524 files = []
572 525 parity = paritygen(self.stripecount)
573 526 for f in ctx.files():
574 527 files.append(tmpl("filenodelink",
575 528 node=hex(n), file=f,
576 529 parity=parity.next()))
577 530
578 531 def diff(**map):
579 532 yield self.diff(tmpl, p1, n, None)
580 533
581 534 return tmpl('changeset',
582 535 diff=diff,
583 536 rev=ctx.rev(),
584 537 node=hex(n),
585 parent=self.siblings(parents),
586 child=self.siblings(ctx.children()),
538 parent=webutil.siblings(parents),
539 child=webutil.siblings(ctx.children()),
587 540 changesettag=showtags,
588 541 author=ctx.user(),
589 542 desc=ctx.description(),
590 543 date=ctx.date(),
591 544 files=files,
592 545 archives=self.archivelist(hex(n)),
593 tags=self.nodetagsdict(n),
594 branch=self.nodebranchnodefault(ctx),
595 inbranch=self.nodeinbranch(ctx),
596 branches=self.nodebranchdict(ctx))
546 tags=webutil.nodetagsdict(self.repo, n),
547 branch=webutil.nodebranchnodefault(ctx),
548 inbranch=webutil.nodeinbranch(self.repo, ctx),
549 branches=webutil.nodebranchdict(self.repo, ctx))
597 550
598 551 def filelog(self, tmpl, fctx):
599 552 f = fctx.path()
600 553 fl = fctx.filelog()
601 554 count = fl.count()
602 555 pagelen = self.maxshortchanges
603 556 pos = fctx.filerev()
604 557 start = max(0, pos - pagelen + 1)
605 558 end = min(count, start + pagelen)
606 559 pos = end - 1
607 560 parity = paritygen(self.stripecount, offset=start-end)
608 561
609 562 def entries(limit=0, **map):
610 563 l = []
611 564
612 565 for i in xrange(start, end):
613 566 ctx = fctx.filectx(i)
614 567 n = fl.node(i)
615 568
616 569 l.insert(0, {"parity": parity.next(),
617 570 "filerev": i,
618 571 "file": f,
619 572 "node": hex(ctx.node()),
620 573 "author": ctx.user(),
621 574 "date": ctx.date(),
622 "rename": self.renamelink(fl, n),
623 "parent": self.siblings(fctx.parents()),
624 "child": self.siblings(fctx.children()),
575 "rename": webutil.renamelink(fl, n),
576 "parent": webutil.siblings(fctx.parents()),
577 "child": webutil.siblings(fctx.children()),
625 578 "desc": ctx.description()})
626 579
627 580 if limit > 0:
628 581 l = l[:limit]
629 582
630 583 for e in l:
631 584 yield e
632 585
633 586 nodefunc = lambda x: fctx.filectx(fileid=x)
634 587 nav = revnavgen(pos, pagelen, count, nodefunc)
635 588 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
636 589 entries=lambda **x: entries(limit=0, **x),
637 590 latestentry=lambda **x: entries(limit=1, **x))
638 591
639 592 def filerevision(self, tmpl, fctx):
640 593 f = fctx.path()
641 594 text = fctx.data()
642 595 fl = fctx.filelog()
643 596 n = fctx.filenode()
644 597 parity = paritygen(self.stripecount)
645 598
646 599 if util.binary(text):
647 600 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
648 601 text = '(binary:%s)' % mt
649 602
650 603 def lines():
651 604 for lineno, t in enumerate(text.splitlines(1)):
652 605 yield {"line": t,
653 606 "lineid": "l%d" % (lineno + 1),
654 607 "linenumber": "% 6d" % (lineno + 1),
655 608 "parity": parity.next()}
656 609
657 610 return tmpl("filerevision",
658 611 file=f,
659 612 path=_up(f),
660 613 text=lines(),
661 614 rev=fctx.rev(),
662 615 node=hex(fctx.node()),
663 616 author=fctx.user(),
664 617 date=fctx.date(),
665 618 desc=fctx.description(),
666 branch=self.nodebranchnodefault(fctx),
667 parent=self.siblings(fctx.parents()),
668 child=self.siblings(fctx.children()),
669 rename=self.renamelink(fl, n),
619 branch=webutil.nodebranchnodefault(fctx),
620 parent=webutil.siblings(fctx.parents()),
621 child=webutil.siblings(fctx.children()),
622 rename=webutil.renamelink(fl, n),
670 623 permissions=fctx.manifest().flags(f))
671 624
672 625 def fileannotate(self, tmpl, fctx):
673 626 f = fctx.path()
674 627 n = fctx.filenode()
675 628 fl = fctx.filelog()
676 629 parity = paritygen(self.stripecount)
677 630
678 631 def annotate(**map):
679 632 last = None
680 633 if util.binary(fctx.data()):
681 634 mt = (mimetypes.guess_type(fctx.path())[0]
682 635 or 'application/octet-stream')
683 636 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
684 637 '(binary:%s)' % mt)])
685 638 else:
686 639 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
687 640 for lineno, ((f, targetline), l) in lines:
688 641 fnode = f.filenode()
689 642 name = self.repo.ui.shortuser(f.user())
690 643
691 644 if last != fnode:
692 645 last = fnode
693 646
694 647 yield {"parity": parity.next(),
695 648 "node": hex(f.node()),
696 649 "rev": f.rev(),
697 650 "author": name,
698 651 "file": f.path(),
699 652 "targetline": targetline,
700 653 "line": l,
701 654 "lineid": "l%d" % (lineno + 1),
702 655 "linenumber": "% 6d" % (lineno + 1)}
703 656
704 657 return tmpl("fileannotate",
705 658 file=f,
706 659 annotate=annotate,
707 660 path=_up(f),
708 661 rev=fctx.rev(),
709 662 node=hex(fctx.node()),
710 663 author=fctx.user(),
711 664 date=fctx.date(),
712 665 desc=fctx.description(),
713 rename=self.renamelink(fl, n),
714 branch=self.nodebranchnodefault(fctx),
715 parent=self.siblings(fctx.parents()),
716 child=self.siblings(fctx.children()),
666 rename=webutil.renamelink(fl, n),
667 branch=webutil.nodebranchnodefault(fctx),
668 parent=webutil.siblings(fctx.parents()),
669 child=webutil.siblings(fctx.children()),
717 670 permissions=fctx.manifest().flags(f))
718 671
719 672 def manifest(self, tmpl, ctx, path):
720 673 mf = ctx.manifest()
721 674 node = ctx.node()
722 675
723 676 files = {}
724 677 parity = paritygen(self.stripecount)
725 678
726 679 if path and path[-1] != "/":
727 680 path += "/"
728 681 l = len(path)
729 682 abspath = "/" + path
730 683
731 684 for f, n in mf.items():
732 685 if f[:l] != path:
733 686 continue
734 687 remain = f[l:]
735 688 if "/" in remain:
736 689 short = remain[:remain.index("/") + 1] # bleah
737 690 files[short] = (f, None)
738 691 else:
739 692 short = os.path.basename(remain)
740 693 files[short] = (f, n)
741 694
742 695 if not files:
743 696 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
744 697
745 698 def filelist(**map):
746 699 fl = files.keys()
747 700 fl.sort()
748 701 for f in fl:
749 702 full, fnode = files[f]
750 703 if not fnode:
751 704 continue
752 705
753 706 fctx = ctx.filectx(full)
754 707 yield {"file": full,
755 708 "parity": parity.next(),
756 709 "basename": f,
757 710 "date": fctx.changectx().date(),
758 711 "size": fctx.size(),
759 712 "permissions": mf.flags(full)}
760 713
761 714 def dirlist(**map):
762 715 fl = files.keys()
763 716 fl.sort()
764 717 for f in fl:
765 718 full, fnode = files[f]
766 719 if fnode:
767 720 continue
768 721
769 722 yield {"parity": parity.next(),
770 723 "path": "%s%s" % (abspath, f),
771 724 "basename": f[:-1]}
772 725
773 726 return tmpl("manifest",
774 727 rev=ctx.rev(),
775 728 node=hex(node),
776 729 path=abspath,
777 730 up=_up(abspath),
778 731 upparity=parity.next(),
779 732 fentries=filelist,
780 733 dentries=dirlist,
781 734 archives=self.archivelist(hex(node)),
782 tags=self.nodetagsdict(node),
783 inbranch=self.nodeinbranch(ctx),
784 branches=self.nodebranchdict(ctx))
735 tags=webutil.nodetagsdict(self.repo, node),
736 inbranch=webutil.nodeinbranch(self.repo, ctx),
737 branches=webutil.nodebranchdict(self.repo, ctx))
785 738
786 739 def tags(self, tmpl):
787 740 i = self.repo.tagslist()
788 741 i.reverse()
789 742 parity = paritygen(self.stripecount)
790 743
791 744 def entries(notip=False,limit=0, **map):
792 745 count = 0
793 746 for k, n in i:
794 747 if notip and k == "tip":
795 748 continue
796 749 if limit > 0 and count >= limit:
797 750 continue
798 751 count = count + 1
799 752 yield {"parity": parity.next(),
800 753 "tag": k,
801 754 "date": self.repo.changectx(n).date(),
802 755 "node": hex(n)}
803 756
804 757 return tmpl("tags",
805 758 node=hex(self.repo.changelog.tip()),
806 759 entries=lambda **x: entries(False,0, **x),
807 760 entriesnotip=lambda **x: entries(True,0, **x),
808 761 latestentry=lambda **x: entries(True,1, **x))
809 762
810 763 def summary(self, tmpl):
811 764 i = self.repo.tagslist()
812 765 i.reverse()
813 766
814 767 def tagentries(**map):
815 768 parity = paritygen(self.stripecount)
816 769 count = 0
817 770 for k, n in i:
818 771 if k == "tip": # skip tip
819 772 continue;
820 773
821 774 count += 1
822 775 if count > 10: # limit to 10 tags
823 776 break;
824 777
825 778 yield tmpl("tagentry",
826 779 parity=parity.next(),
827 780 tag=k,
828 781 node=hex(n),
829 782 date=self.repo.changectx(n).date())
830 783
831 784
832 785 def branches(**map):
833 786 parity = paritygen(self.stripecount)
834 787
835 788 b = self.repo.branchtags()
836 789 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
837 790 l.sort()
838 791
839 792 for r,n,t in l:
840 793 ctx = self.repo.changectx(n)
841 794
842 795 yield {'parity': parity.next(),
843 796 'branch': t,
844 797 'node': hex(n),
845 798 'date': ctx.date()}
846 799
847 800 def changelist(**map):
848 801 parity = paritygen(self.stripecount, offset=start-end)
849 802 l = [] # build a list in forward order for efficiency
850 803 for i in xrange(start, end):
851 804 ctx = self.repo.changectx(i)
852 805 n = ctx.node()
853 806 hn = hex(n)
854 807
855 808 l.insert(0, tmpl(
856 809 'shortlogentry',
857 810 parity=parity.next(),
858 811 author=ctx.user(),
859 812 desc=ctx.description(),
860 813 date=ctx.date(),
861 814 rev=i,
862 815 node=hn,
863 tags=self.nodetagsdict(n),
864 inbranch=self.nodeinbranch(ctx),
865 branches=self.nodebranchdict(ctx)))
816 tags=webutil.nodetagsdict(self.repo, n),
817 inbranch=webutil.nodeinbranch(self.repo, ctx),
818 branches=webutil.nodebranchdict(self.repo, ctx)))
866 819
867 820 yield l
868 821
869 822 cl = self.repo.changelog
870 823 count = cl.count()
871 824 start = max(0, count - self.maxchanges)
872 825 end = min(count, start + self.maxchanges)
873 826
874 827 return tmpl("summary",
875 828 desc=self.config("web", "description", "unknown"),
876 829 owner=get_contact(self.config) or "unknown",
877 830 lastchange=cl.read(cl.tip())[2],
878 831 tags=tagentries,
879 832 branches=branches,
880 833 shortlog=changelist,
881 834 node=hex(cl.tip()),
882 835 archives=self.archivelist("tip"))
883 836
884 837 def filediff(self, tmpl, fctx):
885 838 n = fctx.node()
886 839 path = fctx.path()
887 840 parents = fctx.parents()
888 841 p1 = parents and parents[0].node() or nullid
889 842
890 843 def diff(**map):
891 844 yield self.diff(tmpl, p1, n, [path])
892 845
893 846 return tmpl("filediff",
894 847 file=path,
895 848 node=hex(n),
896 849 rev=fctx.rev(),
897 branch=self.nodebranchnodefault(fctx),
898 parent=self.siblings(parents),
899 child=self.siblings(fctx.children()),
850 branch=webutil.nodebranchnodefault(fctx),
851 parent=webutil.siblings(parents),
852 child=webutil.siblings(fctx.children()),
900 853 diff=diff)
901 854
902 855 archive_specs = {
903 856 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
904 857 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
905 858 'zip': ('application/zip', 'zip', '.zip', None),
906 859 }
907 860
908 861 def archive(self, tmpl, req, key, type_):
909 862 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
910 863 cnode = self.repo.lookup(key)
911 864 arch_version = key
912 865 if cnode == key or key == 'tip':
913 866 arch_version = short(cnode)
914 867 name = "%s-%s" % (reponame, arch_version)
915 868 mimetype, artype, extension, encoding = self.archive_specs[type_]
916 869 headers = [
917 870 ('Content-Type', mimetype),
918 871 ('Content-Disposition', 'attachment; filename=%s%s' %
919 872 (name, extension))
920 873 ]
921 874 if encoding:
922 875 headers.append(('Content-Encoding', encoding))
923 876 req.header(headers)
924 877 req.respond(HTTP_OK)
925 878 archival.archive(self.repo, req, cnode, artype, prefix=name)
926 879
927 # add tags to things
928 # tags -> list of changesets corresponding to tags
929 # find tag, changeset, file
930
931 def cleanpath(self, path):
932 path = path.lstrip('/')
933 return util.canonpath(self.repo.root, '', path)
934
935 def changectx(self, req):
936 if 'node' in req.form:
937 changeid = req.form['node'][0]
938 elif 'manifest' in req.form:
939 changeid = req.form['manifest'][0]
940 else:
941 changeid = self.repo.changelog.count() - 1
942
943 try:
944 ctx = self.repo.changectx(changeid)
945 except RepoError:
946 man = self.repo.manifest
947 mn = man.lookup(changeid)
948 ctx = self.repo.changectx(man.linkrev(mn))
949
950 return ctx
951
952 def filectx(self, req):
953 path = self.cleanpath(req.form['file'][0])
954 if 'node' in req.form:
955 changeid = req.form['node'][0]
956 else:
957 changeid = req.form['filenode'][0]
958 try:
959 ctx = self.repo.changectx(changeid)
960 fctx = ctx.filectx(path)
961 except RepoError:
962 fctx = self.repo.filectx(path, fileid=changeid)
963
964 return fctx
965
966 880 def check_perm(self, req, op, default):
967 881 '''check permission for operation based on user auth.
968 882 return true if op allowed, else false.
969 883 default is policy to use if no config given.'''
970 884
971 885 user = req.env.get('REMOTE_USER')
972 886
973 887 deny = self.configlist('web', 'deny_' + op)
974 888 if deny and (not user or deny == ['*'] or user in deny):
975 889 return False
976 890
977 891 allow = self.configlist('web', 'allow_' + op)
978 892 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,127 +1,129 b''
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
9 from mercurial import revlog, util
9 import webutil
10 from mercurial import revlog
11 from mercurial.util import binary
10 12 from mercurial.repo import RepoError
11 13 from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
12 14
13 15 # __all__ is populated with the allowed commands. Be sure to add to it if
14 16 # you're adding a new command, or the new command won't work.
15 17
16 18 __all__ = [
17 19 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
18 20 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
19 21 'archive', 'static',
20 22 ]
21 23
22 24 def log(web, req, tmpl):
23 25 if 'file' in req.form and req.form['file'][0]:
24 26 return filelog(web, req, tmpl)
25 27 else:
26 28 return changelog(web, req, tmpl)
27 29
28 30 def rawfile(web, req, tmpl):
29 path = web.cleanpath(req.form.get('file', [''])[0])
31 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
30 32 if not path:
31 content = web.manifest(tmpl, web.changectx(req), path)
33 content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
32 34 req.respond(HTTP_OK, web.ctype)
33 35 return content
34 36
35 37 try:
36 fctx = web.filectx(req)
38 fctx = webutil.filectx(web.repo, req)
37 39 except revlog.LookupError, inst:
38 40 try:
39 content = web.manifest(tmpl, web.changectx(req), path)
41 content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
40 42 req.respond(HTTP_OK, web.ctype)
41 43 return content
42 44 except ErrorResponse:
43 45 raise inst
44 46
45 47 path = fctx.path()
46 48 text = fctx.data()
47 49 mt = mimetypes.guess_type(path)[0]
48 if mt is None or util.binary(text):
50 if mt is None or binary(text):
49 51 mt = mt or 'application/octet-stream'
50 52
51 53 req.respond(HTTP_OK, mt, path, len(text))
52 54 return [text]
53 55
54 56 def file(web, req, tmpl):
55 path = web.cleanpath(req.form.get('file', [''])[0])
57 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
56 58 if path:
57 59 try:
58 return web.filerevision(tmpl, web.filectx(req))
60 return web.filerevision(tmpl, webutil.filectx(web.repo, req))
59 61 except revlog.LookupError, inst:
60 62 pass
61 63
62 64 try:
63 return web.manifest(tmpl, web.changectx(req), path)
65 return web.manifest(tmpl, webutil.changectx(web.repo, req), path)
64 66 except ErrorResponse:
65 67 raise inst
66 68
67 69 def changelog(web, req, tmpl, shortlog = False):
68 70 if 'node' in req.form:
69 ctx = web.changectx(req)
71 ctx = webutil.changectx(web.repo, req)
70 72 else:
71 73 if 'rev' in req.form:
72 74 hi = req.form['rev'][0]
73 75 else:
74 76 hi = web.repo.changelog.count() - 1
75 77 try:
76 78 ctx = web.repo.changectx(hi)
77 79 except RepoError:
78 80 return web.search(tmpl, hi) # XXX redirect to 404 page?
79 81
80 82 return web.changelog(tmpl, ctx, shortlog = shortlog)
81 83
82 84 def shortlog(web, req, tmpl):
83 85 return changelog(web, req, tmpl, shortlog = True)
84 86
85 87 def changeset(web, req, tmpl):
86 return web.changeset(tmpl, web.changectx(req))
88 return web.changeset(tmpl, webutil.changectx(web.repo, req))
87 89
88 90 rev = changeset
89 91
90 92 def manifest(web, req, tmpl):
91 return web.manifest(tmpl, web.changectx(req),
92 web.cleanpath(req.form['path'][0]))
93 return web.manifest(tmpl, webutil.changectx(web.repo, req),
94 webutil.cleanpath(web.repo, req.form['path'][0]))
93 95
94 96 def tags(web, req, tmpl):
95 97 return web.tags(tmpl)
96 98
97 99 def summary(web, req, tmpl):
98 100 return web.summary(tmpl)
99 101
100 102 def filediff(web, req, tmpl):
101 return web.filediff(tmpl, web.filectx(req))
103 return web.filediff(tmpl, webutil.filectx(web.repo, req))
102 104
103 105 diff = filediff
104 106
105 107 def annotate(web, req, tmpl):
106 return web.fileannotate(tmpl, web.filectx(req))
108 return web.fileannotate(tmpl, webutil.filectx(web.repo, req))
107 109
108 110 def filelog(web, req, tmpl):
109 return web.filelog(tmpl, web.filectx(req))
111 return web.filelog(tmpl, webutil.filectx(web.repo, req))
110 112
111 113 def archive(web, req, tmpl):
112 114 type_ = req.form['type'][0]
113 115 allowed = web.configlist("web", "allow_archive")
114 116 if (type_ in web.archives and (type_ in allowed or
115 117 web.configbool("web", "allow" + type_, False))):
116 118 web.archive(tmpl, req, req.form['node'][0], type_)
117 119 return []
118 120 raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
119 121
120 122 def static(web, req, tmpl):
121 123 fname = req.form['file'][0]
122 124 # a repo owner may set web.static in .hg/hgrc to get any file
123 125 # readable by the user running the CGI script
124 126 static = web.config("web", "static",
125 127 os.path.join(web.templatepath, "static"),
126 128 untrusted=False)
127 129 return [staticfile(static, fname, req)]
General Comments 0
You need to be logged in to leave comments. Login now