##// END OF EJS Templates
hgweb: fix hgweb_mod as well as hgwebdir_mod
Augie Fackler -
r12691:1b1a9038 default
parent child Browse files
Show More
@@ -1,286 +1,286
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os
9 import os
10 from mercurial import ui, hg, hook, error, encoding, templater
10 from mercurial import ui, hg, hook, error, encoding, templater
11 from common import get_mtime, ErrorResponse, permhooks, caching
11 from common import get_mtime, ErrorResponse, permhooks, caching
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 from request import wsgirequest
13 from request import wsgirequest
14 import webcommands, protocol, webutil
14 import webcommands, protocol, webutil
15
15
16 perms = {
16 perms = {
17 'changegroup': 'pull',
17 'changegroup': 'pull',
18 'changegroupsubset': 'pull',
18 'changegroupsubset': 'pull',
19 'stream_out': 'pull',
19 'stream_out': 'pull',
20 'listkeys': 'pull',
20 'listkeys': 'pull',
21 'unbundle': 'push',
21 'unbundle': 'push',
22 'pushkey': 'push',
22 'pushkey': 'push',
23 }
23 }
24
24
25 class hgweb(object):
25 class hgweb(object):
26 def __init__(self, repo, name=None, baseui=None):
26 def __init__(self, repo, name=None, baseui=None):
27 if isinstance(repo, str):
27 if isinstance(repo, str):
28 if baseui:
28 if baseui:
29 u = baseui.copy()
29 u = baseui.copy()
30 else:
30 else:
31 u = ui.ui()
31 u = webutil.wsgiui()
32 self.repo = hg.repository(u, repo)
32 self.repo = hg.repository(u, repo)
33 else:
33 else:
34 self.repo = repo
34 self.repo = repo
35
35
36 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
36 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
37 self.repo.ui.setconfig('ui', 'interactive', 'off')
37 self.repo.ui.setconfig('ui', 'interactive', 'off')
38 hook.redirect(True)
38 hook.redirect(True)
39 self.mtime = -1
39 self.mtime = -1
40 self.reponame = name
40 self.reponame = name
41 self.archives = 'zip', 'gz', 'bz2'
41 self.archives = 'zip', 'gz', 'bz2'
42 self.stripecount = 1
42 self.stripecount = 1
43 # a repo owner may set web.templates in .hg/hgrc to get any file
43 # a repo owner may set web.templates in .hg/hgrc to get any file
44 # readable by the user running the CGI script
44 # readable by the user running the CGI script
45 self.templatepath = self.config('web', 'templates')
45 self.templatepath = self.config('web', 'templates')
46
46
47 # The CGI scripts are often run by a user different from the repo owner.
47 # The CGI scripts are often run by a user different from the repo owner.
48 # Trust the settings from the .hg/hgrc files by default.
48 # Trust the settings from the .hg/hgrc files by default.
49 def config(self, section, name, default=None, untrusted=True):
49 def config(self, section, name, default=None, untrusted=True):
50 return self.repo.ui.config(section, name, default,
50 return self.repo.ui.config(section, name, default,
51 untrusted=untrusted)
51 untrusted=untrusted)
52
52
53 def configbool(self, section, name, default=False, untrusted=True):
53 def configbool(self, section, name, default=False, untrusted=True):
54 return self.repo.ui.configbool(section, name, default,
54 return self.repo.ui.configbool(section, name, default,
55 untrusted=untrusted)
55 untrusted=untrusted)
56
56
57 def configlist(self, section, name, default=None, untrusted=True):
57 def configlist(self, section, name, default=None, untrusted=True):
58 return self.repo.ui.configlist(section, name, default,
58 return self.repo.ui.configlist(section, name, default,
59 untrusted=untrusted)
59 untrusted=untrusted)
60
60
61 def refresh(self, request=None):
61 def refresh(self, request=None):
62 if request:
62 if request:
63 self.repo.ui.environ = request.env
63 self.repo.ui.environ = request.env
64 mtime = get_mtime(self.repo.spath)
64 mtime = get_mtime(self.repo.spath)
65 if mtime != self.mtime:
65 if mtime != self.mtime:
66 self.mtime = mtime
66 self.mtime = mtime
67 self.repo = hg.repository(self.repo.ui, self.repo.root)
67 self.repo = hg.repository(self.repo.ui, self.repo.root)
68 self.maxchanges = int(self.config("web", "maxchanges", 10))
68 self.maxchanges = int(self.config("web", "maxchanges", 10))
69 self.stripecount = int(self.config("web", "stripes", 1))
69 self.stripecount = int(self.config("web", "stripes", 1))
70 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
70 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
71 self.maxfiles = int(self.config("web", "maxfiles", 10))
71 self.maxfiles = int(self.config("web", "maxfiles", 10))
72 self.allowpull = self.configbool("web", "allowpull", True)
72 self.allowpull = self.configbool("web", "allowpull", True)
73 encoding.encoding = self.config("web", "encoding",
73 encoding.encoding = self.config("web", "encoding",
74 encoding.encoding)
74 encoding.encoding)
75
75
76 def run(self):
76 def run(self):
77 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
77 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
78 raise RuntimeError("This function is only intended to be "
78 raise RuntimeError("This function is only intended to be "
79 "called while running as a CGI script.")
79 "called while running as a CGI script.")
80 import mercurial.hgweb.wsgicgi as wsgicgi
80 import mercurial.hgweb.wsgicgi as wsgicgi
81 wsgicgi.launch(self)
81 wsgicgi.launch(self)
82
82
83 def __call__(self, env, respond):
83 def __call__(self, env, respond):
84 req = wsgirequest(env, respond)
84 req = wsgirequest(env, respond)
85 return self.run_wsgi(req)
85 return self.run_wsgi(req)
86
86
87 def run_wsgi(self, req):
87 def run_wsgi(self, req):
88
88
89 self.refresh(req)
89 self.refresh(req)
90
90
91 # work with CGI variables to create coherent structure
91 # work with CGI variables to create coherent structure
92 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
92 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
93
93
94 req.url = req.env['SCRIPT_NAME']
94 req.url = req.env['SCRIPT_NAME']
95 if not req.url.endswith('/'):
95 if not req.url.endswith('/'):
96 req.url += '/'
96 req.url += '/'
97 if 'REPO_NAME' in req.env:
97 if 'REPO_NAME' in req.env:
98 req.url += req.env['REPO_NAME'] + '/'
98 req.url += req.env['REPO_NAME'] + '/'
99
99
100 if 'PATH_INFO' in req.env:
100 if 'PATH_INFO' in req.env:
101 parts = req.env['PATH_INFO'].strip('/').split('/')
101 parts = req.env['PATH_INFO'].strip('/').split('/')
102 repo_parts = req.env.get('REPO_NAME', '').split('/')
102 repo_parts = req.env.get('REPO_NAME', '').split('/')
103 if parts[:len(repo_parts)] == repo_parts:
103 if parts[:len(repo_parts)] == repo_parts:
104 parts = parts[len(repo_parts):]
104 parts = parts[len(repo_parts):]
105 query = '/'.join(parts)
105 query = '/'.join(parts)
106 else:
106 else:
107 query = req.env['QUERY_STRING'].split('&', 1)[0]
107 query = req.env['QUERY_STRING'].split('&', 1)[0]
108 query = query.split(';', 1)[0]
108 query = query.split(';', 1)[0]
109
109
110 # process this if it's a protocol request
110 # process this if it's a protocol request
111 # protocol bits don't need to create any URLs
111 # protocol bits don't need to create any URLs
112 # and the clients always use the old URL structure
112 # and the clients always use the old URL structure
113
113
114 cmd = req.form.get('cmd', [''])[0]
114 cmd = req.form.get('cmd', [''])[0]
115 if protocol.iscmd(cmd):
115 if protocol.iscmd(cmd):
116 if query:
116 if query:
117 raise ErrorResponse(HTTP_NOT_FOUND)
117 raise ErrorResponse(HTTP_NOT_FOUND)
118 if cmd in perms:
118 if cmd in perms:
119 try:
119 try:
120 self.check_perm(req, perms[cmd])
120 self.check_perm(req, perms[cmd])
121 except ErrorResponse, inst:
121 except ErrorResponse, inst:
122 if cmd == 'unbundle':
122 if cmd == 'unbundle':
123 req.drain()
123 req.drain()
124 req.respond(inst, protocol.HGTYPE)
124 req.respond(inst, protocol.HGTYPE)
125 return '0\n%s\n' % inst.message
125 return '0\n%s\n' % inst.message
126 return protocol.call(self.repo, req, cmd)
126 return protocol.call(self.repo, req, cmd)
127
127
128 # translate user-visible url structure to internal structure
128 # translate user-visible url structure to internal structure
129
129
130 args = query.split('/', 2)
130 args = query.split('/', 2)
131 if 'cmd' not in req.form and args and args[0]:
131 if 'cmd' not in req.form and args and args[0]:
132
132
133 cmd = args.pop(0)
133 cmd = args.pop(0)
134 style = cmd.rfind('-')
134 style = cmd.rfind('-')
135 if style != -1:
135 if style != -1:
136 req.form['style'] = [cmd[:style]]
136 req.form['style'] = [cmd[:style]]
137 cmd = cmd[style + 1:]
137 cmd = cmd[style + 1:]
138
138
139 # avoid accepting e.g. style parameter as command
139 # avoid accepting e.g. style parameter as command
140 if hasattr(webcommands, cmd):
140 if hasattr(webcommands, cmd):
141 req.form['cmd'] = [cmd]
141 req.form['cmd'] = [cmd]
142 else:
142 else:
143 cmd = ''
143 cmd = ''
144
144
145 if cmd == 'static':
145 if cmd == 'static':
146 req.form['file'] = ['/'.join(args)]
146 req.form['file'] = ['/'.join(args)]
147 else:
147 else:
148 if args and args[0]:
148 if args and args[0]:
149 node = args.pop(0)
149 node = args.pop(0)
150 req.form['node'] = [node]
150 req.form['node'] = [node]
151 if args:
151 if args:
152 req.form['file'] = args
152 req.form['file'] = args
153
153
154 ua = req.env.get('HTTP_USER_AGENT', '')
154 ua = req.env.get('HTTP_USER_AGENT', '')
155 if cmd == 'rev' and 'mercurial' in ua:
155 if cmd == 'rev' and 'mercurial' in ua:
156 req.form['style'] = ['raw']
156 req.form['style'] = ['raw']
157
157
158 if cmd == 'archive':
158 if cmd == 'archive':
159 fn = req.form['node'][0]
159 fn = req.form['node'][0]
160 for type_, spec in self.archive_specs.iteritems():
160 for type_, spec in self.archive_specs.iteritems():
161 ext = spec[2]
161 ext = spec[2]
162 if fn.endswith(ext):
162 if fn.endswith(ext):
163 req.form['node'] = [fn[:-len(ext)]]
163 req.form['node'] = [fn[:-len(ext)]]
164 req.form['type'] = [type_]
164 req.form['type'] = [type_]
165
165
166 # process the web interface request
166 # process the web interface request
167
167
168 try:
168 try:
169 tmpl = self.templater(req)
169 tmpl = self.templater(req)
170 ctype = tmpl('mimetype', encoding=encoding.encoding)
170 ctype = tmpl('mimetype', encoding=encoding.encoding)
171 ctype = templater.stringify(ctype)
171 ctype = templater.stringify(ctype)
172
172
173 # check read permissions non-static content
173 # check read permissions non-static content
174 if cmd != 'static':
174 if cmd != 'static':
175 self.check_perm(req, None)
175 self.check_perm(req, None)
176
176
177 if cmd == '':
177 if cmd == '':
178 req.form['cmd'] = [tmpl.cache['default']]
178 req.form['cmd'] = [tmpl.cache['default']]
179 cmd = req.form['cmd'][0]
179 cmd = req.form['cmd'][0]
180
180
181 caching(self, req) # sets ETag header or raises NOT_MODIFIED
181 caching(self, req) # sets ETag header or raises NOT_MODIFIED
182 if cmd not in webcommands.__all__:
182 if cmd not in webcommands.__all__:
183 msg = 'no such method: %s' % cmd
183 msg = 'no such method: %s' % cmd
184 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
184 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
185 elif cmd == 'file' and 'raw' in req.form.get('style', []):
185 elif cmd == 'file' and 'raw' in req.form.get('style', []):
186 self.ctype = ctype
186 self.ctype = ctype
187 content = webcommands.rawfile(self, req, tmpl)
187 content = webcommands.rawfile(self, req, tmpl)
188 else:
188 else:
189 content = getattr(webcommands, cmd)(self, req, tmpl)
189 content = getattr(webcommands, cmd)(self, req, tmpl)
190 req.respond(HTTP_OK, ctype)
190 req.respond(HTTP_OK, ctype)
191
191
192 return content
192 return content
193
193
194 except error.LookupError, err:
194 except error.LookupError, err:
195 req.respond(HTTP_NOT_FOUND, ctype)
195 req.respond(HTTP_NOT_FOUND, ctype)
196 msg = str(err)
196 msg = str(err)
197 if 'manifest' not in msg:
197 if 'manifest' not in msg:
198 msg = 'revision not found: %s' % err.name
198 msg = 'revision not found: %s' % err.name
199 return tmpl('error', error=msg)
199 return tmpl('error', error=msg)
200 except (error.RepoError, error.RevlogError), inst:
200 except (error.RepoError, error.RevlogError), inst:
201 req.respond(HTTP_SERVER_ERROR, ctype)
201 req.respond(HTTP_SERVER_ERROR, ctype)
202 return tmpl('error', error=str(inst))
202 return tmpl('error', error=str(inst))
203 except ErrorResponse, inst:
203 except ErrorResponse, inst:
204 req.respond(inst, ctype)
204 req.respond(inst, ctype)
205 return tmpl('error', error=inst.message)
205 return tmpl('error', error=inst.message)
206
206
207 def templater(self, req):
207 def templater(self, req):
208
208
209 # determine scheme, port and server name
209 # determine scheme, port and server name
210 # this is needed to create absolute urls
210 # this is needed to create absolute urls
211
211
212 proto = req.env.get('wsgi.url_scheme')
212 proto = req.env.get('wsgi.url_scheme')
213 if proto == 'https':
213 if proto == 'https':
214 proto = 'https'
214 proto = 'https'
215 default_port = "443"
215 default_port = "443"
216 else:
216 else:
217 proto = 'http'
217 proto = 'http'
218 default_port = "80"
218 default_port = "80"
219
219
220 port = req.env["SERVER_PORT"]
220 port = req.env["SERVER_PORT"]
221 port = port != default_port and (":" + port) or ""
221 port = port != default_port and (":" + port) or ""
222 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
222 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
223 staticurl = self.config("web", "staticurl") or req.url + 'static/'
223 staticurl = self.config("web", "staticurl") or req.url + 'static/'
224 if not staticurl.endswith('/'):
224 if not staticurl.endswith('/'):
225 staticurl += '/'
225 staticurl += '/'
226
226
227 # some functions for the templater
227 # some functions for the templater
228
228
229 def header(**map):
229 def header(**map):
230 yield tmpl('header', encoding=encoding.encoding, **map)
230 yield tmpl('header', encoding=encoding.encoding, **map)
231
231
232 def footer(**map):
232 def footer(**map):
233 yield tmpl("footer", **map)
233 yield tmpl("footer", **map)
234
234
235 def motd(**map):
235 def motd(**map):
236 yield self.config("web", "motd", "")
236 yield self.config("web", "motd", "")
237
237
238 # figure out which style to use
238 # figure out which style to use
239
239
240 vars = {}
240 vars = {}
241 styles = (
241 styles = (
242 req.form.get('style', [None])[0],
242 req.form.get('style', [None])[0],
243 self.config('web', 'style'),
243 self.config('web', 'style'),
244 'paper',
244 'paper',
245 )
245 )
246 style, mapfile = templater.stylemap(styles, self.templatepath)
246 style, mapfile = templater.stylemap(styles, self.templatepath)
247 if style == styles[0]:
247 if style == styles[0]:
248 vars['style'] = style
248 vars['style'] = style
249
249
250 start = req.url[-1] == '?' and '&' or '?'
250 start = req.url[-1] == '?' and '&' or '?'
251 sessionvars = webutil.sessionvars(vars, start)
251 sessionvars = webutil.sessionvars(vars, start)
252
252
253 if not self.reponame:
253 if not self.reponame:
254 self.reponame = (self.config("web", "name")
254 self.reponame = (self.config("web", "name")
255 or req.env.get('REPO_NAME')
255 or req.env.get('REPO_NAME')
256 or req.url.strip('/') or self.repo.root)
256 or req.url.strip('/') or self.repo.root)
257
257
258 # create the templater
258 # create the templater
259
259
260 tmpl = templater.templater(mapfile,
260 tmpl = templater.templater(mapfile,
261 defaults={"url": req.url,
261 defaults={"url": req.url,
262 "staticurl": staticurl,
262 "staticurl": staticurl,
263 "urlbase": urlbase,
263 "urlbase": urlbase,
264 "repo": self.reponame,
264 "repo": self.reponame,
265 "header": header,
265 "header": header,
266 "footer": footer,
266 "footer": footer,
267 "motd": motd,
267 "motd": motd,
268 "sessionvars": sessionvars
268 "sessionvars": sessionvars
269 })
269 })
270 return tmpl
270 return tmpl
271
271
272 def archivelist(self, nodeid):
272 def archivelist(self, nodeid):
273 allowed = self.configlist("web", "allow_archive")
273 allowed = self.configlist("web", "allow_archive")
274 for i, spec in self.archive_specs.iteritems():
274 for i, spec in self.archive_specs.iteritems():
275 if i in allowed or self.configbool("web", "allow" + i):
275 if i in allowed or self.configbool("web", "allow" + i):
276 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
276 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
277
277
278 archive_specs = {
278 archive_specs = {
279 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
279 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
280 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
280 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
281 'zip': ('application/zip', 'zip', '.zip', None),
281 'zip': ('application/zip', 'zip', '.zip', None),
282 }
282 }
283
283
284 def check_perm(self, req, op):
284 def check_perm(self, req, op):
285 for hook in permhooks:
285 for hook in permhooks:
286 hook(self, req, op)
286 hook(self, req, op)
@@ -1,361 +1,356
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, re, time, urlparse
9 import os, re, time, urlparse
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 def findrepos(paths):
22 def findrepos(paths):
23 repos = []
23 repos = []
24 for prefix, root in cleannames(paths):
24 for prefix, root in cleannames(paths):
25 roothead, roottail = os.path.split(root)
25 roothead, roottail = os.path.split(root)
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 # mounted as foo/subrepo
27 # mounted as foo/subrepo
28 # and "foo = /bar/**" also recurses into the subdirectories,
28 # and "foo = /bar/**" also recurses into the subdirectories,
29 # remember to use it without working dir.
29 # remember to use it without working dir.
30 try:
30 try:
31 recurse = {'*': False, '**': True}[roottail]
31 recurse = {'*': False, '**': True}[roottail]
32 except KeyError:
32 except KeyError:
33 repos.append((prefix, root))
33 repos.append((prefix, root))
34 continue
34 continue
35 roothead = os.path.normpath(os.path.abspath(roothead))
35 roothead = os.path.normpath(os.path.abspath(roothead))
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 path = os.path.normpath(path)
37 path = os.path.normpath(path)
38 name = util.pconvert(path[len(roothead):]).strip('/')
38 name = util.pconvert(path[len(roothead):]).strip('/')
39 if prefix:
39 if prefix:
40 name = prefix + '/' + name
40 name = prefix + '/' + name
41 repos.append((name, path))
41 repos.append((name, path))
42 return repos
42 return repos
43
43
44 class wsgiui(ui.ui):
45 # default termwidth breaks under mod_wsgi
46 def termwidth(self):
47 return 80
48
49 class hgwebdir(object):
44 class hgwebdir(object):
50 refreshinterval = 20
45 refreshinterval = 20
51
46
52 def __init__(self, conf, baseui=None):
47 def __init__(self, conf, baseui=None):
53 self.conf = conf
48 self.conf = conf
54 self.baseui = baseui
49 self.baseui = baseui
55 self.lastrefresh = 0
50 self.lastrefresh = 0
56 self.motd = None
51 self.motd = None
57 self.refresh()
52 self.refresh()
58
53
59 def refresh(self):
54 def refresh(self):
60 if self.lastrefresh + self.refreshinterval > time.time():
55 if self.lastrefresh + self.refreshinterval > time.time():
61 return
56 return
62
57
63 if self.baseui:
58 if self.baseui:
64 u = self.baseui.copy()
59 u = self.baseui.copy()
65 else:
60 else:
66 u = wsgiui()
61 u = webutil.wsgiui()
67 u.setconfig('ui', 'report_untrusted', 'off')
62 u.setconfig('ui', 'report_untrusted', 'off')
68 u.setconfig('ui', 'interactive', 'off')
63 u.setconfig('ui', 'interactive', 'off')
69
64
70 if not isinstance(self.conf, (dict, list, tuple)):
65 if not isinstance(self.conf, (dict, list, tuple)):
71 map = {'paths': 'hgweb-paths'}
66 map = {'paths': 'hgweb-paths'}
72 u.readconfig(self.conf, remap=map, trust=True)
67 u.readconfig(self.conf, remap=map, trust=True)
73 paths = u.configitems('hgweb-paths')
68 paths = u.configitems('hgweb-paths')
74 elif isinstance(self.conf, (list, tuple)):
69 elif isinstance(self.conf, (list, tuple)):
75 paths = self.conf
70 paths = self.conf
76 elif isinstance(self.conf, dict):
71 elif isinstance(self.conf, dict):
77 paths = self.conf.items()
72 paths = self.conf.items()
78
73
79 repos = findrepos(paths)
74 repos = findrepos(paths)
80 for prefix, root in u.configitems('collections'):
75 for prefix, root in u.configitems('collections'):
81 prefix = util.pconvert(prefix)
76 prefix = util.pconvert(prefix)
82 for path in util.walkrepos(root, followsym=True):
77 for path in util.walkrepos(root, followsym=True):
83 repo = os.path.normpath(path)
78 repo = os.path.normpath(path)
84 name = util.pconvert(repo)
79 name = util.pconvert(repo)
85 if name.startswith(prefix):
80 if name.startswith(prefix):
86 name = name[len(prefix):]
81 name = name[len(prefix):]
87 repos.append((name.lstrip('/'), repo))
82 repos.append((name.lstrip('/'), repo))
88
83
89 self.repos = repos
84 self.repos = repos
90 self.ui = u
85 self.ui = u
91 encoding.encoding = self.ui.config('web', 'encoding',
86 encoding.encoding = self.ui.config('web', 'encoding',
92 encoding.encoding)
87 encoding.encoding)
93 self.style = self.ui.config('web', 'style', 'paper')
88 self.style = self.ui.config('web', 'style', 'paper')
94 self.templatepath = self.ui.config('web', 'templates', None)
89 self.templatepath = self.ui.config('web', 'templates', None)
95 self.stripecount = self.ui.config('web', 'stripes', 1)
90 self.stripecount = self.ui.config('web', 'stripes', 1)
96 if self.stripecount:
91 if self.stripecount:
97 self.stripecount = int(self.stripecount)
92 self.stripecount = int(self.stripecount)
98 self._baseurl = self.ui.config('web', 'baseurl')
93 self._baseurl = self.ui.config('web', 'baseurl')
99 self.lastrefresh = time.time()
94 self.lastrefresh = time.time()
100
95
101 def run(self):
96 def run(self):
102 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
97 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
103 raise RuntimeError("This function is only intended to be "
98 raise RuntimeError("This function is only intended to be "
104 "called while running as a CGI script.")
99 "called while running as a CGI script.")
105 import mercurial.hgweb.wsgicgi as wsgicgi
100 import mercurial.hgweb.wsgicgi as wsgicgi
106 wsgicgi.launch(self)
101 wsgicgi.launch(self)
107
102
108 def __call__(self, env, respond):
103 def __call__(self, env, respond):
109 req = wsgirequest(env, respond)
104 req = wsgirequest(env, respond)
110 return self.run_wsgi(req)
105 return self.run_wsgi(req)
111
106
112 def read_allowed(self, ui, req):
107 def read_allowed(self, ui, req):
113 """Check allow_read and deny_read config options of a repo's ui object
108 """Check allow_read and deny_read config options of a repo's ui object
114 to determine user permissions. By default, with neither option set (or
109 to determine user permissions. By default, with neither option set (or
115 both empty), allow all users to read the repo. There are two ways a
110 both empty), allow all users to read the repo. There are two ways a
116 user can be denied read access: (1) deny_read is not empty, and the
111 user can be denied read access: (1) deny_read is not empty, and the
117 user is unauthenticated or deny_read contains user (or *), and (2)
112 user is unauthenticated or deny_read contains user (or *), and (2)
118 allow_read is not empty and the user is not in allow_read. Return True
113 allow_read is not empty and the user is not in allow_read. Return True
119 if user is allowed to read the repo, else return False."""
114 if user is allowed to read the repo, else return False."""
120
115
121 user = req.env.get('REMOTE_USER')
116 user = req.env.get('REMOTE_USER')
122
117
123 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
118 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
124 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
119 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
125 return False
120 return False
126
121
127 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
122 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
128 # by default, allow reading if no allow_read option has been set
123 # by default, allow reading if no allow_read option has been set
129 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
124 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
130 return True
125 return True
131
126
132 return False
127 return False
133
128
134 def run_wsgi(self, req):
129 def run_wsgi(self, req):
135 try:
130 try:
136 try:
131 try:
137 self.refresh()
132 self.refresh()
138
133
139 virtual = req.env.get("PATH_INFO", "").strip('/')
134 virtual = req.env.get("PATH_INFO", "").strip('/')
140 tmpl = self.templater(req)
135 tmpl = self.templater(req)
141 ctype = tmpl('mimetype', encoding=encoding.encoding)
136 ctype = tmpl('mimetype', encoding=encoding.encoding)
142 ctype = templater.stringify(ctype)
137 ctype = templater.stringify(ctype)
143
138
144 # a static file
139 # a static file
145 if virtual.startswith('static/') or 'static' in req.form:
140 if virtual.startswith('static/') or 'static' in req.form:
146 if virtual.startswith('static/'):
141 if virtual.startswith('static/'):
147 fname = virtual[7:]
142 fname = virtual[7:]
148 else:
143 else:
149 fname = req.form['static'][0]
144 fname = req.form['static'][0]
150 static = templater.templatepath('static')
145 static = templater.templatepath('static')
151 return (staticfile(static, fname, req),)
146 return (staticfile(static, fname, req),)
152
147
153 # top-level index
148 # top-level index
154 elif not virtual:
149 elif not virtual:
155 req.respond(HTTP_OK, ctype)
150 req.respond(HTTP_OK, ctype)
156 return self.makeindex(req, tmpl)
151 return self.makeindex(req, tmpl)
157
152
158 # nested indexes and hgwebs
153 # nested indexes and hgwebs
159
154
160 repos = dict(self.repos)
155 repos = dict(self.repos)
161 while virtual:
156 while virtual:
162 real = repos.get(virtual)
157 real = repos.get(virtual)
163 if real:
158 if real:
164 req.env['REPO_NAME'] = virtual
159 req.env['REPO_NAME'] = virtual
165 try:
160 try:
166 repo = hg.repository(self.ui, real)
161 repo = hg.repository(self.ui, real)
167 return hgweb(repo).run_wsgi(req)
162 return hgweb(repo).run_wsgi(req)
168 except IOError, inst:
163 except IOError, inst:
169 msg = inst.strerror
164 msg = inst.strerror
170 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
165 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
171 except error.RepoError, inst:
166 except error.RepoError, inst:
172 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
167 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
173
168
174 # browse subdirectories
169 # browse subdirectories
175 subdir = virtual + '/'
170 subdir = virtual + '/'
176 if [r for r in repos if r.startswith(subdir)]:
171 if [r for r in repos if r.startswith(subdir)]:
177 req.respond(HTTP_OK, ctype)
172 req.respond(HTTP_OK, ctype)
178 return self.makeindex(req, tmpl, subdir)
173 return self.makeindex(req, tmpl, subdir)
179
174
180 up = virtual.rfind('/')
175 up = virtual.rfind('/')
181 if up < 0:
176 if up < 0:
182 break
177 break
183 virtual = virtual[:up]
178 virtual = virtual[:up]
184
179
185 # prefixes not found
180 # prefixes not found
186 req.respond(HTTP_NOT_FOUND, ctype)
181 req.respond(HTTP_NOT_FOUND, ctype)
187 return tmpl("notfound", repo=virtual)
182 return tmpl("notfound", repo=virtual)
188
183
189 except ErrorResponse, err:
184 except ErrorResponse, err:
190 req.respond(err, ctype)
185 req.respond(err, ctype)
191 return tmpl('error', error=err.message or '')
186 return tmpl('error', error=err.message or '')
192 finally:
187 finally:
193 tmpl = None
188 tmpl = None
194
189
195 def makeindex(self, req, tmpl, subdir=""):
190 def makeindex(self, req, tmpl, subdir=""):
196
191
197 def archivelist(ui, nodeid, url):
192 def archivelist(ui, nodeid, url):
198 allowed = ui.configlist("web", "allow_archive", untrusted=True)
193 allowed = ui.configlist("web", "allow_archive", untrusted=True)
199 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
194 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
200 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
195 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
201 untrusted=True):
196 untrusted=True):
202 yield {"type" : i[0], "extension": i[1],
197 yield {"type" : i[0], "extension": i[1],
203 "node": nodeid, "url": url}
198 "node": nodeid, "url": url}
204
199
205 def rawentries(subdir="", **map):
200 def rawentries(subdir="", **map):
206
201
207 descend = self.ui.configbool('web', 'descend', True)
202 descend = self.ui.configbool('web', 'descend', True)
208 for name, path in self.repos:
203 for name, path in self.repos:
209
204
210 if not name.startswith(subdir):
205 if not name.startswith(subdir):
211 continue
206 continue
212 name = name[len(subdir):]
207 name = name[len(subdir):]
213 if not descend and '/' in name:
208 if not descend and '/' in name:
214 continue
209 continue
215
210
216 u = self.ui.copy()
211 u = self.ui.copy()
217 try:
212 try:
218 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
219 except Exception, e:
214 except Exception, e:
220 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
221 continue
216 continue
222 def get(section, name, default=None):
217 def get(section, name, default=None):
223 return u.config(section, name, default, untrusted=True)
218 return u.config(section, name, default, untrusted=True)
224
219
225 if u.configbool("web", "hidden", untrusted=True):
220 if u.configbool("web", "hidden", untrusted=True):
226 continue
221 continue
227
222
228 if not self.read_allowed(u, req):
223 if not self.read_allowed(u, req):
229 continue
224 continue
230
225
231 parts = [name]
226 parts = [name]
232 if 'PATH_INFO' in req.env:
227 if 'PATH_INFO' in req.env:
233 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
234 if req.env['SCRIPT_NAME']:
229 if req.env['SCRIPT_NAME']:
235 parts.insert(0, req.env['SCRIPT_NAME'])
230 parts.insert(0, req.env['SCRIPT_NAME'])
236 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
231 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
237
232
238 # update time with local timezone
233 # update time with local timezone
239 try:
234 try:
240 r = hg.repository(self.ui, path)
235 r = hg.repository(self.ui, path)
241 except error.RepoError:
236 except error.RepoError:
242 u.warn(_('error accessing repository at %s\n') % path)
237 u.warn(_('error accessing repository at %s\n') % path)
243 continue
238 continue
244 try:
239 try:
245 d = (get_mtime(r.spath), util.makedate()[1])
240 d = (get_mtime(r.spath), util.makedate()[1])
246 except OSError:
241 except OSError:
247 continue
242 continue
248
243
249 contact = get_contact(get)
244 contact = get_contact(get)
250 description = get("web", "description", "")
245 description = get("web", "description", "")
251 name = get("web", "name", name)
246 name = get("web", "name", name)
252 row = dict(contact=contact or "unknown",
247 row = dict(contact=contact or "unknown",
253 contact_sort=contact.upper() or "unknown",
248 contact_sort=contact.upper() or "unknown",
254 name=name,
249 name=name,
255 name_sort=name,
250 name_sort=name,
256 url=url,
251 url=url,
257 description=description or "unknown",
252 description=description or "unknown",
258 description_sort=description.upper() or "unknown",
253 description_sort=description.upper() or "unknown",
259 lastchange=d,
254 lastchange=d,
260 lastchange_sort=d[1]-d[0],
255 lastchange_sort=d[1]-d[0],
261 archives=archivelist(u, "tip", url))
256 archives=archivelist(u, "tip", url))
262 yield row
257 yield row
263
258
264 sortdefault = None, False
259 sortdefault = None, False
265 def entries(sortcolumn="", descending=False, subdir="", **map):
260 def entries(sortcolumn="", descending=False, subdir="", **map):
266 rows = rawentries(subdir=subdir, **map)
261 rows = rawentries(subdir=subdir, **map)
267
262
268 if sortcolumn and sortdefault != (sortcolumn, descending):
263 if sortcolumn and sortdefault != (sortcolumn, descending):
269 sortkey = '%s_sort' % sortcolumn
264 sortkey = '%s_sort' % sortcolumn
270 rows = sorted(rows, key=lambda x: x[sortkey],
265 rows = sorted(rows, key=lambda x: x[sortkey],
271 reverse=descending)
266 reverse=descending)
272 for row, parity in zip(rows, paritygen(self.stripecount)):
267 for row, parity in zip(rows, paritygen(self.stripecount)):
273 row['parity'] = parity
268 row['parity'] = parity
274 yield row
269 yield row
275
270
276 self.refresh()
271 self.refresh()
277 sortable = ["name", "description", "contact", "lastchange"]
272 sortable = ["name", "description", "contact", "lastchange"]
278 sortcolumn, descending = sortdefault
273 sortcolumn, descending = sortdefault
279 if 'sort' in req.form:
274 if 'sort' in req.form:
280 sortcolumn = req.form['sort'][0]
275 sortcolumn = req.form['sort'][0]
281 descending = sortcolumn.startswith('-')
276 descending = sortcolumn.startswith('-')
282 if descending:
277 if descending:
283 sortcolumn = sortcolumn[1:]
278 sortcolumn = sortcolumn[1:]
284 if sortcolumn not in sortable:
279 if sortcolumn not in sortable:
285 sortcolumn = ""
280 sortcolumn = ""
286
281
287 sort = [("sort_%s" % column,
282 sort = [("sort_%s" % column,
288 "%s%s" % ((not descending and column == sortcolumn)
283 "%s%s" % ((not descending and column == sortcolumn)
289 and "-" or "", column))
284 and "-" or "", column))
290 for column in sortable]
285 for column in sortable]
291
286
292 self.refresh()
287 self.refresh()
293 self.updatereqenv(req.env)
288 self.updatereqenv(req.env)
294
289
295 return tmpl("index", entries=entries, subdir=subdir,
290 return tmpl("index", entries=entries, subdir=subdir,
296 sortcolumn=sortcolumn, descending=descending,
291 sortcolumn=sortcolumn, descending=descending,
297 **dict(sort))
292 **dict(sort))
298
293
299 def templater(self, req):
294 def templater(self, req):
300
295
301 def header(**map):
296 def header(**map):
302 yield tmpl('header', encoding=encoding.encoding, **map)
297 yield tmpl('header', encoding=encoding.encoding, **map)
303
298
304 def footer(**map):
299 def footer(**map):
305 yield tmpl("footer", **map)
300 yield tmpl("footer", **map)
306
301
307 def motd(**map):
302 def motd(**map):
308 if self.motd is not None:
303 if self.motd is not None:
309 yield self.motd
304 yield self.motd
310 else:
305 else:
311 yield config('web', 'motd', '')
306 yield config('web', 'motd', '')
312
307
313 def config(section, name, default=None, untrusted=True):
308 def config(section, name, default=None, untrusted=True):
314 return self.ui.config(section, name, default, untrusted)
309 return self.ui.config(section, name, default, untrusted)
315
310
316 self.updatereqenv(req.env)
311 self.updatereqenv(req.env)
317
312
318 url = req.env.get('SCRIPT_NAME', '')
313 url = req.env.get('SCRIPT_NAME', '')
319 if not url.endswith('/'):
314 if not url.endswith('/'):
320 url += '/'
315 url += '/'
321
316
322 vars = {}
317 vars = {}
323 styles = (
318 styles = (
324 req.form.get('style', [None])[0],
319 req.form.get('style', [None])[0],
325 config('web', 'style'),
320 config('web', 'style'),
326 'paper'
321 'paper'
327 )
322 )
328 style, mapfile = templater.stylemap(styles, self.templatepath)
323 style, mapfile = templater.stylemap(styles, self.templatepath)
329 if style == styles[0]:
324 if style == styles[0]:
330 vars['style'] = style
325 vars['style'] = style
331
326
332 start = url[-1] == '?' and '&' or '?'
327 start = url[-1] == '?' and '&' or '?'
333 sessionvars = webutil.sessionvars(vars, start)
328 sessionvars = webutil.sessionvars(vars, start)
334 staticurl = config('web', 'staticurl') or url + 'static/'
329 staticurl = config('web', 'staticurl') or url + 'static/'
335 if not staticurl.endswith('/'):
330 if not staticurl.endswith('/'):
336 staticurl += '/'
331 staticurl += '/'
337
332
338 tmpl = templater.templater(mapfile,
333 tmpl = templater.templater(mapfile,
339 defaults={"header": header,
334 defaults={"header": header,
340 "footer": footer,
335 "footer": footer,
341 "motd": motd,
336 "motd": motd,
342 "url": url,
337 "url": url,
343 "staticurl": staticurl,
338 "staticurl": staticurl,
344 "sessionvars": sessionvars})
339 "sessionvars": sessionvars})
345 return tmpl
340 return tmpl
346
341
347 def updatereqenv(self, env):
342 def updatereqenv(self, env):
348 def splitnetloc(netloc):
343 def splitnetloc(netloc):
349 if ':' in netloc:
344 if ':' in netloc:
350 return netloc.split(':', 1)
345 return netloc.split(':', 1)
351 else:
346 else:
352 return (netloc, None)
347 return (netloc, None)
353
348
354 if self._baseurl is not None:
349 if self._baseurl is not None:
355 urlcomp = urlparse.urlparse(self._baseurl)
350 urlcomp = urlparse.urlparse(self._baseurl)
356 host, port = splitnetloc(urlcomp[1])
351 host, port = splitnetloc(urlcomp[1])
357 path = urlcomp[2]
352 path = urlcomp[2]
358 env['SERVER_NAME'] = host
353 env['SERVER_NAME'] = host
359 if port:
354 if port:
360 env['SERVER_PORT'] = port
355 env['SERVER_PORT'] = port
361 env['SCRIPT_NAME'] = path
356 env['SCRIPT_NAME'] = path
@@ -1,221 +1,226
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, copy
9 import os, copy
10 from mercurial import match, patch, util, error
10 from mercurial import match, patch, util, error, ui
11 from mercurial.node import hex, nullid
11 from mercurial.node import hex, nullid
12
12
13 def up(p):
13 def up(p):
14 if p[0] != "/":
14 if p[0] != "/":
15 p = "/" + p
15 p = "/" + p
16 if p[-1] == "/":
16 if p[-1] == "/":
17 p = p[:-1]
17 p = p[:-1]
18 up = os.path.dirname(p)
18 up = os.path.dirname(p)
19 if up == "/":
19 if up == "/":
20 return "/"
20 return "/"
21 return up + "/"
21 return up + "/"
22
22
23 def revnavgen(pos, pagelen, limit, nodefunc):
23 def revnavgen(pos, pagelen, limit, nodefunc):
24 def seq(factor, limit=None):
24 def seq(factor, limit=None):
25 if limit:
25 if limit:
26 yield limit
26 yield limit
27 if limit >= 20 and limit <= 40:
27 if limit >= 20 and limit <= 40:
28 yield 50
28 yield 50
29 else:
29 else:
30 yield 1 * factor
30 yield 1 * factor
31 yield 3 * factor
31 yield 3 * factor
32 for f in seq(factor * 10):
32 for f in seq(factor * 10):
33 yield f
33 yield f
34
34
35 navbefore = []
35 navbefore = []
36 navafter = []
36 navafter = []
37
37
38 last = 0
38 last = 0
39 for f in seq(1, pagelen):
39 for f in seq(1, pagelen):
40 if f < pagelen or f <= last:
40 if f < pagelen or f <= last:
41 continue
41 continue
42 if f > limit:
42 if f > limit:
43 break
43 break
44 last = f
44 last = f
45 if pos + f < limit:
45 if pos + f < limit:
46 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
46 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
47 if pos - f >= 0:
47 if pos - f >= 0:
48 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
48 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
49
49
50 navafter.append(("tip", "tip"))
50 navafter.append(("tip", "tip"))
51 try:
51 try:
52 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
52 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
53 except error.RepoError:
53 except error.RepoError:
54 pass
54 pass
55
55
56 def gen(l):
56 def gen(l):
57 def f(**map):
57 def f(**map):
58 for label, node in l:
58 for label, node in l:
59 yield {"label": label, "node": node}
59 yield {"label": label, "node": node}
60 return f
60 return f
61
61
62 return (dict(before=gen(navbefore), after=gen(navafter)),)
62 return (dict(before=gen(navbefore), after=gen(navafter)),)
63
63
64 def _siblings(siblings=[], hiderev=None):
64 def _siblings(siblings=[], hiderev=None):
65 siblings = [s for s in siblings if s.node() != nullid]
65 siblings = [s for s in siblings if s.node() != nullid]
66 if len(siblings) == 1 and siblings[0].rev() == hiderev:
66 if len(siblings) == 1 and siblings[0].rev() == hiderev:
67 return
67 return
68 for s in siblings:
68 for s in siblings:
69 d = {'node': hex(s.node()), 'rev': s.rev()}
69 d = {'node': hex(s.node()), 'rev': s.rev()}
70 d['user'] = s.user()
70 d['user'] = s.user()
71 d['date'] = s.date()
71 d['date'] = s.date()
72 d['description'] = s.description()
72 d['description'] = s.description()
73 d['branch'] = s.branch()
73 d['branch'] = s.branch()
74 if hasattr(s, 'path'):
74 if hasattr(s, 'path'):
75 d['file'] = s.path()
75 d['file'] = s.path()
76 yield d
76 yield d
77
77
78 def parents(ctx, hide=None):
78 def parents(ctx, hide=None):
79 return _siblings(ctx.parents(), hide)
79 return _siblings(ctx.parents(), hide)
80
80
81 def children(ctx, hide=None):
81 def children(ctx, hide=None):
82 return _siblings(ctx.children(), hide)
82 return _siblings(ctx.children(), hide)
83
83
84 def renamelink(fctx):
84 def renamelink(fctx):
85 r = fctx.renamed()
85 r = fctx.renamed()
86 if r:
86 if r:
87 return [dict(file=r[0], node=hex(r[1]))]
87 return [dict(file=r[0], node=hex(r[1]))]
88 return []
88 return []
89
89
90 def nodetagsdict(repo, node):
90 def nodetagsdict(repo, node):
91 return [{"name": i} for i in repo.nodetags(node)]
91 return [{"name": i} for i in repo.nodetags(node)]
92
92
93 def nodebranchdict(repo, ctx):
93 def nodebranchdict(repo, ctx):
94 branches = []
94 branches = []
95 branch = ctx.branch()
95 branch = ctx.branch()
96 # If this is an empty repo, ctx.node() == nullid,
96 # If this is an empty repo, ctx.node() == nullid,
97 # ctx.branch() == 'default', but branchtags() is
97 # ctx.branch() == 'default', but branchtags() is
98 # an empty dict. Using dict.get avoids a traceback.
98 # an empty dict. Using dict.get avoids a traceback.
99 if repo.branchtags().get(branch) == ctx.node():
99 if repo.branchtags().get(branch) == ctx.node():
100 branches.append({"name": branch})
100 branches.append({"name": branch})
101 return branches
101 return branches
102
102
103 def nodeinbranch(repo, ctx):
103 def nodeinbranch(repo, ctx):
104 branches = []
104 branches = []
105 branch = ctx.branch()
105 branch = ctx.branch()
106 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
106 if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
107 branches.append({"name": branch})
107 branches.append({"name": branch})
108 return branches
108 return branches
109
109
110 def nodebranchnodefault(ctx):
110 def nodebranchnodefault(ctx):
111 branches = []
111 branches = []
112 branch = ctx.branch()
112 branch = ctx.branch()
113 if branch != 'default':
113 if branch != 'default':
114 branches.append({"name": branch})
114 branches.append({"name": branch})
115 return branches
115 return branches
116
116
117 def showtag(repo, tmpl, t1, node=nullid, **args):
117 def showtag(repo, tmpl, t1, node=nullid, **args):
118 for t in repo.nodetags(node):
118 for t in repo.nodetags(node):
119 yield tmpl(t1, tag=t, **args)
119 yield tmpl(t1, tag=t, **args)
120
120
121 def cleanpath(repo, path):
121 def cleanpath(repo, path):
122 path = path.lstrip('/')
122 path = path.lstrip('/')
123 return util.canonpath(repo.root, '', path)
123 return util.canonpath(repo.root, '', path)
124
124
125 def changectx(repo, req):
125 def changectx(repo, req):
126 changeid = "tip"
126 changeid = "tip"
127 if 'node' in req.form:
127 if 'node' in req.form:
128 changeid = req.form['node'][0]
128 changeid = req.form['node'][0]
129 elif 'manifest' in req.form:
129 elif 'manifest' in req.form:
130 changeid = req.form['manifest'][0]
130 changeid = req.form['manifest'][0]
131
131
132 try:
132 try:
133 ctx = repo[changeid]
133 ctx = repo[changeid]
134 except error.RepoError:
134 except error.RepoError:
135 man = repo.manifest
135 man = repo.manifest
136 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
136 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
137
137
138 return ctx
138 return ctx
139
139
140 def filectx(repo, req):
140 def filectx(repo, req):
141 path = cleanpath(repo, req.form['file'][0])
141 path = cleanpath(repo, req.form['file'][0])
142 if 'node' in req.form:
142 if 'node' in req.form:
143 changeid = req.form['node'][0]
143 changeid = req.form['node'][0]
144 else:
144 else:
145 changeid = req.form['filenode'][0]
145 changeid = req.form['filenode'][0]
146 try:
146 try:
147 fctx = repo[changeid][path]
147 fctx = repo[changeid][path]
148 except error.RepoError:
148 except error.RepoError:
149 fctx = repo.filectx(path, fileid=changeid)
149 fctx = repo.filectx(path, fileid=changeid)
150
150
151 return fctx
151 return fctx
152
152
153 def listfilediffs(tmpl, files, node, max):
153 def listfilediffs(tmpl, files, node, max):
154 for f in files[:max]:
154 for f in files[:max]:
155 yield tmpl('filedifflink', node=hex(node), file=f)
155 yield tmpl('filedifflink', node=hex(node), file=f)
156 if len(files) > max:
156 if len(files) > max:
157 yield tmpl('fileellipses')
157 yield tmpl('fileellipses')
158
158
159 def diffs(repo, tmpl, ctx, files, parity, style):
159 def diffs(repo, tmpl, ctx, files, parity, style):
160
160
161 def countgen():
161 def countgen():
162 start = 1
162 start = 1
163 while True:
163 while True:
164 yield start
164 yield start
165 start += 1
165 start += 1
166
166
167 blockcount = countgen()
167 blockcount = countgen()
168 def prettyprintlines(diff):
168 def prettyprintlines(diff):
169 blockno = blockcount.next()
169 blockno = blockcount.next()
170 for lineno, l in enumerate(diff.splitlines(True)):
170 for lineno, l in enumerate(diff.splitlines(True)):
171 lineno = "%d.%d" % (blockno, lineno + 1)
171 lineno = "%d.%d" % (blockno, lineno + 1)
172 if l.startswith('+'):
172 if l.startswith('+'):
173 ltype = "difflineplus"
173 ltype = "difflineplus"
174 elif l.startswith('-'):
174 elif l.startswith('-'):
175 ltype = "difflineminus"
175 ltype = "difflineminus"
176 elif l.startswith('@'):
176 elif l.startswith('@'):
177 ltype = "difflineat"
177 ltype = "difflineat"
178 else:
178 else:
179 ltype = "diffline"
179 ltype = "diffline"
180 yield tmpl(ltype,
180 yield tmpl(ltype,
181 line=l,
181 line=l,
182 lineid="l%s" % lineno,
182 lineid="l%s" % lineno,
183 linenumber="% 8s" % lineno)
183 linenumber="% 8s" % lineno)
184
184
185 if files:
185 if files:
186 m = match.exact(repo.root, repo.getcwd(), files)
186 m = match.exact(repo.root, repo.getcwd(), files)
187 else:
187 else:
188 m = match.always(repo.root, repo.getcwd())
188 m = match.always(repo.root, repo.getcwd())
189
189
190 diffopts = patch.diffopts(repo.ui, untrusted=True)
190 diffopts = patch.diffopts(repo.ui, untrusted=True)
191 parents = ctx.parents()
191 parents = ctx.parents()
192 node1 = parents and parents[0].node() or nullid
192 node1 = parents and parents[0].node() or nullid
193 node2 = ctx.node()
193 node2 = ctx.node()
194
194
195 block = []
195 block = []
196 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
196 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
197 if chunk.startswith('diff') and block:
197 if chunk.startswith('diff') and block:
198 yield tmpl('diffblock', parity=parity.next(),
198 yield tmpl('diffblock', parity=parity.next(),
199 lines=prettyprintlines(''.join(block)))
199 lines=prettyprintlines(''.join(block)))
200 block = []
200 block = []
201 if chunk.startswith('diff') and style != 'raw':
201 if chunk.startswith('diff') and style != 'raw':
202 chunk = ''.join(chunk.splitlines(True)[1:])
202 chunk = ''.join(chunk.splitlines(True)[1:])
203 block.append(chunk)
203 block.append(chunk)
204 yield tmpl('diffblock', parity=parity.next(),
204 yield tmpl('diffblock', parity=parity.next(),
205 lines=prettyprintlines(''.join(block)))
205 lines=prettyprintlines(''.join(block)))
206
206
207 class sessionvars(object):
207 class sessionvars(object):
208 def __init__(self, vars, start='?'):
208 def __init__(self, vars, start='?'):
209 self.start = start
209 self.start = start
210 self.vars = vars
210 self.vars = vars
211 def __getitem__(self, key):
211 def __getitem__(self, key):
212 return self.vars[key]
212 return self.vars[key]
213 def __setitem__(self, key, value):
213 def __setitem__(self, key, value):
214 self.vars[key] = value
214 self.vars[key] = value
215 def __copy__(self):
215 def __copy__(self):
216 return sessionvars(copy.copy(self.vars), self.start)
216 return sessionvars(copy.copy(self.vars), self.start)
217 def __iter__(self):
217 def __iter__(self):
218 separator = self.start
218 separator = self.start
219 for key, value in self.vars.iteritems():
219 for key, value in self.vars.iteritems():
220 yield {'name': key, 'value': str(value), 'separator': separator}
220 yield {'name': key, 'value': str(value), 'separator': separator}
221 separator = '&'
221 separator = '&'
222
223 class wsgiui(ui.ui):
224 # default termwidth breaks under mod_wsgi
225 def termwidth(self):
226 return 80
General Comments 0
You need to be logged in to leave comments. Login now