##// END OF EJS Templates
Support web.baseurl in hgwebdir, overriding SCRIPT_NAME
Brendan Cully -
r6221:2eb18c78 default
parent child Browse files
Show More
@@ -1,279 +1,288 b''
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
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import gettext as _
11 11 from mercurial.repo import RepoError
12 12 from mercurial import ui, hg, util, templater, templatefilters
13 13 from common import ErrorResponse, get_mtime, staticfile, style_map, 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
18 18 # This is a stopgap
19 19 class hgwebdir(object):
20 20 def __init__(self, config, parentui=None):
21 21 def cleannames(items):
22 22 return [(util.pconvert(name).strip('/'), path)
23 23 for name, path in items]
24 24
25 25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 26 interactive = False)
27 27 self.motd = None
28 28 self.style = None
29 29 self.stripecount = None
30 30 self.repos_sorted = ('name', False)
31 self._baseurl = None
31 32 if isinstance(config, (list, tuple)):
32 33 self.repos = cleannames(config)
33 34 self.repos_sorted = ('', False)
34 35 elif isinstance(config, dict):
35 36 self.repos = cleannames(config.items())
36 37 self.repos.sort()
37 38 else:
38 39 if isinstance(config, util.configparser):
39 40 cp = config
40 41 else:
41 42 cp = util.configparser()
42 43 cp.read(config)
43 44 self.repos = []
44 45 if cp.has_section('web'):
45 46 if cp.has_option('web', 'motd'):
46 47 self.motd = cp.get('web', 'motd')
47 48 if cp.has_option('web', 'style'):
48 49 self.style = cp.get('web', 'style')
49 50 if cp.has_option('web', 'stripes'):
50 51 self.stripecount = int(cp.get('web', 'stripes'))
52 if cp.has_option('web', 'baseurl'):
53 self._baseurl = cp.get('web', 'baseurl')
51 54 if cp.has_section('paths'):
52 55 self.repos.extend(cleannames(cp.items('paths')))
53 56 if cp.has_section('collections'):
54 57 for prefix, root in cp.items('collections'):
55 58 for path in util.walkrepos(root):
56 59 repo = os.path.normpath(path)
57 60 name = repo
58 61 if name.startswith(prefix):
59 62 name = name[len(prefix):]
60 63 self.repos.append((name.lstrip(os.sep), repo))
61 64 self.repos.sort()
62 65
63 66 def run(self):
64 67 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
65 68 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
66 69 import mercurial.hgweb.wsgicgi as wsgicgi
67 70 wsgicgi.launch(self)
68 71
69 72 def __call__(self, env, respond):
70 73 req = wsgirequest(env, respond)
71 74 self.run_wsgi(req)
72 75 return req
73 76
74 77 def run_wsgi(self, req):
75 78
76 79 try:
77 80 try:
78 81
79 82 virtual = req.env.get("PATH_INFO", "").strip('/')
80 83 tmpl = self.templater(req)
81 84 ctype = tmpl('mimetype', encoding=util._encoding)
82 85 ctype = templater.stringify(ctype)
83 86
84 87 # a static file
85 88 if virtual.startswith('static/') or 'static' in req.form:
86 89 static = os.path.join(templater.templatepath(), 'static')
87 90 if virtual.startswith('static/'):
88 91 fname = virtual[7:]
89 92 else:
90 93 fname = req.form['static'][0]
91 94 req.write(staticfile(static, fname, req))
92 95 return
93 96
94 97 # top-level index
95 98 elif not virtual:
96 99 req.respond(HTTP_OK, ctype)
97 100 req.write(self.makeindex(req, tmpl))
98 101 return
99 102
100 103 # nested indexes and hgwebs
101 104
102 105 repos = dict(self.repos)
103 106 while virtual:
104 107 real = repos.get(virtual)
105 108 if real:
106 109 req.env['REPO_NAME'] = virtual
107 110 try:
108 111 repo = hg.repository(self.parentui, real)
109 112 hgweb(repo).run_wsgi(req)
110 113 return
111 114 except IOError, inst:
112 115 msg = inst.strerror
113 116 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
114 117 except RepoError, inst:
115 118 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
116 119
117 120 # browse subdirectories
118 121 subdir = virtual + '/'
119 122 if [r for r in repos if r.startswith(subdir)]:
120 123 req.respond(HTTP_OK, ctype)
121 124 req.write(self.makeindex(req, tmpl, subdir))
122 125 return
123 126
124 127 up = virtual.rfind('/')
125 128 if up < 0:
126 129 break
127 130 virtual = virtual[:up]
128 131
129 132 # prefixes not found
130 133 req.respond(HTTP_NOT_FOUND, ctype)
131 134 req.write(tmpl("notfound", repo=virtual))
132 135
133 136 except ErrorResponse, err:
134 137 req.respond(err.code, ctype)
135 138 req.write(tmpl('error', error=err.message or ''))
136 139 finally:
137 140 tmpl = None
138 141
139 142 def makeindex(self, req, tmpl, subdir=""):
140 143
141 144 def archivelist(ui, nodeid, url):
142 145 allowed = ui.configlist("web", "allow_archive", untrusted=True)
143 146 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
144 147 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
145 148 untrusted=True):
146 149 yield {"type" : i[0], "extension": i[1],
147 150 "node": nodeid, "url": url}
148 151
149 152 def entries(sortcolumn="", descending=False, subdir="", **map):
150 153 def sessionvars(**map):
151 154 fields = []
152 155 if 'style' in req.form:
153 156 style = req.form['style'][0]
154 157 if style != get('web', 'style', ''):
155 158 fields.append(('style', style))
156 159
157 160 separator = url[-1] == '?' and ';' or '?'
158 161 for name, value in fields:
159 162 yield dict(name=name, value=value, separator=separator)
160 163 separator = ';'
161 164
162 165 rows = []
163 166 parity = paritygen(self.stripecount)
164 167 for name, path in self.repos:
165 168 if not name.startswith(subdir):
166 169 continue
167 170 name = name[len(subdir):]
168 171
169 172 u = ui.ui(parentui=self.parentui)
170 173 try:
171 174 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
172 175 except Exception, e:
173 176 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
174 177 continue
175 178 def get(section, name, default=None):
176 179 return u.config(section, name, default, untrusted=True)
177 180
178 181 if u.configbool("web", "hidden", untrusted=True):
179 182 continue
180 183
181 184 parts = [req.env['PATH_INFO'].rstrip('/'), name]
182 185 if req.env['SCRIPT_NAME']:
183 186 parts.insert(0, req.env['SCRIPT_NAME'])
184 187 url = ('/'.join(parts).replace("//", "/")) + '/'
185 188
186 189 # update time with local timezone
187 190 try:
188 191 d = (get_mtime(path), util.makedate()[1])
189 192 except OSError:
190 193 continue
191 194
192 195 contact = get_contact(get)
193 196 description = get("web", "description", "")
194 197 name = get("web", "name", name)
195 198 row = dict(contact=contact or "unknown",
196 199 contact_sort=contact.upper() or "unknown",
197 200 name=name,
198 201 name_sort=name,
199 202 url=url,
200 203 description=description or "unknown",
201 204 description_sort=description.upper() or "unknown",
202 205 lastchange=d,
203 206 lastchange_sort=d[1]-d[0],
204 207 sessionvars=sessionvars,
205 208 archives=archivelist(u, "tip", url))
206 209 if (not sortcolumn
207 210 or (sortcolumn, descending) == self.repos_sorted):
208 211 # fast path for unsorted output
209 212 row['parity'] = parity.next()
210 213 yield row
211 214 else:
212 215 rows.append((row["%s_sort" % sortcolumn], row))
213 216 if rows:
214 217 rows.sort()
215 218 if descending:
216 219 rows.reverse()
217 220 for key, row in rows:
218 221 row['parity'] = parity.next()
219 222 yield row
220 223
221 224 sortable = ["name", "description", "contact", "lastchange"]
222 225 sortcolumn, descending = self.repos_sorted
223 226 if 'sort' in req.form:
224 227 sortcolumn = req.form['sort'][0]
225 228 descending = sortcolumn.startswith('-')
226 229 if descending:
227 230 sortcolumn = sortcolumn[1:]
228 231 if sortcolumn not in sortable:
229 232 sortcolumn = ""
230 233
231 234 sort = [("sort_%s" % column,
232 235 "%s%s" % ((not descending and column == sortcolumn)
233 236 and "-" or "", column))
234 237 for column in sortable]
235 238
239 if self._baseurl is not None:
240 req.env['SCRIPT_NAME'] = self._baseurl
241
236 242 return tmpl("index", entries=entries, subdir=subdir,
237 243 sortcolumn=sortcolumn, descending=descending,
238 244 **dict(sort))
239 245
240 246 def templater(self, req):
241 247
242 248 def header(**map):
243 249 yield tmpl('header', encoding=util._encoding, **map)
244 250
245 251 def footer(**map):
246 252 yield tmpl("footer", **map)
247 253
248 254 def motd(**map):
249 255 if self.motd is not None:
250 256 yield self.motd
251 257 else:
252 258 yield config('web', 'motd', '')
253 259
254 260 def config(section, name, default=None, untrusted=True):
255 261 return self.parentui.config(section, name, default, untrusted)
256 262
263 if self._baseurl is not None:
264 req.env['SCRIPT_NAME'] = self._baseurl
265
257 266 url = req.env.get('SCRIPT_NAME', '')
258 267 if not url.endswith('/'):
259 268 url += '/'
260 269
261 270 staticurl = config('web', 'staticurl') or url + 'static/'
262 271 if not staticurl.endswith('/'):
263 272 staticurl += '/'
264 273
265 274 style = self.style
266 275 if style is None:
267 276 style = config('web', 'style', '')
268 277 if 'style' in req.form:
269 278 style = req.form['style'][0]
270 279 if self.stripecount is None:
271 280 self.stripecount = int(config('web', 'stripes', 1))
272 281 mapfile = style_map(templater.templatepath(), style)
273 282 tmpl = templater.templater(mapfile, templatefilters.filters,
274 283 defaults={"header": header,
275 284 "footer": footer,
276 285 "motd": motd,
277 286 "url": url,
278 287 "staticurl": staticurl})
279 288 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now