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