##// END OF EJS Templates
hgweb: refresh repository using URL not path (issue4323)...
Gregory Szorc -
r22223:c39d404f default
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1 #require serve
2
3 $ hg init server
4 $ cd server
5 $ cat >> .hg/hgrc << EOF
6 > [extensions]
7 > strip=
8 > EOF
9
10 $ echo 1 > foo
11 $ hg commit -A -m 'first'
12 adding foo
13 $ echo 2 > bar
14 $ hg commit -A -m 'second'
15 adding bar
16
17 Produce a bundle to use
18
19 $ hg strip -r 1
20 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 saved backup bundle to $TESTTMP/server/.hg/strip-backup/ed602e697e0f-backup.hg (glob)
22
23 Serve from a bundle file
24
25 $ hg serve -R .hg/strip-backup/ed602e697e0f-backup.hg -d -p $HGPORT --pid-file=hg.pid
26 $ cat hg.pid >> $DAEMON_PIDS
27
28 Ensure we're serving from the bundle
29
30 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
31 200 Script output follows
32
33
34 -rw-r--r-- 2 bar
35 -rw-r--r-- 2 foo
36
37
@@ -1,396 +1,396 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 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
10 10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 11 from mercurial.templatefilters import websub
12 12 from mercurial.i18n import _
13 13 from common import get_stat, ErrorResponse, permhooks, caching
14 14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
15 15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 16 from request import wsgirequest
17 17 import webcommands, protocol, webutil
18 18
19 19 perms = {
20 20 'changegroup': 'pull',
21 21 'changegroupsubset': 'pull',
22 22 'getbundle': 'pull',
23 23 'stream_out': 'pull',
24 24 'listkeys': 'pull',
25 25 'unbundle': 'push',
26 26 'pushkey': 'push',
27 27 }
28 28
29 29 def makebreadcrumb(url, prefix=''):
30 30 '''Return a 'URL breadcrumb' list
31 31
32 32 A 'URL breadcrumb' is a list of URL-name pairs,
33 33 corresponding to each of the path items on a URL.
34 34 This can be used to create path navigation entries.
35 35 '''
36 36 if url.endswith('/'):
37 37 url = url[:-1]
38 38 if prefix:
39 39 url = '/' + prefix + url
40 40 relpath = url
41 41 if relpath.startswith('/'):
42 42 relpath = relpath[1:]
43 43
44 44 breadcrumb = []
45 45 urlel = url
46 46 pathitems = [''] + relpath.split('/')
47 47 for pathel in reversed(pathitems):
48 48 if not pathel or not urlel:
49 49 break
50 50 breadcrumb.append({'url': urlel, 'name': pathel})
51 51 urlel = os.path.dirname(urlel)
52 52 return reversed(breadcrumb)
53 53
54 54
55 55 class hgweb(object):
56 56 def __init__(self, repo, name=None, baseui=None):
57 57 if isinstance(repo, str):
58 58 if baseui:
59 59 u = baseui.copy()
60 60 else:
61 61 u = ui.ui()
62 62 r = hg.repository(u, repo)
63 63 else:
64 64 # we trust caller to give us a private copy
65 65 r = repo
66 66
67 67 r = self._getview(r)
68 68 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
69 69 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
70 70 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
71 71 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
72 72 self.repo = r
73 73 hook.redirect(True)
74 74 self.mtime = -1
75 75 self.size = -1
76 76 self.reponame = name
77 77 self.archives = 'zip', 'gz', 'bz2'
78 78 self.stripecount = 1
79 79 # a repo owner may set web.templates in .hg/hgrc to get any file
80 80 # readable by the user running the CGI script
81 81 self.templatepath = self.config('web', 'templates')
82 82 self.websubtable = self.loadwebsub()
83 83
84 84 # The CGI scripts are often run by a user different from the repo owner.
85 85 # Trust the settings from the .hg/hgrc files by default.
86 86 def config(self, section, name, default=None, untrusted=True):
87 87 return self.repo.ui.config(section, name, default,
88 88 untrusted=untrusted)
89 89
90 90 def configbool(self, section, name, default=False, untrusted=True):
91 91 return self.repo.ui.configbool(section, name, default,
92 92 untrusted=untrusted)
93 93
94 94 def configlist(self, section, name, default=None, untrusted=True):
95 95 return self.repo.ui.configlist(section, name, default,
96 96 untrusted=untrusted)
97 97
98 98 def _getview(self, repo):
99 99 viewconfig = repo.ui.config('web', 'view', 'served',
100 100 untrusted=True)
101 101 if viewconfig == 'all':
102 102 return repo.unfiltered()
103 103 elif viewconfig in repoview.filtertable:
104 104 return repo.filtered(viewconfig)
105 105 else:
106 106 return repo.filtered('served')
107 107
108 108 def refresh(self, request=None):
109 109 st = get_stat(self.repo.spath)
110 110 # compare changelog size in addition to mtime to catch
111 111 # rollbacks made less than a second ago
112 112 if st.st_mtime != self.mtime or st.st_size != self.size:
113 r = hg.repository(self.repo.baseui, self.repo.root)
113 r = hg.repository(self.repo.baseui, self.repo.url())
114 114 self.repo = self._getview(r)
115 115 self.maxchanges = int(self.config("web", "maxchanges", 10))
116 116 self.stripecount = int(self.config("web", "stripes", 1))
117 117 self.maxshortchanges = int(self.config("web", "maxshortchanges",
118 118 60))
119 119 self.maxfiles = int(self.config("web", "maxfiles", 10))
120 120 self.allowpull = self.configbool("web", "allowpull", True)
121 121 encoding.encoding = self.config("web", "encoding",
122 122 encoding.encoding)
123 123 # update these last to avoid threads seeing empty settings
124 124 self.mtime = st.st_mtime
125 125 self.size = st.st_size
126 126 if request:
127 127 self.repo.ui.environ = request.env
128 128
129 129 def run(self):
130 130 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
131 131 raise RuntimeError("This function is only intended to be "
132 132 "called while running as a CGI script.")
133 133 import mercurial.hgweb.wsgicgi as wsgicgi
134 134 wsgicgi.launch(self)
135 135
136 136 def __call__(self, env, respond):
137 137 req = wsgirequest(env, respond)
138 138 return self.run_wsgi(req)
139 139
140 140 def run_wsgi(self, req):
141 141
142 142 self.refresh(req)
143 143
144 144 # work with CGI variables to create coherent structure
145 145 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
146 146
147 147 req.url = req.env['SCRIPT_NAME']
148 148 if not req.url.endswith('/'):
149 149 req.url += '/'
150 150 if 'REPO_NAME' in req.env:
151 151 req.url += req.env['REPO_NAME'] + '/'
152 152
153 153 if 'PATH_INFO' in req.env:
154 154 parts = req.env['PATH_INFO'].strip('/').split('/')
155 155 repo_parts = req.env.get('REPO_NAME', '').split('/')
156 156 if parts[:len(repo_parts)] == repo_parts:
157 157 parts = parts[len(repo_parts):]
158 158 query = '/'.join(parts)
159 159 else:
160 160 query = req.env['QUERY_STRING'].split('&', 1)[0]
161 161 query = query.split(';', 1)[0]
162 162
163 163 # process this if it's a protocol request
164 164 # protocol bits don't need to create any URLs
165 165 # and the clients always use the old URL structure
166 166
167 167 cmd = req.form.get('cmd', [''])[0]
168 168 if protocol.iscmd(cmd):
169 169 try:
170 170 if query:
171 171 raise ErrorResponse(HTTP_NOT_FOUND)
172 172 if cmd in perms:
173 173 self.check_perm(req, perms[cmd])
174 174 return protocol.call(self.repo, req, cmd)
175 175 except ErrorResponse, inst:
176 176 # A client that sends unbundle without 100-continue will
177 177 # break if we respond early.
178 178 if (cmd == 'unbundle' and
179 179 (req.env.get('HTTP_EXPECT',
180 180 '').lower() != '100-continue') or
181 181 req.env.get('X-HgHttp2', '')):
182 182 req.drain()
183 183 else:
184 184 req.headers.append(('Connection', 'Close'))
185 185 req.respond(inst, protocol.HGTYPE,
186 186 body='0\n%s\n' % inst.message)
187 187 return ''
188 188
189 189 # translate user-visible url structure to internal structure
190 190
191 191 args = query.split('/', 2)
192 192 if 'cmd' not in req.form and args and args[0]:
193 193
194 194 cmd = args.pop(0)
195 195 style = cmd.rfind('-')
196 196 if style != -1:
197 197 req.form['style'] = [cmd[:style]]
198 198 cmd = cmd[style + 1:]
199 199
200 200 # avoid accepting e.g. style parameter as command
201 201 if util.safehasattr(webcommands, cmd):
202 202 req.form['cmd'] = [cmd]
203 203 else:
204 204 cmd = ''
205 205
206 206 if cmd == 'static':
207 207 req.form['file'] = ['/'.join(args)]
208 208 else:
209 209 if args and args[0]:
210 210 node = args.pop(0)
211 211 req.form['node'] = [node]
212 212 if args:
213 213 req.form['file'] = args
214 214
215 215 ua = req.env.get('HTTP_USER_AGENT', '')
216 216 if cmd == 'rev' and 'mercurial' in ua:
217 217 req.form['style'] = ['raw']
218 218
219 219 if cmd == 'archive':
220 220 fn = req.form['node'][0]
221 221 for type_, spec in self.archive_specs.iteritems():
222 222 ext = spec[2]
223 223 if fn.endswith(ext):
224 224 req.form['node'] = [fn[:-len(ext)]]
225 225 req.form['type'] = [type_]
226 226
227 227 # process the web interface request
228 228
229 229 try:
230 230 tmpl = self.templater(req)
231 231 ctype = tmpl('mimetype', encoding=encoding.encoding)
232 232 ctype = templater.stringify(ctype)
233 233
234 234 # check read permissions non-static content
235 235 if cmd != 'static':
236 236 self.check_perm(req, None)
237 237
238 238 if cmd == '':
239 239 req.form['cmd'] = [tmpl.cache['default']]
240 240 cmd = req.form['cmd'][0]
241 241
242 242 if self.configbool('web', 'cache', True):
243 243 caching(self, req) # sets ETag header or raises NOT_MODIFIED
244 244 if cmd not in webcommands.__all__:
245 245 msg = 'no such method: %s' % cmd
246 246 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
247 247 elif cmd == 'file' and 'raw' in req.form.get('style', []):
248 248 self.ctype = ctype
249 249 content = webcommands.rawfile(self, req, tmpl)
250 250 else:
251 251 content = getattr(webcommands, cmd)(self, req, tmpl)
252 252 req.respond(HTTP_OK, ctype)
253 253
254 254 return content
255 255
256 256 except (error.LookupError, error.RepoLookupError), err:
257 257 req.respond(HTTP_NOT_FOUND, ctype)
258 258 msg = str(err)
259 259 if (util.safehasattr(err, 'name') and
260 260 not isinstance(err, error.ManifestLookupError)):
261 261 msg = 'revision not found: %s' % err.name
262 262 return tmpl('error', error=msg)
263 263 except (error.RepoError, error.RevlogError), inst:
264 264 req.respond(HTTP_SERVER_ERROR, ctype)
265 265 return tmpl('error', error=str(inst))
266 266 except ErrorResponse, inst:
267 267 req.respond(inst, ctype)
268 268 if inst.code == HTTP_NOT_MODIFIED:
269 269 # Not allowed to return a body on a 304
270 270 return ['']
271 271 return tmpl('error', error=inst.message)
272 272
273 273 def loadwebsub(self):
274 274 websubtable = []
275 275 websubdefs = self.repo.ui.configitems('websub')
276 276 # we must maintain interhg backwards compatibility
277 277 websubdefs += self.repo.ui.configitems('interhg')
278 278 for key, pattern in websubdefs:
279 279 # grab the delimiter from the character after the "s"
280 280 unesc = pattern[1]
281 281 delim = re.escape(unesc)
282 282
283 283 # identify portions of the pattern, taking care to avoid escaped
284 284 # delimiters. the replace format and flags are optional, but
285 285 # delimiters are required.
286 286 match = re.match(
287 287 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
288 288 % (delim, delim, delim), pattern)
289 289 if not match:
290 290 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
291 291 % (key, pattern))
292 292 continue
293 293
294 294 # we need to unescape the delimiter for regexp and format
295 295 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
296 296 regexp = delim_re.sub(unesc, match.group(1))
297 297 format = delim_re.sub(unesc, match.group(2))
298 298
299 299 # the pattern allows for 6 regexp flags, so set them if necessary
300 300 flagin = match.group(3)
301 301 flags = 0
302 302 if flagin:
303 303 for flag in flagin.upper():
304 304 flags |= re.__dict__[flag]
305 305
306 306 try:
307 307 regexp = re.compile(regexp, flags)
308 308 websubtable.append((regexp, format))
309 309 except re.error:
310 310 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
311 311 % (key, regexp))
312 312 return websubtable
313 313
314 314 def templater(self, req):
315 315
316 316 # determine scheme, port and server name
317 317 # this is needed to create absolute urls
318 318
319 319 proto = req.env.get('wsgi.url_scheme')
320 320 if proto == 'https':
321 321 proto = 'https'
322 322 default_port = "443"
323 323 else:
324 324 proto = 'http'
325 325 default_port = "80"
326 326
327 327 port = req.env["SERVER_PORT"]
328 328 port = port != default_port and (":" + port) or ""
329 329 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
330 330 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
331 331 logoimg = self.config("web", "logoimg", "hglogo.png")
332 332 staticurl = self.config("web", "staticurl") or req.url + 'static/'
333 333 if not staticurl.endswith('/'):
334 334 staticurl += '/'
335 335
336 336 # some functions for the templater
337 337
338 338 def motd(**map):
339 339 yield self.config("web", "motd", "")
340 340
341 341 # figure out which style to use
342 342
343 343 vars = {}
344 344 styles = (
345 345 req.form.get('style', [None])[0],
346 346 self.config('web', 'style'),
347 347 'paper',
348 348 )
349 349 style, mapfile = templater.stylemap(styles, self.templatepath)
350 350 if style == styles[0]:
351 351 vars['style'] = style
352 352
353 353 start = req.url[-1] == '?' and '&' or '?'
354 354 sessionvars = webutil.sessionvars(vars, start)
355 355
356 356 if not self.reponame:
357 357 self.reponame = (self.config("web", "name")
358 358 or req.env.get('REPO_NAME')
359 359 or req.url.strip('/') or self.repo.root)
360 360
361 361 def websubfilter(text):
362 362 return websub(text, self.websubtable)
363 363
364 364 # create the templater
365 365
366 366 tmpl = templater.templater(mapfile,
367 367 filters={"websub": websubfilter},
368 368 defaults={"url": req.url,
369 369 "logourl": logourl,
370 370 "logoimg": logoimg,
371 371 "staticurl": staticurl,
372 372 "urlbase": urlbase,
373 373 "repo": self.reponame,
374 374 "encoding": encoding.encoding,
375 375 "motd": motd,
376 376 "sessionvars": sessionvars,
377 377 "pathdef": makebreadcrumb(req.url),
378 378 "style": style,
379 379 })
380 380 return tmpl
381 381
382 382 def archivelist(self, nodeid):
383 383 allowed = self.configlist("web", "allow_archive")
384 384 for i, spec in self.archive_specs.iteritems():
385 385 if i in allowed or self.configbool("web", "allow" + i):
386 386 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
387 387
388 388 archive_specs = {
389 389 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
390 390 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
391 391 'zip': ('application/zip', 'zip', '.zip', None),
392 392 }
393 393
394 394 def check_perm(self, req, op):
395 395 for permhook in permhooks:
396 396 permhook(self, req, op)
General Comments 0
You need to be logged in to leave comments. Login now