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