##// END OF EJS Templates
Merge with crew-stable
Bryan O'Sullivan -
r9199:fb5562af merge default
parent child Browse files
Show More
@@ -1,331 +1,333 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, incorporated herein by reference.
8 8
9 import os, time
9 import os, re, time
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[prefix] = root
34 34 continue
35 35 roothead = os.path.normpath(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[name] = path
42 42 return repos.items()
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.refresh()
52 52
53 53 def refresh(self):
54 54 if self.lastrefresh + self.refreshinterval > time.time():
55 55 return
56 56
57 57 if self.baseui:
58 58 self.ui = self.baseui.copy()
59 59 else:
60 60 self.ui = ui.ui()
61 61 self.ui.setconfig('ui', 'report_untrusted', 'off')
62 62 self.ui.setconfig('ui', 'interactive', 'off')
63 63
64 64 if not isinstance(self.conf, (dict, list, tuple)):
65 65 map = {'paths': 'hgweb-paths'}
66 66 self.ui.readconfig(self.conf, remap=map, trust=True)
67 67 paths = self.ui.configitems('hgweb-paths')
68 68 elif isinstance(self.conf, (list, tuple)):
69 69 paths = self.conf
70 70 elif isinstance(self.conf, dict):
71 71 paths = self.conf.items()
72 72
73 73 encoding.encoding = self.ui.config('web', 'encoding',
74 74 encoding.encoding)
75 75 self.motd = self.ui.config('web', 'motd')
76 76 self.style = self.ui.config('web', 'style', 'paper')
77 77 self.stripecount = self.ui.config('web', 'stripes', 1)
78 78 if self.stripecount:
79 79 self.stripecount = int(self.stripecount)
80 80 self._baseurl = self.ui.config('web', 'baseurl')
81 81
82 82 self.repos = findrepos(paths)
83 83 for prefix, root in self.ui.configitems('collections'):
84 84 prefix = util.pconvert(prefix)
85 85 for path in util.walkrepos(root, followsym=True):
86 86 repo = os.path.normpath(path)
87 87 name = util.pconvert(repo)
88 88 if name.startswith(prefix):
89 89 name = name[len(prefix):]
90 90 self.repos.append((name.lstrip('/'), repo))
91 91
92 92 self.repos.sort()
93 93 self.lastrefresh = time.time()
94 94
95 95 def run(self):
96 96 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
97 97 raise RuntimeError("This function is only intended to be "
98 98 "called while running as a CGI script.")
99 99 import mercurial.hgweb.wsgicgi as wsgicgi
100 100 wsgicgi.launch(self)
101 101
102 102 def __call__(self, env, respond):
103 103 req = wsgirequest(env, respond)
104 104 return self.run_wsgi(req)
105 105
106 106 def read_allowed(self, ui, req):
107 107 """Check allow_read and deny_read config options of a repo's ui object
108 108 to determine user permissions. By default, with neither option set (or
109 109 both empty), allow all users to read the repo. There are two ways a
110 110 user can be denied read access: (1) deny_read is not empty, and the
111 111 user is unauthenticated or deny_read contains user (or *), and (2)
112 112 allow_read is not empty and the user is not in allow_read. Return True
113 113 if user is allowed to read the repo, else return False."""
114 114
115 115 user = req.env.get('REMOTE_USER')
116 116
117 117 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
118 118 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
119 119 return False
120 120
121 121 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
122 122 # by default, allow reading if no allow_read option has been set
123 123 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
124 124 return True
125 125
126 126 return False
127 127
128 128 def run_wsgi(self, req):
129 129 try:
130 130 try:
131 131 self.refresh()
132 132
133 133 virtual = req.env.get("PATH_INFO", "").strip('/')
134 134 tmpl = self.templater(req)
135 135 ctype = tmpl('mimetype', encoding=encoding.encoding)
136 136 ctype = templater.stringify(ctype)
137 137
138 138 # a static file
139 139 if virtual.startswith('static/') or 'static' in req.form:
140 140 if virtual.startswith('static/'):
141 141 fname = virtual[7:]
142 142 else:
143 143 fname = req.form['static'][0]
144 144 static = templater.templatepath('static')
145 145 return (staticfile(static, fname, req),)
146 146
147 147 # top-level index
148 148 elif not virtual:
149 149 req.respond(HTTP_OK, ctype)
150 150 return self.makeindex(req, tmpl)
151 151
152 152 # nested indexes and hgwebs
153 153
154 154 repos = dict(self.repos)
155 155 while virtual:
156 156 real = repos.get(virtual)
157 157 if real:
158 158 req.env['REPO_NAME'] = virtual
159 159 try:
160 160 repo = hg.repository(self.ui, real)
161 161 return hgweb(repo).run_wsgi(req)
162 162 except IOError, inst:
163 163 msg = inst.strerror
164 164 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
165 165 except error.RepoError, inst:
166 166 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
167 167
168 168 # browse subdirectories
169 169 subdir = virtual + '/'
170 170 if [r for r in repos if r.startswith(subdir)]:
171 171 req.respond(HTTP_OK, ctype)
172 172 return self.makeindex(req, tmpl, subdir)
173 173
174 174 up = virtual.rfind('/')
175 175 if up < 0:
176 176 break
177 177 virtual = virtual[:up]
178 178
179 179 # prefixes not found
180 180 req.respond(HTTP_NOT_FOUND, ctype)
181 181 return tmpl("notfound", repo=virtual)
182 182
183 183 except ErrorResponse, err:
184 184 req.respond(err, ctype)
185 185 return tmpl('error', error=err.message or '')
186 186 finally:
187 187 tmpl = None
188 188
189 189 def makeindex(self, req, tmpl, subdir=""):
190 190
191 191 def archivelist(ui, nodeid, url):
192 192 allowed = ui.configlist("web", "allow_archive", untrusted=True)
193 193 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
194 194 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
195 195 untrusted=True):
196 196 yield {"type" : i[0], "extension": i[1],
197 197 "node": nodeid, "url": url}
198 198
199 199 sortdefault = 'name', False
200 200 def entries(sortcolumn="", descending=False, subdir="", **map):
201 201 rows = []
202 202 parity = paritygen(self.stripecount)
203 203 for name, path in self.repos:
204 204 if not name.startswith(subdir):
205 205 continue
206 206 name = name[len(subdir):]
207 207
208 208 u = self.ui.copy()
209 209 try:
210 210 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
211 211 except Exception, e:
212 212 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
213 213 continue
214 214 def get(section, name, default=None):
215 215 return u.config(section, name, default, untrusted=True)
216 216
217 217 if u.configbool("web", "hidden", untrusted=True):
218 218 continue
219 219
220 220 if not self.read_allowed(u, req):
221 221 continue
222 222
223 223 parts = [name]
224 224 if 'PATH_INFO' in req.env:
225 225 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
226 226 if req.env['SCRIPT_NAME']:
227 227 parts.insert(0, req.env['SCRIPT_NAME'])
228 url = ('/'.join(parts).replace("//", "/")) + '/'
228 m = re.match('((?:https?://)?)(.*)', '/'.join(parts))
229 # squish repeated slashes out of the path component
230 url = m.group(1) + re.sub('/+', '/', m.group(2)) + '/'
229 231
230 232 # update time with local timezone
231 233 try:
232 234 d = (get_mtime(path), util.makedate()[1])
233 235 except OSError:
234 236 continue
235 237
236 238 contact = get_contact(get)
237 239 description = get("web", "description", "")
238 240 name = get("web", "name", name)
239 241 row = dict(contact=contact or "unknown",
240 242 contact_sort=contact.upper() or "unknown",
241 243 name=name,
242 244 name_sort=name,
243 245 url=url,
244 246 description=description or "unknown",
245 247 description_sort=description.upper() or "unknown",
246 248 lastchange=d,
247 249 lastchange_sort=d[1]-d[0],
248 250 archives=archivelist(u, "tip", url))
249 251 if (not sortcolumn or (sortcolumn, descending) == sortdefault):
250 252 # fast path for unsorted output
251 253 row['parity'] = parity.next()
252 254 yield row
253 255 else:
254 256 rows.append((row["%s_sort" % sortcolumn], row))
255 257 if rows:
256 258 rows.sort()
257 259 if descending:
258 260 rows.reverse()
259 261 for key, row in rows:
260 262 row['parity'] = parity.next()
261 263 yield row
262 264
263 265 self.refresh()
264 266 sortable = ["name", "description", "contact", "lastchange"]
265 267 sortcolumn, descending = sortdefault
266 268 if 'sort' in req.form:
267 269 sortcolumn = req.form['sort'][0]
268 270 descending = sortcolumn.startswith('-')
269 271 if descending:
270 272 sortcolumn = sortcolumn[1:]
271 273 if sortcolumn not in sortable:
272 274 sortcolumn = ""
273 275
274 276 sort = [("sort_%s" % column,
275 277 "%s%s" % ((not descending and column == sortcolumn)
276 278 and "-" or "", column))
277 279 for column in sortable]
278 280
279 281 self.refresh()
280 282 if self._baseurl is not None:
281 283 req.env['SCRIPT_NAME'] = self._baseurl
282 284
283 285 return tmpl("index", entries=entries, subdir=subdir,
284 286 sortcolumn=sortcolumn, descending=descending,
285 287 **dict(sort))
286 288
287 289 def templater(self, req):
288 290
289 291 def header(**map):
290 292 yield tmpl('header', encoding=encoding.encoding, **map)
291 293
292 294 def footer(**map):
293 295 yield tmpl("footer", **map)
294 296
295 297 def motd(**map):
296 298 if self.motd is not None:
297 299 yield self.motd
298 300 else:
299 301 yield config('web', 'motd', '')
300 302
301 303 def config(section, name, default=None, untrusted=True):
302 304 return self.ui.config(section, name, default, untrusted)
303 305
304 306 if self._baseurl is not None:
305 307 req.env['SCRIPT_NAME'] = self._baseurl
306 308
307 309 url = req.env.get('SCRIPT_NAME', '')
308 310 if not url.endswith('/'):
309 311 url += '/'
310 312
311 313 vars = {}
312 314 style = self.style
313 315 if 'style' in req.form:
314 316 vars['style'] = style = req.form['style'][0]
315 317 start = url[-1] == '?' and '&' or '?'
316 318 sessionvars = webutil.sessionvars(vars, start)
317 319
318 320 staticurl = config('web', 'staticurl') or url + 'static/'
319 321 if not staticurl.endswith('/'):
320 322 staticurl += '/'
321 323
322 324 style = 'style' in req.form and req.form['style'][0] or self.style
323 325 mapfile = templater.stylemap(style)
324 326 tmpl = templater.templater(mapfile,
325 327 defaults={"header": header,
326 328 "footer": footer,
327 329 "motd": motd,
328 330 "url": url,
329 331 "staticurl": staticurl,
330 332 "sessionvars": sessionvars})
331 333 return tmpl
@@ -1,106 +1,107 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 cat > paths.conf <<EOF
33 33 [paths]
34 34 a=$root/a
35 35 b=$root/b
36 36 EOF
37 37
38 38 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
39 39 -A access-paths.log -E error-paths-1.log
40 40 cat hg.pid >> $DAEMON_PIDS
41 41
42 42 echo % should give a 404 - file does not exist
43 43 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
44 44
45 45 echo % should succeed
46 46 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
47 47 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
48 48 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
49 49
50 50 echo % should give a 404 - repo is not published
51 51 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
52 52
53 53 cat > paths.conf <<EOF
54 54 [paths]
55 55 t/a/=$root/a
56 56 b=$root/b
57 57 coll=$root/*
58 58 rcoll=$root/**
59 59 EOF
60 60
61 61 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
62 62 -A access-paths.log -E error-paths-2.log
63 63 cat hg.pid >> $DAEMON_PIDS
64 64
65 65 echo % should succeed, slashy names
66 66 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
67 67 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=paper' \
68 68 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
69 69 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
70 70 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
71 71 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=paper' \
72 72 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
73 73 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
74 74 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
75 75 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
76 76 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
77 77 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
78 78 # Test [paths] '*' extension
79 79 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/?style=raw'
80 80 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/a/file/tip/a?style=raw'
81 81 #test [paths] '**' extension
82 82 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/?style=raw'
83 83 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/b/d/file/tip/d?style=raw'
84 84
85 85
86 86 cat > collections.conf <<EOF
87 87 [collections]
88 88 $root=$root
89 89 EOF
90 90
91 hg serve -p $HGPORT2 -d --pid-file=hg.pid --webdir-conf collections.conf \
91 hg serve --config web.baseurl=http://hg.example.com:8080/ -p $HGPORT2 -d \
92 --pid-file=hg.pid --webdir-conf collections.conf \
92 93 -A access-collections.log -E error-collections.log
93 94 cat hg.pid >> $DAEMON_PIDS
94 95
95 96 echo % collections: should succeed
96 97 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
97 98 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
98 99 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
99 100 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
100 101
101 102 echo % paths errors 1
102 103 cat error-paths-1.log
103 104 echo % paths errors 2
104 105 cat error-paths-2.log
105 106 echo % collections errors
106 107 cat error-collections.log
@@ -1,330 +1,330 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 % should succeed, slashy names
29 29 200 Script output follows
30 30
31 31
32 32 /b/
33 33 /coll/a/
34 34 /coll/a/.hg/patches/
35 35 /coll/b/
36 36 /coll/c/
37 37 /rcoll/a/
38 38 /rcoll/a/.hg/patches/
39 39 /rcoll/b/
40 40 /rcoll/b/d/
41 41 /rcoll/c/
42 42 /t/a/
43 43
44 44 200 Script output follows
45 45
46 46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
47 47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
48 48 <head>
49 49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
50 50 <meta name="robots" content="index, nofollow" />
51 51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
52 52
53 53 <title>Mercurial repositories index</title>
54 54 </head>
55 55 <body>
56 56
57 57 <div class="container">
58 58 <div class="menu">
59 59 <a href="http://mercurial.selenic.com/">
60 60 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
61 61 </div>
62 62 <div class="main">
63 63 <h2>Mercurial Repositories</h2>
64 64
65 65 <table class="bigtable">
66 66 <tr>
67 67 <th><a href="?sort=-name">Name</a></th>
68 68 <th><a href="?sort=description">Description</a></th>
69 69 <th><a href="?sort=contact">Contact</a></th>
70 70 <th><a href="?sort=lastchange">Last change</a></th>
71 71 <th>&nbsp;</th>
72 72 </tr>
73 73
74 74 <tr class="parity0">
75 75 <td><a href="/b/?style=paper">b</a></td>
76 76 <td>unknown</td>
77 77 <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>
78 78 <td class="age">seconds ago</td>
79 79 <td class="indexlinks"></td>
80 80 </tr>
81 81
82 82 <tr class="parity1">
83 83 <td><a href="/coll/a/?style=paper">coll/a</a></td>
84 84 <td>unknown</td>
85 85 <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>
86 86 <td class="age">seconds ago</td>
87 87 <td class="indexlinks"></td>
88 88 </tr>
89 89
90 90 <tr class="parity0">
91 91 <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td>
92 92 <td>unknown</td>
93 93 <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>
94 94 <td class="age">seconds ago</td>
95 95 <td class="indexlinks"></td>
96 96 </tr>
97 97
98 98 <tr class="parity1">
99 99 <td><a href="/coll/b/?style=paper">coll/b</a></td>
100 100 <td>unknown</td>
101 101 <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>
102 102 <td class="age">seconds ago</td>
103 103 <td class="indexlinks"></td>
104 104 </tr>
105 105
106 106 <tr class="parity0">
107 107 <td><a href="/coll/c/?style=paper">coll/c</a></td>
108 108 <td>unknown</td>
109 109 <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>
110 110 <td class="age">seconds ago</td>
111 111 <td class="indexlinks"></td>
112 112 </tr>
113 113
114 114 <tr class="parity1">
115 115 <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td>
116 116 <td>unknown</td>
117 117 <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>
118 118 <td class="age">seconds ago</td>
119 119 <td class="indexlinks"></td>
120 120 </tr>
121 121
122 122 <tr class="parity0">
123 123 <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td>
124 124 <td>unknown</td>
125 125 <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>
126 126 <td class="age">seconds ago</td>
127 127 <td class="indexlinks"></td>
128 128 </tr>
129 129
130 130 <tr class="parity1">
131 131 <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td>
132 132 <td>unknown</td>
133 133 <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>
134 134 <td class="age">seconds ago</td>
135 135 <td class="indexlinks"></td>
136 136 </tr>
137 137
138 138 <tr class="parity0">
139 139 <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td>
140 140 <td>unknown</td>
141 141 <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>
142 142 <td class="age">seconds ago</td>
143 143 <td class="indexlinks"></td>
144 144 </tr>
145 145
146 146 <tr class="parity1">
147 147 <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td>
148 148 <td>unknown</td>
149 149 <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>
150 150 <td class="age">seconds ago</td>
151 151 <td class="indexlinks"></td>
152 152 </tr>
153 153
154 154 <tr class="parity0">
155 155 <td><a href="/t/a/?style=paper">t/a</a></td>
156 156 <td>unknown</td>
157 157 <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>
158 158 <td class="age">seconds ago</td>
159 159 <td class="indexlinks"></td>
160 160 </tr>
161 161
162 162 </table>
163 163 </div>
164 164 </div>
165 165
166 166
167 167 </body>
168 168 </html>
169 169
170 170 200 Script output follows
171 171
172 172
173 173 /t/a/
174 174
175 175 200 Script output follows
176 176
177 177
178 178 /t/a/
179 179
180 180 200 Script output follows
181 181
182 182 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
183 183 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
184 184 <head>
185 185 <link rel="icon" href="/static/hgicon.png" type="image/png" />
186 186 <meta name="robots" content="index, nofollow" />
187 187 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
188 188
189 189 <title>Mercurial repositories index</title>
190 190 </head>
191 191 <body>
192 192
193 193 <div class="container">
194 194 <div class="menu">
195 195 <a href="http://mercurial.selenic.com/">
196 196 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
197 197 </div>
198 198 <div class="main">
199 199 <h2>Mercurial Repositories</h2>
200 200
201 201 <table class="bigtable">
202 202 <tr>
203 203 <th><a href="?sort=-name">Name</a></th>
204 204 <th><a href="?sort=description">Description</a></th>
205 205 <th><a href="?sort=contact">Contact</a></th>
206 206 <th><a href="?sort=lastchange">Last change</a></th>
207 207 <th>&nbsp;</th>
208 208 </tr>
209 209
210 210 <tr class="parity0">
211 211 <td><a href="/t/a/?style=paper">a</a></td>
212 212 <td>unknown</td>
213 213 <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>
214 214 <td class="age">seconds ago</td>
215 215 <td class="indexlinks"></td>
216 216 </tr>
217 217
218 218 </table>
219 219 </div>
220 220 </div>
221 221
222 222
223 223 </body>
224 224 </html>
225 225
226 226 200 Script output follows
227 227
228 228 <?xml version="1.0" encoding="ascii"?>
229 229 <feed xmlns="http://127.0.0.1/2005/Atom">
230 230 <!-- Changelog -->
231 231 <id>http://127.0.0.1/t/a/</id>
232 232 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
233 233 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
234 234 <title>t/a Changelog</title>
235 235 <updated>1970-01-01T00:00:01+00:00</updated>
236 236
237 237 <entry>
238 238 <title>a</title>
239 239 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
240 240 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
241 241 <author>
242 242 <name>test</name>
243 243 <email>&#116;&#101;&#115;&#116;</email>
244 244 </author>
245 245 <updated>1970-01-01T00:00:01+00:00</updated>
246 246 <published>1970-01-01T00:00:01+00:00</published>
247 247 <content type="xhtml">
248 248 <div xmlns="http://127.0.0.1/1999/xhtml">
249 249 <pre xml:space="preserve">a</pre>
250 250 </div>
251 251 </content>
252 252 </entry>
253 253
254 254 </feed>
255 255 200 Script output follows
256 256
257 257 <?xml version="1.0" encoding="ascii"?>
258 258 <feed xmlns="http://127.0.0.1/2005/Atom">
259 259 <!-- Changelog -->
260 260 <id>http://127.0.0.1/t/a/</id>
261 261 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
262 262 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
263 263 <title>t/a Changelog</title>
264 264 <updated>1970-01-01T00:00:01+00:00</updated>
265 265
266 266 <entry>
267 267 <title>a</title>
268 268 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
269 269 <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
270 270 <author>
271 271 <name>test</name>
272 272 <email>&#116;&#101;&#115;&#116;</email>
273 273 </author>
274 274 <updated>1970-01-01T00:00:01+00:00</updated>
275 275 <published>1970-01-01T00:00:01+00:00</published>
276 276 <content type="xhtml">
277 277 <div xmlns="http://127.0.0.1/1999/xhtml">
278 278 <pre xml:space="preserve">a</pre>
279 279 </div>
280 280 </content>
281 281 </entry>
282 282
283 283 </feed>
284 284 200 Script output follows
285 285
286 286 a
287 287 200 Script output follows
288 288
289 289
290 290 /coll/a/
291 291 /coll/a/.hg/patches/
292 292 /coll/b/
293 293 /coll/c/
294 294
295 295 200 Script output follows
296 296
297 297 a
298 298 200 Script output follows
299 299
300 300
301 301 /rcoll/a/
302 302 /rcoll/a/.hg/patches/
303 303 /rcoll/b/
304 304 /rcoll/b/d/
305 305 /rcoll/c/
306 306
307 307 200 Script output follows
308 308
309 309 d
310 310 % collections: should succeed
311 311 200 Script output follows
312 312
313 313
314 /a/
315 /a/.hg/patches/
316 /b/
317 /c/
314 http://hg.example.com:8080/a/
315 http://hg.example.com:8080/a/.hg/patches/
316 http://hg.example.com:8080/b/
317 http://hg.example.com:8080/c/
318 318
319 319 200 Script output follows
320 320
321 321 a
322 322 200 Script output follows
323 323
324 324 b
325 325 200 Script output follows
326 326
327 327 c
328 328 % paths errors 1
329 329 % paths errors 2
330 330 % collections errors
General Comments 0
You need to be logged in to leave comments. Login now