##// END OF EJS Templates
hgweb: handle exception of misconfigured path on index page...
Yuya Nishihara -
r12038:9617803b stable
parent child Browse files
Show More
@@ -1,352 +1,356 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 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 44 class hgwebdir(object):
45 45 refreshinterval = 20
46 46
47 47 def __init__(self, conf, baseui=None):
48 48 self.conf = conf
49 49 self.baseui = baseui
50 50 self.lastrefresh = 0
51 51 self.motd = None
52 52 self.refresh()
53 53
54 54 def refresh(self):
55 55 if self.lastrefresh + self.refreshinterval > time.time():
56 56 return
57 57
58 58 if self.baseui:
59 59 u = self.baseui.copy()
60 60 else:
61 61 u = ui.ui()
62 62 u.setconfig('ui', 'report_untrusted', 'off')
63 63 u.setconfig('ui', 'interactive', 'off')
64 64
65 65 if not isinstance(self.conf, (dict, list, tuple)):
66 66 map = {'paths': 'hgweb-paths'}
67 67 u.readconfig(self.conf, remap=map, trust=True)
68 68 paths = u.configitems('hgweb-paths')
69 69 elif isinstance(self.conf, (list, tuple)):
70 70 paths = self.conf
71 71 elif isinstance(self.conf, dict):
72 72 paths = self.conf.items()
73 73
74 74 repos = findrepos(paths)
75 75 for prefix, root in u.configitems('collections'):
76 76 prefix = util.pconvert(prefix)
77 77 for path in util.walkrepos(root, followsym=True):
78 78 repo = os.path.normpath(path)
79 79 name = util.pconvert(repo)
80 80 if name.startswith(prefix):
81 81 name = name[len(prefix):]
82 82 repos.append((name.lstrip('/'), repo))
83 83
84 84 self.repos = repos
85 85 self.ui = u
86 86 encoding.encoding = self.ui.config('web', 'encoding',
87 87 encoding.encoding)
88 88 self.style = self.ui.config('web', 'style', 'paper')
89 89 self.templatepath = self.ui.config('web', 'templates', None)
90 90 self.stripecount = self.ui.config('web', 'stripes', 1)
91 91 if self.stripecount:
92 92 self.stripecount = int(self.stripecount)
93 93 self._baseurl = self.ui.config('web', 'baseurl')
94 94 self.lastrefresh = time.time()
95 95
96 96 def run(self):
97 97 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
98 98 raise RuntimeError("This function is only intended to be "
99 99 "called while running as a CGI script.")
100 100 import mercurial.hgweb.wsgicgi as wsgicgi
101 101 wsgicgi.launch(self)
102 102
103 103 def __call__(self, env, respond):
104 104 req = wsgirequest(env, respond)
105 105 return self.run_wsgi(req)
106 106
107 107 def read_allowed(self, ui, req):
108 108 """Check allow_read and deny_read config options of a repo's ui object
109 109 to determine user permissions. By default, with neither option set (or
110 110 both empty), allow all users to read the repo. There are two ways a
111 111 user can be denied read access: (1) deny_read is not empty, and the
112 112 user is unauthenticated or deny_read contains user (or *), and (2)
113 113 allow_read is not empty and the user is not in allow_read. Return True
114 114 if user is allowed to read the repo, else return False."""
115 115
116 116 user = req.env.get('REMOTE_USER')
117 117
118 118 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
119 119 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
120 120 return False
121 121
122 122 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
123 123 # by default, allow reading if no allow_read option has been set
124 124 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
125 125 return True
126 126
127 127 return False
128 128
129 129 def run_wsgi(self, req):
130 130 try:
131 131 try:
132 132 self.refresh()
133 133
134 134 virtual = req.env.get("PATH_INFO", "").strip('/')
135 135 tmpl = self.templater(req)
136 136 ctype = tmpl('mimetype', encoding=encoding.encoding)
137 137 ctype = templater.stringify(ctype)
138 138
139 139 # a static file
140 140 if virtual.startswith('static/') or 'static' in req.form:
141 141 if virtual.startswith('static/'):
142 142 fname = virtual[7:]
143 143 else:
144 144 fname = req.form['static'][0]
145 145 static = templater.templatepath('static')
146 146 return (staticfile(static, fname, req),)
147 147
148 148 # top-level index
149 149 elif not virtual:
150 150 req.respond(HTTP_OK, ctype)
151 151 return self.makeindex(req, tmpl)
152 152
153 153 # nested indexes and hgwebs
154 154
155 155 repos = dict(self.repos)
156 156 while virtual:
157 157 real = repos.get(virtual)
158 158 if real:
159 159 req.env['REPO_NAME'] = virtual
160 160 try:
161 161 repo = hg.repository(self.ui, real)
162 162 return hgweb(repo).run_wsgi(req)
163 163 except IOError, inst:
164 164 msg = inst.strerror
165 165 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
166 166 except error.RepoError, inst:
167 167 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
168 168
169 169 # browse subdirectories
170 170 subdir = virtual + '/'
171 171 if [r for r in repos if r.startswith(subdir)]:
172 172 req.respond(HTTP_OK, ctype)
173 173 return self.makeindex(req, tmpl, subdir)
174 174
175 175 up = virtual.rfind('/')
176 176 if up < 0:
177 177 break
178 178 virtual = virtual[:up]
179 179
180 180 # prefixes not found
181 181 req.respond(HTTP_NOT_FOUND, ctype)
182 182 return tmpl("notfound", repo=virtual)
183 183
184 184 except ErrorResponse, err:
185 185 req.respond(err, ctype)
186 186 return tmpl('error', error=err.message or '')
187 187 finally:
188 188 tmpl = None
189 189
190 190 def makeindex(self, req, tmpl, subdir=""):
191 191
192 192 def archivelist(ui, nodeid, url):
193 193 allowed = ui.configlist("web", "allow_archive", untrusted=True)
194 194 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
195 195 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
196 196 untrusted=True):
197 197 yield {"type" : i[0], "extension": i[1],
198 198 "node": nodeid, "url": url}
199 199
200 200 def rawentries(subdir="", **map):
201 201
202 202 descend = self.ui.configbool('web', 'descend', True)
203 203 for name, path in self.repos:
204 204
205 205 if not name.startswith(subdir):
206 206 continue
207 207 name = name[len(subdir):]
208 208 if not descend and '/' in name:
209 209 continue
210 210
211 211 u = self.ui.copy()
212 212 try:
213 213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
214 214 except Exception, e:
215 215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
216 216 continue
217 217 def get(section, name, default=None):
218 218 return u.config(section, name, default, untrusted=True)
219 219
220 220 if u.configbool("web", "hidden", untrusted=True):
221 221 continue
222 222
223 223 if not self.read_allowed(u, req):
224 224 continue
225 225
226 226 parts = [name]
227 227 if 'PATH_INFO' in req.env:
228 228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
229 229 if req.env['SCRIPT_NAME']:
230 230 parts.insert(0, req.env['SCRIPT_NAME'])
231 231 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
232 232
233 233 # update time with local timezone
234 234 try:
235 235 r = hg.repository(self.ui, path)
236 except error.RepoError:
237 u.warn(_('error accessing repository at %s\n') % path)
238 continue
239 try:
236 240 d = (get_mtime(r.spath), util.makedate()[1])
237 241 except OSError:
238 242 continue
239 243
240 244 contact = get_contact(get)
241 245 description = get("web", "description", "")
242 246 name = get("web", "name", name)
243 247 row = dict(contact=contact or "unknown",
244 248 contact_sort=contact.upper() or "unknown",
245 249 name=name,
246 250 name_sort=name,
247 251 url=url,
248 252 description=description or "unknown",
249 253 description_sort=description.upper() or "unknown",
250 254 lastchange=d,
251 255 lastchange_sort=d[1]-d[0],
252 256 archives=archivelist(u, "tip", url))
253 257 yield row
254 258
255 259 sortdefault = None, False
256 260 def entries(sortcolumn="", descending=False, subdir="", **map):
257 261 rows = rawentries(subdir=subdir, **map)
258 262
259 263 if sortcolumn and sortdefault != (sortcolumn, descending):
260 264 sortkey = '%s_sort' % sortcolumn
261 265 rows = sorted(rows, key=lambda x: x[sortkey],
262 266 reverse=descending)
263 267 for row, parity in zip(rows, paritygen(self.stripecount)):
264 268 row['parity'] = parity
265 269 yield row
266 270
267 271 self.refresh()
268 272 sortable = ["name", "description", "contact", "lastchange"]
269 273 sortcolumn, descending = sortdefault
270 274 if 'sort' in req.form:
271 275 sortcolumn = req.form['sort'][0]
272 276 descending = sortcolumn.startswith('-')
273 277 if descending:
274 278 sortcolumn = sortcolumn[1:]
275 279 if sortcolumn not in sortable:
276 280 sortcolumn = ""
277 281
278 282 sort = [("sort_%s" % column,
279 283 "%s%s" % ((not descending and column == sortcolumn)
280 284 and "-" or "", column))
281 285 for column in sortable]
282 286
283 287 self.refresh()
284 288 self.updatereqenv(req.env)
285 289
286 290 return tmpl("index", entries=entries, subdir=subdir,
287 291 sortcolumn=sortcolumn, descending=descending,
288 292 **dict(sort))
289 293
290 294 def templater(self, req):
291 295
292 296 def header(**map):
293 297 yield tmpl('header', encoding=encoding.encoding, **map)
294 298
295 299 def footer(**map):
296 300 yield tmpl("footer", **map)
297 301
298 302 def motd(**map):
299 303 if self.motd is not None:
300 304 yield self.motd
301 305 else:
302 306 yield config('web', 'motd', '')
303 307
304 308 def config(section, name, default=None, untrusted=True):
305 309 return self.ui.config(section, name, default, untrusted)
306 310
307 311 self.updatereqenv(req.env)
308 312
309 313 url = req.env.get('SCRIPT_NAME', '')
310 314 if not url.endswith('/'):
311 315 url += '/'
312 316
313 317 vars = {}
314 318 styles = (
315 319 req.form.get('style', [None])[0],
316 320 config('web', 'style'),
317 321 'paper'
318 322 )
319 323 style, mapfile = templater.stylemap(styles, self.templatepath)
320 324 if style == styles[0]:
321 325 vars['style'] = style
322 326
323 327 start = url[-1] == '?' and '&' or '?'
324 328 sessionvars = webutil.sessionvars(vars, start)
325 329 staticurl = config('web', 'staticurl') or url + 'static/'
326 330 if not staticurl.endswith('/'):
327 331 staticurl += '/'
328 332
329 333 tmpl = templater.templater(mapfile,
330 334 defaults={"header": header,
331 335 "footer": footer,
332 336 "motd": motd,
333 337 "url": url,
334 338 "staticurl": staticurl,
335 339 "sessionvars": sessionvars})
336 340 return tmpl
337 341
338 342 def updatereqenv(self, env):
339 343 def splitnetloc(netloc):
340 344 if ':' in netloc:
341 345 return netloc.split(':', 1)
342 346 else:
343 347 return (netloc, None)
344 348
345 349 if self._baseurl is not None:
346 350 urlcomp = urlparse.urlparse(self._baseurl)
347 351 host, port = splitnetloc(urlcomp[1])
348 352 path = urlcomp[2]
349 353 env['SERVER_NAME'] = host
350 354 if port:
351 355 env['SERVER_PORT'] = port
352 356 env['SCRIPT_NAME'] = path
@@ -1,163 +1,181 b''
1 1 #!/bin/sh
2 2 # Tests some basic hgwebdir functionality. Tests setting up paths and
3 3 # collection, different forms of 404s and the subdirectory support.
4 4
5 5 mkdir webdir
6 6 cd webdir
7 7
8 8 hg init a
9 9 echo a > a/a
10 10 hg --cwd a ci -Ama -d'1 0'
11 11 # create a mercurial queue repository
12 12 hg --cwd a qinit --config extensions.hgext.mq= -c
13 13
14 14 hg init b
15 15 echo b > b/b
16 16 hg --cwd b ci -Amb -d'2 0'
17 17
18 18 # create a nested repository
19 19 cd b
20 20 hg init d
21 21 echo d > d/d
22 22 hg --cwd d ci -Amd -d'3 0'
23 23 cd ..
24 24
25 25 hg init c
26 26 echo c > c/c
27 27 hg --cwd c ci -Amc -d'3 0'
28 28
29 # create repository without .hg/store
30 hg init nostore
31 rm -R nostore/.hg/store
32
29 33 root=`pwd`
30 34 cd ..
31 35
32 36
33 37 cat > paths.conf <<EOF
34 38 [paths]
35 39 a=$root/a
36 40 b=$root/b
37 41 EOF
38 42
39 43 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
40 44 -A access-paths.log -E error-paths-1.log
41 45 cat hg.pid >> $DAEMON_PIDS
42 46
43 47 echo % should give a 404 - file does not exist
44 48 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
45 49
46 50 echo % should succeed
47 51 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
48 52 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
49 53 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
50 54
51 55 echo % should give a 404 - repo is not published
52 56 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
53 57
54 58 echo % atom-log without basedir
55 59 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/atom-log' \
56 60 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
57 61
58 62 echo % rss-log without basedir
59 63 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/rss-log' \
60 64 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
61 65
62 66 cat > paths.conf <<EOF
63 67 [paths]
64 68 t/a/=$root/a
65 69 b=$root/b
66 70 coll=$root/*
67 71 rcoll=$root/**
68 72 star=*
69 73 starstar=**
70 74 EOF
71 75
72 76 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
73 77 -A access-paths.log -E error-paths-2.log
74 78 cat hg.pid >> $DAEMON_PIDS
75 79
76 80 echo % should succeed, slashy names
77 81 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
78 82 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=paper' \
79 83 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
80 84 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
81 85 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
82 86 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=paper' \
83 87 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
84 88 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
85 89 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
86 90 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
87 91 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
88 92 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
89 93 # Test [paths] '*' extension
90 94 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/?style=raw'
91 95 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/a/file/tip/a?style=raw'
92 96 #test [paths] '**' extension
93 97 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/?style=raw'
94 98 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/b/d/file/tip/d?style=raw'
95 99
96 100
97 101 "$TESTDIR/killdaemons.py"
98 102 cat > paths.conf <<EOF
99 103 [paths]
100 104 t/a = $root/a
101 105 t/b = $root/b
102 106 c = $root/c
103 107 [web]
104 108 descend=false
105 109 EOF
106 110
107 111 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
108 112 -A access-paths.log -E error-paths-3.log
109 113 cat hg.pid >> $DAEMON_PIDS
110 114 echo % test descend = False
111 115 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
112 116 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
113 117
114 118
119 "$TESTDIR/killdaemons.py"
120 cat > paths.conf <<EOF
121 [paths]
122 nostore = $root/nostore
123 inexistent = $root/inexistent
124 EOF
125
126 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
127 -A access-paths.log -E error-paths-4.log
128 cat hg.pid >> $DAEMON_PIDS
129 echo % test inexistent and inaccessible repo should be ignored silently
130 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/'
131
132
115 133 cat > collections.conf <<EOF
116 134 [collections]
117 135 $root=$root
118 136 EOF
119 137
120 138 hg serve --config web.baseurl=http://hg.example.com:8080/ -p $HGPORT2 -d \
121 139 --pid-file=hg.pid --webdir-conf collections.conf \
122 140 -A access-collections.log -E error-collections.log
123 141 cat hg.pid >> $DAEMON_PIDS
124 142
125 143 echo % collections: should succeed
126 144 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
127 145 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
128 146 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
129 147 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
130 148
131 149 echo % atom-log with basedir /
132 150 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
133 151 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
134 152
135 153 echo % rss-log with basedir /
136 154 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
137 155 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
138 156
139 157 "$TESTDIR/killdaemons.py"
140 158
141 159 hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \
142 160 --pid-file=hg.pid --webdir-conf collections.conf \
143 161 -A access-collections-2.log -E error-collections-2.log
144 162 cat hg.pid >> $DAEMON_PIDS
145 163
146 164 echo % atom-log with basedir /foo/
147 165 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
148 166 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
149 167
150 168 echo % rss-log with basedir /foo/
151 169 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
152 170 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
153 171
154 172 echo % paths errors 1
155 173 cat error-paths-1.log
156 174 echo % paths errors 2
157 175 cat error-paths-2.log
158 176 echo % paths errors 3
159 177 cat error-paths-3.log
160 178 echo % collections errors
161 179 cat error-collections.log
162 180 echo % collections errors 2
163 181 cat error-collections-2.log
@@ -1,443 +1,482 b''
1 1 adding a
2 2 adding b
3 3 adding d
4 4 adding c
5 5 % should give a 404 - file does not exist
6 6 404 Not Found
7 7
8 8
9 9 error: bork@8580ff50825a: not found in manifest
10 10 % should succeed
11 11 200 Script output follows
12 12
13 13
14 14 /a/
15 15 /b/
16 16
17 17 200 Script output follows
18 18
19 19 a
20 20 200 Script output follows
21 21
22 22 b
23 23 % should give a 404 - repo is not published
24 24 404 Not Found
25 25
26 26
27 27 error: repository c not found
28 28 % atom-log without basedir
29 29 <link rel="self" href="http://example.com:8080/a/atom-log"/>
30 30 <link rel="alternate" href="http://example.com:8080/a/"/>
31 31 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
32 32 % rss-log without basedir
33 33 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
34 34 % should succeed, slashy names
35 35 200 Script output follows
36 36
37 37
38 38 /t/a/
39 39 /b/
40 40 /coll/a/
41 41 /coll/a/.hg/patches/
42 42 /coll/b/
43 43 /coll/c/
44 44 /rcoll/a/
45 45 /rcoll/a/.hg/patches/
46 46 /rcoll/b/
47 47 /rcoll/b/d/
48 48 /rcoll/c/
49 49 /star/webdir/a/
50 50 /star/webdir/a/.hg/patches/
51 51 /star/webdir/b/
52 52 /star/webdir/c/
53 53 /starstar/webdir/a/
54 54 /starstar/webdir/a/.hg/patches/
55 55 /starstar/webdir/b/
56 56 /starstar/webdir/b/d/
57 57 /starstar/webdir/c/
58 58
59 59 200 Script output follows
60 60
61 61 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
62 62 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
63 63 <head>
64 64 <link rel="icon" href="/static/hgicon.png" type="image/png" />
65 65 <meta name="robots" content="index, nofollow" />
66 66 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
67 67
68 68 <title>Mercurial repositories index</title>
69 69 </head>
70 70 <body>
71 71
72 72 <div class="container">
73 73 <div class="menu">
74 74 <a href="http://mercurial.selenic.com/">
75 75 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
76 76 </div>
77 77 <div class="main">
78 78 <h2>Mercurial Repositories</h2>
79 79
80 80 <table class="bigtable">
81 81 <tr>
82 82 <th><a href="?sort=name">Name</a></th>
83 83 <th><a href="?sort=description">Description</a></th>
84 84 <th><a href="?sort=contact">Contact</a></th>
85 85 <th><a href="?sort=lastchange">Last modified</a></th>
86 86 <th>&nbsp;</th>
87 87 </tr>
88 88
89 89 <tr class="parity0">
90 90 <td><a href="/t/a/?style=paper">t/a</a></td>
91 91 <td>unknown</td>
92 92 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
93 93 <td class="age">seconds ago</td>
94 94 <td class="indexlinks"></td>
95 95 </tr>
96 96
97 97 <tr class="parity1">
98 98 <td><a href="/b/?style=paper">b</a></td>
99 99 <td>unknown</td>
100 100 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
101 101 <td class="age">seconds ago</td>
102 102 <td class="indexlinks"></td>
103 103 </tr>
104 104
105 105 <tr class="parity0">
106 106 <td><a href="/coll/a/?style=paper">coll/a</a></td>
107 107 <td>unknown</td>
108 108 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
109 109 <td class="age">seconds ago</td>
110 110 <td class="indexlinks"></td>
111 111 </tr>
112 112
113 113 <tr class="parity1">
114 114 <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td>
115 115 <td>unknown</td>
116 116 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
117 117 <td class="age">seconds ago</td>
118 118 <td class="indexlinks"></td>
119 119 </tr>
120 120
121 121 <tr class="parity0">
122 122 <td><a href="/coll/b/?style=paper">coll/b</a></td>
123 123 <td>unknown</td>
124 124 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
125 125 <td class="age">seconds ago</td>
126 126 <td class="indexlinks"></td>
127 127 </tr>
128 128
129 129 <tr class="parity1">
130 130 <td><a href="/coll/c/?style=paper">coll/c</a></td>
131 131 <td>unknown</td>
132 132 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
133 133 <td class="age">seconds ago</td>
134 134 <td class="indexlinks"></td>
135 135 </tr>
136 136
137 137 <tr class="parity0">
138 138 <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td>
139 139 <td>unknown</td>
140 140 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
141 141 <td class="age">seconds ago</td>
142 142 <td class="indexlinks"></td>
143 143 </tr>
144 144
145 145 <tr class="parity1">
146 146 <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td>
147 147 <td>unknown</td>
148 148 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
149 149 <td class="age">seconds ago</td>
150 150 <td class="indexlinks"></td>
151 151 </tr>
152 152
153 153 <tr class="parity0">
154 154 <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td>
155 155 <td>unknown</td>
156 156 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
157 157 <td class="age">seconds ago</td>
158 158 <td class="indexlinks"></td>
159 159 </tr>
160 160
161 161 <tr class="parity1">
162 162 <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td>
163 163 <td>unknown</td>
164 164 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
165 165 <td class="age">seconds ago</td>
166 166 <td class="indexlinks"></td>
167 167 </tr>
168 168
169 169 <tr class="parity0">
170 170 <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td>
171 171 <td>unknown</td>
172 172 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
173 173 <td class="age">seconds ago</td>
174 174 <td class="indexlinks"></td>
175 175 </tr>
176 176
177 177 <tr class="parity1">
178 178 <td><a href="/star/webdir/a/?style=paper">star/webdir/a</a></td>
179 179 <td>unknown</td>
180 180 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
181 181 <td class="age">seconds ago</td>
182 182 <td class="indexlinks"></td>
183 183 </tr>
184 184
185 185 <tr class="parity0">
186 186 <td><a href="/star/webdir/a/.hg/patches/?style=paper">star/webdir/a/.hg/patches</a></td>
187 187 <td>unknown</td>
188 188 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
189 189 <td class="age">seconds ago</td>
190 190 <td class="indexlinks"></td>
191 191 </tr>
192 192
193 193 <tr class="parity1">
194 194 <td><a href="/star/webdir/b/?style=paper">star/webdir/b</a></td>
195 195 <td>unknown</td>
196 196 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
197 197 <td class="age">seconds ago</td>
198 198 <td class="indexlinks"></td>
199 199 </tr>
200 200
201 201 <tr class="parity0">
202 202 <td><a href="/star/webdir/c/?style=paper">star/webdir/c</a></td>
203 203 <td>unknown</td>
204 204 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
205 205 <td class="age">seconds ago</td>
206 206 <td class="indexlinks"></td>
207 207 </tr>
208 208
209 209 <tr class="parity1">
210 210 <td><a href="/starstar/webdir/a/?style=paper">starstar/webdir/a</a></td>
211 211 <td>unknown</td>
212 212 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
213 213 <td class="age">seconds ago</td>
214 214 <td class="indexlinks"></td>
215 215 </tr>
216 216
217 217 <tr class="parity0">
218 218 <td><a href="/starstar/webdir/a/.hg/patches/?style=paper">starstar/webdir/a/.hg/patches</a></td>
219 219 <td>unknown</td>
220 220 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
221 221 <td class="age">seconds ago</td>
222 222 <td class="indexlinks"></td>
223 223 </tr>
224 224
225 225 <tr class="parity1">
226 226 <td><a href="/starstar/webdir/b/?style=paper">starstar/webdir/b</a></td>
227 227 <td>unknown</td>
228 228 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
229 229 <td class="age">seconds ago</td>
230 230 <td class="indexlinks"></td>
231 231 </tr>
232 232
233 233 <tr class="parity0">
234 234 <td><a href="/starstar/webdir/b/d/?style=paper">starstar/webdir/b/d</a></td>
235 235 <td>unknown</td>
236 236 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
237 237 <td class="age">seconds ago</td>
238 238 <td class="indexlinks"></td>
239 239 </tr>
240 240
241 241 <tr class="parity1">
242 242 <td><a href="/starstar/webdir/c/?style=paper">starstar/webdir/c</a></td>
243 243 <td>unknown</td>
244 244 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
245 245 <td class="age">seconds ago</td>
246 246 <td class="indexlinks"></td>
247 247 </tr>
248 248
249 249 </table>
250 250 </div>
251 251 </div>
252 252
253 253
254 254 </body>
255 255 </html>
256 256
257 257 200 Script output follows
258 258
259 259
260 260 /t/a/
261 261
262 262 200 Script output follows
263 263
264 264
265 265 /t/a/
266 266
267 267 200 Script output follows
268 268
269 269 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
270 270 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
271 271 <head>
272 272 <link rel="icon" href="/static/hgicon.png" type="image/png" />
273 273 <meta name="robots" content="index, nofollow" />
274 274 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
275 275
276 276 <title>Mercurial repositories index</title>
277 277 </head>
278 278 <body>
279 279
280 280 <div class="container">
281 281 <div class="menu">
282 282 <a href="http://mercurial.selenic.com/">
283 283 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
284 284 </div>
285 285 <div class="main">
286 286 <h2>Mercurial Repositories</h2>
287 287
288 288 <table class="bigtable">
289 289 <tr>
290 290 <th><a href="?sort=name">Name</a></th>
291 291 <th><a href="?sort=description">Description</a></th>
292 292 <th><a href="?sort=contact">Contact</a></th>
293 293 <th><a href="?sort=lastchange">Last modified</a></th>
294 294 <th>&nbsp;</th>
295 295 </tr>
296 296
297 297 <tr class="parity0">
298 298 <td><a href="/t/a/?style=paper">a</a></td>
299 299 <td>unknown</td>
300 300 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
301 301 <td class="age">seconds ago</td>
302 302 <td class="indexlinks"></td>
303 303 </tr>
304 304
305 305 </table>
306 306 </div>
307 307 </div>
308 308
309 309
310 310 </body>
311 311 </html>
312 312
313 313 200 Script output follows
314 314
315 315 <?xml version="1.0" encoding="ascii"?>
316 316 <feed xmlns="http://127.0.0.1/2005/Atom">
317 317 <!-- Changelog -->
318 318 <id>http://127.0.0.1/t/a/</id>
319 319 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
320 320 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
321 321 <title>t/a Changelog</title>
322 322 <updated>1970-01-01T00:00:01+00:00</updated>
323 323
324 324 <entry>
325 325 <title>a</title>
326 326 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
327 327 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
328 328 <author>
329 329 <name>test</name>
330 330 <email>&#116;&#101;&#115;&#116;</email>
331 331 </author>
332 332 <updated>1970-01-01T00:00:01+00:00</updated>
333 333 <published>1970-01-01T00:00:01+00:00</published>
334 334 <content type="xhtml">
335 335 <div xmlns="http://127.0.0.1/1999/xhtml">
336 336 <pre xml:space="preserve">a</pre>
337 337 </div>
338 338 </content>
339 339 </entry>
340 340
341 341 </feed>
342 342 200 Script output follows
343 343
344 344 <?xml version="1.0" encoding="ascii"?>
345 345 <feed xmlns="http://127.0.0.1/2005/Atom">
346 346 <!-- Changelog -->
347 347 <id>http://127.0.0.1/t/a/</id>
348 348 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
349 349 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
350 350 <title>t/a Changelog</title>
351 351 <updated>1970-01-01T00:00:01+00:00</updated>
352 352
353 353 <entry>
354 354 <title>a</title>
355 355 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
356 356 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
357 357 <author>
358 358 <name>test</name>
359 359 <email>&#116;&#101;&#115;&#116;</email>
360 360 </author>
361 361 <updated>1970-01-01T00:00:01+00:00</updated>
362 362 <published>1970-01-01T00:00:01+00:00</published>
363 363 <content type="xhtml">
364 364 <div xmlns="http://127.0.0.1/1999/xhtml">
365 365 <pre xml:space="preserve">a</pre>
366 366 </div>
367 367 </content>
368 368 </entry>
369 369
370 370 </feed>
371 371 200 Script output follows
372 372
373 373 a
374 374 200 Script output follows
375 375
376 376
377 377 /coll/a/
378 378 /coll/a/.hg/patches/
379 379 /coll/b/
380 380 /coll/c/
381 381
382 382 200 Script output follows
383 383
384 384 a
385 385 200 Script output follows
386 386
387 387
388 388 /rcoll/a/
389 389 /rcoll/a/.hg/patches/
390 390 /rcoll/b/
391 391 /rcoll/b/d/
392 392 /rcoll/c/
393 393
394 394 200 Script output follows
395 395
396 396 d
397 397 % test descend = False
398 398 200 Script output follows
399 399
400 400
401 401 /c/
402 402
403 403 200 Script output follows
404 404
405 405
406 406 /t/a/
407 407 /t/b/
408 408
409 % test inexistent and inaccessible repo should be ignored silently
410 200 Script output follows
411
412 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
413 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
414 <head>
415 <link rel="icon" href="/static/hgicon.png" type="image/png" />
416 <meta name="robots" content="index, nofollow" />
417 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
418
419 <title>Mercurial repositories index</title>
420 </head>
421 <body>
422
423 <div class="container">
424 <div class="menu">
425 <a href="http://mercurial.selenic.com/">
426 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
427 </div>
428 <div class="main">
429 <h2>Mercurial Repositories</h2>
430
431 <table class="bigtable">
432 <tr>
433 <th><a href="?sort=name">Name</a></th>
434 <th><a href="?sort=description">Description</a></th>
435 <th><a href="?sort=contact">Contact</a></th>
436 <th><a href="?sort=lastchange">Last modified</a></th>
437 <th>&nbsp;</th>
438 </tr>
439
440 </table>
441 </div>
442 </div>
443
444
445 </body>
446 </html>
447
409 448 % collections: should succeed
410 449 200 Script output follows
411 450
412 451
413 452 /a/
414 453 /a/.hg/patches/
415 454 /b/
416 455 /c/
417 456
418 457 200 Script output follows
419 458
420 459 a
421 460 200 Script output follows
422 461
423 462 b
424 463 200 Script output follows
425 464
426 465 c
427 466 % atom-log with basedir /
428 467 <link rel="self" href="http://example.com:8080/a/atom-log"/>
429 468 <link rel="alternate" href="http://example.com:8080/a/"/>
430 469 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
431 470 % rss-log with basedir /
432 471 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
433 472 % atom-log with basedir /foo/
434 473 <link rel="self" href="http://example.com:8080/foo/a/atom-log"/>
435 474 <link rel="alternate" href="http://example.com:8080/foo/a/"/>
436 475 <link href="http://example.com:8080/foo/a/rev/8580ff50825a"/>
437 476 % rss-log with basedir /foo/
438 477 <guid isPermaLink="true">http://example.com:8080/foo/a/rev/8580ff50825a</guid>
439 478 % paths errors 1
440 479 % paths errors 2
441 480 % paths errors 3
442 481 % collections errors
443 482 % collections errors 2
General Comments 0
You need to be logged in to leave comments. Login now