##// END OF EJS Templates
hgweb: another fix for the help termwidth bug
Matt Mackall -
r12696:ef969e58 default
parent child Browse files
Show More
@@ -1,286 +1,286 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
10 10 from mercurial import ui, hg, hook, error, encoding, templater
11 11 from common import get_mtime, ErrorResponse, permhooks, caching
12 12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 13 from request import wsgirequest
14 14 import webcommands, protocol, webutil
15 15
16 16 perms = {
17 17 'changegroup': 'pull',
18 18 'changegroupsubset': 'pull',
19 19 'stream_out': 'pull',
20 20 'listkeys': 'pull',
21 21 'unbundle': 'push',
22 22 'pushkey': 'push',
23 23 }
24 24
25 25 class hgweb(object):
26 26 def __init__(self, repo, name=None, baseui=None):
27 27 if isinstance(repo, str):
28 28 if baseui:
29 29 u = baseui.copy()
30 30 else:
31 u = webutil.wsgiui()
31 u = ui.ui()
32 32 self.repo = hg.repository(u, repo)
33 33 else:
34 34 self.repo = repo
35 35
36 36 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
37 37 self.repo.ui.setconfig('ui', 'interactive', 'off')
38 38 hook.redirect(True)
39 39 self.mtime = -1
40 40 self.reponame = name
41 41 self.archives = 'zip', 'gz', 'bz2'
42 42 self.stripecount = 1
43 43 # a repo owner may set web.templates in .hg/hgrc to get any file
44 44 # readable by the user running the CGI script
45 45 self.templatepath = self.config('web', 'templates')
46 46
47 47 # The CGI scripts are often run by a user different from the repo owner.
48 48 # Trust the settings from the .hg/hgrc files by default.
49 49 def config(self, section, name, default=None, untrusted=True):
50 50 return self.repo.ui.config(section, name, default,
51 51 untrusted=untrusted)
52 52
53 53 def configbool(self, section, name, default=False, untrusted=True):
54 54 return self.repo.ui.configbool(section, name, default,
55 55 untrusted=untrusted)
56 56
57 57 def configlist(self, section, name, default=None, untrusted=True):
58 58 return self.repo.ui.configlist(section, name, default,
59 59 untrusted=untrusted)
60 60
61 61 def refresh(self, request=None):
62 62 if request:
63 63 self.repo.ui.environ = request.env
64 64 mtime = get_mtime(self.repo.spath)
65 65 if mtime != self.mtime:
66 66 self.mtime = mtime
67 67 self.repo = hg.repository(self.repo.ui, self.repo.root)
68 68 self.maxchanges = int(self.config("web", "maxchanges", 10))
69 69 self.stripecount = int(self.config("web", "stripes", 1))
70 70 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
71 71 self.maxfiles = int(self.config("web", "maxfiles", 10))
72 72 self.allowpull = self.configbool("web", "allowpull", True)
73 73 encoding.encoding = self.config("web", "encoding",
74 74 encoding.encoding)
75 75
76 76 def run(self):
77 77 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
78 78 raise RuntimeError("This function is only intended to be "
79 79 "called while running as a CGI script.")
80 80 import mercurial.hgweb.wsgicgi as wsgicgi
81 81 wsgicgi.launch(self)
82 82
83 83 def __call__(self, env, respond):
84 84 req = wsgirequest(env, respond)
85 85 return self.run_wsgi(req)
86 86
87 87 def run_wsgi(self, req):
88 88
89 89 self.refresh(req)
90 90
91 91 # work with CGI variables to create coherent structure
92 92 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
93 93
94 94 req.url = req.env['SCRIPT_NAME']
95 95 if not req.url.endswith('/'):
96 96 req.url += '/'
97 97 if 'REPO_NAME' in req.env:
98 98 req.url += req.env['REPO_NAME'] + '/'
99 99
100 100 if 'PATH_INFO' in req.env:
101 101 parts = req.env['PATH_INFO'].strip('/').split('/')
102 102 repo_parts = req.env.get('REPO_NAME', '').split('/')
103 103 if parts[:len(repo_parts)] == repo_parts:
104 104 parts = parts[len(repo_parts):]
105 105 query = '/'.join(parts)
106 106 else:
107 107 query = req.env['QUERY_STRING'].split('&', 1)[0]
108 108 query = query.split(';', 1)[0]
109 109
110 110 # process this if it's a protocol request
111 111 # protocol bits don't need to create any URLs
112 112 # and the clients always use the old URL structure
113 113
114 114 cmd = req.form.get('cmd', [''])[0]
115 115 if protocol.iscmd(cmd):
116 116 if query:
117 117 raise ErrorResponse(HTTP_NOT_FOUND)
118 118 if cmd in perms:
119 119 try:
120 120 self.check_perm(req, perms[cmd])
121 121 except ErrorResponse, inst:
122 122 if cmd == 'unbundle':
123 123 req.drain()
124 124 req.respond(inst, protocol.HGTYPE)
125 125 return '0\n%s\n' % inst.message
126 126 return protocol.call(self.repo, req, cmd)
127 127
128 128 # translate user-visible url structure to internal structure
129 129
130 130 args = query.split('/', 2)
131 131 if 'cmd' not in req.form and args and args[0]:
132 132
133 133 cmd = args.pop(0)
134 134 style = cmd.rfind('-')
135 135 if style != -1:
136 136 req.form['style'] = [cmd[:style]]
137 137 cmd = cmd[style + 1:]
138 138
139 139 # avoid accepting e.g. style parameter as command
140 140 if hasattr(webcommands, cmd):
141 141 req.form['cmd'] = [cmd]
142 142 else:
143 143 cmd = ''
144 144
145 145 if cmd == 'static':
146 146 req.form['file'] = ['/'.join(args)]
147 147 else:
148 148 if args and args[0]:
149 149 node = args.pop(0)
150 150 req.form['node'] = [node]
151 151 if args:
152 152 req.form['file'] = args
153 153
154 154 ua = req.env.get('HTTP_USER_AGENT', '')
155 155 if cmd == 'rev' and 'mercurial' in ua:
156 156 req.form['style'] = ['raw']
157 157
158 158 if cmd == 'archive':
159 159 fn = req.form['node'][0]
160 160 for type_, spec in self.archive_specs.iteritems():
161 161 ext = spec[2]
162 162 if fn.endswith(ext):
163 163 req.form['node'] = [fn[:-len(ext)]]
164 164 req.form['type'] = [type_]
165 165
166 166 # process the web interface request
167 167
168 168 try:
169 169 tmpl = self.templater(req)
170 170 ctype = tmpl('mimetype', encoding=encoding.encoding)
171 171 ctype = templater.stringify(ctype)
172 172
173 173 # check read permissions non-static content
174 174 if cmd != 'static':
175 175 self.check_perm(req, None)
176 176
177 177 if cmd == '':
178 178 req.form['cmd'] = [tmpl.cache['default']]
179 179 cmd = req.form['cmd'][0]
180 180
181 181 caching(self, req) # sets ETag header or raises NOT_MODIFIED
182 182 if cmd not in webcommands.__all__:
183 183 msg = 'no such method: %s' % cmd
184 184 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
185 185 elif cmd == 'file' and 'raw' in req.form.get('style', []):
186 186 self.ctype = ctype
187 187 content = webcommands.rawfile(self, req, tmpl)
188 188 else:
189 189 content = getattr(webcommands, cmd)(self, req, tmpl)
190 190 req.respond(HTTP_OK, ctype)
191 191
192 192 return content
193 193
194 194 except error.LookupError, err:
195 195 req.respond(HTTP_NOT_FOUND, ctype)
196 196 msg = str(err)
197 197 if 'manifest' not in msg:
198 198 msg = 'revision not found: %s' % err.name
199 199 return tmpl('error', error=msg)
200 200 except (error.RepoError, error.RevlogError), inst:
201 201 req.respond(HTTP_SERVER_ERROR, ctype)
202 202 return tmpl('error', error=str(inst))
203 203 except ErrorResponse, inst:
204 204 req.respond(inst, ctype)
205 205 return tmpl('error', error=inst.message)
206 206
207 207 def templater(self, req):
208 208
209 209 # determine scheme, port and server name
210 210 # this is needed to create absolute urls
211 211
212 212 proto = req.env.get('wsgi.url_scheme')
213 213 if proto == 'https':
214 214 proto = 'https'
215 215 default_port = "443"
216 216 else:
217 217 proto = 'http'
218 218 default_port = "80"
219 219
220 220 port = req.env["SERVER_PORT"]
221 221 port = port != default_port and (":" + port) or ""
222 222 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
223 223 staticurl = self.config("web", "staticurl") or req.url + 'static/'
224 224 if not staticurl.endswith('/'):
225 225 staticurl += '/'
226 226
227 227 # some functions for the templater
228 228
229 229 def header(**map):
230 230 yield tmpl('header', encoding=encoding.encoding, **map)
231 231
232 232 def footer(**map):
233 233 yield tmpl("footer", **map)
234 234
235 235 def motd(**map):
236 236 yield self.config("web", "motd", "")
237 237
238 238 # figure out which style to use
239 239
240 240 vars = {}
241 241 styles = (
242 242 req.form.get('style', [None])[0],
243 243 self.config('web', 'style'),
244 244 'paper',
245 245 )
246 246 style, mapfile = templater.stylemap(styles, self.templatepath)
247 247 if style == styles[0]:
248 248 vars['style'] = style
249 249
250 250 start = req.url[-1] == '?' and '&' or '?'
251 251 sessionvars = webutil.sessionvars(vars, start)
252 252
253 253 if not self.reponame:
254 254 self.reponame = (self.config("web", "name")
255 255 or req.env.get('REPO_NAME')
256 256 or req.url.strip('/') or self.repo.root)
257 257
258 258 # create the templater
259 259
260 260 tmpl = templater.templater(mapfile,
261 261 defaults={"url": req.url,
262 262 "staticurl": staticurl,
263 263 "urlbase": urlbase,
264 264 "repo": self.reponame,
265 265 "header": header,
266 266 "footer": footer,
267 267 "motd": motd,
268 268 "sessionvars": sessionvars
269 269 })
270 270 return tmpl
271 271
272 272 def archivelist(self, nodeid):
273 273 allowed = self.configlist("web", "allow_archive")
274 274 for i, spec in self.archive_specs.iteritems():
275 275 if i in allowed or self.configbool("web", "allow" + i):
276 276 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
277 277
278 278 archive_specs = {
279 279 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
280 280 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
281 281 'zip': ('application/zip', 'zip', '.zip', None),
282 282 }
283 283
284 284 def check_perm(self, req, op):
285 285 for hook in permhooks:
286 286 hook(self, req, op)
@@ -1,356 +1,356 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re, time, urlparse
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater
12 12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 def findrepos(paths):
23 23 repos = []
24 24 for prefix, root in cleannames(paths):
25 25 roothead, roottail = os.path.split(root)
26 26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 27 # mounted as foo/subrepo
28 28 # and "foo = /bar/**" also recurses into the subdirectories,
29 29 # remember to use it without working dir.
30 30 try:
31 31 recurse = {'*': False, '**': True}[roottail]
32 32 except KeyError:
33 33 repos.append((prefix, root))
34 34 continue
35 35 roothead = os.path.normpath(os.path.abspath(roothead))
36 36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 37 path = os.path.normpath(path)
38 38 name = util.pconvert(path[len(roothead):]).strip('/')
39 39 if prefix:
40 40 name = prefix + '/' + name
41 41 repos.append((name, path))
42 42 return repos
43 43
44 44 class hgwebdir(object):
45 45 refreshinterval = 20
46 46
47 47 def __init__(self, conf, baseui=None):
48 48 self.conf = conf
49 49 self.baseui = baseui
50 50 self.lastrefresh = 0
51 51 self.motd = None
52 52 self.refresh()
53 53
54 54 def refresh(self):
55 55 if self.lastrefresh + self.refreshinterval > time.time():
56 56 return
57 57
58 58 if self.baseui:
59 59 u = self.baseui.copy()
60 60 else:
61 u = webutil.wsgiui()
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 except error.RepoError:
237 237 u.warn(_('error accessing repository at %s\n') % path)
238 238 continue
239 239 try:
240 240 d = (get_mtime(r.spath), util.makedate()[1])
241 241 except OSError:
242 242 continue
243 243
244 244 contact = get_contact(get)
245 245 description = get("web", "description", "")
246 246 name = get("web", "name", name)
247 247 row = dict(contact=contact or "unknown",
248 248 contact_sort=contact.upper() or "unknown",
249 249 name=name,
250 250 name_sort=name,
251 251 url=url,
252 252 description=description or "unknown",
253 253 description_sort=description.upper() or "unknown",
254 254 lastchange=d,
255 255 lastchange_sort=d[1]-d[0],
256 256 archives=archivelist(u, "tip", url))
257 257 yield row
258 258
259 259 sortdefault = None, False
260 260 def entries(sortcolumn="", descending=False, subdir="", **map):
261 261 rows = rawentries(subdir=subdir, **map)
262 262
263 263 if sortcolumn and sortdefault != (sortcolumn, descending):
264 264 sortkey = '%s_sort' % sortcolumn
265 265 rows = sorted(rows, key=lambda x: x[sortkey],
266 266 reverse=descending)
267 267 for row, parity in zip(rows, paritygen(self.stripecount)):
268 268 row['parity'] = parity
269 269 yield row
270 270
271 271 self.refresh()
272 272 sortable = ["name", "description", "contact", "lastchange"]
273 273 sortcolumn, descending = sortdefault
274 274 if 'sort' in req.form:
275 275 sortcolumn = req.form['sort'][0]
276 276 descending = sortcolumn.startswith('-')
277 277 if descending:
278 278 sortcolumn = sortcolumn[1:]
279 279 if sortcolumn not in sortable:
280 280 sortcolumn = ""
281 281
282 282 sort = [("sort_%s" % column,
283 283 "%s%s" % ((not descending and column == sortcolumn)
284 284 and "-" or "", column))
285 285 for column in sortable]
286 286
287 287 self.refresh()
288 288 self.updatereqenv(req.env)
289 289
290 290 return tmpl("index", entries=entries, subdir=subdir,
291 291 sortcolumn=sortcolumn, descending=descending,
292 292 **dict(sort))
293 293
294 294 def templater(self, req):
295 295
296 296 def header(**map):
297 297 yield tmpl('header', encoding=encoding.encoding, **map)
298 298
299 299 def footer(**map):
300 300 yield tmpl("footer", **map)
301 301
302 302 def motd(**map):
303 303 if self.motd is not None:
304 304 yield self.motd
305 305 else:
306 306 yield config('web', 'motd', '')
307 307
308 308 def config(section, name, default=None, untrusted=True):
309 309 return self.ui.config(section, name, default, untrusted)
310 310
311 311 self.updatereqenv(req.env)
312 312
313 313 url = req.env.get('SCRIPT_NAME', '')
314 314 if not url.endswith('/'):
315 315 url += '/'
316 316
317 317 vars = {}
318 318 styles = (
319 319 req.form.get('style', [None])[0],
320 320 config('web', 'style'),
321 321 'paper'
322 322 )
323 323 style, mapfile = templater.stylemap(styles, self.templatepath)
324 324 if style == styles[0]:
325 325 vars['style'] = style
326 326
327 327 start = url[-1] == '?' and '&' or '?'
328 328 sessionvars = webutil.sessionvars(vars, start)
329 329 staticurl = config('web', 'staticurl') or url + 'static/'
330 330 if not staticurl.endswith('/'):
331 331 staticurl += '/'
332 332
333 333 tmpl = templater.templater(mapfile,
334 334 defaults={"header": header,
335 335 "footer": footer,
336 336 "motd": motd,
337 337 "url": url,
338 338 "staticurl": staticurl,
339 339 "sessionvars": sessionvars})
340 340 return tmpl
341 341
342 342 def updatereqenv(self, env):
343 343 def splitnetloc(netloc):
344 344 if ':' in netloc:
345 345 return netloc.split(':', 1)
346 346 else:
347 347 return (netloc, None)
348 348
349 349 if self._baseurl is not None:
350 350 urlcomp = urlparse.urlparse(self._baseurl)
351 351 host, port = splitnetloc(urlcomp[1])
352 352 path = urlcomp[2]
353 353 env['SERVER_NAME'] = host
354 354 if port:
355 355 env['SERVER_PORT'] = port
356 356 env['SCRIPT_NAME'] = path
@@ -1,783 +1,783 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, encoding, archival, templater, templatefilters
11 11 from mercurial.node import short, hex
12 12 from mercurial.util import binary
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod
16 16 from mercurial import help as helpmod
17 17 from mercurial.i18n import _
18 18
19 19 # __all__ is populated with the allowed commands. Be sure to add to it if
20 20 # you're adding a new command, or the new command won't work.
21 21
22 22 __all__ = [
23 23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 24 'manifest', 'tags', 'branches', 'summary', 'filediff', 'diff', 'annotate',
25 25 'filelog', 'archive', 'static', 'graph', 'help',
26 26 ]
27 27
28 28 def log(web, req, tmpl):
29 29 if 'file' in req.form and req.form['file'][0]:
30 30 return filelog(web, req, tmpl)
31 31 else:
32 32 return changelog(web, req, tmpl)
33 33
34 34 def rawfile(web, req, tmpl):
35 35 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
36 36 if not path:
37 37 content = manifest(web, req, tmpl)
38 38 req.respond(HTTP_OK, web.ctype)
39 39 return content
40 40
41 41 try:
42 42 fctx = webutil.filectx(web.repo, req)
43 43 except error.LookupError, inst:
44 44 try:
45 45 content = manifest(web, req, tmpl)
46 46 req.respond(HTTP_OK, web.ctype)
47 47 return content
48 48 except ErrorResponse:
49 49 raise inst
50 50
51 51 path = fctx.path()
52 52 text = fctx.data()
53 53 mt = mimetypes.guess_type(path)[0]
54 54 if mt is None:
55 55 mt = binary(text) and 'application/octet-stream' or 'text/plain'
56 56 if mt.startswith('text/'):
57 57 mt += '; charset="%s"' % encoding.encoding
58 58
59 59 req.respond(HTTP_OK, mt, path, len(text))
60 60 return [text]
61 61
62 62 def _filerevision(web, tmpl, fctx):
63 63 f = fctx.path()
64 64 text = fctx.data()
65 65 parity = paritygen(web.stripecount)
66 66
67 67 if binary(text):
68 68 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
69 69 text = '(binary:%s)' % mt
70 70
71 71 def lines():
72 72 for lineno, t in enumerate(text.splitlines(True)):
73 73 yield {"line": t,
74 74 "lineid": "l%d" % (lineno + 1),
75 75 "linenumber": "% 6d" % (lineno + 1),
76 76 "parity": parity.next()}
77 77
78 78 return tmpl("filerevision",
79 79 file=f,
80 80 path=webutil.up(f),
81 81 text=lines(),
82 82 rev=fctx.rev(),
83 83 node=hex(fctx.node()),
84 84 author=fctx.user(),
85 85 date=fctx.date(),
86 86 desc=fctx.description(),
87 87 branch=webutil.nodebranchnodefault(fctx),
88 88 parent=webutil.parents(fctx),
89 89 child=webutil.children(fctx),
90 90 rename=webutil.renamelink(fctx),
91 91 permissions=fctx.manifest().flags(f))
92 92
93 93 def file(web, req, tmpl):
94 94 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
95 95 if not path:
96 96 return manifest(web, req, tmpl)
97 97 try:
98 98 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
99 99 except error.LookupError, inst:
100 100 try:
101 101 return manifest(web, req, tmpl)
102 102 except ErrorResponse:
103 103 raise inst
104 104
105 105 def _search(web, req, tmpl):
106 106
107 107 query = req.form['rev'][0]
108 108 revcount = web.maxchanges
109 109 if 'revcount' in req.form:
110 110 revcount = int(req.form.get('revcount', [revcount])[0])
111 111 tmpl.defaults['sessionvars']['revcount'] = revcount
112 112
113 113 lessvars = copy.copy(tmpl.defaults['sessionvars'])
114 114 lessvars['revcount'] = revcount / 2
115 115 lessvars['rev'] = query
116 116 morevars = copy.copy(tmpl.defaults['sessionvars'])
117 117 morevars['revcount'] = revcount * 2
118 118 morevars['rev'] = query
119 119
120 120 def changelist(**map):
121 121 count = 0
122 122 qw = query.lower().split()
123 123
124 124 def revgen():
125 125 for i in xrange(len(web.repo) - 1, 0, -100):
126 126 l = []
127 127 for j in xrange(max(0, i - 100), i + 1):
128 128 ctx = web.repo[j]
129 129 l.append(ctx)
130 130 l.reverse()
131 131 for e in l:
132 132 yield e
133 133
134 134 for ctx in revgen():
135 135 miss = 0
136 136 for q in qw:
137 137 if not (q in ctx.user().lower() or
138 138 q in ctx.description().lower() or
139 139 q in " ".join(ctx.files()).lower()):
140 140 miss = 1
141 141 break
142 142 if miss:
143 143 continue
144 144
145 145 count += 1
146 146 n = ctx.node()
147 147 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
148 148 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
149 149
150 150 yield tmpl('searchentry',
151 151 parity=parity.next(),
152 152 author=ctx.user(),
153 153 parent=webutil.parents(ctx),
154 154 child=webutil.children(ctx),
155 155 changelogtag=showtags,
156 156 desc=ctx.description(),
157 157 date=ctx.date(),
158 158 files=files,
159 159 rev=ctx.rev(),
160 160 node=hex(n),
161 161 tags=webutil.nodetagsdict(web.repo, n),
162 162 inbranch=webutil.nodeinbranch(web.repo, ctx),
163 163 branches=webutil.nodebranchdict(web.repo, ctx))
164 164
165 165 if count >= revcount:
166 166 break
167 167
168 168 tip = web.repo['tip']
169 169 parity = paritygen(web.stripecount)
170 170
171 171 return tmpl('search', query=query, node=tip.hex(),
172 172 entries=changelist, archives=web.archivelist("tip"),
173 173 morevars=morevars, lessvars=lessvars)
174 174
175 175 def changelog(web, req, tmpl, shortlog=False):
176 176
177 177 if 'node' in req.form:
178 178 ctx = webutil.changectx(web.repo, req)
179 179 else:
180 180 if 'rev' in req.form:
181 181 hi = req.form['rev'][0]
182 182 else:
183 183 hi = len(web.repo) - 1
184 184 try:
185 185 ctx = web.repo[hi]
186 186 except error.RepoError:
187 187 return _search(web, req, tmpl) # XXX redirect to 404 page?
188 188
189 189 def changelist(limit=0, **map):
190 190 l = [] # build a list in forward order for efficiency
191 191 for i in xrange(start, end):
192 192 ctx = web.repo[i]
193 193 n = ctx.node()
194 194 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
195 195 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
196 196
197 197 l.insert(0, {"parity": parity.next(),
198 198 "author": ctx.user(),
199 199 "parent": webutil.parents(ctx, i - 1),
200 200 "child": webutil.children(ctx, i + 1),
201 201 "changelogtag": showtags,
202 202 "desc": ctx.description(),
203 203 "date": ctx.date(),
204 204 "files": files,
205 205 "rev": i,
206 206 "node": hex(n),
207 207 "tags": webutil.nodetagsdict(web.repo, n),
208 208 "inbranch": webutil.nodeinbranch(web.repo, ctx),
209 209 "branches": webutil.nodebranchdict(web.repo, ctx)
210 210 })
211 211
212 212 if limit > 0:
213 213 l = l[:limit]
214 214
215 215 for e in l:
216 216 yield e
217 217
218 218 revcount = shortlog and web.maxshortchanges or web.maxchanges
219 219 if 'revcount' in req.form:
220 220 revcount = int(req.form.get('revcount', [revcount])[0])
221 221 tmpl.defaults['sessionvars']['revcount'] = revcount
222 222
223 223 lessvars = copy.copy(tmpl.defaults['sessionvars'])
224 224 lessvars['revcount'] = revcount / 2
225 225 morevars = copy.copy(tmpl.defaults['sessionvars'])
226 226 morevars['revcount'] = revcount * 2
227 227
228 228 count = len(web.repo)
229 229 pos = ctx.rev()
230 230 start = max(0, pos - revcount + 1)
231 231 end = min(count, start + revcount)
232 232 pos = end - 1
233 233 parity = paritygen(web.stripecount, offset=start - end)
234 234
235 235 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
236 236
237 237 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
238 238 node=hex(ctx.node()), rev=pos, changesets=count,
239 239 entries=lambda **x: changelist(limit=0,**x),
240 240 latestentry=lambda **x: changelist(limit=1,**x),
241 241 archives=web.archivelist("tip"), revcount=revcount,
242 242 morevars=morevars, lessvars=lessvars)
243 243
244 244 def shortlog(web, req, tmpl):
245 245 return changelog(web, req, tmpl, shortlog = True)
246 246
247 247 def changeset(web, req, tmpl):
248 248 ctx = webutil.changectx(web.repo, req)
249 249 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
250 250 showbranch = webutil.nodebranchnodefault(ctx)
251 251
252 252 files = []
253 253 parity = paritygen(web.stripecount)
254 254 for f in ctx.files():
255 255 template = f in ctx and 'filenodelink' or 'filenolink'
256 256 files.append(tmpl(template,
257 257 node=ctx.hex(), file=f,
258 258 parity=parity.next()))
259 259
260 260 parity = paritygen(web.stripecount)
261 261 style = web.config('web', 'style', 'paper')
262 262 if 'style' in req.form:
263 263 style = req.form['style'][0]
264 264
265 265 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
266 266 return tmpl('changeset',
267 267 diff=diffs,
268 268 rev=ctx.rev(),
269 269 node=ctx.hex(),
270 270 parent=webutil.parents(ctx),
271 271 child=webutil.children(ctx),
272 272 changesettag=showtags,
273 273 changesetbranch=showbranch,
274 274 author=ctx.user(),
275 275 desc=ctx.description(),
276 276 date=ctx.date(),
277 277 files=files,
278 278 archives=web.archivelist(ctx.hex()),
279 279 tags=webutil.nodetagsdict(web.repo, ctx.node()),
280 280 branch=webutil.nodebranchnodefault(ctx),
281 281 inbranch=webutil.nodeinbranch(web.repo, ctx),
282 282 branches=webutil.nodebranchdict(web.repo, ctx))
283 283
284 284 rev = changeset
285 285
286 286 def manifest(web, req, tmpl):
287 287 ctx = webutil.changectx(web.repo, req)
288 288 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
289 289 mf = ctx.manifest()
290 290 node = ctx.node()
291 291
292 292 files = {}
293 293 dirs = {}
294 294 parity = paritygen(web.stripecount)
295 295
296 296 if path and path[-1] != "/":
297 297 path += "/"
298 298 l = len(path)
299 299 abspath = "/" + path
300 300
301 301 for f, n in mf.iteritems():
302 302 if f[:l] != path:
303 303 continue
304 304 remain = f[l:]
305 305 elements = remain.split('/')
306 306 if len(elements) == 1:
307 307 files[remain] = f
308 308 else:
309 309 h = dirs # need to retain ref to dirs (root)
310 310 for elem in elements[0:-1]:
311 311 if elem not in h:
312 312 h[elem] = {}
313 313 h = h[elem]
314 314 if len(h) > 1:
315 315 break
316 316 h[None] = None # denotes files present
317 317
318 318 if mf and not files and not dirs:
319 319 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
320 320
321 321 def filelist(**map):
322 322 for f in sorted(files):
323 323 full = files[f]
324 324
325 325 fctx = ctx.filectx(full)
326 326 yield {"file": full,
327 327 "parity": parity.next(),
328 328 "basename": f,
329 329 "date": fctx.date(),
330 330 "size": fctx.size(),
331 331 "permissions": mf.flags(full)}
332 332
333 333 def dirlist(**map):
334 334 for d in sorted(dirs):
335 335
336 336 emptydirs = []
337 337 h = dirs[d]
338 338 while isinstance(h, dict) and len(h) == 1:
339 339 k, v = h.items()[0]
340 340 if v:
341 341 emptydirs.append(k)
342 342 h = v
343 343
344 344 path = "%s%s" % (abspath, d)
345 345 yield {"parity": parity.next(),
346 346 "path": path,
347 347 "emptydirs": "/".join(emptydirs),
348 348 "basename": d}
349 349
350 350 return tmpl("manifest",
351 351 rev=ctx.rev(),
352 352 node=hex(node),
353 353 path=abspath,
354 354 up=webutil.up(abspath),
355 355 upparity=parity.next(),
356 356 fentries=filelist,
357 357 dentries=dirlist,
358 358 archives=web.archivelist(hex(node)),
359 359 tags=webutil.nodetagsdict(web.repo, node),
360 360 inbranch=webutil.nodeinbranch(web.repo, ctx),
361 361 branches=webutil.nodebranchdict(web.repo, ctx))
362 362
363 363 def tags(web, req, tmpl):
364 364 i = web.repo.tagslist()
365 365 i.reverse()
366 366 parity = paritygen(web.stripecount)
367 367
368 368 def entries(notip=False, limit=0, **map):
369 369 count = 0
370 370 for k, n in i:
371 371 if notip and k == "tip":
372 372 continue
373 373 if limit > 0 and count >= limit:
374 374 continue
375 375 count = count + 1
376 376 yield {"parity": parity.next(),
377 377 "tag": k,
378 378 "date": web.repo[n].date(),
379 379 "node": hex(n)}
380 380
381 381 return tmpl("tags",
382 382 node=hex(web.repo.changelog.tip()),
383 383 entries=lambda **x: entries(False, 0, **x),
384 384 entriesnotip=lambda **x: entries(True, 0, **x),
385 385 latestentry=lambda **x: entries(True, 1, **x))
386 386
387 387 def branches(web, req, tmpl):
388 388 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
389 389 heads = web.repo.heads()
390 390 parity = paritygen(web.stripecount)
391 391 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
392 392
393 393 def entries(limit, **map):
394 394 count = 0
395 395 for ctx in sorted(tips, key=sortkey, reverse=True):
396 396 if limit > 0 and count >= limit:
397 397 return
398 398 count += 1
399 399 if ctx.node() not in heads:
400 400 status = 'inactive'
401 401 elif not web.repo.branchheads(ctx.branch()):
402 402 status = 'closed'
403 403 else:
404 404 status = 'open'
405 405 yield {'parity': parity.next(),
406 406 'branch': ctx.branch(),
407 407 'status': status,
408 408 'node': ctx.hex(),
409 409 'date': ctx.date()}
410 410
411 411 return tmpl('branches', node=hex(web.repo.changelog.tip()),
412 412 entries=lambda **x: entries(0, **x),
413 413 latestentry=lambda **x: entries(1, **x))
414 414
415 415 def summary(web, req, tmpl):
416 416 i = web.repo.tagslist()
417 417 i.reverse()
418 418
419 419 def tagentries(**map):
420 420 parity = paritygen(web.stripecount)
421 421 count = 0
422 422 for k, n in i:
423 423 if k == "tip": # skip tip
424 424 continue
425 425
426 426 count += 1
427 427 if count > 10: # limit to 10 tags
428 428 break
429 429
430 430 yield tmpl("tagentry",
431 431 parity=parity.next(),
432 432 tag=k,
433 433 node=hex(n),
434 434 date=web.repo[n].date())
435 435
436 436 def branches(**map):
437 437 parity = paritygen(web.stripecount)
438 438
439 439 b = web.repo.branchtags()
440 440 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
441 441 for r, n, t in sorted(l):
442 442 yield {'parity': parity.next(),
443 443 'branch': t,
444 444 'node': hex(n),
445 445 'date': web.repo[n].date()}
446 446
447 447 def changelist(**map):
448 448 parity = paritygen(web.stripecount, offset=start - end)
449 449 l = [] # build a list in forward order for efficiency
450 450 for i in xrange(start, end):
451 451 ctx = web.repo[i]
452 452 n = ctx.node()
453 453 hn = hex(n)
454 454
455 455 l.insert(0, tmpl(
456 456 'shortlogentry',
457 457 parity=parity.next(),
458 458 author=ctx.user(),
459 459 desc=ctx.description(),
460 460 date=ctx.date(),
461 461 rev=i,
462 462 node=hn,
463 463 tags=webutil.nodetagsdict(web.repo, n),
464 464 inbranch=webutil.nodeinbranch(web.repo, ctx),
465 465 branches=webutil.nodebranchdict(web.repo, ctx)))
466 466
467 467 yield l
468 468
469 469 tip = web.repo['tip']
470 470 count = len(web.repo)
471 471 start = max(0, count - web.maxchanges)
472 472 end = min(count, start + web.maxchanges)
473 473
474 474 return tmpl("summary",
475 475 desc=web.config("web", "description", "unknown"),
476 476 owner=get_contact(web.config) or "unknown",
477 477 lastchange=tip.date(),
478 478 tags=tagentries,
479 479 branches=branches,
480 480 shortlog=changelist,
481 481 node=tip.hex(),
482 482 archives=web.archivelist("tip"))
483 483
484 484 def filediff(web, req, tmpl):
485 485 fctx, ctx = None, None
486 486 try:
487 487 fctx = webutil.filectx(web.repo, req)
488 488 except LookupError:
489 489 ctx = webutil.changectx(web.repo, req)
490 490 path = webutil.cleanpath(web.repo, req.form['file'][0])
491 491 if path not in ctx.files():
492 492 raise
493 493
494 494 if fctx is not None:
495 495 n = fctx.node()
496 496 path = fctx.path()
497 497 else:
498 498 n = ctx.node()
499 499 # path already defined in except clause
500 500
501 501 parity = paritygen(web.stripecount)
502 502 style = web.config('web', 'style', 'paper')
503 503 if 'style' in req.form:
504 504 style = req.form['style'][0]
505 505
506 506 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
507 507 rename = fctx and webutil.renamelink(fctx) or []
508 508 ctx = fctx and fctx or ctx
509 509 return tmpl("filediff",
510 510 file=path,
511 511 node=hex(n),
512 512 rev=ctx.rev(),
513 513 date=ctx.date(),
514 514 desc=ctx.description(),
515 515 author=ctx.user(),
516 516 rename=rename,
517 517 branch=webutil.nodebranchnodefault(ctx),
518 518 parent=webutil.parents(ctx),
519 519 child=webutil.children(ctx),
520 520 diff=diffs)
521 521
522 522 diff = filediff
523 523
524 524 def annotate(web, req, tmpl):
525 525 fctx = webutil.filectx(web.repo, req)
526 526 f = fctx.path()
527 527 parity = paritygen(web.stripecount)
528 528
529 529 def annotate(**map):
530 530 last = None
531 531 if binary(fctx.data()):
532 532 mt = (mimetypes.guess_type(fctx.path())[0]
533 533 or 'application/octet-stream')
534 534 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
535 535 '(binary:%s)' % mt)])
536 536 else:
537 537 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
538 538 for lineno, ((f, targetline), l) in lines:
539 539 fnode = f.filenode()
540 540
541 541 if last != fnode:
542 542 last = fnode
543 543
544 544 yield {"parity": parity.next(),
545 545 "node": hex(f.node()),
546 546 "rev": f.rev(),
547 547 "author": f.user(),
548 548 "desc": f.description(),
549 549 "file": f.path(),
550 550 "targetline": targetline,
551 551 "line": l,
552 552 "lineid": "l%d" % (lineno + 1),
553 553 "linenumber": "% 6d" % (lineno + 1)}
554 554
555 555 return tmpl("fileannotate",
556 556 file=f,
557 557 annotate=annotate,
558 558 path=webutil.up(f),
559 559 rev=fctx.rev(),
560 560 node=hex(fctx.node()),
561 561 author=fctx.user(),
562 562 date=fctx.date(),
563 563 desc=fctx.description(),
564 564 rename=webutil.renamelink(fctx),
565 565 branch=webutil.nodebranchnodefault(fctx),
566 566 parent=webutil.parents(fctx),
567 567 child=webutil.children(fctx),
568 568 permissions=fctx.manifest().flags(f))
569 569
570 570 def filelog(web, req, tmpl):
571 571
572 572 try:
573 573 fctx = webutil.filectx(web.repo, req)
574 574 f = fctx.path()
575 575 fl = fctx.filelog()
576 576 except error.LookupError:
577 577 f = webutil.cleanpath(web.repo, req.form['file'][0])
578 578 fl = web.repo.file(f)
579 579 numrevs = len(fl)
580 580 if not numrevs: # file doesn't exist at all
581 581 raise
582 582 rev = webutil.changectx(web.repo, req).rev()
583 583 first = fl.linkrev(0)
584 584 if rev < first: # current rev is from before file existed
585 585 raise
586 586 frev = numrevs - 1
587 587 while fl.linkrev(frev) > rev:
588 588 frev -= 1
589 589 fctx = web.repo.filectx(f, fl.linkrev(frev))
590 590
591 591 revcount = web.maxshortchanges
592 592 if 'revcount' in req.form:
593 593 revcount = int(req.form.get('revcount', [revcount])[0])
594 594 tmpl.defaults['sessionvars']['revcount'] = revcount
595 595
596 596 lessvars = copy.copy(tmpl.defaults['sessionvars'])
597 597 lessvars['revcount'] = revcount / 2
598 598 morevars = copy.copy(tmpl.defaults['sessionvars'])
599 599 morevars['revcount'] = revcount * 2
600 600
601 601 count = fctx.filerev() + 1
602 602 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
603 603 end = min(count, start + revcount) # last rev on this page
604 604 parity = paritygen(web.stripecount, offset=start - end)
605 605
606 606 def entries(limit=0, **map):
607 607 l = []
608 608
609 609 repo = web.repo
610 610 for i in xrange(start, end):
611 611 iterfctx = fctx.filectx(i)
612 612
613 613 l.insert(0, {"parity": parity.next(),
614 614 "filerev": i,
615 615 "file": f,
616 616 "node": hex(iterfctx.node()),
617 617 "author": iterfctx.user(),
618 618 "date": iterfctx.date(),
619 619 "rename": webutil.renamelink(iterfctx),
620 620 "parent": webutil.parents(iterfctx),
621 621 "child": webutil.children(iterfctx),
622 622 "desc": iterfctx.description(),
623 623 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
624 624 "branch": webutil.nodebranchnodefault(iterfctx),
625 625 "inbranch": webutil.nodeinbranch(repo, iterfctx),
626 626 "branches": webutil.nodebranchdict(repo, iterfctx)})
627 627
628 628 if limit > 0:
629 629 l = l[:limit]
630 630
631 631 for e in l:
632 632 yield e
633 633
634 634 nodefunc = lambda x: fctx.filectx(fileid=x)
635 635 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
636 636 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
637 637 entries=lambda **x: entries(limit=0, **x),
638 638 latestentry=lambda **x: entries(limit=1, **x),
639 639 revcount=revcount, morevars=morevars, lessvars=lessvars)
640 640
641 641 def archive(web, req, tmpl):
642 642 type_ = req.form.get('type', [None])[0]
643 643 allowed = web.configlist("web", "allow_archive")
644 644 key = req.form['node'][0]
645 645
646 646 if type_ not in web.archives:
647 647 msg = 'Unsupported archive type: %s' % type_
648 648 raise ErrorResponse(HTTP_NOT_FOUND, msg)
649 649
650 650 if not ((type_ in allowed or
651 651 web.configbool("web", "allow" + type_, False))):
652 652 msg = 'Archive type not allowed: %s' % type_
653 653 raise ErrorResponse(HTTP_FORBIDDEN, msg)
654 654
655 655 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
656 656 cnode = web.repo.lookup(key)
657 657 arch_version = key
658 658 if cnode == key or key == 'tip':
659 659 arch_version = short(cnode)
660 660 name = "%s-%s" % (reponame, arch_version)
661 661 mimetype, artype, extension, encoding = web.archive_specs[type_]
662 662 headers = [
663 663 ('Content-Type', mimetype),
664 664 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
665 665 ]
666 666 if encoding:
667 667 headers.append(('Content-Encoding', encoding))
668 668 req.header(headers)
669 669 req.respond(HTTP_OK)
670 670 archival.archive(web.repo, req, cnode, artype, prefix=name)
671 671 return []
672 672
673 673
674 674 def static(web, req, tmpl):
675 675 fname = req.form['file'][0]
676 676 # a repo owner may set web.static in .hg/hgrc to get any file
677 677 # readable by the user running the CGI script
678 678 static = web.config("web", "static", None, untrusted=False)
679 679 if not static:
680 680 tp = web.templatepath or templater.templatepath()
681 681 if isinstance(tp, str):
682 682 tp = [tp]
683 683 static = [os.path.join(p, 'static') for p in tp]
684 684 return [staticfile(static, fname, req)]
685 685
686 686 def graph(web, req, tmpl):
687 687
688 688 rev = webutil.changectx(web.repo, req).rev()
689 689 bg_height = 39
690 690 revcount = web.maxshortchanges
691 691 if 'revcount' in req.form:
692 692 revcount = int(req.form.get('revcount', [revcount])[0])
693 693 tmpl.defaults['sessionvars']['revcount'] = revcount
694 694
695 695 lessvars = copy.copy(tmpl.defaults['sessionvars'])
696 696 lessvars['revcount'] = revcount / 2
697 697 morevars = copy.copy(tmpl.defaults['sessionvars'])
698 698 morevars['revcount'] = revcount * 2
699 699
700 700 max_rev = len(web.repo) - 1
701 701 revcount = min(max_rev, revcount)
702 702 revnode = web.repo.changelog.node(rev)
703 703 revnode_hex = hex(revnode)
704 704 uprev = min(max_rev, rev + revcount)
705 705 downrev = max(0, rev - revcount)
706 706 count = len(web.repo)
707 707 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
708 708
709 709 dag = graphmod.revisions(web.repo, rev, downrev)
710 710 tree = list(graphmod.colored(dag))
711 711 canvasheight = (len(tree) + 1) * bg_height - 27
712 712 data = []
713 713 for (id, type, ctx, vtx, edges) in tree:
714 714 if type != graphmod.CHANGESET:
715 715 continue
716 716 node = short(ctx.node())
717 717 age = templatefilters.age(ctx.date())
718 718 desc = templatefilters.firstline(ctx.description())
719 719 desc = cgi.escape(templatefilters.nonempty(desc))
720 720 user = cgi.escape(templatefilters.person(ctx.user()))
721 721 branch = ctx.branch()
722 722 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
723 723 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
724 724
725 725 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
726 726 lessvars=lessvars, morevars=morevars, downrev=downrev,
727 727 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
728 728 node=revnode_hex, changenav=changenav)
729 729
730 730 def _getdoc(e):
731 731 doc = e[0].__doc__
732 732 if doc:
733 733 doc = doc.split('\n')[0]
734 734 else:
735 735 doc = _('(no help text available)')
736 736 return doc
737 737
738 738 def help(web, req, tmpl):
739 739 from mercurial import commands # avoid cycle
740 740
741 741 topicname = req.form.get('node', [None])[0]
742 742 if not topicname:
743 743 topic = []
744 744
745 745 def topics(**map):
746 746 for entries, summary, _ in helpmod.helptable:
747 747 entries = sorted(entries, key=len)
748 748 yield {'topic': entries[-1], 'summary': summary}
749 749
750 750 early, other = [], []
751 751 primary = lambda s: s.split('|')[0]
752 752 for c, e in commands.table.iteritems():
753 753 doc = _getdoc(e)
754 754 if 'DEPRECATED' in doc or c.startswith('debug'):
755 755 continue
756 756 cmd = primary(c)
757 757 if cmd.startswith('^'):
758 758 early.append((cmd[1:], doc))
759 759 else:
760 760 other.append((cmd, doc))
761 761
762 762 early.sort()
763 763 other.sort()
764 764
765 765 def earlycommands(**map):
766 766 for c, doc in early:
767 767 yield {'topic': c, 'summary': doc}
768 768
769 769 def othercommands(**map):
770 770 for c, doc in other:
771 771 yield {'topic': c, 'summary': doc}
772 772
773 773 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
774 774 othercommands=othercommands, title='Index')
775 775
776 u = web.repo.ui
776 u = webutil.wsgiui()
777 777 u.pushbuffer()
778 778 try:
779 779 commands.help_(u, topicname)
780 780 except error.UnknownCommand:
781 781 raise ErrorResponse(HTTP_NOT_FOUND)
782 782 doc = u.popbuffer()
783 783 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now