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