##// END OF EJS Templates
split out hgweb commands into a separate file, move some code around
Dirkjan Ochtman -
r5591:08887121 default
parent child Browse files
Show More
@@ -0,0 +1,320 b''
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 import cStringIO, zlib, tempfile, errno, os, sys
9 from mercurial import revlog, util, streamclone
10 from mercurial.i18n import gettext as _
11 from mercurial.node import *
12 from common import staticfile
13
14 def log(web, req):
15 if req.form.has_key('file') and req.form['file'][0]:
16 filelog(web, req)
17 else:
18 changelog(web, req)
19
20 def file(web, req):
21 path = web.cleanpath(req.form.get('file', [''])[0])
22 if path:
23 try:
24 req.write(web.filerevision(web.filectx(req)))
25 return
26 except revlog.LookupError:
27 pass
28
29 req.write(web.manifest(web.changectx(req), path))
30
31 def changelog(web, req, shortlog = False):
32 if req.form.has_key('node'):
33 ctx = web.changectx(req)
34 else:
35 if req.form.has_key('rev'):
36 hi = req.form['rev'][0]
37 else:
38 hi = web.repo.changelog.count() - 1
39 try:
40 ctx = web.repo.changectx(hi)
41 except hg.RepoError:
42 req.write(web.search(hi)) # XXX redirect to 404 page?
43 return
44
45 req.write(web.changelog(ctx, shortlog = shortlog))
46
47 def shortlog(web, req):
48 changelog(web, req, shortlog = True)
49
50 def changeset(web, req):
51 req.write(web.changeset(web.changectx(req)))
52
53 rev = changeset
54
55 def manifest(web, req):
56 req.write(web.manifest(web.changectx(req),
57 web.cleanpath(req.form['path'][0])))
58
59 def tags(web, req):
60 req.write(web.tags())
61
62 def summary(web, req):
63 req.write(web.summary())
64
65 def filediff(web, req):
66 req.write(web.filediff(web.filectx(req)))
67
68 diff = filediff
69
70 def annotate(web, req):
71 req.write(web.fileannotate(web.filectx(req)))
72
73 def filelog(web, req):
74 req.write(web.filelog(web.filectx(req)))
75
76 def lookup(web, req):
77 try:
78 r = hex(web.repo.lookup(req.form['key'][0]))
79 success = 1
80 except Exception,inst:
81 r = str(inst)
82 success = 0
83 resp = "%s %s\n" % (success, r)
84 req.httphdr("application/mercurial-0.1", length=len(resp))
85 req.write(resp)
86
87 def heads(web, req):
88 resp = " ".join(map(hex, web.repo.heads())) + "\n"
89 req.httphdr("application/mercurial-0.1", length=len(resp))
90 req.write(resp)
91
92 def branches(web, req):
93 nodes = []
94 if req.form.has_key('nodes'):
95 nodes = map(bin, req.form['nodes'][0].split(" "))
96 resp = cStringIO.StringIO()
97 for b in web.repo.branches(nodes):
98 resp.write(" ".join(map(hex, b)) + "\n")
99 resp = resp.getvalue()
100 req.httphdr("application/mercurial-0.1", length=len(resp))
101 req.write(resp)
102
103 def between(web, req):
104 if req.form.has_key('pairs'):
105 pairs = [map(bin, p.split("-"))
106 for p in req.form['pairs'][0].split(" ")]
107 resp = cStringIO.StringIO()
108 for b in web.repo.between(pairs):
109 resp.write(" ".join(map(hex, b)) + "\n")
110 resp = resp.getvalue()
111 req.httphdr("application/mercurial-0.1", length=len(resp))
112 req.write(resp)
113
114 def changegroup(web, req):
115 req.httphdr("application/mercurial-0.1")
116 nodes = []
117 if not web.allowpull:
118 return
119
120 if req.form.has_key('roots'):
121 nodes = map(bin, req.form['roots'][0].split(" "))
122
123 z = zlib.compressobj()
124 f = web.repo.changegroup(nodes, 'serve')
125 while 1:
126 chunk = f.read(4096)
127 if not chunk:
128 break
129 req.write(z.compress(chunk))
130
131 req.write(z.flush())
132
133 def changegroupsubset(web, req):
134 req.httphdr("application/mercurial-0.1")
135 bases = []
136 heads = []
137 if not web.allowpull:
138 return
139
140 if req.form.has_key('bases'):
141 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
142 if req.form.has_key('heads'):
143 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
144
145 z = zlib.compressobj()
146 f = web.repo.changegroupsubset(bases, heads, 'serve')
147 while 1:
148 chunk = f.read(4096)
149 if not chunk:
150 break
151 req.write(z.compress(chunk))
152
153 req.write(z.flush())
154
155 def archive(web, req):
156 type_ = req.form['type'][0]
157 allowed = web.configlist("web", "allow_archive")
158 if (type_ in web.archives and (type_ in allowed or
159 web.configbool("web", "allow" + type_, False))):
160 web.archive(req, req.form['node'][0], type_)
161 return
162
163 req.respond(400, web.t('error',
164 error='Unsupported archive type: %s' % type_))
165
166 def static(web, req):
167 fname = req.form['file'][0]
168 # a repo owner may set web.static in .hg/hgrc to get any file
169 # readable by the user running the CGI script
170 static = web.config("web", "static",
171 os.path.join(web.templatepath, "static"),
172 untrusted=False)
173 req.write(staticfile(static, fname, req))
174
175 def capabilities(web, req):
176 caps = ['lookup', 'changegroupsubset']
177 if web.configbool('server', 'uncompressed'):
178 caps.append('stream=%d' % web.repo.changelog.version)
179 # XXX: make configurable and/or share code with do_unbundle:
180 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
181 if unbundleversions:
182 caps.append('unbundle=%s' % ','.join(unbundleversions))
183 resp = ' '.join(caps)
184 req.httphdr("application/mercurial-0.1", length=len(resp))
185 req.write(resp)
186
187 def unbundle(web, req):
188 def bail(response, headers={}):
189 length = int(req.env['CONTENT_LENGTH'])
190 for s in util.filechunkiter(req, limit=length):
191 # drain incoming bundle, else client will not see
192 # response when run outside cgi script
193 pass
194 req.httphdr("application/mercurial-0.1", headers=headers)
195 req.write('0\n')
196 req.write(response)
197
198 # require ssl by default, auth info cannot be sniffed and
199 # replayed
200 ssl_req = web.configbool('web', 'push_ssl', True)
201 if ssl_req:
202 if req.env.get('wsgi.url_scheme') != 'https':
203 bail(_('ssl required\n'))
204 return
205 proto = 'https'
206 else:
207 proto = 'http'
208
209 # do not allow push unless explicitly allowed
210 if not web.check_perm(req, 'push', False):
211 bail(_('push not authorized\n'),
212 headers={'status': '401 Unauthorized'})
213 return
214
215 their_heads = req.form['heads'][0].split(' ')
216
217 def check_heads():
218 heads = map(hex, web.repo.heads())
219 return their_heads == [hex('force')] or their_heads == heads
220
221 # fail early if possible
222 if not check_heads():
223 bail(_('unsynced changes\n'))
224 return
225
226 req.httphdr("application/mercurial-0.1")
227
228 # do not lock repo until all changegroup data is
229 # streamed. save to temporary file.
230
231 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
232 fp = os.fdopen(fd, 'wb+')
233 try:
234 length = int(req.env['CONTENT_LENGTH'])
235 for s in util.filechunkiter(req, limit=length):
236 fp.write(s)
237
238 try:
239 lock = web.repo.lock()
240 try:
241 if not check_heads():
242 req.write('0\n')
243 req.write(_('unsynced changes\n'))
244 return
245
246 fp.seek(0)
247 header = fp.read(6)
248 if not header.startswith("HG"):
249 # old client with uncompressed bundle
250 def generator(f):
251 yield header
252 for chunk in f:
253 yield chunk
254 elif not header.startswith("HG10"):
255 req.write("0\n")
256 req.write(_("unknown bundle version\n"))
257 return
258 elif header == "HG10GZ":
259 def generator(f):
260 zd = zlib.decompressobj()
261 for chunk in f:
262 yield zd.decompress(chunk)
263 elif header == "HG10BZ":
264 def generator(f):
265 zd = bz2.BZ2Decompressor()
266 zd.decompress("BZ")
267 for chunk in f:
268 yield zd.decompress(chunk)
269 elif header == "HG10UN":
270 def generator(f):
271 for chunk in f:
272 yield chunk
273 else:
274 req.write("0\n")
275 req.write(_("unknown bundle compression type\n"))
276 return
277 gen = generator(util.filechunkiter(fp, 4096))
278
279 # send addchangegroup output to client
280
281 old_stdout = sys.stdout
282 sys.stdout = cStringIO.StringIO()
283
284 try:
285 url = 'remote:%s:%s' % (proto,
286 req.env.get('REMOTE_HOST', ''))
287 try:
288 ret = web.repo.addchangegroup(
289 util.chunkbuffer(gen), 'serve', url)
290 except util.Abort, inst:
291 sys.stdout.write("abort: %s\n" % inst)
292 ret = 0
293 finally:
294 val = sys.stdout.getvalue()
295 sys.stdout = old_stdout
296 req.write('%d\n' % ret)
297 req.write(val)
298 finally:
299 del lock
300 except (OSError, IOError), inst:
301 req.write('0\n')
302 filename = getattr(inst, 'filename', '')
303 # Don't send our filesystem layout to the client
304 if filename.startswith(web.repo.root):
305 filename = filename[len(web.repo.root)+1:]
306 else:
307 filename = ''
308 error = getattr(inst, 'strerror', 'Unknown error')
309 if inst.errno == errno.ENOENT:
310 code = 404
311 else:
312 code = 500
313 req.respond(code, '%s: %s\n' % (error, filename))
314 finally:
315 fp.close()
316 os.unlink(tempname)
317
318 def stream_out(web, req):
319 req.httphdr("application/mercurial-0.1")
320 streamclone.stream_out(web.repo, req, untrusted=True)
This diff has been collapsed as it changes many lines, (687 lines changed) Show them Hide them
@@ -1,1208 +1,897 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 import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
9 import os, mimetypes, re, mimetools, cStringIO, sys, urllib, bz2
11 10 from mercurial.node import *
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
11 from mercurial import mdiff, ui, hg, util, archival, patch
14 12 from mercurial import revlog, templater
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
13 from common import ErrorResponse, get_mtime, style_map, paritygen
16 14 from request import wsgirequest
15 import webcommands
17 16
18 17 def _up(p):
19 18 if p[0] != "/":
20 19 p = "/" + p
21 20 if p[-1] == "/":
22 21 p = p[:-1]
23 22 up = os.path.dirname(p)
24 23 if up == "/":
25 24 return "/"
26 25 return up + "/"
27 26
28 27 def revnavgen(pos, pagelen, limit, nodefunc):
29 28 def seq(factor, limit=None):
30 29 if limit:
31 30 yield limit
32 31 if limit >= 20 and limit <= 40:
33 32 yield 50
34 33 else:
35 34 yield 1 * factor
36 35 yield 3 * factor
37 36 for f in seq(factor * 10):
38 37 yield f
39 38
40 39 def nav(**map):
41 40 l = []
42 41 last = 0
43 42 for f in seq(1, pagelen):
44 43 if f < pagelen or f <= last:
45 44 continue
46 45 if f > limit:
47 46 break
48 47 last = f
49 48 if pos + f < limit:
50 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
51 50 if pos - f >= 0:
52 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
53 52
54 53 try:
55 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
56 55
57 56 for label, node in l:
58 57 yield {"label": label, "node": node}
59 58
60 59 yield {"label": "tip", "node": "tip"}
61 60 except hg.RepoError:
62 61 pass
63 62
64 63 return nav
65 64
66 65 class hgweb(object):
67 66 def __init__(self, repo, name=None):
68 67 if isinstance(repo, str):
69 68 parentui = ui.ui(report_untrusted=False, interactive=False)
70 69 self.repo = hg.repository(parentui, repo)
71 70 else:
72 71 self.repo = repo
73 72
74 73 self.mtime = -1
75 74 self.reponame = name
76 75 self.archives = 'zip', 'gz', 'bz2'
77 76 self.stripecount = 1
78 77 # a repo owner may set web.templates in .hg/hgrc to get any file
79 78 # readable by the user running the CGI script
80 79 self.templatepath = self.config("web", "templates",
81 80 templater.templatepath(),
82 81 untrusted=False)
83 82
84 83 # The CGI scripts are often run by a user different from the repo owner.
85 84 # Trust the settings from the .hg/hgrc files by default.
86 85 def config(self, section, name, default=None, untrusted=True):
87 86 return self.repo.ui.config(section, name, default,
88 87 untrusted=untrusted)
89 88
90 89 def configbool(self, section, name, default=False, untrusted=True):
91 90 return self.repo.ui.configbool(section, name, default,
92 91 untrusted=untrusted)
93 92
94 93 def configlist(self, section, name, default=None, untrusted=True):
95 94 return self.repo.ui.configlist(section, name, default,
96 95 untrusted=untrusted)
97 96
98 97 def refresh(self):
99 98 mtime = get_mtime(self.repo.root)
100 99 if mtime != self.mtime:
101 100 self.mtime = mtime
102 101 self.repo = hg.repository(self.repo.ui, self.repo.root)
103 102 self.maxchanges = int(self.config("web", "maxchanges", 10))
104 103 self.stripecount = int(self.config("web", "stripes", 1))
105 104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
106 105 self.maxfiles = int(self.config("web", "maxfiles", 10))
107 106 self.allowpull = self.configbool("web", "allowpull", True)
108 107 self.encoding = self.config("web", "encoding", util._encoding)
109 108
109 def run(self):
110 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
111 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
112 import mercurial.hgweb.wsgicgi as wsgicgi
113 wsgicgi.launch(self)
114
115 def __call__(self, env, respond):
116 req = wsgirequest(env, respond)
117 self.run_wsgi(req)
118 return req
119
120 def run_wsgi(self, req):
121 def header(**map):
122 header_file = cStringIO.StringIO(
123 ''.join(self.t("header", encoding=self.encoding, **map)))
124 msg = mimetools.Message(header_file, 0)
125 req.header(msg.items())
126 yield header_file.read()
127
128 def rawfileheader(**map):
129 req.header([('Content-type', map['mimetype']),
130 ('Content-disposition', 'filename=%s' % map['file']),
131 ('Content-length', str(len(map['raw'])))])
132 yield ''
133
134 def footer(**map):
135 yield self.t("footer", **map)
136
137 def motd(**map):
138 yield self.config("web", "motd", "")
139
140 def expand_form(form):
141 shortcuts = {
142 'cl': [('cmd', ['changelog']), ('rev', None)],
143 'sl': [('cmd', ['shortlog']), ('rev', None)],
144 'cs': [('cmd', ['changeset']), ('node', None)],
145 'f': [('cmd', ['file']), ('filenode', None)],
146 'fl': [('cmd', ['filelog']), ('filenode', None)],
147 'fd': [('cmd', ['filediff']), ('node', None)],
148 'fa': [('cmd', ['annotate']), ('filenode', None)],
149 'mf': [('cmd', ['manifest']), ('manifest', None)],
150 'ca': [('cmd', ['archive']), ('node', None)],
151 'tags': [('cmd', ['tags'])],
152 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
153 'static': [('cmd', ['static']), ('file', None)]
154 }
155
156 for k in shortcuts.iterkeys():
157 if form.has_key(k):
158 for name, value in shortcuts[k]:
159 if value is None:
160 value = form[k]
161 form[name] = value
162 del form[k]
163
164 def rewrite_request(req):
165 '''translate new web interface to traditional format'''
166
167 req.url = req.env['SCRIPT_NAME']
168 if not req.url.endswith('/'):
169 req.url += '/'
170 if req.env.has_key('REPO_NAME'):
171 req.url += req.env['REPO_NAME'] + '/'
172
173 if req.env.get('PATH_INFO'):
174 parts = req.env.get('PATH_INFO').strip('/').split('/')
175 repo_parts = req.env.get('REPO_NAME', '').split('/')
176 if parts[:len(repo_parts)] == repo_parts:
177 parts = parts[len(repo_parts):]
178 query = '/'.join(parts)
179 else:
180 query = req.env['QUERY_STRING'].split('&', 1)[0]
181 query = query.split(';', 1)[0]
182
183 if req.form.has_key('cmd'):
184 # old style
185 return
186
187 args = query.split('/', 2)
188 if not args or not args[0]:
189 return
190
191 cmd = args.pop(0)
192 style = cmd.rfind('-')
193 if style != -1:
194 req.form['style'] = [cmd[:style]]
195 cmd = cmd[style+1:]
196 # avoid accepting e.g. style parameter as command
197 if hasattr(webcommands, cmd):
198 req.form['cmd'] = [cmd]
199
200 if args and args[0]:
201 node = args.pop(0)
202 req.form['node'] = [node]
203 if args:
204 req.form['file'] = args
205
206 if cmd == 'static':
207 req.form['file'] = req.form['node']
208 elif cmd == 'archive':
209 fn = req.form['node'][0]
210 for type_, spec in self.archive_specs.iteritems():
211 ext = spec[2]
212 if fn.endswith(ext):
213 req.form['node'] = [fn[:-len(ext)]]
214 req.form['type'] = [type_]
215
216 def sessionvars(**map):
217 fields = []
218 if req.form.has_key('style'):
219 style = req.form['style'][0]
220 if style != self.config('web', 'style', ''):
221 fields.append(('style', style))
222
223 separator = req.url[-1] == '?' and ';' or '?'
224 for name, value in fields:
225 yield dict(name=name, value=value, separator=separator)
226 separator = ';'
227
228 self.refresh()
229
230 expand_form(req.form)
231 rewrite_request(req)
232
233 style = self.config("web", "style", "")
234 if req.form.has_key('style'):
235 style = req.form['style'][0]
236 mapfile = style_map(self.templatepath, style)
237
238 proto = req.env.get('wsgi.url_scheme')
239 if proto == 'https':
240 proto = 'https'
241 default_port = "443"
242 else:
243 proto = 'http'
244 default_port = "80"
245
246 port = req.env["SERVER_PORT"]
247 port = port != default_port and (":" + port) or ""
248 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
249 staticurl = self.config("web", "staticurl") or req.url + 'static/'
250 if not staticurl.endswith('/'):
251 staticurl += '/'
252
253 if not self.reponame:
254 self.reponame = (self.config("web", "name")
255 or req.env.get('REPO_NAME')
256 or req.url.strip('/') or self.repo.root)
257
258 self.t = templater.templater(mapfile, templater.common_filters,
259 defaults={"url": req.url,
260 "staticurl": staticurl,
261 "urlbase": urlbase,
262 "repo": self.reponame,
263 "header": header,
264 "footer": footer,
265 "motd": motd,
266 "rawfileheader": rawfileheader,
267 "sessionvars": sessionvars
268 })
269
270 try:
271 if not req.form.has_key('cmd'):
272 req.form['cmd'] = [self.t.cache['default']]
273
274 cmd = req.form['cmd'][0]
275
276 try:
277 method = getattr(webcommands, cmd)
278 method(self, req)
279 except revlog.LookupError, err:
280 req.respond(404, self.t(
281 'error', error='revision not found: %s' % err.name))
282 except (hg.RepoError, revlog.RevlogError), inst:
283 req.respond('500 Internal Server Error',
284 self.t('error', error=str(inst)))
285 except ErrorResponse, inst:
286 req.respond(inst.code, self.t('error', error=inst.message))
287 except AttributeError:
288 req.respond(400,
289 self.t('error', error='No such method: ' + cmd))
290 finally:
291 self.t = None
292
110 293 def archivelist(self, nodeid):
111 294 allowed = self.configlist("web", "allow_archive")
112 295 for i, spec in self.archive_specs.iteritems():
113 296 if i in allowed or self.configbool("web", "allow" + i):
114 297 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
115 298
116 299 def listfilediffs(self, files, changeset):
117 300 for f in files[:self.maxfiles]:
118 301 yield self.t("filedifflink", node=hex(changeset), file=f)
119 302 if len(files) > self.maxfiles:
120 303 yield self.t("fileellipses")
121 304
122 305 def siblings(self, siblings=[], hiderev=None, **args):
123 306 siblings = [s for s in siblings if s.node() != nullid]
124 307 if len(siblings) == 1 and siblings[0].rev() == hiderev:
125 308 return
126 309 for s in siblings:
127 310 d = {'node': hex(s.node()), 'rev': s.rev()}
128 311 if hasattr(s, 'path'):
129 312 d['file'] = s.path()
130 313 d.update(args)
131 314 yield d
132 315
133 316 def renamelink(self, fl, node):
134 317 r = fl.renamed(node)
135 318 if r:
136 319 return [dict(file=r[0], node=hex(r[1]))]
137 320 return []
138 321
139 322 def nodetagsdict(self, node):
140 323 return [{"name": i} for i in self.repo.nodetags(node)]
141 324
142 325 def nodebranchdict(self, ctx):
143 326 branches = []
144 327 branch = ctx.branch()
145 328 # If this is an empty repo, ctx.node() == nullid,
146 329 # ctx.branch() == 'default', but branchtags() is
147 330 # an empty dict. Using dict.get avoids a traceback.
148 331 if self.repo.branchtags().get(branch) == ctx.node():
149 332 branches.append({"name": branch})
150 333 return branches
151 334
152 335 def showtag(self, t1, node=nullid, **args):
153 336 for t in self.repo.nodetags(node):
154 337 yield self.t(t1, tag=t, **args)
155 338
156 339 def diff(self, node1, node2, files):
157 340 def filterfiles(filters, files):
158 341 l = [x for x in files if x in filters]
159 342
160 343 for t in filters:
161 344 if t and t[-1] != os.sep:
162 345 t += os.sep
163 346 l += [x for x in files if x.startswith(t)]
164 347 return l
165 348
166 349 parity = paritygen(self.stripecount)
167 350 def diffblock(diff, f, fn):
168 351 yield self.t("diffblock",
169 352 lines=prettyprintlines(diff),
170 353 parity=parity.next(),
171 354 file=f,
172 355 filenode=hex(fn or nullid))
173 356
174 357 def prettyprintlines(diff):
175 358 for l in diff.splitlines(1):
176 359 if l.startswith('+'):
177 360 yield self.t("difflineplus", line=l)
178 361 elif l.startswith('-'):
179 362 yield self.t("difflineminus", line=l)
180 363 elif l.startswith('@'):
181 364 yield self.t("difflineat", line=l)
182 365 else:
183 366 yield self.t("diffline", line=l)
184 367
185 368 r = self.repo
186 369 c1 = r.changectx(node1)
187 370 c2 = r.changectx(node2)
188 371 date1 = util.datestr(c1.date())
189 372 date2 = util.datestr(c2.date())
190 373
191 374 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
192 375 if files:
193 376 modified, added, removed = map(lambda x: filterfiles(files, x),
194 377 (modified, added, removed))
195 378
196 379 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
197 380 for f in modified:
198 381 to = c1.filectx(f).data()
199 382 tn = c2.filectx(f).data()
200 383 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
201 384 opts=diffopts), f, tn)
202 385 for f in added:
203 386 to = None
204 387 tn = c2.filectx(f).data()
205 388 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
206 389 opts=diffopts), f, tn)
207 390 for f in removed:
208 391 to = c1.filectx(f).data()
209 392 tn = None
210 393 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
211 394 opts=diffopts), f, tn)
212 395
213 396 def changelog(self, ctx, shortlog=False):
214 397 def changelist(limit=0,**map):
215 398 cl = self.repo.changelog
216 399 l = [] # build a list in forward order for efficiency
217 400 for i in xrange(start, end):
218 401 ctx = self.repo.changectx(i)
219 402 n = ctx.node()
220 403
221 404 l.insert(0, {"parity": parity.next(),
222 405 "author": ctx.user(),
223 406 "parent": self.siblings(ctx.parents(), i - 1),
224 407 "child": self.siblings(ctx.children(), i + 1),
225 408 "changelogtag": self.showtag("changelogtag",n),
226 409 "desc": ctx.description(),
227 410 "date": ctx.date(),
228 411 "files": self.listfilediffs(ctx.files(), n),
229 412 "rev": i,
230 413 "node": hex(n),
231 414 "tags": self.nodetagsdict(n),
232 415 "branches": self.nodebranchdict(ctx)})
233 416
234 417 if limit > 0:
235 418 l = l[:limit]
236 419
237 420 for e in l:
238 421 yield e
239 422
240 423 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
241 424 cl = self.repo.changelog
242 425 count = cl.count()
243 426 pos = ctx.rev()
244 427 start = max(0, pos - maxchanges + 1)
245 428 end = min(count, start + maxchanges)
246 429 pos = end - 1
247 430 parity = paritygen(self.stripecount, offset=start-end)
248 431
249 432 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
250 433
251 434 yield self.t(shortlog and 'shortlog' or 'changelog',
252 435 changenav=changenav,
253 436 node=hex(cl.tip()),
254 437 rev=pos, changesets=count,
255 438 entries=lambda **x: changelist(limit=0,**x),
256 439 latestentry=lambda **x: changelist(limit=1,**x),
257 440 archives=self.archivelist("tip"))
258 441
259 442 def search(self, query):
260 443
261 444 def changelist(**map):
262 445 cl = self.repo.changelog
263 446 count = 0
264 447 qw = query.lower().split()
265 448
266 449 def revgen():
267 450 for i in xrange(cl.count() - 1, 0, -100):
268 451 l = []
269 452 for j in xrange(max(0, i - 100), i):
270 453 ctx = self.repo.changectx(j)
271 454 l.append(ctx)
272 455 l.reverse()
273 456 for e in l:
274 457 yield e
275 458
276 459 for ctx in revgen():
277 460 miss = 0
278 461 for q in qw:
279 462 if not (q in ctx.user().lower() or
280 463 q in ctx.description().lower() or
281 464 q in " ".join(ctx.files()).lower()):
282 465 miss = 1
283 466 break
284 467 if miss:
285 468 continue
286 469
287 470 count += 1
288 471 n = ctx.node()
289 472
290 473 yield self.t('searchentry',
291 474 parity=parity.next(),
292 475 author=ctx.user(),
293 476 parent=self.siblings(ctx.parents()),
294 477 child=self.siblings(ctx.children()),
295 478 changelogtag=self.showtag("changelogtag",n),
296 479 desc=ctx.description(),
297 480 date=ctx.date(),
298 481 files=self.listfilediffs(ctx.files(), n),
299 482 rev=ctx.rev(),
300 483 node=hex(n),
301 484 tags=self.nodetagsdict(n),
302 485 branches=self.nodebranchdict(ctx))
303 486
304 487 if count >= self.maxchanges:
305 488 break
306 489
307 490 cl = self.repo.changelog
308 491 parity = paritygen(self.stripecount)
309 492
310 493 yield self.t('search',
311 494 query=query,
312 495 node=hex(cl.tip()),
313 496 entries=changelist,
314 497 archives=self.archivelist("tip"))
315 498
316 499 def changeset(self, ctx):
317 500 n = ctx.node()
318 501 parents = ctx.parents()
319 502 p1 = parents[0].node()
320 503
321 504 files = []
322 505 parity = paritygen(self.stripecount)
323 506 for f in ctx.files():
324 507 files.append(self.t("filenodelink",
325 508 node=hex(n), file=f,
326 509 parity=parity.next()))
327 510
328 511 def diff(**map):
329 512 yield self.diff(p1, n, None)
330 513
331 514 yield self.t('changeset',
332 515 diff=diff,
333 516 rev=ctx.rev(),
334 517 node=hex(n),
335 518 parent=self.siblings(parents),
336 519 child=self.siblings(ctx.children()),
337 520 changesettag=self.showtag("changesettag",n),
338 521 author=ctx.user(),
339 522 desc=ctx.description(),
340 523 date=ctx.date(),
341 524 files=files,
342 525 archives=self.archivelist(hex(n)),
343 526 tags=self.nodetagsdict(n),
344 527 branches=self.nodebranchdict(ctx))
345 528
346 529 def filelog(self, fctx):
347 530 f = fctx.path()
348 531 fl = fctx.filelog()
349 532 count = fl.count()
350 533 pagelen = self.maxshortchanges
351 534 pos = fctx.filerev()
352 535 start = max(0, pos - pagelen + 1)
353 536 end = min(count, start + pagelen)
354 537 pos = end - 1
355 538 parity = paritygen(self.stripecount, offset=start-end)
356 539
357 540 def entries(limit=0, **map):
358 541 l = []
359 542
360 543 for i in xrange(start, end):
361 544 ctx = fctx.filectx(i)
362 545 n = fl.node(i)
363 546
364 547 l.insert(0, {"parity": parity.next(),
365 548 "filerev": i,
366 549 "file": f,
367 550 "node": hex(ctx.node()),
368 551 "author": ctx.user(),
369 552 "date": ctx.date(),
370 553 "rename": self.renamelink(fl, n),
371 554 "parent": self.siblings(fctx.parents()),
372 555 "child": self.siblings(fctx.children()),
373 556 "desc": ctx.description()})
374 557
375 558 if limit > 0:
376 559 l = l[:limit]
377 560
378 561 for e in l:
379 562 yield e
380 563
381 564 nodefunc = lambda x: fctx.filectx(fileid=x)
382 565 nav = revnavgen(pos, pagelen, count, nodefunc)
383 566 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
384 567 entries=lambda **x: entries(limit=0, **x),
385 568 latestentry=lambda **x: entries(limit=1, **x))
386 569
387 570 def filerevision(self, fctx):
388 571 f = fctx.path()
389 572 text = fctx.data()
390 573 fl = fctx.filelog()
391 574 n = fctx.filenode()
392 575 parity = paritygen(self.stripecount)
393 576
394 577 mt = mimetypes.guess_type(f)[0]
395 578 rawtext = text
396 579 if util.binary(text):
397 580 mt = mt or 'application/octet-stream'
398 581 text = "(binary:%s)" % mt
399 582 mt = mt or 'text/plain'
400 583
401 584 def lines():
402 585 for l, t in enumerate(text.splitlines(1)):
403 586 yield {"line": t,
404 587 "linenumber": "% 6d" % (l + 1),
405 588 "parity": parity.next()}
406 589
407 590 yield self.t("filerevision",
408 591 file=f,
409 592 path=_up(f),
410 593 text=lines(),
411 594 raw=rawtext,
412 595 mimetype=mt,
413 596 rev=fctx.rev(),
414 597 node=hex(fctx.node()),
415 598 author=fctx.user(),
416 599 date=fctx.date(),
417 600 desc=fctx.description(),
418 601 parent=self.siblings(fctx.parents()),
419 602 child=self.siblings(fctx.children()),
420 603 rename=self.renamelink(fl, n),
421 604 permissions=fctx.manifest().flags(f))
422 605
423 606 def fileannotate(self, fctx):
424 607 f = fctx.path()
425 608 n = fctx.filenode()
426 609 fl = fctx.filelog()
427 610 parity = paritygen(self.stripecount)
428 611
429 612 def annotate(**map):
430 613 last = None
431 614 for f, l in fctx.annotate(follow=True):
432 615 fnode = f.filenode()
433 616 name = self.repo.ui.shortuser(f.user())
434 617
435 618 if last != fnode:
436 619 last = fnode
437 620
438 621 yield {"parity": parity.next(),
439 622 "node": hex(f.node()),
440 623 "rev": f.rev(),
441 624 "author": name,
442 625 "file": f.path(),
443 626 "line": l}
444 627
445 628 yield self.t("fileannotate",
446 629 file=f,
447 630 annotate=annotate,
448 631 path=_up(f),
449 632 rev=fctx.rev(),
450 633 node=hex(fctx.node()),
451 634 author=fctx.user(),
452 635 date=fctx.date(),
453 636 desc=fctx.description(),
454 637 rename=self.renamelink(fl, n),
455 638 parent=self.siblings(fctx.parents()),
456 639 child=self.siblings(fctx.children()),
457 640 permissions=fctx.manifest().flags(f))
458 641
459 642 def manifest(self, ctx, path):
460 643 mf = ctx.manifest()
461 644 node = ctx.node()
462 645
463 646 files = {}
464 647 parity = paritygen(self.stripecount)
465 648
466 649 if path and path[-1] != "/":
467 650 path += "/"
468 651 l = len(path)
469 652 abspath = "/" + path
470 653
471 654 for f, n in mf.items():
472 655 if f[:l] != path:
473 656 continue
474 657 remain = f[l:]
475 658 if "/" in remain:
476 659 short = remain[:remain.index("/") + 1] # bleah
477 660 files[short] = (f, None)
478 661 else:
479 662 short = os.path.basename(remain)
480 663 files[short] = (f, n)
481 664
482 665 if not files:
483 666 raise ErrorResponse(404, 'Path not found: ' + path)
484 667
485 668 def filelist(**map):
486 669 fl = files.keys()
487 670 fl.sort()
488 671 for f in fl:
489 672 full, fnode = files[f]
490 673 if not fnode:
491 674 continue
492 675
493 676 fctx = ctx.filectx(full)
494 677 yield {"file": full,
495 678 "parity": parity.next(),
496 679 "basename": f,
497 680 "date": fctx.changectx().date(),
498 681 "size": fctx.size(),
499 682 "permissions": mf.flags(full)}
500 683
501 684 def dirlist(**map):
502 685 fl = files.keys()
503 686 fl.sort()
504 687 for f in fl:
505 688 full, fnode = files[f]
506 689 if fnode:
507 690 continue
508 691
509 692 yield {"parity": parity.next(),
510 693 "path": "%s%s" % (abspath, f),
511 694 "basename": f[:-1]}
512 695
513 696 yield self.t("manifest",
514 697 rev=ctx.rev(),
515 698 node=hex(node),
516 699 path=abspath,
517 700 up=_up(abspath),
518 701 upparity=parity.next(),
519 702 fentries=filelist,
520 703 dentries=dirlist,
521 704 archives=self.archivelist(hex(node)),
522 705 tags=self.nodetagsdict(node),
523 706 branches=self.nodebranchdict(ctx))
524 707
525 708 def tags(self):
526 709 i = self.repo.tagslist()
527 710 i.reverse()
528 711 parity = paritygen(self.stripecount)
529 712
530 713 def entries(notip=False,limit=0, **map):
531 714 count = 0
532 715 for k, n in i:
533 716 if notip and k == "tip":
534 717 continue
535 718 if limit > 0 and count >= limit:
536 719 continue
537 720 count = count + 1
538 721 yield {"parity": parity.next(),
539 722 "tag": k,
540 723 "date": self.repo.changectx(n).date(),
541 724 "node": hex(n)}
542 725
543 726 yield self.t("tags",
544 727 node=hex(self.repo.changelog.tip()),
545 728 entries=lambda **x: entries(False,0, **x),
546 729 entriesnotip=lambda **x: entries(True,0, **x),
547 730 latestentry=lambda **x: entries(True,1, **x))
548 731
549 732 def summary(self):
550 733 i = self.repo.tagslist()
551 734 i.reverse()
552 735
553 736 def tagentries(**map):
554 737 parity = paritygen(self.stripecount)
555 738 count = 0
556 739 for k, n in i:
557 740 if k == "tip": # skip tip
558 741 continue;
559 742
560 743 count += 1
561 744 if count > 10: # limit to 10 tags
562 745 break;
563 746
564 747 yield self.t("tagentry",
565 748 parity=parity.next(),
566 749 tag=k,
567 750 node=hex(n),
568 751 date=self.repo.changectx(n).date())
569 752
570 753
571 754 def branches(**map):
572 755 parity = paritygen(self.stripecount)
573 756
574 757 b = self.repo.branchtags()
575 758 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
576 759 l.sort()
577 760
578 761 for r,n,t in l:
579 762 ctx = self.repo.changectx(n)
580 763
581 764 yield {'parity': parity.next(),
582 765 'branch': t,
583 766 'node': hex(n),
584 767 'date': ctx.date()}
585 768
586 769 def changelist(**map):
587 770 parity = paritygen(self.stripecount, offset=start-end)
588 771 l = [] # build a list in forward order for efficiency
589 772 for i in xrange(start, end):
590 773 ctx = self.repo.changectx(i)
591 774 n = ctx.node()
592 775 hn = hex(n)
593 776
594 777 l.insert(0, self.t(
595 778 'shortlogentry',
596 779 parity=parity.next(),
597 780 author=ctx.user(),
598 781 desc=ctx.description(),
599 782 date=ctx.date(),
600 783 rev=i,
601 784 node=hn,
602 785 tags=self.nodetagsdict(n),
603 786 branches=self.nodebranchdict(ctx)))
604 787
605 788 yield l
606 789
607 790 cl = self.repo.changelog
608 791 count = cl.count()
609 792 start = max(0, count - self.maxchanges)
610 793 end = min(count, start + self.maxchanges)
611 794
612 795 yield self.t("summary",
613 796 desc=self.config("web", "description", "unknown"),
614 797 owner=(self.config("ui", "username") or # preferred
615 798 self.config("web", "contact") or # deprecated
616 799 self.config("web", "author", "unknown")), # also
617 800 lastchange=cl.read(cl.tip())[2],
618 801 tags=tagentries,
619 802 branches=branches,
620 803 shortlog=changelist,
621 804 node=hex(cl.tip()),
622 805 archives=self.archivelist("tip"))
623 806
624 807 def filediff(self, fctx):
625 808 n = fctx.node()
626 809 path = fctx.path()
627 810 parents = fctx.parents()
628 811 p1 = parents and parents[0].node() or nullid
629 812
630 813 def diff(**map):
631 814 yield self.diff(p1, n, [path])
632 815
633 816 yield self.t("filediff",
634 817 file=path,
635 818 node=hex(n),
636 819 rev=fctx.rev(),
637 820 parent=self.siblings(parents),
638 821 child=self.siblings(fctx.children()),
639 822 diff=diff)
640 823
641 824 archive_specs = {
642 825 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
643 826 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
644 827 'zip': ('application/zip', 'zip', '.zip', None),
645 828 }
646 829
647 830 def archive(self, req, key, type_):
648 831 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
649 832 cnode = self.repo.lookup(key)
650 833 arch_version = key
651 834 if cnode == key or key == 'tip':
652 835 arch_version = short(cnode)
653 836 name = "%s-%s" % (reponame, arch_version)
654 837 mimetype, artype, extension, encoding = self.archive_specs[type_]
655 838 headers = [('Content-type', mimetype),
656 839 ('Content-disposition', 'attachment; filename=%s%s' %
657 840 (name, extension))]
658 841 if encoding:
659 842 headers.append(('Content-encoding', encoding))
660 843 req.header(headers)
661 844 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
662 845
663 846 # add tags to things
664 847 # tags -> list of changesets corresponding to tags
665 848 # find tag, changeset, file
666 849
667 850 def cleanpath(self, path):
668 851 path = path.lstrip('/')
669 852 return util.canonpath(self.repo.root, '', path)
670 853
671 def run(self):
672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
673 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
674 import mercurial.hgweb.wsgicgi as wsgicgi
675 wsgicgi.launch(self)
676
677 def __call__(self, env, respond):
678 req = wsgirequest(env, respond)
679 self.run_wsgi(req)
680 return req
681
682 def run_wsgi(self, req):
683 def header(**map):
684 header_file = cStringIO.StringIO(
685 ''.join(self.t("header", encoding=self.encoding, **map)))
686 msg = mimetools.Message(header_file, 0)
687 req.header(msg.items())
688 yield header_file.read()
689
690 def rawfileheader(**map):
691 req.header([('Content-type', map['mimetype']),
692 ('Content-disposition', 'filename=%s' % map['file']),
693 ('Content-length', str(len(map['raw'])))])
694 yield ''
695
696 def footer(**map):
697 yield self.t("footer", **map)
698
699 def motd(**map):
700 yield self.config("web", "motd", "")
701
702 def expand_form(form):
703 shortcuts = {
704 'cl': [('cmd', ['changelog']), ('rev', None)],
705 'sl': [('cmd', ['shortlog']), ('rev', None)],
706 'cs': [('cmd', ['changeset']), ('node', None)],
707 'f': [('cmd', ['file']), ('filenode', None)],
708 'fl': [('cmd', ['filelog']), ('filenode', None)],
709 'fd': [('cmd', ['filediff']), ('node', None)],
710 'fa': [('cmd', ['annotate']), ('filenode', None)],
711 'mf': [('cmd', ['manifest']), ('manifest', None)],
712 'ca': [('cmd', ['archive']), ('node', None)],
713 'tags': [('cmd', ['tags'])],
714 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
715 'static': [('cmd', ['static']), ('file', None)]
716 }
717
718 for k in shortcuts.iterkeys():
719 if form.has_key(k):
720 for name, value in shortcuts[k]:
721 if value is None:
722 value = form[k]
723 form[name] = value
724 del form[k]
725
726 def rewrite_request(req):
727 '''translate new web interface to traditional format'''
728
729 req.url = req.env['SCRIPT_NAME']
730 if not req.url.endswith('/'):
731 req.url += '/'
732 if req.env.has_key('REPO_NAME'):
733 req.url += req.env['REPO_NAME'] + '/'
734
735 if req.env.get('PATH_INFO'):
736 parts = req.env.get('PATH_INFO').strip('/').split('/')
737 repo_parts = req.env.get('REPO_NAME', '').split('/')
738 if parts[:len(repo_parts)] == repo_parts:
739 parts = parts[len(repo_parts):]
740 query = '/'.join(parts)
741 else:
742 query = req.env['QUERY_STRING'].split('&', 1)[0]
743 query = query.split(';', 1)[0]
744
745 if req.form.has_key('cmd'):
746 # old style
747 return
748
749 args = query.split('/', 2)
750 if not args or not args[0]:
751 return
752
753 cmd = args.pop(0)
754 style = cmd.rfind('-')
755 if style != -1:
756 req.form['style'] = [cmd[:style]]
757 cmd = cmd[style+1:]
758 # avoid accepting e.g. style parameter as command
759 if hasattr(self, 'do_' + cmd):
760 req.form['cmd'] = [cmd]
761
762 if args and args[0]:
763 node = args.pop(0)
764 req.form['node'] = [node]
765 if args:
766 req.form['file'] = args
767
768 if cmd == 'static':
769 req.form['file'] = req.form['node']
770 elif cmd == 'archive':
771 fn = req.form['node'][0]
772 for type_, spec in self.archive_specs.iteritems():
773 ext = spec[2]
774 if fn.endswith(ext):
775 req.form['node'] = [fn[:-len(ext)]]
776 req.form['type'] = [type_]
777
778 def sessionvars(**map):
779 fields = []
780 if req.form.has_key('style'):
781 style = req.form['style'][0]
782 if style != self.config('web', 'style', ''):
783 fields.append(('style', style))
784
785 separator = req.url[-1] == '?' and ';' or '?'
786 for name, value in fields:
787 yield dict(name=name, value=value, separator=separator)
788 separator = ';'
789
790 self.refresh()
791
792 expand_form(req.form)
793 rewrite_request(req)
794
795 style = self.config("web", "style", "")
796 if req.form.has_key('style'):
797 style = req.form['style'][0]
798 mapfile = style_map(self.templatepath, style)
799
800 proto = req.env.get('wsgi.url_scheme')
801 if proto == 'https':
802 proto = 'https'
803 default_port = "443"
804 else:
805 proto = 'http'
806 default_port = "80"
807
808 port = req.env["SERVER_PORT"]
809 port = port != default_port and (":" + port) or ""
810 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
811 staticurl = self.config("web", "staticurl") or req.url + 'static/'
812 if not staticurl.endswith('/'):
813 staticurl += '/'
814
815 if not self.reponame:
816 self.reponame = (self.config("web", "name")
817 or req.env.get('REPO_NAME')
818 or req.url.strip('/') or self.repo.root)
819
820 self.t = templater.templater(mapfile, templater.common_filters,
821 defaults={"url": req.url,
822 "staticurl": staticurl,
823 "urlbase": urlbase,
824 "repo": self.reponame,
825 "header": header,
826 "footer": footer,
827 "motd": motd,
828 "rawfileheader": rawfileheader,
829 "sessionvars": sessionvars
830 })
831
832 try:
833 if not req.form.has_key('cmd'):
834 req.form['cmd'] = [self.t.cache['default']]
835
836 cmd = req.form['cmd'][0]
837
838 try:
839 method = getattr(self, 'do_' + cmd)
840 method(req)
841 except revlog.LookupError, err:
842 req.respond(404, self.t(
843 'error', error='revision not found: %s' % err.name))
844 except (hg.RepoError, revlog.RevlogError), inst:
845 req.respond('500 Internal Server Error',
846 self.t('error', error=str(inst)))
847 except ErrorResponse, inst:
848 req.respond(inst.code, self.t('error', error=inst.message))
849 except AttributeError:
850 req.respond(400,
851 self.t('error', error='No such method: ' + cmd))
852 finally:
853 self.t = None
854
855 854 def changectx(self, req):
856 855 if req.form.has_key('node'):
857 856 changeid = req.form['node'][0]
858 857 elif req.form.has_key('manifest'):
859 858 changeid = req.form['manifest'][0]
860 859 else:
861 860 changeid = self.repo.changelog.count() - 1
862 861
863 862 try:
864 863 ctx = self.repo.changectx(changeid)
865 864 except hg.RepoError:
866 865 man = self.repo.manifest
867 866 mn = man.lookup(changeid)
868 867 ctx = self.repo.changectx(man.linkrev(mn))
869 868
870 869 return ctx
871 870
872 871 def filectx(self, req):
873 872 path = self.cleanpath(req.form['file'][0])
874 873 if req.form.has_key('node'):
875 874 changeid = req.form['node'][0]
876 875 else:
877 876 changeid = req.form['filenode'][0]
878 877 try:
879 878 ctx = self.repo.changectx(changeid)
880 879 fctx = ctx.filectx(path)
881 880 except hg.RepoError:
882 881 fctx = self.repo.filectx(path, fileid=changeid)
883 882
884 883 return fctx
885 884
886 def do_log(self, req):
887 if req.form.has_key('file') and req.form['file'][0]:
888 self.do_filelog(req)
889 else:
890 self.do_changelog(req)
891
892 def do_rev(self, req):
893 self.do_changeset(req)
894
895 def do_file(self, req):
896 path = self.cleanpath(req.form.get('file', [''])[0])
897 if path:
898 try:
899 req.write(self.filerevision(self.filectx(req)))
900 return
901 except revlog.LookupError:
902 pass
903
904 req.write(self.manifest(self.changectx(req), path))
905
906 def do_diff(self, req):
907 self.do_filediff(req)
908
909 def do_changelog(self, req, shortlog = False):
910 if req.form.has_key('node'):
911 ctx = self.changectx(req)
912 else:
913 if req.form.has_key('rev'):
914 hi = req.form['rev'][0]
915 else:
916 hi = self.repo.changelog.count() - 1
917 try:
918 ctx = self.repo.changectx(hi)
919 except hg.RepoError:
920 req.write(self.search(hi)) # XXX redirect to 404 page?
921 return
922
923 req.write(self.changelog(ctx, shortlog = shortlog))
924
925 def do_shortlog(self, req):
926 self.do_changelog(req, shortlog = True)
927
928 def do_changeset(self, req):
929 req.write(self.changeset(self.changectx(req)))
930
931 def do_manifest(self, req):
932 req.write(self.manifest(self.changectx(req),
933 self.cleanpath(req.form['path'][0])))
934
935 def do_tags(self, req):
936 req.write(self.tags())
937
938 def do_summary(self, req):
939 req.write(self.summary())
940
941 def do_filediff(self, req):
942 req.write(self.filediff(self.filectx(req)))
943
944 def do_annotate(self, req):
945 req.write(self.fileannotate(self.filectx(req)))
946
947 def do_filelog(self, req):
948 req.write(self.filelog(self.filectx(req)))
949
950 def do_lookup(self, req):
951 try:
952 r = hex(self.repo.lookup(req.form['key'][0]))
953 success = 1
954 except Exception,inst:
955 r = str(inst)
956 success = 0
957 resp = "%s %s\n" % (success, r)
958 req.httphdr("application/mercurial-0.1", length=len(resp))
959 req.write(resp)
960
961 def do_heads(self, req):
962 resp = " ".join(map(hex, self.repo.heads())) + "\n"
963 req.httphdr("application/mercurial-0.1", length=len(resp))
964 req.write(resp)
965
966 def do_branches(self, req):
967 nodes = []
968 if req.form.has_key('nodes'):
969 nodes = map(bin, req.form['nodes'][0].split(" "))
970 resp = cStringIO.StringIO()
971 for b in self.repo.branches(nodes):
972 resp.write(" ".join(map(hex, b)) + "\n")
973 resp = resp.getvalue()
974 req.httphdr("application/mercurial-0.1", length=len(resp))
975 req.write(resp)
976
977 def do_between(self, req):
978 if req.form.has_key('pairs'):
979 pairs = [map(bin, p.split("-"))
980 for p in req.form['pairs'][0].split(" ")]
981 resp = cStringIO.StringIO()
982 for b in self.repo.between(pairs):
983 resp.write(" ".join(map(hex, b)) + "\n")
984 resp = resp.getvalue()
985 req.httphdr("application/mercurial-0.1", length=len(resp))
986 req.write(resp)
987
988 def do_changegroup(self, req):
989 req.httphdr("application/mercurial-0.1")
990 nodes = []
991 if not self.allowpull:
992 return
993
994 if req.form.has_key('roots'):
995 nodes = map(bin, req.form['roots'][0].split(" "))
996
997 z = zlib.compressobj()
998 f = self.repo.changegroup(nodes, 'serve')
999 while 1:
1000 chunk = f.read(4096)
1001 if not chunk:
1002 break
1003 req.write(z.compress(chunk))
1004
1005 req.write(z.flush())
1006
1007 def do_changegroupsubset(self, req):
1008 req.httphdr("application/mercurial-0.1")
1009 bases = []
1010 heads = []
1011 if not self.allowpull:
1012 return
1013
1014 if req.form.has_key('bases'):
1015 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1016 if req.form.has_key('heads'):
1017 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1018
1019 z = zlib.compressobj()
1020 f = self.repo.changegroupsubset(bases, heads, 'serve')
1021 while 1:
1022 chunk = f.read(4096)
1023 if not chunk:
1024 break
1025 req.write(z.compress(chunk))
1026
1027 req.write(z.flush())
1028
1029 def do_archive(self, req):
1030 type_ = req.form['type'][0]
1031 allowed = self.configlist("web", "allow_archive")
1032 if (type_ in self.archives and (type_ in allowed or
1033 self.configbool("web", "allow" + type_, False))):
1034 self.archive(req, req.form['node'][0], type_)
1035 return
1036
1037 req.respond(400, self.t('error',
1038 error='Unsupported archive type: %s' % type_))
1039
1040 def do_static(self, req):
1041 fname = req.form['file'][0]
1042 # a repo owner may set web.static in .hg/hgrc to get any file
1043 # readable by the user running the CGI script
1044 static = self.config("web", "static",
1045 os.path.join(self.templatepath, "static"),
1046 untrusted=False)
1047 req.write(staticfile(static, fname, req))
1048
1049 def do_capabilities(self, req):
1050 caps = ['lookup', 'changegroupsubset']
1051 if self.configbool('server', 'uncompressed'):
1052 caps.append('stream=%d' % self.repo.changelog.version)
1053 # XXX: make configurable and/or share code with do_unbundle:
1054 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1055 if unbundleversions:
1056 caps.append('unbundle=%s' % ','.join(unbundleversions))
1057 resp = ' '.join(caps)
1058 req.httphdr("application/mercurial-0.1", length=len(resp))
1059 req.write(resp)
1060
1061 885 def check_perm(self, req, op, default):
1062 886 '''check permission for operation based on user auth.
1063 887 return true if op allowed, else false.
1064 888 default is policy to use if no config given.'''
1065 889
1066 890 user = req.env.get('REMOTE_USER')
1067 891
1068 892 deny = self.configlist('web', 'deny_' + op)
1069 893 if deny and (not user or deny == ['*'] or user in deny):
1070 894 return False
1071 895
1072 896 allow = self.configlist('web', 'allow_' + op)
1073 897 return (allow and (allow == ['*'] or user in allow)) or default
1074
1075 def do_unbundle(self, req):
1076 def bail(response, headers={}):
1077 length = int(req.env['CONTENT_LENGTH'])
1078 for s in util.filechunkiter(req, limit=length):
1079 # drain incoming bundle, else client will not see
1080 # response when run outside cgi script
1081 pass
1082 req.httphdr("application/mercurial-0.1", headers=headers)
1083 req.write('0\n')
1084 req.write(response)
1085
1086 # require ssl by default, auth info cannot be sniffed and
1087 # replayed
1088 ssl_req = self.configbool('web', 'push_ssl', True)
1089 if ssl_req:
1090 if req.env.get('wsgi.url_scheme') != 'https':
1091 bail(_('ssl required\n'))
1092 return
1093 proto = 'https'
1094 else:
1095 proto = 'http'
1096
1097 # do not allow push unless explicitly allowed
1098 if not self.check_perm(req, 'push', False):
1099 bail(_('push not authorized\n'),
1100 headers={'status': '401 Unauthorized'})
1101 return
1102
1103 their_heads = req.form['heads'][0].split(' ')
1104
1105 def check_heads():
1106 heads = map(hex, self.repo.heads())
1107 return their_heads == [hex('force')] or their_heads == heads
1108
1109 # fail early if possible
1110 if not check_heads():
1111 bail(_('unsynced changes\n'))
1112 return
1113
1114 req.httphdr("application/mercurial-0.1")
1115
1116 # do not lock repo until all changegroup data is
1117 # streamed. save to temporary file.
1118
1119 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1120 fp = os.fdopen(fd, 'wb+')
1121 try:
1122 length = int(req.env['CONTENT_LENGTH'])
1123 for s in util.filechunkiter(req, limit=length):
1124 fp.write(s)
1125
1126 try:
1127 lock = self.repo.lock()
1128 try:
1129 if not check_heads():
1130 req.write('0\n')
1131 req.write(_('unsynced changes\n'))
1132 return
1133
1134 fp.seek(0)
1135 header = fp.read(6)
1136 if not header.startswith("HG"):
1137 # old client with uncompressed bundle
1138 def generator(f):
1139 yield header
1140 for chunk in f:
1141 yield chunk
1142 elif not header.startswith("HG10"):
1143 req.write("0\n")
1144 req.write(_("unknown bundle version\n"))
1145 return
1146 elif header == "HG10GZ":
1147 def generator(f):
1148 zd = zlib.decompressobj()
1149 for chunk in f:
1150 yield zd.decompress(chunk)
1151 elif header == "HG10BZ":
1152 def generator(f):
1153 zd = bz2.BZ2Decompressor()
1154 zd.decompress("BZ")
1155 for chunk in f:
1156 yield zd.decompress(chunk)
1157 elif header == "HG10UN":
1158 def generator(f):
1159 for chunk in f:
1160 yield chunk
1161 else:
1162 req.write("0\n")
1163 req.write(_("unknown bundle compression type\n"))
1164 return
1165 gen = generator(util.filechunkiter(fp, 4096))
1166
1167 # send addchangegroup output to client
1168
1169 old_stdout = sys.stdout
1170 sys.stdout = cStringIO.StringIO()
1171
1172 try:
1173 url = 'remote:%s:%s' % (proto,
1174 req.env.get('REMOTE_HOST', ''))
1175 try:
1176 ret = self.repo.addchangegroup(
1177 util.chunkbuffer(gen), 'serve', url)
1178 except util.Abort, inst:
1179 sys.stdout.write("abort: %s\n" % inst)
1180 ret = 0
1181 finally:
1182 val = sys.stdout.getvalue()
1183 sys.stdout = old_stdout
1184 req.write('%d\n' % ret)
1185 req.write(val)
1186 finally:
1187 del lock
1188 except (OSError, IOError), inst:
1189 req.write('0\n')
1190 filename = getattr(inst, 'filename', '')
1191 # Don't send our filesystem layout to the client
1192 if filename.startswith(self.repo.root):
1193 filename = filename[len(self.repo.root)+1:]
1194 else:
1195 filename = ''
1196 error = getattr(inst, 'strerror', 'Unknown error')
1197 if inst.errno == errno.ENOENT:
1198 code = 404
1199 else:
1200 code = 500
1201 req.respond(code, '%s: %s\n' % (error, filename))
1202 finally:
1203 fp.close()
1204 os.unlink(tempname)
1205
1206 def do_stream_out(self, req):
1207 req.httphdr("application/mercurial-0.1")
1208 streamclone.stream_out(self.repo, req, untrusted=True)
General Comments 0
You need to be logged in to leave comments. Login now