##// END OF EJS Templates
hgwebdir: allow pure relative globs in paths...
Mads Kiilerich -
r11677:8f8a7976 stable
parent child Browse files
Show More
@@ -1,352 +1,352 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 roothead = os.path.normpath(roothead)
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 236 d = (get_mtime(r.spath), util.makedate()[1])
237 237 except OSError:
238 238 continue
239 239
240 240 contact = get_contact(get)
241 241 description = get("web", "description", "")
242 242 name = get("web", "name", name)
243 243 row = dict(contact=contact or "unknown",
244 244 contact_sort=contact.upper() or "unknown",
245 245 name=name,
246 246 name_sort=name,
247 247 url=url,
248 248 description=description or "unknown",
249 249 description_sort=description.upper() or "unknown",
250 250 lastchange=d,
251 251 lastchange_sort=d[1]-d[0],
252 252 archives=archivelist(u, "tip", url))
253 253 yield row
254 254
255 255 sortdefault = None, False
256 256 def entries(sortcolumn="", descending=False, subdir="", **map):
257 257 rows = rawentries(subdir=subdir, **map)
258 258
259 259 if sortcolumn and sortdefault != (sortcolumn, descending):
260 260 sortkey = '%s_sort' % sortcolumn
261 261 rows = sorted(rows, key=lambda x: x[sortkey],
262 262 reverse=descending)
263 263 for row, parity in zip(rows, paritygen(self.stripecount)):
264 264 row['parity'] = parity
265 265 yield row
266 266
267 267 self.refresh()
268 268 sortable = ["name", "description", "contact", "lastchange"]
269 269 sortcolumn, descending = sortdefault
270 270 if 'sort' in req.form:
271 271 sortcolumn = req.form['sort'][0]
272 272 descending = sortcolumn.startswith('-')
273 273 if descending:
274 274 sortcolumn = sortcolumn[1:]
275 275 if sortcolumn not in sortable:
276 276 sortcolumn = ""
277 277
278 278 sort = [("sort_%s" % column,
279 279 "%s%s" % ((not descending and column == sortcolumn)
280 280 and "-" or "", column))
281 281 for column in sortable]
282 282
283 283 self.refresh()
284 284 self.updatereqenv(req.env)
285 285
286 286 return tmpl("index", entries=entries, subdir=subdir,
287 287 sortcolumn=sortcolumn, descending=descending,
288 288 **dict(sort))
289 289
290 290 def templater(self, req):
291 291
292 292 def header(**map):
293 293 yield tmpl('header', encoding=encoding.encoding, **map)
294 294
295 295 def footer(**map):
296 296 yield tmpl("footer", **map)
297 297
298 298 def motd(**map):
299 299 if self.motd is not None:
300 300 yield self.motd
301 301 else:
302 302 yield config('web', 'motd', '')
303 303
304 304 def config(section, name, default=None, untrusted=True):
305 305 return self.ui.config(section, name, default, untrusted)
306 306
307 307 self.updatereqenv(req.env)
308 308
309 309 url = req.env.get('SCRIPT_NAME', '')
310 310 if not url.endswith('/'):
311 311 url += '/'
312 312
313 313 vars = {}
314 314 styles = (
315 315 req.form.get('style', [None])[0],
316 316 config('web', 'style'),
317 317 'paper'
318 318 )
319 319 style, mapfile = templater.stylemap(styles, self.templatepath)
320 320 if style == styles[0]:
321 321 vars['style'] = style
322 322
323 323 start = url[-1] == '?' and '&' or '?'
324 324 sessionvars = webutil.sessionvars(vars, start)
325 325 staticurl = config('web', 'staticurl') or url + 'static/'
326 326 if not staticurl.endswith('/'):
327 327 staticurl += '/'
328 328
329 329 tmpl = templater.templater(mapfile,
330 330 defaults={"header": header,
331 331 "footer": footer,
332 332 "motd": motd,
333 333 "url": url,
334 334 "staticurl": staticurl,
335 335 "sessionvars": sessionvars})
336 336 return tmpl
337 337
338 338 def updatereqenv(self, env):
339 339 def splitnetloc(netloc):
340 340 if ':' in netloc:
341 341 return netloc.split(':', 1)
342 342 else:
343 343 return (netloc, None)
344 344
345 345 if self._baseurl is not None:
346 346 urlcomp = urlparse.urlparse(self._baseurl)
347 347 host, port = splitnetloc(urlcomp[1])
348 348 path = urlcomp[2]
349 349 env['SERVER_NAME'] = host
350 350 if port:
351 351 env['SERVER_PORT'] = port
352 352 env['SCRIPT_NAME'] = path
@@ -1,161 +1,163 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 29 root=`pwd`
30 30 cd ..
31 31
32 32
33 33 cat > paths.conf <<EOF
34 34 [paths]
35 35 a=$root/a
36 36 b=$root/b
37 37 EOF
38 38
39 39 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
40 40 -A access-paths.log -E error-paths-1.log
41 41 cat hg.pid >> $DAEMON_PIDS
42 42
43 43 echo % should give a 404 - file does not exist
44 44 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
45 45
46 46 echo % should succeed
47 47 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
48 48 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
49 49 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
50 50
51 51 echo % should give a 404 - repo is not published
52 52 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
53 53
54 54 echo % atom-log without basedir
55 55 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/atom-log' \
56 56 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
57 57
58 58 echo % rss-log without basedir
59 59 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/rss-log' \
60 60 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
61 61
62 62 cat > paths.conf <<EOF
63 63 [paths]
64 64 t/a/=$root/a
65 65 b=$root/b
66 66 coll=$root/*
67 67 rcoll=$root/**
68 star=*
69 starstar=**
68 70 EOF
69 71
70 72 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
71 73 -A access-paths.log -E error-paths-2.log
72 74 cat hg.pid >> $DAEMON_PIDS
73 75
74 76 echo % should succeed, slashy names
75 77 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
76 78 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=paper' \
77 79 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
78 80 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
79 81 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
80 82 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=paper' \
81 83 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
82 84 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
83 85 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
84 86 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
85 87 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
86 88 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
87 89 # Test [paths] '*' extension
88 90 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/?style=raw'
89 91 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/a/file/tip/a?style=raw'
90 92 #test [paths] '**' extension
91 93 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/?style=raw'
92 94 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/b/d/file/tip/d?style=raw'
93 95
94 96
95 97 "$TESTDIR/killdaemons.py"
96 98 cat > paths.conf <<EOF
97 99 [paths]
98 100 t/a = $root/a
99 101 t/b = $root/b
100 102 c = $root/c
101 103 [web]
102 104 descend=false
103 105 EOF
104 106
105 107 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
106 108 -A access-paths.log -E error-paths-3.log
107 109 cat hg.pid >> $DAEMON_PIDS
108 110 echo % test descend = False
109 111 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
110 112 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
111 113
112 114
113 115 cat > collections.conf <<EOF
114 116 [collections]
115 117 $root=$root
116 118 EOF
117 119
118 120 hg serve --config web.baseurl=http://hg.example.com:8080/ -p $HGPORT2 -d \
119 121 --pid-file=hg.pid --webdir-conf collections.conf \
120 122 -A access-collections.log -E error-collections.log
121 123 cat hg.pid >> $DAEMON_PIDS
122 124
123 125 echo % collections: should succeed
124 126 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
125 127 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
126 128 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
127 129 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
128 130
129 131 echo % atom-log with basedir /
130 132 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
131 133 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
132 134
133 135 echo % rss-log with basedir /
134 136 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
135 137 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
136 138
137 139 "$TESTDIR/killdaemons.py"
138 140
139 141 hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \
140 142 --pid-file=hg.pid --webdir-conf collections.conf \
141 143 -A access-collections-2.log -E error-collections-2.log
142 144 cat hg.pid >> $DAEMON_PIDS
143 145
144 146 echo % atom-log with basedir /foo/
145 147 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
146 148 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
147 149
148 150 echo % rss-log with basedir /foo/
149 151 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
150 152 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
151 153
152 154 echo % paths errors 1
153 155 cat error-paths-1.log
154 156 echo % paths errors 2
155 157 cat error-paths-2.log
156 158 echo % paths errors 3
157 159 cat error-paths-3.log
158 160 echo % collections errors
159 161 cat error-collections.log
160 162 echo % collections errors 2
161 163 cat error-collections-2.log
@@ -1,362 +1,443 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 /star/webdir/a/
50 /star/webdir/a/.hg/patches/
51 /star/webdir/b/
52 /star/webdir/c/
53 /starstar/webdir/a/
54 /starstar/webdir/a/.hg/patches/
55 /starstar/webdir/b/
56 /starstar/webdir/b/d/
57 /starstar/webdir/c/
49 58
50 59 200 Script output follows
51 60
52 61 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
53 62 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
54 63 <head>
55 64 <link rel="icon" href="/static/hgicon.png" type="image/png" />
56 65 <meta name="robots" content="index, nofollow" />
57 66 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
58 67
59 68 <title>Mercurial repositories index</title>
60 69 </head>
61 70 <body>
62 71
63 72 <div class="container">
64 73 <div class="menu">
65 74 <a href="http://mercurial.selenic.com/">
66 75 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
67 76 </div>
68 77 <div class="main">
69 78 <h2>Mercurial Repositories</h2>
70 79
71 80 <table class="bigtable">
72 81 <tr>
73 82 <th><a href="?sort=name">Name</a></th>
74 83 <th><a href="?sort=description">Description</a></th>
75 84 <th><a href="?sort=contact">Contact</a></th>
76 85 <th><a href="?sort=lastchange">Last modified</a></th>
77 86 <th>&nbsp;</th>
78 87 </tr>
79 88
80 89 <tr class="parity0">
81 90 <td><a href="/t/a/?style=paper">t/a</a></td>
82 91 <td>unknown</td>
83 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>
84 93 <td class="age">seconds ago</td>
85 94 <td class="indexlinks"></td>
86 95 </tr>
87 96
88 97 <tr class="parity1">
89 98 <td><a href="/b/?style=paper">b</a></td>
90 99 <td>unknown</td>
91 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>
92 101 <td class="age">seconds ago</td>
93 102 <td class="indexlinks"></td>
94 103 </tr>
95 104
96 105 <tr class="parity0">
97 106 <td><a href="/coll/a/?style=paper">coll/a</a></td>
98 107 <td>unknown</td>
99 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>
100 109 <td class="age">seconds ago</td>
101 110 <td class="indexlinks"></td>
102 111 </tr>
103 112
104 113 <tr class="parity1">
105 114 <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td>
106 115 <td>unknown</td>
107 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>
108 117 <td class="age">seconds ago</td>
109 118 <td class="indexlinks"></td>
110 119 </tr>
111 120
112 121 <tr class="parity0">
113 122 <td><a href="/coll/b/?style=paper">coll/b</a></td>
114 123 <td>unknown</td>
115 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>
116 125 <td class="age">seconds ago</td>
117 126 <td class="indexlinks"></td>
118 127 </tr>
119 128
120 129 <tr class="parity1">
121 130 <td><a href="/coll/c/?style=paper">coll/c</a></td>
122 131 <td>unknown</td>
123 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>
124 133 <td class="age">seconds ago</td>
125 134 <td class="indexlinks"></td>
126 135 </tr>
127 136
128 137 <tr class="parity0">
129 138 <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td>
130 139 <td>unknown</td>
131 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>
132 141 <td class="age">seconds ago</td>
133 142 <td class="indexlinks"></td>
134 143 </tr>
135 144
136 145 <tr class="parity1">
137 146 <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td>
138 147 <td>unknown</td>
139 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>
140 149 <td class="age">seconds ago</td>
141 150 <td class="indexlinks"></td>
142 151 </tr>
143 152
144 153 <tr class="parity0">
145 154 <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td>
146 155 <td>unknown</td>
147 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>
148 157 <td class="age">seconds ago</td>
149 158 <td class="indexlinks"></td>
150 159 </tr>
151 160
152 161 <tr class="parity1">
153 162 <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td>
154 163 <td>unknown</td>
155 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>
156 165 <td class="age">seconds ago</td>
157 166 <td class="indexlinks"></td>
158 167 </tr>
159 168
160 169 <tr class="parity0">
161 170 <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td>
162 171 <td>unknown</td>
163 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>
164 173 <td class="age">seconds ago</td>
165 174 <td class="indexlinks"></td>
166 175 </tr>
167 176
177 <tr class="parity1">
178 <td><a href="/star/webdir/a/?style=paper">star/webdir/a</a></td>
179 <td>unknown</td>
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 <td class="age">seconds ago</td>
182 <td class="indexlinks"></td>
183 </tr>
184
185 <tr class="parity0">
186 <td><a href="/star/webdir/a/.hg/patches/?style=paper">star/webdir/a/.hg/patches</a></td>
187 <td>unknown</td>
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 <td class="age">seconds ago</td>
190 <td class="indexlinks"></td>
191 </tr>
192
193 <tr class="parity1">
194 <td><a href="/star/webdir/b/?style=paper">star/webdir/b</a></td>
195 <td>unknown</td>
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 <td class="age">seconds ago</td>
198 <td class="indexlinks"></td>
199 </tr>
200
201 <tr class="parity0">
202 <td><a href="/star/webdir/c/?style=paper">star/webdir/c</a></td>
203 <td>unknown</td>
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 <td class="age">seconds ago</td>
206 <td class="indexlinks"></td>
207 </tr>
208
209 <tr class="parity1">
210 <td><a href="/starstar/webdir/a/?style=paper">starstar/webdir/a</a></td>
211 <td>unknown</td>
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 <td class="age">seconds ago</td>
214 <td class="indexlinks"></td>
215 </tr>
216
217 <tr class="parity0">
218 <td><a href="/starstar/webdir/a/.hg/patches/?style=paper">starstar/webdir/a/.hg/patches</a></td>
219 <td>unknown</td>
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 <td class="age">seconds ago</td>
222 <td class="indexlinks"></td>
223 </tr>
224
225 <tr class="parity1">
226 <td><a href="/starstar/webdir/b/?style=paper">starstar/webdir/b</a></td>
227 <td>unknown</td>
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 <td class="age">seconds ago</td>
230 <td class="indexlinks"></td>
231 </tr>
232
233 <tr class="parity0">
234 <td><a href="/starstar/webdir/b/d/?style=paper">starstar/webdir/b/d</a></td>
235 <td>unknown</td>
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 <td class="age">seconds ago</td>
238 <td class="indexlinks"></td>
239 </tr>
240
241 <tr class="parity1">
242 <td><a href="/starstar/webdir/c/?style=paper">starstar/webdir/c</a></td>
243 <td>unknown</td>
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 <td class="age">seconds ago</td>
246 <td class="indexlinks"></td>
247 </tr>
248
168 249 </table>
169 250 </div>
170 251 </div>
171 252
172 253
173 254 </body>
174 255 </html>
175 256
176 257 200 Script output follows
177 258
178 259
179 260 /t/a/
180 261
181 262 200 Script output follows
182 263
183 264
184 265 /t/a/
185 266
186 267 200 Script output follows
187 268
188 269 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
189 270 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
190 271 <head>
191 272 <link rel="icon" href="/static/hgicon.png" type="image/png" />
192 273 <meta name="robots" content="index, nofollow" />
193 274 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
194 275
195 276 <title>Mercurial repositories index</title>
196 277 </head>
197 278 <body>
198 279
199 280 <div class="container">
200 281 <div class="menu">
201 282 <a href="http://mercurial.selenic.com/">
202 283 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
203 284 </div>
204 285 <div class="main">
205 286 <h2>Mercurial Repositories</h2>
206 287
207 288 <table class="bigtable">
208 289 <tr>
209 290 <th><a href="?sort=name">Name</a></th>
210 291 <th><a href="?sort=description">Description</a></th>
211 292 <th><a href="?sort=contact">Contact</a></th>
212 293 <th><a href="?sort=lastchange">Last modified</a></th>
213 294 <th>&nbsp;</th>
214 295 </tr>
215 296
216 297 <tr class="parity0">
217 298 <td><a href="/t/a/?style=paper">a</a></td>
218 299 <td>unknown</td>
219 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>
220 301 <td class="age">seconds ago</td>
221 302 <td class="indexlinks"></td>
222 303 </tr>
223 304
224 305 </table>
225 306 </div>
226 307 </div>
227 308
228 309
229 310 </body>
230 311 </html>
231 312
232 313 200 Script output follows
233 314
234 315 <?xml version="1.0" encoding="ascii"?>
235 316 <feed xmlns="http://127.0.0.1/2005/Atom">
236 317 <!-- Changelog -->
237 318 <id>http://127.0.0.1/t/a/</id>
238 319 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
239 320 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
240 321 <title>t/a Changelog</title>
241 322 <updated>1970-01-01T00:00:01+00:00</updated>
242 323
243 324 <entry>
244 325 <title>a</title>
245 326 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
246 327 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
247 328 <author>
248 329 <name>test</name>
249 330 <email>&#116;&#101;&#115;&#116;</email>
250 331 </author>
251 332 <updated>1970-01-01T00:00:01+00:00</updated>
252 333 <published>1970-01-01T00:00:01+00:00</published>
253 334 <content type="xhtml">
254 335 <div xmlns="http://127.0.0.1/1999/xhtml">
255 336 <pre xml:space="preserve">a</pre>
256 337 </div>
257 338 </content>
258 339 </entry>
259 340
260 341 </feed>
261 342 200 Script output follows
262 343
263 344 <?xml version="1.0" encoding="ascii"?>
264 345 <feed xmlns="http://127.0.0.1/2005/Atom">
265 346 <!-- Changelog -->
266 347 <id>http://127.0.0.1/t/a/</id>
267 348 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
268 349 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
269 350 <title>t/a Changelog</title>
270 351 <updated>1970-01-01T00:00:01+00:00</updated>
271 352
272 353 <entry>
273 354 <title>a</title>
274 355 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
275 356 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
276 357 <author>
277 358 <name>test</name>
278 359 <email>&#116;&#101;&#115;&#116;</email>
279 360 </author>
280 361 <updated>1970-01-01T00:00:01+00:00</updated>
281 362 <published>1970-01-01T00:00:01+00:00</published>
282 363 <content type="xhtml">
283 364 <div xmlns="http://127.0.0.1/1999/xhtml">
284 365 <pre xml:space="preserve">a</pre>
285 366 </div>
286 367 </content>
287 368 </entry>
288 369
289 370 </feed>
290 371 200 Script output follows
291 372
292 373 a
293 374 200 Script output follows
294 375
295 376
296 377 /coll/a/
297 378 /coll/a/.hg/patches/
298 379 /coll/b/
299 380 /coll/c/
300 381
301 382 200 Script output follows
302 383
303 384 a
304 385 200 Script output follows
305 386
306 387
307 388 /rcoll/a/
308 389 /rcoll/a/.hg/patches/
309 390 /rcoll/b/
310 391 /rcoll/b/d/
311 392 /rcoll/c/
312 393
313 394 200 Script output follows
314 395
315 396 d
316 397 % test descend = False
317 398 200 Script output follows
318 399
319 400
320 401 /c/
321 402
322 403 200 Script output follows
323 404
324 405
325 406 /t/a/
326 407 /t/b/
327 408
328 409 % collections: should succeed
329 410 200 Script output follows
330 411
331 412
332 413 /a/
333 414 /a/.hg/patches/
334 415 /b/
335 416 /c/
336 417
337 418 200 Script output follows
338 419
339 420 a
340 421 200 Script output follows
341 422
342 423 b
343 424 200 Script output follows
344 425
345 426 c
346 427 % atom-log with basedir /
347 428 <link rel="self" href="http://example.com:8080/a/atom-log"/>
348 429 <link rel="alternate" href="http://example.com:8080/a/"/>
349 430 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
350 431 % rss-log with basedir /
351 432 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
352 433 % atom-log with basedir /foo/
353 434 <link rel="self" href="http://example.com:8080/foo/a/atom-log"/>
354 435 <link rel="alternate" href="http://example.com:8080/foo/a/"/>
355 436 <link href="http://example.com:8080/foo/a/rev/8580ff50825a"/>
356 437 % rss-log with basedir /foo/
357 438 <guid isPermaLink="true">http://example.com:8080/foo/a/rev/8580ff50825a</guid>
358 439 % paths errors 1
359 440 % paths errors 2
360 441 % paths errors 3
361 442 % collections errors
362 443 % collections errors 2
General Comments 0
You need to be logged in to leave comments. Login now