##// END OF EJS Templates
hgweb: simplify internal staticfile return codes
Mads Kiilerich -
r18645:76ff3a71 default
parent child Browse files
Show More
@@ -1,186 +1,185 b''
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import errno, mimetypes, os
9 import errno, mimetypes, os
10
10
11 HTTP_OK = 200
11 HTTP_OK = 200
12 HTTP_NOT_MODIFIED = 304
12 HTTP_NOT_MODIFIED = 304
13 HTTP_BAD_REQUEST = 400
13 HTTP_BAD_REQUEST = 400
14 HTTP_UNAUTHORIZED = 401
14 HTTP_UNAUTHORIZED = 401
15 HTTP_FORBIDDEN = 403
15 HTTP_FORBIDDEN = 403
16 HTTP_NOT_FOUND = 404
16 HTTP_NOT_FOUND = 404
17 HTTP_METHOD_NOT_ALLOWED = 405
17 HTTP_METHOD_NOT_ALLOWED = 405
18 HTTP_SERVER_ERROR = 500
18 HTTP_SERVER_ERROR = 500
19
19
20
20
21 def checkauthz(hgweb, req, op):
21 def checkauthz(hgweb, req, op):
22 '''Check permission for operation based on request data (including
22 '''Check permission for operation based on request data (including
23 authentication info). Return if op allowed, else raise an ErrorResponse
23 authentication info). Return if op allowed, else raise an ErrorResponse
24 exception.'''
24 exception.'''
25
25
26 user = req.env.get('REMOTE_USER')
26 user = req.env.get('REMOTE_USER')
27
27
28 deny_read = hgweb.configlist('web', 'deny_read')
28 deny_read = hgweb.configlist('web', 'deny_read')
29 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
29 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
30 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
30 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
31
31
32 allow_read = hgweb.configlist('web', 'allow_read')
32 allow_read = hgweb.configlist('web', 'allow_read')
33 result = (not allow_read) or (allow_read == ['*'])
33 result = (not allow_read) or (allow_read == ['*'])
34 if not (result or user in allow_read):
34 if not (result or user in allow_read):
35 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
35 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
36
36
37 if op == 'pull' and not hgweb.allowpull:
37 if op == 'pull' and not hgweb.allowpull:
38 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
38 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
39 elif op == 'pull' or op is None: # op is None for interface requests
39 elif op == 'pull' or op is None: # op is None for interface requests
40 return
40 return
41
41
42 # enforce that you can only push using POST requests
42 # enforce that you can only push using POST requests
43 if req.env['REQUEST_METHOD'] != 'POST':
43 if req.env['REQUEST_METHOD'] != 'POST':
44 msg = 'push requires POST request'
44 msg = 'push requires POST request'
45 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
45 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
46
46
47 # require ssl by default for pushing, auth info cannot be sniffed
47 # require ssl by default for pushing, auth info cannot be sniffed
48 # and replayed
48 # and replayed
49 scheme = req.env.get('wsgi.url_scheme')
49 scheme = req.env.get('wsgi.url_scheme')
50 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
50 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
51 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
51 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
52
52
53 deny = hgweb.configlist('web', 'deny_push')
53 deny = hgweb.configlist('web', 'deny_push')
54 if deny and (not user or deny == ['*'] or user in deny):
54 if deny and (not user or deny == ['*'] or user in deny):
55 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
55 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
56
56
57 allow = hgweb.configlist('web', 'allow_push')
57 allow = hgweb.configlist('web', 'allow_push')
58 result = allow and (allow == ['*'] or user in allow)
58 result = allow and (allow == ['*'] or user in allow)
59 if not result:
59 if not result:
60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
61
61
62 # Hooks for hgweb permission checks; extensions can add hooks here.
62 # Hooks for hgweb permission checks; extensions can add hooks here.
63 # Each hook is invoked like this: hook(hgweb, request, operation),
63 # Each hook is invoked like this: hook(hgweb, request, operation),
64 # where operation is either read, pull or push. Hooks should either
64 # where operation is either read, pull or push. Hooks should either
65 # raise an ErrorResponse exception, or just return.
65 # raise an ErrorResponse exception, or just return.
66 #
66 #
67 # It is possible to do both authentication and authorization through
67 # It is possible to do both authentication and authorization through
68 # this.
68 # this.
69 permhooks = [checkauthz]
69 permhooks = [checkauthz]
70
70
71
71
72 class ErrorResponse(Exception):
72 class ErrorResponse(Exception):
73 def __init__(self, code, message=None, headers=[]):
73 def __init__(self, code, message=None, headers=[]):
74 if message is None:
74 if message is None:
75 message = _statusmessage(code)
75 message = _statusmessage(code)
76 Exception.__init__(self)
76 Exception.__init__(self)
77 self.code = code
77 self.code = code
78 self.message = message
78 self.message = message
79 self.headers = headers
79 self.headers = headers
80 def __str__(self):
80 def __str__(self):
81 return self.message
81 return self.message
82
82
83 class continuereader(object):
83 class continuereader(object):
84 def __init__(self, f, write):
84 def __init__(self, f, write):
85 self.f = f
85 self.f = f
86 self._write = write
86 self._write = write
87 self.continued = False
87 self.continued = False
88
88
89 def read(self, amt=-1):
89 def read(self, amt=-1):
90 if not self.continued:
90 if not self.continued:
91 self.continued = True
91 self.continued = True
92 self._write('HTTP/1.1 100 Continue\r\n\r\n')
92 self._write('HTTP/1.1 100 Continue\r\n\r\n')
93 return self.f.read(amt)
93 return self.f.read(amt)
94
94
95 def __getattr__(self, attr):
95 def __getattr__(self, attr):
96 if attr in ('close', 'readline', 'readlines', '__iter__'):
96 if attr in ('close', 'readline', 'readlines', '__iter__'):
97 return getattr(self.f, attr)
97 return getattr(self.f, attr)
98 raise AttributeError
98 raise AttributeError
99
99
100 def _statusmessage(code):
100 def _statusmessage(code):
101 from BaseHTTPServer import BaseHTTPRequestHandler
101 from BaseHTTPServer import BaseHTTPRequestHandler
102 responses = BaseHTTPRequestHandler.responses
102 responses = BaseHTTPRequestHandler.responses
103 return responses.get(code, ('Error', 'Unknown error'))[0]
103 return responses.get(code, ('Error', 'Unknown error'))[0]
104
104
105 def statusmessage(code, message=None):
105 def statusmessage(code, message=None):
106 return '%d %s' % (code, message or _statusmessage(code))
106 return '%d %s' % (code, message or _statusmessage(code))
107
107
108 def get_stat(spath):
108 def get_stat(spath):
109 """stat changelog if it exists, spath otherwise"""
109 """stat changelog if it exists, spath otherwise"""
110 cl_path = os.path.join(spath, "00changelog.i")
110 cl_path = os.path.join(spath, "00changelog.i")
111 if os.path.exists(cl_path):
111 if os.path.exists(cl_path):
112 return os.stat(cl_path)
112 return os.stat(cl_path)
113 else:
113 else:
114 return os.stat(spath)
114 return os.stat(spath)
115
115
116 def get_mtime(spath):
116 def get_mtime(spath):
117 return get_stat(spath).st_mtime
117 return get_stat(spath).st_mtime
118
118
119 def staticfile(directory, fname, req):
119 def staticfile(directory, fname, req):
120 """return a file inside directory with guessed Content-Type header
120 """return a file inside directory with guessed Content-Type header
121
121
122 fname always uses '/' as directory separator and isn't allowed to
122 fname always uses '/' as directory separator and isn't allowed to
123 contain unusual path components.
123 contain unusual path components.
124 Content-Type is guessed using the mimetypes module.
124 Content-Type is guessed using the mimetypes module.
125 Return an empty string if fname is illegal or file not found.
125 Return an empty string if fname is illegal or file not found.
126
126
127 """
127 """
128 parts = fname.split('/')
128 parts = fname.split('/')
129 for part in parts:
129 for part in parts:
130 if (part in ('', os.curdir, os.pardir) or
130 if (part in ('', os.curdir, os.pardir) or
131 os.sep in part or os.altsep is not None and os.altsep in part):
131 os.sep in part or os.altsep is not None and os.altsep in part):
132 return ""
132 return
133 fpath = os.path.join(*parts)
133 fpath = os.path.join(*parts)
134 if isinstance(directory, str):
134 if isinstance(directory, str):
135 directory = [directory]
135 directory = [directory]
136 for d in directory:
136 for d in directory:
137 path = os.path.join(d, fpath)
137 path = os.path.join(d, fpath)
138 if os.path.exists(path):
138 if os.path.exists(path):
139 break
139 break
140 try:
140 try:
141 os.stat(path)
141 os.stat(path)
142 ct = mimetypes.guess_type(path)[0] or "text/plain"
142 ct = mimetypes.guess_type(path)[0] or "text/plain"
143 fp = open(path, 'rb')
143 fp = open(path, 'rb')
144 data = fp.read()
144 data = fp.read()
145 fp.close()
145 fp.close()
146 req.respond(HTTP_OK, ct, body=data)
146 req.respond(HTTP_OK, ct, body=data)
147 return ""
148 except TypeError:
147 except TypeError:
149 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
148 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
150 except OSError, err:
149 except OSError, err:
151 if err.errno == errno.ENOENT:
150 if err.errno == errno.ENOENT:
152 raise ErrorResponse(HTTP_NOT_FOUND)
151 raise ErrorResponse(HTTP_NOT_FOUND)
153 else:
152 else:
154 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
153 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
155
154
156 def paritygen(stripecount, offset=0):
155 def paritygen(stripecount, offset=0):
157 """count parity of horizontal stripes for easier reading"""
156 """count parity of horizontal stripes for easier reading"""
158 if stripecount and offset:
157 if stripecount and offset:
159 # account for offset, e.g. due to building the list in reverse
158 # account for offset, e.g. due to building the list in reverse
160 count = (stripecount + offset) % stripecount
159 count = (stripecount + offset) % stripecount
161 parity = (stripecount + offset) / stripecount & 1
160 parity = (stripecount + offset) / stripecount & 1
162 else:
161 else:
163 count = 0
162 count = 0
164 parity = 0
163 parity = 0
165 while True:
164 while True:
166 yield parity
165 yield parity
167 count += 1
166 count += 1
168 if stripecount and count >= stripecount:
167 if stripecount and count >= stripecount:
169 parity = 1 - parity
168 parity = 1 - parity
170 count = 0
169 count = 0
171
170
172 def get_contact(config):
171 def get_contact(config):
173 """Return repo contact information or empty string.
172 """Return repo contact information or empty string.
174
173
175 web.contact is the primary source, but if that is not set, try
174 web.contact is the primary source, but if that is not set, try
176 ui.username or $EMAIL as a fallback to display something useful.
175 ui.username or $EMAIL as a fallback to display something useful.
177 """
176 """
178 return (config("web", "contact") or
177 return (config("web", "contact") or
179 config("ui", "username") or
178 config("ui", "username") or
180 os.environ.get("EMAIL") or "")
179 os.environ.get("EMAIL") or "")
181
180
182 def caching(web, req):
181 def caching(web, req):
183 tag = str(web.mtime)
182 tag = str(web.mtime)
184 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
183 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
185 raise ErrorResponse(HTTP_NOT_MODIFIED)
184 raise ErrorResponse(HTTP_NOT_MODIFIED)
186 req.headers.append(('ETag', tag))
185 req.headers.append(('ETag', tag))
@@ -1,465 +1,466 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, re, time
9 import os, re, time
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import ui, hg, scmutil, util, templater
11 from mercurial import ui, hg, scmutil, util, templater
12 from mercurial import error, encoding
12 from mercurial import error, encoding
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb, makebreadcrumb
15 from hgweb_mod import hgweb, makebreadcrumb
16 from request import wsgirequest
16 from request import wsgirequest
17 import webutil
17 import webutil
18
18
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21
21
22 def findrepos(paths):
22 def findrepos(paths):
23 repos = []
23 repos = []
24 for prefix, root in cleannames(paths):
24 for prefix, root in cleannames(paths):
25 roothead, roottail = os.path.split(root)
25 roothead, roottail = os.path.split(root)
26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
26 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
27 # /bar/ be served as as foo/N .
27 # /bar/ be served as as foo/N .
28 # '*' will not search inside dirs with .hg (except .hg/patches),
28 # '*' will not search inside dirs with .hg (except .hg/patches),
29 # '**' will search inside dirs with .hg (and thus also find subrepos).
29 # '**' will search inside dirs with .hg (and thus also find subrepos).
30 try:
30 try:
31 recurse = {'*': False, '**': True}[roottail]
31 recurse = {'*': False, '**': True}[roottail]
32 except KeyError:
32 except KeyError:
33 repos.append((prefix, root))
33 repos.append((prefix, root))
34 continue
34 continue
35 roothead = os.path.normpath(os.path.abspath(roothead))
35 roothead = os.path.normpath(os.path.abspath(roothead))
36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
36 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
37 repos.extend(urlrepos(prefix, roothead, paths))
37 repos.extend(urlrepos(prefix, roothead, paths))
38 return repos
38 return repos
39
39
40 def urlrepos(prefix, roothead, paths):
40 def urlrepos(prefix, roothead, paths):
41 """yield url paths and filesystem paths from a list of repo paths
41 """yield url paths and filesystem paths from a list of repo paths
42
42
43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
43 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
44 >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
45 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
46 >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
47 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
48 """
48 """
49 for path in paths:
49 for path in paths:
50 path = os.path.normpath(path)
50 path = os.path.normpath(path)
51 yield (prefix + '/' +
51 yield (prefix + '/' +
52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
52 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
53
53
54 def geturlcgivars(baseurl, port):
54 def geturlcgivars(baseurl, port):
55 """
55 """
56 Extract CGI variables from baseurl
56 Extract CGI variables from baseurl
57
57
58 >>> geturlcgivars("http://host.org/base", "80")
58 >>> geturlcgivars("http://host.org/base", "80")
59 ('host.org', '80', '/base')
59 ('host.org', '80', '/base')
60 >>> geturlcgivars("http://host.org:8000/base", "80")
60 >>> geturlcgivars("http://host.org:8000/base", "80")
61 ('host.org', '8000', '/base')
61 ('host.org', '8000', '/base')
62 >>> geturlcgivars('/base', 8000)
62 >>> geturlcgivars('/base', 8000)
63 ('', '8000', '/base')
63 ('', '8000', '/base')
64 >>> geturlcgivars("base", '8000')
64 >>> geturlcgivars("base", '8000')
65 ('', '8000', '/base')
65 ('', '8000', '/base')
66 >>> geturlcgivars("http://host", '8000')
66 >>> geturlcgivars("http://host", '8000')
67 ('host', '8000', '/')
67 ('host', '8000', '/')
68 >>> geturlcgivars("http://host/", '8000')
68 >>> geturlcgivars("http://host/", '8000')
69 ('host', '8000', '/')
69 ('host', '8000', '/')
70 """
70 """
71 u = util.url(baseurl)
71 u = util.url(baseurl)
72 name = u.host or ''
72 name = u.host or ''
73 if u.port:
73 if u.port:
74 port = u.port
74 port = u.port
75 path = u.path or ""
75 path = u.path or ""
76 if not path.startswith('/'):
76 if not path.startswith('/'):
77 path = '/' + path
77 path = '/' + path
78
78
79 return name, str(port), path
79 return name, str(port), path
80
80
81 class hgwebdir(object):
81 class hgwebdir(object):
82 refreshinterval = 20
82 refreshinterval = 20
83
83
84 def __init__(self, conf, baseui=None):
84 def __init__(self, conf, baseui=None):
85 self.conf = conf
85 self.conf = conf
86 self.baseui = baseui
86 self.baseui = baseui
87 self.lastrefresh = 0
87 self.lastrefresh = 0
88 self.motd = None
88 self.motd = None
89 self.refresh()
89 self.refresh()
90
90
91 def refresh(self):
91 def refresh(self):
92 if self.lastrefresh + self.refreshinterval > time.time():
92 if self.lastrefresh + self.refreshinterval > time.time():
93 return
93 return
94
94
95 if self.baseui:
95 if self.baseui:
96 u = self.baseui.copy()
96 u = self.baseui.copy()
97 else:
97 else:
98 u = ui.ui()
98 u = ui.ui()
99 u.setconfig('ui', 'report_untrusted', 'off')
99 u.setconfig('ui', 'report_untrusted', 'off')
100 u.setconfig('ui', 'nontty', 'true')
100 u.setconfig('ui', 'nontty', 'true')
101
101
102 if not isinstance(self.conf, (dict, list, tuple)):
102 if not isinstance(self.conf, (dict, list, tuple)):
103 map = {'paths': 'hgweb-paths'}
103 map = {'paths': 'hgweb-paths'}
104 if not os.path.exists(self.conf):
104 if not os.path.exists(self.conf):
105 raise util.Abort(_('config file %s not found!') % self.conf)
105 raise util.Abort(_('config file %s not found!') % self.conf)
106 u.readconfig(self.conf, remap=map, trust=True)
106 u.readconfig(self.conf, remap=map, trust=True)
107 paths = []
107 paths = []
108 for name, ignored in u.configitems('hgweb-paths'):
108 for name, ignored in u.configitems('hgweb-paths'):
109 for path in u.configlist('hgweb-paths', name):
109 for path in u.configlist('hgweb-paths', name):
110 paths.append((name, path))
110 paths.append((name, path))
111 elif isinstance(self.conf, (list, tuple)):
111 elif isinstance(self.conf, (list, tuple)):
112 paths = self.conf
112 paths = self.conf
113 elif isinstance(self.conf, dict):
113 elif isinstance(self.conf, dict):
114 paths = self.conf.items()
114 paths = self.conf.items()
115
115
116 repos = findrepos(paths)
116 repos = findrepos(paths)
117 for prefix, root in u.configitems('collections'):
117 for prefix, root in u.configitems('collections'):
118 prefix = util.pconvert(prefix)
118 prefix = util.pconvert(prefix)
119 for path in scmutil.walkrepos(root, followsym=True):
119 for path in scmutil.walkrepos(root, followsym=True):
120 repo = os.path.normpath(path)
120 repo = os.path.normpath(path)
121 name = util.pconvert(repo)
121 name = util.pconvert(repo)
122 if name.startswith(prefix):
122 if name.startswith(prefix):
123 name = name[len(prefix):]
123 name = name[len(prefix):]
124 repos.append((name.lstrip('/'), repo))
124 repos.append((name.lstrip('/'), repo))
125
125
126 self.repos = repos
126 self.repos = repos
127 self.ui = u
127 self.ui = u
128 encoding.encoding = self.ui.config('web', 'encoding',
128 encoding.encoding = self.ui.config('web', 'encoding',
129 encoding.encoding)
129 encoding.encoding)
130 self.style = self.ui.config('web', 'style', 'paper')
130 self.style = self.ui.config('web', 'style', 'paper')
131 self.templatepath = self.ui.config('web', 'templates', None)
131 self.templatepath = self.ui.config('web', 'templates', None)
132 self.stripecount = self.ui.config('web', 'stripes', 1)
132 self.stripecount = self.ui.config('web', 'stripes', 1)
133 if self.stripecount:
133 if self.stripecount:
134 self.stripecount = int(self.stripecount)
134 self.stripecount = int(self.stripecount)
135 self._baseurl = self.ui.config('web', 'baseurl')
135 self._baseurl = self.ui.config('web', 'baseurl')
136 prefix = self.ui.config('web', 'prefix', '')
136 prefix = self.ui.config('web', 'prefix', '')
137 if prefix.startswith('/'):
137 if prefix.startswith('/'):
138 prefix = prefix[1:]
138 prefix = prefix[1:]
139 if prefix.endswith('/'):
139 if prefix.endswith('/'):
140 prefix = prefix[:-1]
140 prefix = prefix[:-1]
141 self.prefix = prefix
141 self.prefix = prefix
142 self.lastrefresh = time.time()
142 self.lastrefresh = time.time()
143
143
144 def run(self):
144 def run(self):
145 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
145 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
146 raise RuntimeError("This function is only intended to be "
146 raise RuntimeError("This function is only intended to be "
147 "called while running as a CGI script.")
147 "called while running as a CGI script.")
148 import mercurial.hgweb.wsgicgi as wsgicgi
148 import mercurial.hgweb.wsgicgi as wsgicgi
149 wsgicgi.launch(self)
149 wsgicgi.launch(self)
150
150
151 def __call__(self, env, respond):
151 def __call__(self, env, respond):
152 req = wsgirequest(env, respond)
152 req = wsgirequest(env, respond)
153 return self.run_wsgi(req)
153 return self.run_wsgi(req)
154
154
155 def read_allowed(self, ui, req):
155 def read_allowed(self, ui, req):
156 """Check allow_read and deny_read config options of a repo's ui object
156 """Check allow_read and deny_read config options of a repo's ui object
157 to determine user permissions. By default, with neither option set (or
157 to determine user permissions. By default, with neither option set (or
158 both empty), allow all users to read the repo. There are two ways a
158 both empty), allow all users to read the repo. There are two ways a
159 user can be denied read access: (1) deny_read is not empty, and the
159 user can be denied read access: (1) deny_read is not empty, and the
160 user is unauthenticated or deny_read contains user (or *), and (2)
160 user is unauthenticated or deny_read contains user (or *), and (2)
161 allow_read is not empty and the user is not in allow_read. Return True
161 allow_read is not empty and the user is not in allow_read. Return True
162 if user is allowed to read the repo, else return False."""
162 if user is allowed to read the repo, else return False."""
163
163
164 user = req.env.get('REMOTE_USER')
164 user = req.env.get('REMOTE_USER')
165
165
166 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
166 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
167 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
167 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
168 return False
168 return False
169
169
170 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
170 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
171 # by default, allow reading if no allow_read option has been set
171 # by default, allow reading if no allow_read option has been set
172 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
172 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
173 return True
173 return True
174
174
175 return False
175 return False
176
176
177 def run_wsgi(self, req):
177 def run_wsgi(self, req):
178 try:
178 try:
179 try:
179 try:
180 self.refresh()
180 self.refresh()
181
181
182 virtual = req.env.get("PATH_INFO", "").strip('/')
182 virtual = req.env.get("PATH_INFO", "").strip('/')
183 tmpl = self.templater(req)
183 tmpl = self.templater(req)
184 ctype = tmpl('mimetype', encoding=encoding.encoding)
184 ctype = tmpl('mimetype', encoding=encoding.encoding)
185 ctype = templater.stringify(ctype)
185 ctype = templater.stringify(ctype)
186
186
187 # a static file
187 # a static file
188 if virtual.startswith('static/') or 'static' in req.form:
188 if virtual.startswith('static/') or 'static' in req.form:
189 if virtual.startswith('static/'):
189 if virtual.startswith('static/'):
190 fname = virtual[7:]
190 fname = virtual[7:]
191 else:
191 else:
192 fname = req.form['static'][0]
192 fname = req.form['static'][0]
193 static = self.ui.config("web", "static", None,
193 static = self.ui.config("web", "static", None,
194 untrusted=False)
194 untrusted=False)
195 if not static:
195 if not static:
196 tp = self.templatepath or templater.templatepath()
196 tp = self.templatepath or templater.templatepath()
197 if isinstance(tp, str):
197 if isinstance(tp, str):
198 tp = [tp]
198 tp = [tp]
199 static = [os.path.join(p, 'static') for p in tp]
199 static = [os.path.join(p, 'static') for p in tp]
200 return (staticfile(static, fname, req),)
200 staticfile(static, fname, req)
201 return []
201
202
202 # top-level index
203 # top-level index
203 elif not virtual:
204 elif not virtual:
204 req.respond(HTTP_OK, ctype)
205 req.respond(HTTP_OK, ctype)
205 return self.makeindex(req, tmpl)
206 return self.makeindex(req, tmpl)
206
207
207 # nested indexes and hgwebs
208 # nested indexes and hgwebs
208
209
209 repos = dict(self.repos)
210 repos = dict(self.repos)
210 virtualrepo = virtual
211 virtualrepo = virtual
211 while virtualrepo:
212 while virtualrepo:
212 real = repos.get(virtualrepo)
213 real = repos.get(virtualrepo)
213 if real:
214 if real:
214 req.env['REPO_NAME'] = virtualrepo
215 req.env['REPO_NAME'] = virtualrepo
215 try:
216 try:
216 repo = hg.repository(self.ui, real)
217 repo = hg.repository(self.ui, real)
217 return hgweb(repo).run_wsgi(req)
218 return hgweb(repo).run_wsgi(req)
218 except IOError, inst:
219 except IOError, inst:
219 msg = inst.strerror
220 msg = inst.strerror
220 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
221 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
221 except error.RepoError, inst:
222 except error.RepoError, inst:
222 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
223 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
223
224
224 up = virtualrepo.rfind('/')
225 up = virtualrepo.rfind('/')
225 if up < 0:
226 if up < 0:
226 break
227 break
227 virtualrepo = virtualrepo[:up]
228 virtualrepo = virtualrepo[:up]
228
229
229 # browse subdirectories
230 # browse subdirectories
230 subdir = virtual + '/'
231 subdir = virtual + '/'
231 if [r for r in repos if r.startswith(subdir)]:
232 if [r for r in repos if r.startswith(subdir)]:
232 req.respond(HTTP_OK, ctype)
233 req.respond(HTTP_OK, ctype)
233 return self.makeindex(req, tmpl, subdir)
234 return self.makeindex(req, tmpl, subdir)
234
235
235 # prefixes not found
236 # prefixes not found
236 req.respond(HTTP_NOT_FOUND, ctype)
237 req.respond(HTTP_NOT_FOUND, ctype)
237 return tmpl("notfound", repo=virtual)
238 return tmpl("notfound", repo=virtual)
238
239
239 except ErrorResponse, err:
240 except ErrorResponse, err:
240 req.respond(err, ctype)
241 req.respond(err, ctype)
241 return tmpl('error', error=err.message or '')
242 return tmpl('error', error=err.message or '')
242 finally:
243 finally:
243 tmpl = None
244 tmpl = None
244
245
245 def makeindex(self, req, tmpl, subdir=""):
246 def makeindex(self, req, tmpl, subdir=""):
246
247
247 def archivelist(ui, nodeid, url):
248 def archivelist(ui, nodeid, url):
248 allowed = ui.configlist("web", "allow_archive", untrusted=True)
249 allowed = ui.configlist("web", "allow_archive", untrusted=True)
249 archives = []
250 archives = []
250 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
251 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
251 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
252 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
252 untrusted=True):
253 untrusted=True):
253 archives.append({"type" : i[0], "extension": i[1],
254 archives.append({"type" : i[0], "extension": i[1],
254 "node": nodeid, "url": url})
255 "node": nodeid, "url": url})
255 return archives
256 return archives
256
257
257 def rawentries(subdir="", **map):
258 def rawentries(subdir="", **map):
258
259
259 descend = self.ui.configbool('web', 'descend', True)
260 descend = self.ui.configbool('web', 'descend', True)
260 collapse = self.ui.configbool('web', 'collapse', False)
261 collapse = self.ui.configbool('web', 'collapse', False)
261 seenrepos = set()
262 seenrepos = set()
262 seendirs = set()
263 seendirs = set()
263 for name, path in self.repos:
264 for name, path in self.repos:
264
265
265 if not name.startswith(subdir):
266 if not name.startswith(subdir):
266 continue
267 continue
267 name = name[len(subdir):]
268 name = name[len(subdir):]
268 directory = False
269 directory = False
269
270
270 if '/' in name:
271 if '/' in name:
271 if not descend:
272 if not descend:
272 continue
273 continue
273
274
274 nameparts = name.split('/')
275 nameparts = name.split('/')
275 rootname = nameparts[0]
276 rootname = nameparts[0]
276
277
277 if not collapse:
278 if not collapse:
278 pass
279 pass
279 elif rootname in seendirs:
280 elif rootname in seendirs:
280 continue
281 continue
281 elif rootname in seenrepos:
282 elif rootname in seenrepos:
282 pass
283 pass
283 else:
284 else:
284 directory = True
285 directory = True
285 name = rootname
286 name = rootname
286
287
287 # redefine the path to refer to the directory
288 # redefine the path to refer to the directory
288 discarded = '/'.join(nameparts[1:])
289 discarded = '/'.join(nameparts[1:])
289
290
290 # remove name parts plus accompanying slash
291 # remove name parts plus accompanying slash
291 path = path[:-len(discarded) - 1]
292 path = path[:-len(discarded) - 1]
292
293
293 parts = [name]
294 parts = [name]
294 if 'PATH_INFO' in req.env:
295 if 'PATH_INFO' in req.env:
295 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
296 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
296 if req.env['SCRIPT_NAME']:
297 if req.env['SCRIPT_NAME']:
297 parts.insert(0, req.env['SCRIPT_NAME'])
298 parts.insert(0, req.env['SCRIPT_NAME'])
298 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
299 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
299
300
300 # show either a directory entry or a repository
301 # show either a directory entry or a repository
301 if directory:
302 if directory:
302 # get the directory's time information
303 # get the directory's time information
303 try:
304 try:
304 d = (get_mtime(path), util.makedate()[1])
305 d = (get_mtime(path), util.makedate()[1])
305 except OSError:
306 except OSError:
306 continue
307 continue
307
308
308 # add '/' to the name to make it obvious that
309 # add '/' to the name to make it obvious that
309 # the entry is a directory, not a regular repository
310 # the entry is a directory, not a regular repository
310 row = dict(contact="",
311 row = dict(contact="",
311 contact_sort="",
312 contact_sort="",
312 name=name + '/',
313 name=name + '/',
313 name_sort=name,
314 name_sort=name,
314 url=url,
315 url=url,
315 description="",
316 description="",
316 description_sort="",
317 description_sort="",
317 lastchange=d,
318 lastchange=d,
318 lastchange_sort=d[1]-d[0],
319 lastchange_sort=d[1]-d[0],
319 archives=[],
320 archives=[],
320 isdirectory=True)
321 isdirectory=True)
321
322
322 seendirs.add(name)
323 seendirs.add(name)
323 yield row
324 yield row
324 continue
325 continue
325
326
326 u = self.ui.copy()
327 u = self.ui.copy()
327 try:
328 try:
328 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
329 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
329 except Exception, e:
330 except Exception, e:
330 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
331 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
331 continue
332 continue
332 def get(section, name, default=None):
333 def get(section, name, default=None):
333 return u.config(section, name, default, untrusted=True)
334 return u.config(section, name, default, untrusted=True)
334
335
335 if u.configbool("web", "hidden", untrusted=True):
336 if u.configbool("web", "hidden", untrusted=True):
336 continue
337 continue
337
338
338 if not self.read_allowed(u, req):
339 if not self.read_allowed(u, req):
339 continue
340 continue
340
341
341 # update time with local timezone
342 # update time with local timezone
342 try:
343 try:
343 r = hg.repository(self.ui, path)
344 r = hg.repository(self.ui, path)
344 except IOError:
345 except IOError:
345 u.warn(_('error accessing repository at %s\n') % path)
346 u.warn(_('error accessing repository at %s\n') % path)
346 continue
347 continue
347 except error.RepoError:
348 except error.RepoError:
348 u.warn(_('error accessing repository at %s\n') % path)
349 u.warn(_('error accessing repository at %s\n') % path)
349 continue
350 continue
350 try:
351 try:
351 d = (get_mtime(r.spath), util.makedate()[1])
352 d = (get_mtime(r.spath), util.makedate()[1])
352 except OSError:
353 except OSError:
353 continue
354 continue
354
355
355 contact = get_contact(get)
356 contact = get_contact(get)
356 description = get("web", "description", "")
357 description = get("web", "description", "")
357 name = get("web", "name", name)
358 name = get("web", "name", name)
358 row = dict(contact=contact or "unknown",
359 row = dict(contact=contact or "unknown",
359 contact_sort=contact.upper() or "unknown",
360 contact_sort=contact.upper() or "unknown",
360 name=name,
361 name=name,
361 name_sort=name,
362 name_sort=name,
362 url=url,
363 url=url,
363 description=description or "unknown",
364 description=description or "unknown",
364 description_sort=description.upper() or "unknown",
365 description_sort=description.upper() or "unknown",
365 lastchange=d,
366 lastchange=d,
366 lastchange_sort=d[1]-d[0],
367 lastchange_sort=d[1]-d[0],
367 archives=archivelist(u, "tip", url))
368 archives=archivelist(u, "tip", url))
368
369
369 seenrepos.add(name)
370 seenrepos.add(name)
370 yield row
371 yield row
371
372
372 sortdefault = None, False
373 sortdefault = None, False
373 def entries(sortcolumn="", descending=False, subdir="", **map):
374 def entries(sortcolumn="", descending=False, subdir="", **map):
374 rows = rawentries(subdir=subdir, **map)
375 rows = rawentries(subdir=subdir, **map)
375
376
376 if sortcolumn and sortdefault != (sortcolumn, descending):
377 if sortcolumn and sortdefault != (sortcolumn, descending):
377 sortkey = '%s_sort' % sortcolumn
378 sortkey = '%s_sort' % sortcolumn
378 rows = sorted(rows, key=lambda x: x[sortkey],
379 rows = sorted(rows, key=lambda x: x[sortkey],
379 reverse=descending)
380 reverse=descending)
380 for row, parity in zip(rows, paritygen(self.stripecount)):
381 for row, parity in zip(rows, paritygen(self.stripecount)):
381 row['parity'] = parity
382 row['parity'] = parity
382 yield row
383 yield row
383
384
384 self.refresh()
385 self.refresh()
385 sortable = ["name", "description", "contact", "lastchange"]
386 sortable = ["name", "description", "contact", "lastchange"]
386 sortcolumn, descending = sortdefault
387 sortcolumn, descending = sortdefault
387 if 'sort' in req.form:
388 if 'sort' in req.form:
388 sortcolumn = req.form['sort'][0]
389 sortcolumn = req.form['sort'][0]
389 descending = sortcolumn.startswith('-')
390 descending = sortcolumn.startswith('-')
390 if descending:
391 if descending:
391 sortcolumn = sortcolumn[1:]
392 sortcolumn = sortcolumn[1:]
392 if sortcolumn not in sortable:
393 if sortcolumn not in sortable:
393 sortcolumn = ""
394 sortcolumn = ""
394
395
395 sort = [("sort_%s" % column,
396 sort = [("sort_%s" % column,
396 "%s%s" % ((not descending and column == sortcolumn)
397 "%s%s" % ((not descending and column == sortcolumn)
397 and "-" or "", column))
398 and "-" or "", column))
398 for column in sortable]
399 for column in sortable]
399
400
400 self.refresh()
401 self.refresh()
401 self.updatereqenv(req.env)
402 self.updatereqenv(req.env)
402
403
403 return tmpl("index", entries=entries, subdir=subdir,
404 return tmpl("index", entries=entries, subdir=subdir,
404 pathdef=makebreadcrumb('/' + subdir, self.prefix),
405 pathdef=makebreadcrumb('/' + subdir, self.prefix),
405 sortcolumn=sortcolumn, descending=descending,
406 sortcolumn=sortcolumn, descending=descending,
406 **dict(sort))
407 **dict(sort))
407
408
408 def templater(self, req):
409 def templater(self, req):
409
410
410 def header(**map):
411 def header(**map):
411 yield tmpl('header', encoding=encoding.encoding, **map)
412 yield tmpl('header', encoding=encoding.encoding, **map)
412
413
413 def footer(**map):
414 def footer(**map):
414 yield tmpl("footer", **map)
415 yield tmpl("footer", **map)
415
416
416 def motd(**map):
417 def motd(**map):
417 if self.motd is not None:
418 if self.motd is not None:
418 yield self.motd
419 yield self.motd
419 else:
420 else:
420 yield config('web', 'motd', '')
421 yield config('web', 'motd', '')
421
422
422 def config(section, name, default=None, untrusted=True):
423 def config(section, name, default=None, untrusted=True):
423 return self.ui.config(section, name, default, untrusted)
424 return self.ui.config(section, name, default, untrusted)
424
425
425 self.updatereqenv(req.env)
426 self.updatereqenv(req.env)
426
427
427 url = req.env.get('SCRIPT_NAME', '')
428 url = req.env.get('SCRIPT_NAME', '')
428 if not url.endswith('/'):
429 if not url.endswith('/'):
429 url += '/'
430 url += '/'
430
431
431 vars = {}
432 vars = {}
432 styles = (
433 styles = (
433 req.form.get('style', [None])[0],
434 req.form.get('style', [None])[0],
434 config('web', 'style'),
435 config('web', 'style'),
435 'paper'
436 'paper'
436 )
437 )
437 style, mapfile = templater.stylemap(styles, self.templatepath)
438 style, mapfile = templater.stylemap(styles, self.templatepath)
438 if style == styles[0]:
439 if style == styles[0]:
439 vars['style'] = style
440 vars['style'] = style
440
441
441 start = url[-1] == '?' and '&' or '?'
442 start = url[-1] == '?' and '&' or '?'
442 sessionvars = webutil.sessionvars(vars, start)
443 sessionvars = webutil.sessionvars(vars, start)
443 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
444 logourl = config('web', 'logourl', 'http://mercurial.selenic.com/')
444 logoimg = config('web', 'logoimg', 'hglogo.png')
445 logoimg = config('web', 'logoimg', 'hglogo.png')
445 staticurl = config('web', 'staticurl') or url + 'static/'
446 staticurl = config('web', 'staticurl') or url + 'static/'
446 if not staticurl.endswith('/'):
447 if not staticurl.endswith('/'):
447 staticurl += '/'
448 staticurl += '/'
448
449
449 tmpl = templater.templater(mapfile,
450 tmpl = templater.templater(mapfile,
450 defaults={"header": header,
451 defaults={"header": header,
451 "footer": footer,
452 "footer": footer,
452 "motd": motd,
453 "motd": motd,
453 "url": url,
454 "url": url,
454 "logourl": logourl,
455 "logourl": logourl,
455 "logoimg": logoimg,
456 "logoimg": logoimg,
456 "staticurl": staticurl,
457 "staticurl": staticurl,
457 "sessionvars": sessionvars})
458 "sessionvars": sessionvars})
458 return tmpl
459 return tmpl
459
460
460 def updatereqenv(self, env):
461 def updatereqenv(self, env):
461 if self._baseurl is not None:
462 if self._baseurl is not None:
462 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
463 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
463 env['SERVER_NAME'] = name
464 env['SERVER_NAME'] = name
464 env['SERVER_PORT'] = port
465 env['SERVER_PORT'] = port
465 env['SCRIPT_NAME'] = path
466 env['SCRIPT_NAME'] = path
@@ -1,1002 +1,1003 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os, mimetypes, re, cgi, copy
8 import os, mimetypes, re, cgi, copy
9 import webutil
9 import webutil
10 from mercurial import error, encoding, archival, templater, templatefilters
10 from mercurial import error, encoding, archival, templater, templatefilters
11 from mercurial.node import short, hex, nullid
11 from mercurial.node import short, hex, nullid
12 from mercurial.util import binary
12 from mercurial.util import binary
13 from common import paritygen, staticfile, get_contact, ErrorResponse
13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 from mercurial import graphmod, patch
15 from mercurial import graphmod, patch
16 from mercurial import help as helpmod
16 from mercurial import help as helpmod
17 from mercurial import scmutil
17 from mercurial import scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 # __all__ is populated with the allowed commands. Be sure to add to it if
20 # __all__ is populated with the allowed commands. Be sure to add to it if
21 # you're adding a new command, or the new command won't work.
21 # you're adding a new command, or the new command won't work.
22
22
23 __all__ = [
23 __all__ = [
24 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
25 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
25 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
26 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
26 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
27 ]
27 ]
28
28
29 def log(web, req, tmpl):
29 def log(web, req, tmpl):
30 if 'file' in req.form and req.form['file'][0]:
30 if 'file' in req.form and req.form['file'][0]:
31 return filelog(web, req, tmpl)
31 return filelog(web, req, tmpl)
32 else:
32 else:
33 return changelog(web, req, tmpl)
33 return changelog(web, req, tmpl)
34
34
35 def rawfile(web, req, tmpl):
35 def rawfile(web, req, tmpl):
36 guessmime = web.configbool('web', 'guessmime', False)
36 guessmime = web.configbool('web', 'guessmime', False)
37
37
38 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
38 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
39 if not path:
39 if not path:
40 content = manifest(web, req, tmpl)
40 content = manifest(web, req, tmpl)
41 req.respond(HTTP_OK, web.ctype)
41 req.respond(HTTP_OK, web.ctype)
42 return content
42 return content
43
43
44 try:
44 try:
45 fctx = webutil.filectx(web.repo, req)
45 fctx = webutil.filectx(web.repo, req)
46 except error.LookupError, inst:
46 except error.LookupError, inst:
47 try:
47 try:
48 content = manifest(web, req, tmpl)
48 content = manifest(web, req, tmpl)
49 req.respond(HTTP_OK, web.ctype)
49 req.respond(HTTP_OK, web.ctype)
50 return content
50 return content
51 except ErrorResponse:
51 except ErrorResponse:
52 raise inst
52 raise inst
53
53
54 path = fctx.path()
54 path = fctx.path()
55 text = fctx.data()
55 text = fctx.data()
56 mt = 'application/binary'
56 mt = 'application/binary'
57 if guessmime:
57 if guessmime:
58 mt = mimetypes.guess_type(path)[0]
58 mt = mimetypes.guess_type(path)[0]
59 if mt is None:
59 if mt is None:
60 mt = binary(text) and 'application/binary' or 'text/plain'
60 mt = binary(text) and 'application/binary' or 'text/plain'
61 if mt.startswith('text/'):
61 if mt.startswith('text/'):
62 mt += '; charset="%s"' % encoding.encoding
62 mt += '; charset="%s"' % encoding.encoding
63
63
64 req.respond(HTTP_OK, mt, path, body=text)
64 req.respond(HTTP_OK, mt, path, body=text)
65 return []
65 return []
66
66
67 def _filerevision(web, tmpl, fctx):
67 def _filerevision(web, tmpl, fctx):
68 f = fctx.path()
68 f = fctx.path()
69 text = fctx.data()
69 text = fctx.data()
70 parity = paritygen(web.stripecount)
70 parity = paritygen(web.stripecount)
71
71
72 if binary(text):
72 if binary(text):
73 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
73 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
74 text = '(binary:%s)' % mt
74 text = '(binary:%s)' % mt
75
75
76 def lines():
76 def lines():
77 for lineno, t in enumerate(text.splitlines(True)):
77 for lineno, t in enumerate(text.splitlines(True)):
78 yield {"line": t,
78 yield {"line": t,
79 "lineid": "l%d" % (lineno + 1),
79 "lineid": "l%d" % (lineno + 1),
80 "linenumber": "% 6d" % (lineno + 1),
80 "linenumber": "% 6d" % (lineno + 1),
81 "parity": parity.next()}
81 "parity": parity.next()}
82
82
83 return tmpl("filerevision",
83 return tmpl("filerevision",
84 file=f,
84 file=f,
85 path=webutil.up(f),
85 path=webutil.up(f),
86 text=lines(),
86 text=lines(),
87 rev=fctx.rev(),
87 rev=fctx.rev(),
88 node=fctx.hex(),
88 node=fctx.hex(),
89 author=fctx.user(),
89 author=fctx.user(),
90 date=fctx.date(),
90 date=fctx.date(),
91 desc=fctx.description(),
91 desc=fctx.description(),
92 extra=fctx.extra(),
92 extra=fctx.extra(),
93 branch=webutil.nodebranchnodefault(fctx),
93 branch=webutil.nodebranchnodefault(fctx),
94 parent=webutil.parents(fctx),
94 parent=webutil.parents(fctx),
95 child=webutil.children(fctx),
95 child=webutil.children(fctx),
96 rename=webutil.renamelink(fctx),
96 rename=webutil.renamelink(fctx),
97 permissions=fctx.manifest().flags(f))
97 permissions=fctx.manifest().flags(f))
98
98
99 def file(web, req, tmpl):
99 def file(web, req, tmpl):
100 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
100 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
101 if not path:
101 if not path:
102 return manifest(web, req, tmpl)
102 return manifest(web, req, tmpl)
103 try:
103 try:
104 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
104 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
105 except error.LookupError, inst:
105 except error.LookupError, inst:
106 try:
106 try:
107 return manifest(web, req, tmpl)
107 return manifest(web, req, tmpl)
108 except ErrorResponse:
108 except ErrorResponse:
109 raise inst
109 raise inst
110
110
111 def _search(web, req, tmpl):
111 def _search(web, req, tmpl):
112
112
113 query = req.form['rev'][0]
113 query = req.form['rev'][0]
114 revcount = web.maxchanges
114 revcount = web.maxchanges
115 if 'revcount' in req.form:
115 if 'revcount' in req.form:
116 revcount = int(req.form.get('revcount', [revcount])[0])
116 revcount = int(req.form.get('revcount', [revcount])[0])
117 revcount = max(revcount, 1)
117 revcount = max(revcount, 1)
118 tmpl.defaults['sessionvars']['revcount'] = revcount
118 tmpl.defaults['sessionvars']['revcount'] = revcount
119
119
120 lessvars = copy.copy(tmpl.defaults['sessionvars'])
120 lessvars = copy.copy(tmpl.defaults['sessionvars'])
121 lessvars['revcount'] = max(revcount / 2, 1)
121 lessvars['revcount'] = max(revcount / 2, 1)
122 lessvars['rev'] = query
122 lessvars['rev'] = query
123 morevars = copy.copy(tmpl.defaults['sessionvars'])
123 morevars = copy.copy(tmpl.defaults['sessionvars'])
124 morevars['revcount'] = revcount * 2
124 morevars['revcount'] = revcount * 2
125 morevars['rev'] = query
125 morevars['rev'] = query
126
126
127 def changelist(**map):
127 def changelist(**map):
128 count = 0
128 count = 0
129 lower = encoding.lower
129 lower = encoding.lower
130 qw = lower(query).split()
130 qw = lower(query).split()
131
131
132 def revgen():
132 def revgen():
133 cl = web.repo.changelog
133 cl = web.repo.changelog
134 for i in xrange(len(web.repo) - 1, 0, -100):
134 for i in xrange(len(web.repo) - 1, 0, -100):
135 l = []
135 l = []
136 for j in cl.revs(max(0, i - 100), i + 1):
136 for j in cl.revs(max(0, i - 100), i + 1):
137 ctx = web.repo[j]
137 ctx = web.repo[j]
138 l.append(ctx)
138 l.append(ctx)
139 l.reverse()
139 l.reverse()
140 for e in l:
140 for e in l:
141 yield e
141 yield e
142
142
143 for ctx in revgen():
143 for ctx in revgen():
144 miss = 0
144 miss = 0
145 for q in qw:
145 for q in qw:
146 if not (q in lower(ctx.user()) or
146 if not (q in lower(ctx.user()) or
147 q in lower(ctx.description()) or
147 q in lower(ctx.description()) or
148 q in lower(" ".join(ctx.files()))):
148 q in lower(" ".join(ctx.files()))):
149 miss = 1
149 miss = 1
150 break
150 break
151 if miss:
151 if miss:
152 continue
152 continue
153
153
154 count += 1
154 count += 1
155 n = ctx.node()
155 n = ctx.node()
156 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
156 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
157 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
157 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
158
158
159 yield tmpl('searchentry',
159 yield tmpl('searchentry',
160 parity=parity.next(),
160 parity=parity.next(),
161 author=ctx.user(),
161 author=ctx.user(),
162 parent=webutil.parents(ctx),
162 parent=webutil.parents(ctx),
163 child=webutil.children(ctx),
163 child=webutil.children(ctx),
164 changelogtag=showtags,
164 changelogtag=showtags,
165 desc=ctx.description(),
165 desc=ctx.description(),
166 extra=ctx.extra(),
166 extra=ctx.extra(),
167 date=ctx.date(),
167 date=ctx.date(),
168 files=files,
168 files=files,
169 rev=ctx.rev(),
169 rev=ctx.rev(),
170 node=hex(n),
170 node=hex(n),
171 tags=webutil.nodetagsdict(web.repo, n),
171 tags=webutil.nodetagsdict(web.repo, n),
172 bookmarks=webutil.nodebookmarksdict(web.repo, n),
172 bookmarks=webutil.nodebookmarksdict(web.repo, n),
173 inbranch=webutil.nodeinbranch(web.repo, ctx),
173 inbranch=webutil.nodeinbranch(web.repo, ctx),
174 branches=webutil.nodebranchdict(web.repo, ctx))
174 branches=webutil.nodebranchdict(web.repo, ctx))
175
175
176 if count >= revcount:
176 if count >= revcount:
177 break
177 break
178
178
179 tip = web.repo['tip']
179 tip = web.repo['tip']
180 parity = paritygen(web.stripecount)
180 parity = paritygen(web.stripecount)
181
181
182 return tmpl('search', query=query, node=tip.hex(),
182 return tmpl('search', query=query, node=tip.hex(),
183 entries=changelist, archives=web.archivelist("tip"),
183 entries=changelist, archives=web.archivelist("tip"),
184 morevars=morevars, lessvars=lessvars)
184 morevars=morevars, lessvars=lessvars)
185
185
186 def changelog(web, req, tmpl, shortlog=False):
186 def changelog(web, req, tmpl, shortlog=False):
187
187
188 if 'node' in req.form:
188 if 'node' in req.form:
189 ctx = webutil.changectx(web.repo, req)
189 ctx = webutil.changectx(web.repo, req)
190 else:
190 else:
191 if 'rev' in req.form:
191 if 'rev' in req.form:
192 hi = req.form['rev'][0]
192 hi = req.form['rev'][0]
193 else:
193 else:
194 hi = 'tip'
194 hi = 'tip'
195 try:
195 try:
196 ctx = web.repo[hi]
196 ctx = web.repo[hi]
197 except error.RepoError:
197 except error.RepoError:
198 return _search(web, req, tmpl) # XXX redirect to 404 page?
198 return _search(web, req, tmpl) # XXX redirect to 404 page?
199
199
200 def changelist(latestonly, **map):
200 def changelist(latestonly, **map):
201 l = [] # build a list in forward order for efficiency
201 l = [] # build a list in forward order for efficiency
202 revs = []
202 revs = []
203 if start < end:
203 if start < end:
204 revs = web.repo.changelog.revs(start, end - 1)
204 revs = web.repo.changelog.revs(start, end - 1)
205 if latestonly:
205 if latestonly:
206 for r in revs:
206 for r in revs:
207 pass
207 pass
208 revs = (r,)
208 revs = (r,)
209 for i in revs:
209 for i in revs:
210 ctx = web.repo[i]
210 ctx = web.repo[i]
211 n = ctx.node()
211 n = ctx.node()
212 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
212 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
213 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
213 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
214
214
215 l.append({"parity": parity.next(),
215 l.append({"parity": parity.next(),
216 "author": ctx.user(),
216 "author": ctx.user(),
217 "parent": webutil.parents(ctx, i - 1),
217 "parent": webutil.parents(ctx, i - 1),
218 "child": webutil.children(ctx, i + 1),
218 "child": webutil.children(ctx, i + 1),
219 "changelogtag": showtags,
219 "changelogtag": showtags,
220 "desc": ctx.description(),
220 "desc": ctx.description(),
221 "extra": ctx.extra(),
221 "extra": ctx.extra(),
222 "date": ctx.date(),
222 "date": ctx.date(),
223 "files": files,
223 "files": files,
224 "rev": i,
224 "rev": i,
225 "node": hex(n),
225 "node": hex(n),
226 "tags": webutil.nodetagsdict(web.repo, n),
226 "tags": webutil.nodetagsdict(web.repo, n),
227 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
227 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
228 "inbranch": webutil.nodeinbranch(web.repo, ctx),
228 "inbranch": webutil.nodeinbranch(web.repo, ctx),
229 "branches": webutil.nodebranchdict(web.repo, ctx)
229 "branches": webutil.nodebranchdict(web.repo, ctx)
230 })
230 })
231 for e in reversed(l):
231 for e in reversed(l):
232 yield e
232 yield e
233
233
234 revcount = shortlog and web.maxshortchanges or web.maxchanges
234 revcount = shortlog and web.maxshortchanges or web.maxchanges
235 if 'revcount' in req.form:
235 if 'revcount' in req.form:
236 revcount = int(req.form.get('revcount', [revcount])[0])
236 revcount = int(req.form.get('revcount', [revcount])[0])
237 revcount = max(revcount, 1)
237 revcount = max(revcount, 1)
238 tmpl.defaults['sessionvars']['revcount'] = revcount
238 tmpl.defaults['sessionvars']['revcount'] = revcount
239
239
240 lessvars = copy.copy(tmpl.defaults['sessionvars'])
240 lessvars = copy.copy(tmpl.defaults['sessionvars'])
241 lessvars['revcount'] = max(revcount / 2, 1)
241 lessvars['revcount'] = max(revcount / 2, 1)
242 morevars = copy.copy(tmpl.defaults['sessionvars'])
242 morevars = copy.copy(tmpl.defaults['sessionvars'])
243 morevars['revcount'] = revcount * 2
243 morevars['revcount'] = revcount * 2
244
244
245 count = len(web.repo)
245 count = len(web.repo)
246 pos = ctx.rev()
246 pos = ctx.rev()
247 start = max(0, pos - revcount + 1)
247 start = max(0, pos - revcount + 1)
248 end = min(count, start + revcount)
248 end = min(count, start + revcount)
249 pos = end - 1
249 pos = end - 1
250 parity = paritygen(web.stripecount, offset=start - end)
250 parity = paritygen(web.stripecount, offset=start - end)
251
251
252 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
252 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
253
253
254 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
254 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
255 node=ctx.hex(), rev=pos, changesets=count,
255 node=ctx.hex(), rev=pos, changesets=count,
256 entries=lambda **x: changelist(latestonly=False, **x),
256 entries=lambda **x: changelist(latestonly=False, **x),
257 latestentry=lambda **x: changelist(latestonly=True, **x),
257 latestentry=lambda **x: changelist(latestonly=True, **x),
258 archives=web.archivelist("tip"), revcount=revcount,
258 archives=web.archivelist("tip"), revcount=revcount,
259 morevars=morevars, lessvars=lessvars)
259 morevars=morevars, lessvars=lessvars)
260
260
261 def shortlog(web, req, tmpl):
261 def shortlog(web, req, tmpl):
262 return changelog(web, req, tmpl, shortlog = True)
262 return changelog(web, req, tmpl, shortlog = True)
263
263
264 def changeset(web, req, tmpl):
264 def changeset(web, req, tmpl):
265 ctx = webutil.changectx(web.repo, req)
265 ctx = webutil.changectx(web.repo, req)
266 basectx = webutil.basechangectx(web.repo, req)
266 basectx = webutil.basechangectx(web.repo, req)
267 if basectx is None:
267 if basectx is None:
268 basectx = ctx.p1()
268 basectx = ctx.p1()
269 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
269 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
270 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
270 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
271 ctx.node())
271 ctx.node())
272 showbranch = webutil.nodebranchnodefault(ctx)
272 showbranch = webutil.nodebranchnodefault(ctx)
273
273
274 files = []
274 files = []
275 parity = paritygen(web.stripecount)
275 parity = paritygen(web.stripecount)
276 for blockno, f in enumerate(ctx.files()):
276 for blockno, f in enumerate(ctx.files()):
277 template = f in ctx and 'filenodelink' or 'filenolink'
277 template = f in ctx and 'filenodelink' or 'filenolink'
278 files.append(tmpl(template,
278 files.append(tmpl(template,
279 node=ctx.hex(), file=f, blockno=blockno + 1,
279 node=ctx.hex(), file=f, blockno=blockno + 1,
280 parity=parity.next()))
280 parity=parity.next()))
281
281
282 style = web.config('web', 'style', 'paper')
282 style = web.config('web', 'style', 'paper')
283 if 'style' in req.form:
283 if 'style' in req.form:
284 style = req.form['style'][0]
284 style = req.form['style'][0]
285
285
286 parity = paritygen(web.stripecount)
286 parity = paritygen(web.stripecount)
287 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
287 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
288
288
289 parity = paritygen(web.stripecount)
289 parity = paritygen(web.stripecount)
290 diffstatgen = webutil.diffstatgen(ctx, basectx)
290 diffstatgen = webutil.diffstatgen(ctx, basectx)
291 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
291 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
292
292
293 return tmpl('changeset',
293 return tmpl('changeset',
294 diff=diffs,
294 diff=diffs,
295 rev=ctx.rev(),
295 rev=ctx.rev(),
296 node=ctx.hex(),
296 node=ctx.hex(),
297 parent=webutil.parents(ctx),
297 parent=webutil.parents(ctx),
298 child=webutil.children(ctx),
298 child=webutil.children(ctx),
299 basenode=basectx.hex(),
299 basenode=basectx.hex(),
300 changesettag=showtags,
300 changesettag=showtags,
301 changesetbookmark=showbookmarks,
301 changesetbookmark=showbookmarks,
302 changesetbranch=showbranch,
302 changesetbranch=showbranch,
303 author=ctx.user(),
303 author=ctx.user(),
304 desc=ctx.description(),
304 desc=ctx.description(),
305 extra=ctx.extra(),
305 extra=ctx.extra(),
306 date=ctx.date(),
306 date=ctx.date(),
307 files=files,
307 files=files,
308 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
308 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
309 diffstat=diffstat,
309 diffstat=diffstat,
310 archives=web.archivelist(ctx.hex()),
310 archives=web.archivelist(ctx.hex()),
311 tags=webutil.nodetagsdict(web.repo, ctx.node()),
311 tags=webutil.nodetagsdict(web.repo, ctx.node()),
312 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
312 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
313 branch=webutil.nodebranchnodefault(ctx),
313 branch=webutil.nodebranchnodefault(ctx),
314 inbranch=webutil.nodeinbranch(web.repo, ctx),
314 inbranch=webutil.nodeinbranch(web.repo, ctx),
315 branches=webutil.nodebranchdict(web.repo, ctx))
315 branches=webutil.nodebranchdict(web.repo, ctx))
316
316
317 rev = changeset
317 rev = changeset
318
318
319 def decodepath(path):
319 def decodepath(path):
320 """Hook for mapping a path in the repository to a path in the
320 """Hook for mapping a path in the repository to a path in the
321 working copy.
321 working copy.
322
322
323 Extensions (e.g., largefiles) can override this to remap files in
323 Extensions (e.g., largefiles) can override this to remap files in
324 the virtual file system presented by the manifest command below."""
324 the virtual file system presented by the manifest command below."""
325 return path
325 return path
326
326
327 def manifest(web, req, tmpl):
327 def manifest(web, req, tmpl):
328 ctx = webutil.changectx(web.repo, req)
328 ctx = webutil.changectx(web.repo, req)
329 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
329 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
330 mf = ctx.manifest()
330 mf = ctx.manifest()
331 node = ctx.node()
331 node = ctx.node()
332
332
333 files = {}
333 files = {}
334 dirs = {}
334 dirs = {}
335 parity = paritygen(web.stripecount)
335 parity = paritygen(web.stripecount)
336
336
337 if path and path[-1] != "/":
337 if path and path[-1] != "/":
338 path += "/"
338 path += "/"
339 l = len(path)
339 l = len(path)
340 abspath = "/" + path
340 abspath = "/" + path
341
341
342 for full, n in mf.iteritems():
342 for full, n in mf.iteritems():
343 # the virtual path (working copy path) used for the full
343 # the virtual path (working copy path) used for the full
344 # (repository) path
344 # (repository) path
345 f = decodepath(full)
345 f = decodepath(full)
346
346
347 if f[:l] != path:
347 if f[:l] != path:
348 continue
348 continue
349 remain = f[l:]
349 remain = f[l:]
350 elements = remain.split('/')
350 elements = remain.split('/')
351 if len(elements) == 1:
351 if len(elements) == 1:
352 files[remain] = full
352 files[remain] = full
353 else:
353 else:
354 h = dirs # need to retain ref to dirs (root)
354 h = dirs # need to retain ref to dirs (root)
355 for elem in elements[0:-1]:
355 for elem in elements[0:-1]:
356 if elem not in h:
356 if elem not in h:
357 h[elem] = {}
357 h[elem] = {}
358 h = h[elem]
358 h = h[elem]
359 if len(h) > 1:
359 if len(h) > 1:
360 break
360 break
361 h[None] = None # denotes files present
361 h[None] = None # denotes files present
362
362
363 if mf and not files and not dirs:
363 if mf and not files and not dirs:
364 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
364 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
365
365
366 def filelist(**map):
366 def filelist(**map):
367 for f in sorted(files):
367 for f in sorted(files):
368 full = files[f]
368 full = files[f]
369
369
370 fctx = ctx.filectx(full)
370 fctx = ctx.filectx(full)
371 yield {"file": full,
371 yield {"file": full,
372 "parity": parity.next(),
372 "parity": parity.next(),
373 "basename": f,
373 "basename": f,
374 "date": fctx.date(),
374 "date": fctx.date(),
375 "size": fctx.size(),
375 "size": fctx.size(),
376 "permissions": mf.flags(full)}
376 "permissions": mf.flags(full)}
377
377
378 def dirlist(**map):
378 def dirlist(**map):
379 for d in sorted(dirs):
379 for d in sorted(dirs):
380
380
381 emptydirs = []
381 emptydirs = []
382 h = dirs[d]
382 h = dirs[d]
383 while isinstance(h, dict) and len(h) == 1:
383 while isinstance(h, dict) and len(h) == 1:
384 k, v = h.items()[0]
384 k, v = h.items()[0]
385 if v:
385 if v:
386 emptydirs.append(k)
386 emptydirs.append(k)
387 h = v
387 h = v
388
388
389 path = "%s%s" % (abspath, d)
389 path = "%s%s" % (abspath, d)
390 yield {"parity": parity.next(),
390 yield {"parity": parity.next(),
391 "path": path,
391 "path": path,
392 "emptydirs": "/".join(emptydirs),
392 "emptydirs": "/".join(emptydirs),
393 "basename": d}
393 "basename": d}
394
394
395 return tmpl("manifest",
395 return tmpl("manifest",
396 rev=ctx.rev(),
396 rev=ctx.rev(),
397 node=hex(node),
397 node=hex(node),
398 path=abspath,
398 path=abspath,
399 up=webutil.up(abspath),
399 up=webutil.up(abspath),
400 upparity=parity.next(),
400 upparity=parity.next(),
401 fentries=filelist,
401 fentries=filelist,
402 dentries=dirlist,
402 dentries=dirlist,
403 archives=web.archivelist(hex(node)),
403 archives=web.archivelist(hex(node)),
404 tags=webutil.nodetagsdict(web.repo, node),
404 tags=webutil.nodetagsdict(web.repo, node),
405 bookmarks=webutil.nodebookmarksdict(web.repo, node),
405 bookmarks=webutil.nodebookmarksdict(web.repo, node),
406 inbranch=webutil.nodeinbranch(web.repo, ctx),
406 inbranch=webutil.nodeinbranch(web.repo, ctx),
407 branches=webutil.nodebranchdict(web.repo, ctx))
407 branches=webutil.nodebranchdict(web.repo, ctx))
408
408
409 def tags(web, req, tmpl):
409 def tags(web, req, tmpl):
410 i = list(reversed(web.repo.tagslist()))
410 i = list(reversed(web.repo.tagslist()))
411 parity = paritygen(web.stripecount)
411 parity = paritygen(web.stripecount)
412
412
413 def entries(notip, latestonly, **map):
413 def entries(notip, latestonly, **map):
414 t = i
414 t = i
415 if notip:
415 if notip:
416 t = [(k, n) for k, n in i if k != "tip"]
416 t = [(k, n) for k, n in i if k != "tip"]
417 if latestonly:
417 if latestonly:
418 t = t[:1]
418 t = t[:1]
419 for k, n in t:
419 for k, n in t:
420 yield {"parity": parity.next(),
420 yield {"parity": parity.next(),
421 "tag": k,
421 "tag": k,
422 "date": web.repo[n].date(),
422 "date": web.repo[n].date(),
423 "node": hex(n)}
423 "node": hex(n)}
424
424
425 return tmpl("tags",
425 return tmpl("tags",
426 node=hex(web.repo.changelog.tip()),
426 node=hex(web.repo.changelog.tip()),
427 entries=lambda **x: entries(False, False, **x),
427 entries=lambda **x: entries(False, False, **x),
428 entriesnotip=lambda **x: entries(True, False, **x),
428 entriesnotip=lambda **x: entries(True, False, **x),
429 latestentry=lambda **x: entries(True, True, **x))
429 latestentry=lambda **x: entries(True, True, **x))
430
430
431 def bookmarks(web, req, tmpl):
431 def bookmarks(web, req, tmpl):
432 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
432 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
433 parity = paritygen(web.stripecount)
433 parity = paritygen(web.stripecount)
434
434
435 def entries(latestonly, **map):
435 def entries(latestonly, **map):
436 if latestonly:
436 if latestonly:
437 t = [min(i)]
437 t = [min(i)]
438 else:
438 else:
439 t = sorted(i)
439 t = sorted(i)
440 for k, n in t:
440 for k, n in t:
441 yield {"parity": parity.next(),
441 yield {"parity": parity.next(),
442 "bookmark": k,
442 "bookmark": k,
443 "date": web.repo[n].date(),
443 "date": web.repo[n].date(),
444 "node": hex(n)}
444 "node": hex(n)}
445
445
446 return tmpl("bookmarks",
446 return tmpl("bookmarks",
447 node=hex(web.repo.changelog.tip()),
447 node=hex(web.repo.changelog.tip()),
448 entries=lambda **x: entries(latestonly=False, **x),
448 entries=lambda **x: entries(latestonly=False, **x),
449 latestentry=lambda **x: entries(latestonly=True, **x))
449 latestentry=lambda **x: entries(latestonly=True, **x))
450
450
451 def branches(web, req, tmpl):
451 def branches(web, req, tmpl):
452 tips = []
452 tips = []
453 heads = web.repo.heads()
453 heads = web.repo.heads()
454 parity = paritygen(web.stripecount)
454 parity = paritygen(web.stripecount)
455 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
455 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
456
456
457 def entries(limit, **map):
457 def entries(limit, **map):
458 count = 0
458 count = 0
459 if not tips:
459 if not tips:
460 for t, n in web.repo.branchtags().iteritems():
460 for t, n in web.repo.branchtags().iteritems():
461 tips.append(web.repo[n])
461 tips.append(web.repo[n])
462 for ctx in sorted(tips, key=sortkey, reverse=True):
462 for ctx in sorted(tips, key=sortkey, reverse=True):
463 if limit > 0 and count >= limit:
463 if limit > 0 and count >= limit:
464 return
464 return
465 count += 1
465 count += 1
466 if not web.repo.branchheads(ctx.branch()):
466 if not web.repo.branchheads(ctx.branch()):
467 status = 'closed'
467 status = 'closed'
468 elif ctx.node() not in heads:
468 elif ctx.node() not in heads:
469 status = 'inactive'
469 status = 'inactive'
470 else:
470 else:
471 status = 'open'
471 status = 'open'
472 yield {'parity': parity.next(),
472 yield {'parity': parity.next(),
473 'branch': ctx.branch(),
473 'branch': ctx.branch(),
474 'status': status,
474 'status': status,
475 'node': ctx.hex(),
475 'node': ctx.hex(),
476 'date': ctx.date()}
476 'date': ctx.date()}
477
477
478 return tmpl('branches', node=hex(web.repo.changelog.tip()),
478 return tmpl('branches', node=hex(web.repo.changelog.tip()),
479 entries=lambda **x: entries(0, **x),
479 entries=lambda **x: entries(0, **x),
480 latestentry=lambda **x: entries(1, **x))
480 latestentry=lambda **x: entries(1, **x))
481
481
482 def summary(web, req, tmpl):
482 def summary(web, req, tmpl):
483 i = reversed(web.repo.tagslist())
483 i = reversed(web.repo.tagslist())
484
484
485 def tagentries(**map):
485 def tagentries(**map):
486 parity = paritygen(web.stripecount)
486 parity = paritygen(web.stripecount)
487 count = 0
487 count = 0
488 for k, n in i:
488 for k, n in i:
489 if k == "tip": # skip tip
489 if k == "tip": # skip tip
490 continue
490 continue
491
491
492 count += 1
492 count += 1
493 if count > 10: # limit to 10 tags
493 if count > 10: # limit to 10 tags
494 break
494 break
495
495
496 yield tmpl("tagentry",
496 yield tmpl("tagentry",
497 parity=parity.next(),
497 parity=parity.next(),
498 tag=k,
498 tag=k,
499 node=hex(n),
499 node=hex(n),
500 date=web.repo[n].date())
500 date=web.repo[n].date())
501
501
502 def bookmarks(**map):
502 def bookmarks(**map):
503 parity = paritygen(web.stripecount)
503 parity = paritygen(web.stripecount)
504 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
504 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
505 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
505 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
506 yield {'parity': parity.next(),
506 yield {'parity': parity.next(),
507 'bookmark': k,
507 'bookmark': k,
508 'date': web.repo[n].date(),
508 'date': web.repo[n].date(),
509 'node': hex(n)}
509 'node': hex(n)}
510
510
511 def branches(**map):
511 def branches(**map):
512 parity = paritygen(web.stripecount)
512 parity = paritygen(web.stripecount)
513
513
514 b = web.repo.branchtags()
514 b = web.repo.branchtags()
515 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
515 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
516 for r, n, t in sorted(l):
516 for r, n, t in sorted(l):
517 yield {'parity': parity.next(),
517 yield {'parity': parity.next(),
518 'branch': t,
518 'branch': t,
519 'node': hex(n),
519 'node': hex(n),
520 'date': web.repo[n].date()}
520 'date': web.repo[n].date()}
521
521
522 def changelist(**map):
522 def changelist(**map):
523 parity = paritygen(web.stripecount, offset=start - end)
523 parity = paritygen(web.stripecount, offset=start - end)
524 l = [] # build a list in forward order for efficiency
524 l = [] # build a list in forward order for efficiency
525 revs = []
525 revs = []
526 if start < end:
526 if start < end:
527 revs = web.repo.changelog.revs(start, end - 1)
527 revs = web.repo.changelog.revs(start, end - 1)
528 for i in revs:
528 for i in revs:
529 ctx = web.repo[i]
529 ctx = web.repo[i]
530 n = ctx.node()
530 n = ctx.node()
531 hn = hex(n)
531 hn = hex(n)
532
532
533 l.append(tmpl(
533 l.append(tmpl(
534 'shortlogentry',
534 'shortlogentry',
535 parity=parity.next(),
535 parity=parity.next(),
536 author=ctx.user(),
536 author=ctx.user(),
537 desc=ctx.description(),
537 desc=ctx.description(),
538 extra=ctx.extra(),
538 extra=ctx.extra(),
539 date=ctx.date(),
539 date=ctx.date(),
540 rev=i,
540 rev=i,
541 node=hn,
541 node=hn,
542 tags=webutil.nodetagsdict(web.repo, n),
542 tags=webutil.nodetagsdict(web.repo, n),
543 bookmarks=webutil.nodebookmarksdict(web.repo, n),
543 bookmarks=webutil.nodebookmarksdict(web.repo, n),
544 inbranch=webutil.nodeinbranch(web.repo, ctx),
544 inbranch=webutil.nodeinbranch(web.repo, ctx),
545 branches=webutil.nodebranchdict(web.repo, ctx)))
545 branches=webutil.nodebranchdict(web.repo, ctx)))
546
546
547 l.reverse()
547 l.reverse()
548 yield l
548 yield l
549
549
550 tip = web.repo['tip']
550 tip = web.repo['tip']
551 count = len(web.repo)
551 count = len(web.repo)
552 start = max(0, count - web.maxchanges)
552 start = max(0, count - web.maxchanges)
553 end = min(count, start + web.maxchanges)
553 end = min(count, start + web.maxchanges)
554
554
555 return tmpl("summary",
555 return tmpl("summary",
556 desc=web.config("web", "description", "unknown"),
556 desc=web.config("web", "description", "unknown"),
557 owner=get_contact(web.config) or "unknown",
557 owner=get_contact(web.config) or "unknown",
558 lastchange=tip.date(),
558 lastchange=tip.date(),
559 tags=tagentries,
559 tags=tagentries,
560 bookmarks=bookmarks,
560 bookmarks=bookmarks,
561 branches=branches,
561 branches=branches,
562 shortlog=changelist,
562 shortlog=changelist,
563 node=tip.hex(),
563 node=tip.hex(),
564 archives=web.archivelist("tip"))
564 archives=web.archivelist("tip"))
565
565
566 def filediff(web, req, tmpl):
566 def filediff(web, req, tmpl):
567 fctx, ctx = None, None
567 fctx, ctx = None, None
568 try:
568 try:
569 fctx = webutil.filectx(web.repo, req)
569 fctx = webutil.filectx(web.repo, req)
570 except LookupError:
570 except LookupError:
571 ctx = webutil.changectx(web.repo, req)
571 ctx = webutil.changectx(web.repo, req)
572 path = webutil.cleanpath(web.repo, req.form['file'][0])
572 path = webutil.cleanpath(web.repo, req.form['file'][0])
573 if path not in ctx.files():
573 if path not in ctx.files():
574 raise
574 raise
575
575
576 if fctx is not None:
576 if fctx is not None:
577 n = fctx.node()
577 n = fctx.node()
578 path = fctx.path()
578 path = fctx.path()
579 ctx = fctx.changectx()
579 ctx = fctx.changectx()
580 else:
580 else:
581 n = ctx.node()
581 n = ctx.node()
582 # path already defined in except clause
582 # path already defined in except clause
583
583
584 parity = paritygen(web.stripecount)
584 parity = paritygen(web.stripecount)
585 style = web.config('web', 'style', 'paper')
585 style = web.config('web', 'style', 'paper')
586 if 'style' in req.form:
586 if 'style' in req.form:
587 style = req.form['style'][0]
587 style = req.form['style'][0]
588
588
589 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
589 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
590 rename = fctx and webutil.renamelink(fctx) or []
590 rename = fctx and webutil.renamelink(fctx) or []
591 ctx = fctx and fctx or ctx
591 ctx = fctx and fctx or ctx
592 return tmpl("filediff",
592 return tmpl("filediff",
593 file=path,
593 file=path,
594 node=hex(n),
594 node=hex(n),
595 rev=ctx.rev(),
595 rev=ctx.rev(),
596 date=ctx.date(),
596 date=ctx.date(),
597 desc=ctx.description(),
597 desc=ctx.description(),
598 extra=ctx.extra(),
598 extra=ctx.extra(),
599 author=ctx.user(),
599 author=ctx.user(),
600 rename=rename,
600 rename=rename,
601 branch=webutil.nodebranchnodefault(ctx),
601 branch=webutil.nodebranchnodefault(ctx),
602 parent=webutil.parents(ctx),
602 parent=webutil.parents(ctx),
603 child=webutil.children(ctx),
603 child=webutil.children(ctx),
604 diff=diffs)
604 diff=diffs)
605
605
606 diff = filediff
606 diff = filediff
607
607
608 def comparison(web, req, tmpl):
608 def comparison(web, req, tmpl):
609 ctx = webutil.changectx(web.repo, req)
609 ctx = webutil.changectx(web.repo, req)
610 if 'file' not in req.form:
610 if 'file' not in req.form:
611 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
611 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
612 path = webutil.cleanpath(web.repo, req.form['file'][0])
612 path = webutil.cleanpath(web.repo, req.form['file'][0])
613 rename = path in ctx and webutil.renamelink(ctx[path]) or []
613 rename = path in ctx and webutil.renamelink(ctx[path]) or []
614
614
615 parsecontext = lambda v: v == 'full' and -1 or int(v)
615 parsecontext = lambda v: v == 'full' and -1 or int(v)
616 if 'context' in req.form:
616 if 'context' in req.form:
617 context = parsecontext(req.form['context'][0])
617 context = parsecontext(req.form['context'][0])
618 else:
618 else:
619 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
619 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
620
620
621 def filelines(f):
621 def filelines(f):
622 if binary(f.data()):
622 if binary(f.data()):
623 mt = mimetypes.guess_type(f.path())[0]
623 mt = mimetypes.guess_type(f.path())[0]
624 if not mt:
624 if not mt:
625 mt = 'application/octet-stream'
625 mt = 'application/octet-stream'
626 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
626 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
627 return f.data().splitlines()
627 return f.data().splitlines()
628
628
629 if path in ctx:
629 if path in ctx:
630 fctx = ctx[path]
630 fctx = ctx[path]
631 rightrev = fctx.filerev()
631 rightrev = fctx.filerev()
632 rightnode = fctx.filenode()
632 rightnode = fctx.filenode()
633 rightlines = filelines(fctx)
633 rightlines = filelines(fctx)
634 parents = fctx.parents()
634 parents = fctx.parents()
635 if not parents:
635 if not parents:
636 leftrev = -1
636 leftrev = -1
637 leftnode = nullid
637 leftnode = nullid
638 leftlines = ()
638 leftlines = ()
639 else:
639 else:
640 pfctx = parents[0]
640 pfctx = parents[0]
641 leftrev = pfctx.filerev()
641 leftrev = pfctx.filerev()
642 leftnode = pfctx.filenode()
642 leftnode = pfctx.filenode()
643 leftlines = filelines(pfctx)
643 leftlines = filelines(pfctx)
644 else:
644 else:
645 rightrev = -1
645 rightrev = -1
646 rightnode = nullid
646 rightnode = nullid
647 rightlines = ()
647 rightlines = ()
648 fctx = ctx.parents()[0][path]
648 fctx = ctx.parents()[0][path]
649 leftrev = fctx.filerev()
649 leftrev = fctx.filerev()
650 leftnode = fctx.filenode()
650 leftnode = fctx.filenode()
651 leftlines = filelines(fctx)
651 leftlines = filelines(fctx)
652
652
653 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
653 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
654 return tmpl('filecomparison',
654 return tmpl('filecomparison',
655 file=path,
655 file=path,
656 node=hex(ctx.node()),
656 node=hex(ctx.node()),
657 rev=ctx.rev(),
657 rev=ctx.rev(),
658 date=ctx.date(),
658 date=ctx.date(),
659 desc=ctx.description(),
659 desc=ctx.description(),
660 extra=ctx.extra(),
660 extra=ctx.extra(),
661 author=ctx.user(),
661 author=ctx.user(),
662 rename=rename,
662 rename=rename,
663 branch=webutil.nodebranchnodefault(ctx),
663 branch=webutil.nodebranchnodefault(ctx),
664 parent=webutil.parents(fctx),
664 parent=webutil.parents(fctx),
665 child=webutil.children(fctx),
665 child=webutil.children(fctx),
666 leftrev=leftrev,
666 leftrev=leftrev,
667 leftnode=hex(leftnode),
667 leftnode=hex(leftnode),
668 rightrev=rightrev,
668 rightrev=rightrev,
669 rightnode=hex(rightnode),
669 rightnode=hex(rightnode),
670 comparison=comparison)
670 comparison=comparison)
671
671
672 def annotate(web, req, tmpl):
672 def annotate(web, req, tmpl):
673 fctx = webutil.filectx(web.repo, req)
673 fctx = webutil.filectx(web.repo, req)
674 f = fctx.path()
674 f = fctx.path()
675 parity = paritygen(web.stripecount)
675 parity = paritygen(web.stripecount)
676 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
676 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
677
677
678 def annotate(**map):
678 def annotate(**map):
679 last = None
679 last = None
680 if binary(fctx.data()):
680 if binary(fctx.data()):
681 mt = (mimetypes.guess_type(fctx.path())[0]
681 mt = (mimetypes.guess_type(fctx.path())[0]
682 or 'application/octet-stream')
682 or 'application/octet-stream')
683 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
683 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
684 '(binary:%s)' % mt)])
684 '(binary:%s)' % mt)])
685 else:
685 else:
686 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
686 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
687 diffopts=diffopts))
687 diffopts=diffopts))
688 for lineno, ((f, targetline), l) in lines:
688 for lineno, ((f, targetline), l) in lines:
689 fnode = f.filenode()
689 fnode = f.filenode()
690
690
691 if last != fnode:
691 if last != fnode:
692 last = fnode
692 last = fnode
693
693
694 yield {"parity": parity.next(),
694 yield {"parity": parity.next(),
695 "node": f.hex(),
695 "node": f.hex(),
696 "rev": f.rev(),
696 "rev": f.rev(),
697 "author": f.user(),
697 "author": f.user(),
698 "desc": f.description(),
698 "desc": f.description(),
699 "extra": f.extra(),
699 "extra": f.extra(),
700 "file": f.path(),
700 "file": f.path(),
701 "targetline": targetline,
701 "targetline": targetline,
702 "line": l,
702 "line": l,
703 "lineid": "l%d" % (lineno + 1),
703 "lineid": "l%d" % (lineno + 1),
704 "linenumber": "% 6d" % (lineno + 1),
704 "linenumber": "% 6d" % (lineno + 1),
705 "revdate": f.date()}
705 "revdate": f.date()}
706
706
707 return tmpl("fileannotate",
707 return tmpl("fileannotate",
708 file=f,
708 file=f,
709 annotate=annotate,
709 annotate=annotate,
710 path=webutil.up(f),
710 path=webutil.up(f),
711 rev=fctx.rev(),
711 rev=fctx.rev(),
712 node=fctx.hex(),
712 node=fctx.hex(),
713 author=fctx.user(),
713 author=fctx.user(),
714 date=fctx.date(),
714 date=fctx.date(),
715 desc=fctx.description(),
715 desc=fctx.description(),
716 extra=fctx.extra(),
716 extra=fctx.extra(),
717 rename=webutil.renamelink(fctx),
717 rename=webutil.renamelink(fctx),
718 branch=webutil.nodebranchnodefault(fctx),
718 branch=webutil.nodebranchnodefault(fctx),
719 parent=webutil.parents(fctx),
719 parent=webutil.parents(fctx),
720 child=webutil.children(fctx),
720 child=webutil.children(fctx),
721 permissions=fctx.manifest().flags(f))
721 permissions=fctx.manifest().flags(f))
722
722
723 def filelog(web, req, tmpl):
723 def filelog(web, req, tmpl):
724
724
725 try:
725 try:
726 fctx = webutil.filectx(web.repo, req)
726 fctx = webutil.filectx(web.repo, req)
727 f = fctx.path()
727 f = fctx.path()
728 fl = fctx.filelog()
728 fl = fctx.filelog()
729 except error.LookupError:
729 except error.LookupError:
730 f = webutil.cleanpath(web.repo, req.form['file'][0])
730 f = webutil.cleanpath(web.repo, req.form['file'][0])
731 fl = web.repo.file(f)
731 fl = web.repo.file(f)
732 numrevs = len(fl)
732 numrevs = len(fl)
733 if not numrevs: # file doesn't exist at all
733 if not numrevs: # file doesn't exist at all
734 raise
734 raise
735 rev = webutil.changectx(web.repo, req).rev()
735 rev = webutil.changectx(web.repo, req).rev()
736 first = fl.linkrev(0)
736 first = fl.linkrev(0)
737 if rev < first: # current rev is from before file existed
737 if rev < first: # current rev is from before file existed
738 raise
738 raise
739 frev = numrevs - 1
739 frev = numrevs - 1
740 while fl.linkrev(frev) > rev:
740 while fl.linkrev(frev) > rev:
741 frev -= 1
741 frev -= 1
742 fctx = web.repo.filectx(f, fl.linkrev(frev))
742 fctx = web.repo.filectx(f, fl.linkrev(frev))
743
743
744 revcount = web.maxshortchanges
744 revcount = web.maxshortchanges
745 if 'revcount' in req.form:
745 if 'revcount' in req.form:
746 revcount = int(req.form.get('revcount', [revcount])[0])
746 revcount = int(req.form.get('revcount', [revcount])[0])
747 revcount = max(revcount, 1)
747 revcount = max(revcount, 1)
748 tmpl.defaults['sessionvars']['revcount'] = revcount
748 tmpl.defaults['sessionvars']['revcount'] = revcount
749
749
750 lessvars = copy.copy(tmpl.defaults['sessionvars'])
750 lessvars = copy.copy(tmpl.defaults['sessionvars'])
751 lessvars['revcount'] = max(revcount / 2, 1)
751 lessvars['revcount'] = max(revcount / 2, 1)
752 morevars = copy.copy(tmpl.defaults['sessionvars'])
752 morevars = copy.copy(tmpl.defaults['sessionvars'])
753 morevars['revcount'] = revcount * 2
753 morevars['revcount'] = revcount * 2
754
754
755 count = fctx.filerev() + 1
755 count = fctx.filerev() + 1
756 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
756 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
757 end = min(count, start + revcount) # last rev on this page
757 end = min(count, start + revcount) # last rev on this page
758 parity = paritygen(web.stripecount, offset=start - end)
758 parity = paritygen(web.stripecount, offset=start - end)
759
759
760 def entries(latestonly, **map):
760 def entries(latestonly, **map):
761 l = []
761 l = []
762
762
763 repo = web.repo
763 repo = web.repo
764 revs = repo.changelog.revs(start, end - 1)
764 revs = repo.changelog.revs(start, end - 1)
765 if latestonly:
765 if latestonly:
766 for r in revs:
766 for r in revs:
767 pass
767 pass
768 revs = (r,)
768 revs = (r,)
769 for i in revs:
769 for i in revs:
770 iterfctx = fctx.filectx(i)
770 iterfctx = fctx.filectx(i)
771
771
772 l.append({"parity": parity.next(),
772 l.append({"parity": parity.next(),
773 "filerev": i,
773 "filerev": i,
774 "file": f,
774 "file": f,
775 "node": iterfctx.hex(),
775 "node": iterfctx.hex(),
776 "author": iterfctx.user(),
776 "author": iterfctx.user(),
777 "date": iterfctx.date(),
777 "date": iterfctx.date(),
778 "rename": webutil.renamelink(iterfctx),
778 "rename": webutil.renamelink(iterfctx),
779 "parent": webutil.parents(iterfctx),
779 "parent": webutil.parents(iterfctx),
780 "child": webutil.children(iterfctx),
780 "child": webutil.children(iterfctx),
781 "desc": iterfctx.description(),
781 "desc": iterfctx.description(),
782 "extra": iterfctx.extra(),
782 "extra": iterfctx.extra(),
783 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
783 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
784 "bookmarks": webutil.nodebookmarksdict(
784 "bookmarks": webutil.nodebookmarksdict(
785 repo, iterfctx.node()),
785 repo, iterfctx.node()),
786 "branch": webutil.nodebranchnodefault(iterfctx),
786 "branch": webutil.nodebranchnodefault(iterfctx),
787 "inbranch": webutil.nodeinbranch(repo, iterfctx),
787 "inbranch": webutil.nodeinbranch(repo, iterfctx),
788 "branches": webutil.nodebranchdict(repo, iterfctx)})
788 "branches": webutil.nodebranchdict(repo, iterfctx)})
789 for e in reversed(l):
789 for e in reversed(l):
790 yield e
790 yield e
791
791
792 revnav = webutil.filerevnav(web.repo, fctx.path())
792 revnav = webutil.filerevnav(web.repo, fctx.path())
793 nav = revnav.gen(end - 1, revcount, count)
793 nav = revnav.gen(end - 1, revcount, count)
794 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
794 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
795 entries=lambda **x: entries(latestonly=False, **x),
795 entries=lambda **x: entries(latestonly=False, **x),
796 latestentry=lambda **x: entries(latestonly=True, **x),
796 latestentry=lambda **x: entries(latestonly=True, **x),
797 revcount=revcount, morevars=morevars, lessvars=lessvars)
797 revcount=revcount, morevars=morevars, lessvars=lessvars)
798
798
799 def archive(web, req, tmpl):
799 def archive(web, req, tmpl):
800 type_ = req.form.get('type', [None])[0]
800 type_ = req.form.get('type', [None])[0]
801 allowed = web.configlist("web", "allow_archive")
801 allowed = web.configlist("web", "allow_archive")
802 key = req.form['node'][0]
802 key = req.form['node'][0]
803
803
804 if type_ not in web.archives:
804 if type_ not in web.archives:
805 msg = 'Unsupported archive type: %s' % type_
805 msg = 'Unsupported archive type: %s' % type_
806 raise ErrorResponse(HTTP_NOT_FOUND, msg)
806 raise ErrorResponse(HTTP_NOT_FOUND, msg)
807
807
808 if not ((type_ in allowed or
808 if not ((type_ in allowed or
809 web.configbool("web", "allow" + type_, False))):
809 web.configbool("web", "allow" + type_, False))):
810 msg = 'Archive type not allowed: %s' % type_
810 msg = 'Archive type not allowed: %s' % type_
811 raise ErrorResponse(HTTP_FORBIDDEN, msg)
811 raise ErrorResponse(HTTP_FORBIDDEN, msg)
812
812
813 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
813 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
814 cnode = web.repo.lookup(key)
814 cnode = web.repo.lookup(key)
815 arch_version = key
815 arch_version = key
816 if cnode == key or key == 'tip':
816 if cnode == key or key == 'tip':
817 arch_version = short(cnode)
817 arch_version = short(cnode)
818 name = "%s-%s" % (reponame, arch_version)
818 name = "%s-%s" % (reponame, arch_version)
819 mimetype, artype, extension, encoding = web.archive_specs[type_]
819 mimetype, artype, extension, encoding = web.archive_specs[type_]
820 headers = [
820 headers = [
821 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
821 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
822 ]
822 ]
823 if encoding:
823 if encoding:
824 headers.append(('Content-Encoding', encoding))
824 headers.append(('Content-Encoding', encoding))
825 req.headers.extend(headers)
825 req.headers.extend(headers)
826 req.respond(HTTP_OK, mimetype)
826 req.respond(HTTP_OK, mimetype)
827
827
828 ctx = webutil.changectx(web.repo, req)
828 ctx = webutil.changectx(web.repo, req)
829 archival.archive(web.repo, req, cnode, artype, prefix=name,
829 archival.archive(web.repo, req, cnode, artype, prefix=name,
830 matchfn=scmutil.match(ctx, []),
830 matchfn=scmutil.match(ctx, []),
831 subrepos=web.configbool("web", "archivesubrepos"))
831 subrepos=web.configbool("web", "archivesubrepos"))
832 return []
832 return []
833
833
834
834
835 def static(web, req, tmpl):
835 def static(web, req, tmpl):
836 fname = req.form['file'][0]
836 fname = req.form['file'][0]
837 # a repo owner may set web.static in .hg/hgrc to get any file
837 # a repo owner may set web.static in .hg/hgrc to get any file
838 # readable by the user running the CGI script
838 # readable by the user running the CGI script
839 static = web.config("web", "static", None, untrusted=False)
839 static = web.config("web", "static", None, untrusted=False)
840 if not static:
840 if not static:
841 tp = web.templatepath or templater.templatepath()
841 tp = web.templatepath or templater.templatepath()
842 if isinstance(tp, str):
842 if isinstance(tp, str):
843 tp = [tp]
843 tp = [tp]
844 static = [os.path.join(p, 'static') for p in tp]
844 static = [os.path.join(p, 'static') for p in tp]
845 return [staticfile(static, fname, req)]
845 staticfile(static, fname, req)
846 return []
846
847
847 def graph(web, req, tmpl):
848 def graph(web, req, tmpl):
848
849
849 ctx = webutil.changectx(web.repo, req)
850 ctx = webutil.changectx(web.repo, req)
850 rev = ctx.rev()
851 rev = ctx.rev()
851
852
852 bg_height = 39
853 bg_height = 39
853 revcount = web.maxshortchanges
854 revcount = web.maxshortchanges
854 if 'revcount' in req.form:
855 if 'revcount' in req.form:
855 revcount = int(req.form.get('revcount', [revcount])[0])
856 revcount = int(req.form.get('revcount', [revcount])[0])
856 revcount = max(revcount, 1)
857 revcount = max(revcount, 1)
857 tmpl.defaults['sessionvars']['revcount'] = revcount
858 tmpl.defaults['sessionvars']['revcount'] = revcount
858
859
859 lessvars = copy.copy(tmpl.defaults['sessionvars'])
860 lessvars = copy.copy(tmpl.defaults['sessionvars'])
860 lessvars['revcount'] = max(revcount / 2, 1)
861 lessvars['revcount'] = max(revcount / 2, 1)
861 morevars = copy.copy(tmpl.defaults['sessionvars'])
862 morevars = copy.copy(tmpl.defaults['sessionvars'])
862 morevars['revcount'] = revcount * 2
863 morevars['revcount'] = revcount * 2
863
864
864 count = len(web.repo)
865 count = len(web.repo)
865 pos = rev
866 pos = rev
866 start = max(0, pos - revcount + 1)
867 start = max(0, pos - revcount + 1)
867 end = min(count, start + revcount)
868 end = min(count, start + revcount)
868 pos = end - 1
869 pos = end - 1
869
870
870 uprev = min(max(0, count - 1), rev + revcount)
871 uprev = min(max(0, count - 1), rev + revcount)
871 downrev = max(0, rev - revcount)
872 downrev = max(0, rev - revcount)
872 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
873 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
873
874
874 tree = []
875 tree = []
875 if start < end:
876 if start < end:
876 revs = list(web.repo.changelog.revs(end - 1, start))
877 revs = list(web.repo.changelog.revs(end - 1, start))
877 dag = graphmod.dagwalker(web.repo, revs)
878 dag = graphmod.dagwalker(web.repo, revs)
878 tree = list(graphmod.colored(dag, web.repo))
879 tree = list(graphmod.colored(dag, web.repo))
879
880
880 def getcolumns(tree):
881 def getcolumns(tree):
881 cols = 0
882 cols = 0
882 for (id, type, ctx, vtx, edges) in tree:
883 for (id, type, ctx, vtx, edges) in tree:
883 if type != graphmod.CHANGESET:
884 if type != graphmod.CHANGESET:
884 continue
885 continue
885 cols = max(cols, max([edge[0] for edge in edges] or [0]),
886 cols = max(cols, max([edge[0] for edge in edges] or [0]),
886 max([edge[1] for edge in edges] or [0]))
887 max([edge[1] for edge in edges] or [0]))
887 return cols
888 return cols
888
889
889 def graphdata(usetuples, **map):
890 def graphdata(usetuples, **map):
890 data = []
891 data = []
891
892
892 row = 0
893 row = 0
893 for (id, type, ctx, vtx, edges) in tree:
894 for (id, type, ctx, vtx, edges) in tree:
894 if type != graphmod.CHANGESET:
895 if type != graphmod.CHANGESET:
895 continue
896 continue
896 node = str(ctx)
897 node = str(ctx)
897 age = templatefilters.age(ctx.date())
898 age = templatefilters.age(ctx.date())
898 desc = templatefilters.firstline(ctx.description())
899 desc = templatefilters.firstline(ctx.description())
899 desc = cgi.escape(templatefilters.nonempty(desc))
900 desc = cgi.escape(templatefilters.nonempty(desc))
900 user = cgi.escape(templatefilters.person(ctx.user()))
901 user = cgi.escape(templatefilters.person(ctx.user()))
901 branch = ctx.branch()
902 branch = ctx.branch()
902 try:
903 try:
903 branchnode = web.repo.branchtip(branch)
904 branchnode = web.repo.branchtip(branch)
904 except error.RepoLookupError:
905 except error.RepoLookupError:
905 branchnode = None
906 branchnode = None
906 branch = branch, branchnode == ctx.node()
907 branch = branch, branchnode == ctx.node()
907
908
908 if usetuples:
909 if usetuples:
909 data.append((node, vtx, edges, desc, user, age, branch,
910 data.append((node, vtx, edges, desc, user, age, branch,
910 ctx.tags(), ctx.bookmarks()))
911 ctx.tags(), ctx.bookmarks()))
911 else:
912 else:
912 edgedata = [dict(col=edge[0], nextcol=edge[1],
913 edgedata = [dict(col=edge[0], nextcol=edge[1],
913 color=(edge[2] - 1) % 6 + 1,
914 color=(edge[2] - 1) % 6 + 1,
914 width=edge[3], bcolor=edge[4])
915 width=edge[3], bcolor=edge[4])
915 for edge in edges]
916 for edge in edges]
916
917
917 data.append(
918 data.append(
918 dict(node=node,
919 dict(node=node,
919 col=vtx[0],
920 col=vtx[0],
920 color=(vtx[1] - 1) % 6 + 1,
921 color=(vtx[1] - 1) % 6 + 1,
921 edges=edgedata,
922 edges=edgedata,
922 row=row,
923 row=row,
923 nextrow=row + 1,
924 nextrow=row + 1,
924 desc=desc,
925 desc=desc,
925 user=user,
926 user=user,
926 age=age,
927 age=age,
927 bookmarks=webutil.nodebookmarksdict(
928 bookmarks=webutil.nodebookmarksdict(
928 web.repo, ctx.node()),
929 web.repo, ctx.node()),
929 branches=webutil.nodebranchdict(web.repo, ctx),
930 branches=webutil.nodebranchdict(web.repo, ctx),
930 inbranch=webutil.nodeinbranch(web.repo, ctx),
931 inbranch=webutil.nodeinbranch(web.repo, ctx),
931 tags=webutil.nodetagsdict(web.repo, ctx.node())))
932 tags=webutil.nodetagsdict(web.repo, ctx.node())))
932
933
933 row += 1
934 row += 1
934
935
935 return data
936 return data
936
937
937 cols = getcolumns(tree)
938 cols = getcolumns(tree)
938 rows = len(tree)
939 rows = len(tree)
939 canvasheight = (rows + 1) * bg_height - 27
940 canvasheight = (rows + 1) * bg_height - 27
940
941
941 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
942 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
942 lessvars=lessvars, morevars=morevars, downrev=downrev,
943 lessvars=lessvars, morevars=morevars, downrev=downrev,
943 cols=cols, rows=rows,
944 cols=cols, rows=rows,
944 canvaswidth=(cols + 1) * bg_height,
945 canvaswidth=(cols + 1) * bg_height,
945 truecanvasheight=rows * bg_height,
946 truecanvasheight=rows * bg_height,
946 canvasheight=canvasheight, bg_height=bg_height,
947 canvasheight=canvasheight, bg_height=bg_height,
947 jsdata=lambda **x: graphdata(True, **x),
948 jsdata=lambda **x: graphdata(True, **x),
948 nodes=lambda **x: graphdata(False, **x),
949 nodes=lambda **x: graphdata(False, **x),
949 node=ctx.hex(), changenav=changenav)
950 node=ctx.hex(), changenav=changenav)
950
951
951 def _getdoc(e):
952 def _getdoc(e):
952 doc = e[0].__doc__
953 doc = e[0].__doc__
953 if doc:
954 if doc:
954 doc = _(doc).split('\n')[0]
955 doc = _(doc).split('\n')[0]
955 else:
956 else:
956 doc = _('(no help text available)')
957 doc = _('(no help text available)')
957 return doc
958 return doc
958
959
959 def help(web, req, tmpl):
960 def help(web, req, tmpl):
960 from mercurial import commands # avoid cycle
961 from mercurial import commands # avoid cycle
961
962
962 topicname = req.form.get('node', [None])[0]
963 topicname = req.form.get('node', [None])[0]
963 if not topicname:
964 if not topicname:
964 def topics(**map):
965 def topics(**map):
965 for entries, summary, _ in helpmod.helptable:
966 for entries, summary, _ in helpmod.helptable:
966 yield {'topic': entries[0], 'summary': summary}
967 yield {'topic': entries[0], 'summary': summary}
967
968
968 early, other = [], []
969 early, other = [], []
969 primary = lambda s: s.split('|')[0]
970 primary = lambda s: s.split('|')[0]
970 for c, e in commands.table.iteritems():
971 for c, e in commands.table.iteritems():
971 doc = _getdoc(e)
972 doc = _getdoc(e)
972 if 'DEPRECATED' in doc or c.startswith('debug'):
973 if 'DEPRECATED' in doc or c.startswith('debug'):
973 continue
974 continue
974 cmd = primary(c)
975 cmd = primary(c)
975 if cmd.startswith('^'):
976 if cmd.startswith('^'):
976 early.append((cmd[1:], doc))
977 early.append((cmd[1:], doc))
977 else:
978 else:
978 other.append((cmd, doc))
979 other.append((cmd, doc))
979
980
980 early.sort()
981 early.sort()
981 other.sort()
982 other.sort()
982
983
983 def earlycommands(**map):
984 def earlycommands(**map):
984 for c, doc in early:
985 for c, doc in early:
985 yield {'topic': c, 'summary': doc}
986 yield {'topic': c, 'summary': doc}
986
987
987 def othercommands(**map):
988 def othercommands(**map):
988 for c, doc in other:
989 for c, doc in other:
989 yield {'topic': c, 'summary': doc}
990 yield {'topic': c, 'summary': doc}
990
991
991 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
992 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
992 othercommands=othercommands, title='Index')
993 othercommands=othercommands, title='Index')
993
994
994 u = webutil.wsgiui()
995 u = webutil.wsgiui()
995 u.pushbuffer()
996 u.pushbuffer()
996 u.verbose = True
997 u.verbose = True
997 try:
998 try:
998 commands.help_(u, topicname)
999 commands.help_(u, topicname)
999 except error.UnknownCommand:
1000 except error.UnknownCommand:
1000 raise ErrorResponse(HTTP_NOT_FOUND)
1001 raise ErrorResponse(HTTP_NOT_FOUND)
1001 doc = u.popbuffer()
1002 doc = u.popbuffer()
1002 return tmpl('help', topic=topicname, doc=doc)
1003 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now