##// END OF EJS Templates
hgweb: use patch.diff() to make sensible diffs (issue1223, issue1258)...
Dirkjan Ochtman -
r7309:e74a9173 default
parent child Browse files
Show More
@@ -0,0 +1,34
1 echo % setting up repo
2 hg init test
3 cd test
4 echo a > a
5 echo b > b
6 hg ci -Ama
7
8 echo % change permissions for git diffs
9 chmod 755 a
10 hg ci -Amb
11
12 echo % set up hgweb
13 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
14 cat hg.pid >> $DAEMON_PIDS
15
16 echo % revision
17 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
18
19 echo % diff removed file
20 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
21
22 echo % set up hgweb with git diffs
23 kill `cat hg.pid`
24 hg serve --config 'diff.git=1' -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
25 cat hg.pid >> $DAEMON_PIDS
26
27 echo % revision
28 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/0'
29
30 echo % diff removed file
31 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/tip/a'
32
33 echo % errors
34 cat errors.log
@@ -0,0 +1,272
1 % setting up repo
2 adding a
3 adding b
4 % change permissions for git diffs
5 % set up hgweb
6 % revision
7 200 Script output follows
8
9 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
10 <html>
11 <head>
12 <link rel="icon" href="/static/hgicon.png" type="image/png">
13 <meta name="robots" content="index, nofollow" />
14 <link rel="stylesheet" href="/static/style.css" type="text/css" />
15
16 <title>test: changeset 0cd96de13884</title>
17 </head>
18 <body>
19
20 <div class="buttons">
21 <a href="/log/0">changelog</a>
22 <a href="/shortlog/0">shortlog</a>
23 <a href="/graph">graph</a>
24 <a href="/tags">tags</a>
25 <a href="/file/0cd96de13884">files</a>
26 <a href="/raw-rev/0cd96de13884">raw</a>
27
28 </div>
29
30 <h2>changeset: a</h2>
31
32 <table id="changesetEntry">
33 <tr>
34 <th class="changeset">changeset 0:</th>
35 <td class="changeset"><a href="/rev/0cd96de13884">0cd96de13884</a></td>
36 </tr>
37
38 <tr><th class="child">child 1:</th><td class="child"><a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td></tr>
39
40 <tr>
41 <th class="author">author:</th>
42 <td class="author">&#116;&#101;&#115;&#116;</td>
43 </tr>
44 <tr>
45 <th class="date">date:</th>
46 <td class="date">Thu Jan 01 00:00:00 1970 +0000 (38 years ago)</td>
47 </tr>
48 <tr>
49 <th class="files">files:</th>
50 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
51 </tr>
52 <tr>
53 <th class="description">description:</th>
54 <td class="description">a</td>
55 </tr>
56 </table>
57
58 <div id="changesetDiff">
59 <pre class="parity0"><span class="minusline"><a class="lineno" href="#l1" id="l1"> 1</a>--- /dev/null Thu Jan 01 00:00:00 1970 +0000
60 </span><span class="plusline"><a class="lineno" href="#l2" id="l2"> 2</a>+++ b/a Thu Jan 01 00:00:00 1970 +0000
61 </span><span class="atline"><a class="lineno" href="#l3" id="l3"> 3</a>@@ -0,0 +1,1 @@
62 </span><span class="plusline"><a class="lineno" href="#l4" id="l4"> 4</a>+a
63 </span></pre><pre class="parity1"><span class="minusline"><a class="lineno" href="#l1.1" id="l1.1"> 1.1</a>--- /dev/null Thu Jan 01 00:00:00 1970 +0000
64 </span><span class="plusline"><a class="lineno" href="#l1.2" id="l1.2"> 1.2</a>+++ b/b Thu Jan 01 00:00:00 1970 +0000
65 </span><span class="atline"><a class="lineno" href="#l1.3" id="l1.3"> 1.3</a>@@ -0,0 +1,1 @@
66 </span><span class="plusline"><a class="lineno" href="#l1.4" id="l1.4"> 1.4</a>+b
67 </span></pre>
68 </div>
69
70
71 <div class="logo">
72 <a href="http://www.selenic.com/mercurial/">
73 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
74 </div>
75
76 </body>
77 </html>
78
79
80
81 % diff removed file
82 200 Script output follows
83
84 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
85 <html>
86 <head>
87 <link rel="icon" href="/static/hgicon.png" type="image/png">
88 <meta name="robots" content="index, nofollow" />
89 <link rel="stylesheet" href="/static/style.css" type="text/css" />
90
91 <title>test: a diff</title>
92 </head>
93 <body>
94
95 <div class="buttons">
96 <a href="/log/1">changelog</a>
97 <a href="/shortlog/1">shortlog</a>
98 <a href="/graph">graph</a>
99 <a href="/tags">tags</a>
100 <a href="/rev/78e4ebad7cdf">changeset</a>
101 <a href="/file/78e4ebad7cdf/a">file</a>
102 <a href="/log/78e4ebad7cdf/a">revisions</a>
103 <a href="/annotate/78e4ebad7cdf/a">annotate</a>
104 <a href="/raw-diff/78e4ebad7cdf/a">raw</a>
105 </div>
106
107 <h2>a</h2>
108
109 <table id="filediffEntry">
110 <tr>
111 <th class="revision">revision 1:</th>
112 <td class="revision"><a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
113 </tr>
114
115
116 </table>
117
118 <div id="fileDiff">
119 <pre class="parity0"><span class="minusline"><a class="lineno" href="#l1" id="l1"> 1</a>--- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 </span><span class="plusline"><a class="lineno" href="#l2" id="l2"> 2</a>+++ b/a Thu Jan 01 00:00:00 1970 +0000
121 </span><span class="atline"><a class="lineno" href="#l3" id="l3"> 3</a>@@ -0,0 +1,1 @@
122 </span><span class="plusline"><a class="lineno" href="#l4" id="l4"> 4</a>+a
123 </span></pre>
124 </div>
125
126
127 <div class="logo">
128 <a href="http://www.selenic.com/mercurial/">
129 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
130 </div>
131
132 </body>
133 </html>
134
135
136
137 % set up hgweb with git diffs
138 % revision
139 200 Script output follows
140
141 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
142 <html>
143 <head>
144 <link rel="icon" href="/static/hgicon.png" type="image/png">
145 <meta name="robots" content="index, nofollow" />
146 <link rel="stylesheet" href="/static/style.css" type="text/css" />
147
148 <title>test: changeset 0cd96de13884</title>
149 </head>
150 <body>
151
152 <div class="buttons">
153 <a href="/log/0">changelog</a>
154 <a href="/shortlog/0">shortlog</a>
155 <a href="/graph">graph</a>
156 <a href="/tags">tags</a>
157 <a href="/file/0cd96de13884">files</a>
158 <a href="/raw-rev/0cd96de13884">raw</a>
159
160 </div>
161
162 <h2>changeset: a</h2>
163
164 <table id="changesetEntry">
165 <tr>
166 <th class="changeset">changeset 0:</th>
167 <td class="changeset"><a href="/rev/0cd96de13884">0cd96de13884</a></td>
168 </tr>
169
170 <tr><th class="child">child 1:</th><td class="child"><a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td></tr>
171
172 <tr>
173 <th class="author">author:</th>
174 <td class="author">&#116;&#101;&#115;&#116;</td>
175 </tr>
176 <tr>
177 <th class="date">date:</th>
178 <td class="date">Thu Jan 01 00:00:00 1970 +0000 (38 years ago)</td>
179 </tr>
180 <tr>
181 <th class="files">files:</th>
182 <td class="files"><a href="/file/0cd96de13884/a">a</a> <a href="/file/0cd96de13884/b">b</a> </td>
183 </tr>
184 <tr>
185 <th class="description">description:</th>
186 <td class="description">a</td>
187 </tr>
188 </table>
189
190 <div id="changesetDiff">
191 <pre class="parity0"><a class="lineno" href="#l1" id="l1"> 1</a>new file mode 100644
192 <span class="minusline"><a class="lineno" href="#l2" id="l2"> 2</a>--- /dev/null
193 </span><span class="plusline"><a class="lineno" href="#l3" id="l3"> 3</a>+++ b/a
194 </span><span class="atline"><a class="lineno" href="#l4" id="l4"> 4</a>@@ -0,0 +1,1 @@
195 </span><span class="plusline"><a class="lineno" href="#l5" id="l5"> 5</a>+a
196 </span></pre><pre class="parity1"><a class="lineno" href="#l1.1" id="l1.1"> 1.1</a>new file mode 100644
197 <span class="minusline"><a class="lineno" href="#l1.2" id="l1.2"> 1.2</a>--- /dev/null
198 </span><span class="plusline"><a class="lineno" href="#l1.3" id="l1.3"> 1.3</a>+++ b/b
199 </span><span class="atline"><a class="lineno" href="#l1.4" id="l1.4"> 1.4</a>@@ -0,0 +1,1 @@
200 </span><span class="plusline"><a class="lineno" href="#l1.5" id="l1.5"> 1.5</a>+b
201 </span></pre>
202 </div>
203
204
205 <div class="logo">
206 <a href="http://www.selenic.com/mercurial/">
207 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
208 </div>
209
210 </body>
211 </html>
212
213
214
215 % diff removed file
216 200 Script output follows
217
218 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
219 <html>
220 <head>
221 <link rel="icon" href="/static/hgicon.png" type="image/png">
222 <meta name="robots" content="index, nofollow" />
223 <link rel="stylesheet" href="/static/style.css" type="text/css" />
224
225 <title>test: a diff</title>
226 </head>
227 <body>
228
229 <div class="buttons">
230 <a href="/log/1">changelog</a>
231 <a href="/shortlog/1">shortlog</a>
232 <a href="/graph">graph</a>
233 <a href="/tags">tags</a>
234 <a href="/rev/78e4ebad7cdf">changeset</a>
235 <a href="/file/78e4ebad7cdf/a">file</a>
236 <a href="/log/78e4ebad7cdf/a">revisions</a>
237 <a href="/annotate/78e4ebad7cdf/a">annotate</a>
238 <a href="/raw-diff/78e4ebad7cdf/a">raw</a>
239 </div>
240
241 <h2>a</h2>
242
243 <table id="filediffEntry">
244 <tr>
245 <th class="revision">revision 1:</th>
246 <td class="revision"><a href="/rev/78e4ebad7cdf">78e4ebad7cdf</a></td>
247 </tr>
248
249
250 </table>
251
252 <div id="fileDiff">
253 <pre class="parity0"><a class="lineno" href="#l1" id="l1"> 1</a>new file mode 100755
254 <span class="minusline"><a class="lineno" href="#l2" id="l2"> 2</a>--- /dev/null
255 </span><span class="plusline"><a class="lineno" href="#l3" id="l3"> 3</a>+++ b/a
256 </span><span class="atline"><a class="lineno" href="#l4" id="l4"> 4</a>@@ -0,0 +1,1 @@
257 </span><span class="plusline"><a class="lineno" href="#l5" id="l5"> 5</a>+a
258 </span></pre>
259 </div>
260
261
262 <div class="logo">
263 <a href="http://www.selenic.com/mercurial/">
264 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
265 </div>
266
267 </body>
268 </html>
269
270
271
272 % errors
@@ -1,381 +1,356
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, patch, hook
12 from mercurial import ui, hg, util, patch, hook, match
13 from mercurial import revlog, templater, templatefilters
13 from mercurial import revlog, templater, templatefilters
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
16 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
17 from request import wsgirequest
17 from request import wsgirequest
18 import webcommands, protocol, webutil
18 import webcommands, protocol, webutil
19
19
20 perms = {
20 perms = {
21 'changegroup': 'pull',
21 'changegroup': 'pull',
22 'changegroupsubset': 'pull',
22 'changegroupsubset': 'pull',
23 'unbundle': 'push',
23 'unbundle': 'push',
24 'stream_out': 'pull',
24 'stream_out': 'pull',
25 }
25 }
26
26
27 class hgweb(object):
27 class hgweb(object):
28 def __init__(self, repo, name=None):
28 def __init__(self, repo, name=None):
29 if isinstance(repo, str):
29 if isinstance(repo, str):
30 parentui = ui.ui(report_untrusted=False, interactive=False)
30 parentui = ui.ui(report_untrusted=False, interactive=False)
31 self.repo = hg.repository(parentui, repo)
31 self.repo = hg.repository(parentui, repo)
32 else:
32 else:
33 self.repo = repo
33 self.repo = repo
34
34
35 hook.redirect(True)
35 hook.redirect(True)
36 self.mtime = -1
36 self.mtime = -1
37 self.reponame = name
37 self.reponame = name
38 self.archives = 'zip', 'gz', 'bz2'
38 self.archives = 'zip', 'gz', 'bz2'
39 self.stripecount = 1
39 self.stripecount = 1
40 # a repo owner may set web.templates in .hg/hgrc to get any file
40 # a repo owner may set web.templates in .hg/hgrc to get any file
41 # readable by the user running the CGI script
41 # readable by the user running the CGI script
42 self.templatepath = self.config("web", "templates",
42 self.templatepath = self.config("web", "templates",
43 templater.templatepath(),
43 templater.templatepath(),
44 untrusted=False)
44 untrusted=False)
45
45
46 # The CGI scripts are often run by a user different from the repo owner.
46 # The CGI scripts are often run by a user different from the repo owner.
47 # Trust the settings from the .hg/hgrc files by default.
47 # Trust the settings from the .hg/hgrc files by default.
48 def config(self, section, name, default=None, untrusted=True):
48 def config(self, section, name, default=None, untrusted=True):
49 return self.repo.ui.config(section, name, default,
49 return self.repo.ui.config(section, name, default,
50 untrusted=untrusted)
50 untrusted=untrusted)
51
51
52 def configbool(self, section, name, default=False, untrusted=True):
52 def configbool(self, section, name, default=False, untrusted=True):
53 return self.repo.ui.configbool(section, name, default,
53 return self.repo.ui.configbool(section, name, default,
54 untrusted=untrusted)
54 untrusted=untrusted)
55
55
56 def configlist(self, section, name, default=None, untrusted=True):
56 def configlist(self, section, name, default=None, untrusted=True):
57 return self.repo.ui.configlist(section, name, default,
57 return self.repo.ui.configlist(section, name, default,
58 untrusted=untrusted)
58 untrusted=untrusted)
59
59
60 def refresh(self):
60 def refresh(self):
61 mtime = get_mtime(self.repo.root)
61 mtime = get_mtime(self.repo.root)
62 if mtime != self.mtime:
62 if mtime != self.mtime:
63 self.mtime = mtime
63 self.mtime = mtime
64 self.repo = hg.repository(self.repo.ui, self.repo.root)
64 self.repo = hg.repository(self.repo.ui, self.repo.root)
65 self.maxchanges = int(self.config("web", "maxchanges", 10))
65 self.maxchanges = int(self.config("web", "maxchanges", 10))
66 self.stripecount = int(self.config("web", "stripes", 1))
66 self.stripecount = int(self.config("web", "stripes", 1))
67 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
67 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
68 self.maxfiles = int(self.config("web", "maxfiles", 10))
68 self.maxfiles = int(self.config("web", "maxfiles", 10))
69 self.allowpull = self.configbool("web", "allowpull", True)
69 self.allowpull = self.configbool("web", "allowpull", True)
70 self.encoding = self.config("web", "encoding", util._encoding)
70 self.encoding = self.config("web", "encoding", util._encoding)
71
71
72 def run(self):
72 def run(self):
73 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
73 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
74 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
74 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
75 import mercurial.hgweb.wsgicgi as wsgicgi
75 import mercurial.hgweb.wsgicgi as wsgicgi
76 wsgicgi.launch(self)
76 wsgicgi.launch(self)
77
77
78 def __call__(self, env, respond):
78 def __call__(self, env, respond):
79 req = wsgirequest(env, respond)
79 req = wsgirequest(env, respond)
80 return self.run_wsgi(req)
80 return self.run_wsgi(req)
81
81
82 def run_wsgi(self, req):
82 def run_wsgi(self, req):
83
83
84 self.refresh()
84 self.refresh()
85
85
86 # process this if it's a protocol request
86 # process this if it's a protocol request
87 # protocol bits don't need to create any URLs
87 # protocol bits don't need to create any URLs
88 # and the clients always use the old URL structure
88 # and the clients always use the old URL structure
89
89
90 cmd = req.form.get('cmd', [''])[0]
90 cmd = req.form.get('cmd', [''])[0]
91 if cmd and cmd in protocol.__all__:
91 if cmd and cmd in protocol.__all__:
92 try:
92 try:
93 if cmd in perms:
93 if cmd in perms:
94 try:
94 try:
95 self.check_perm(req, perms[cmd])
95 self.check_perm(req, perms[cmd])
96 except ErrorResponse, inst:
96 except ErrorResponse, inst:
97 if cmd == 'unbundle':
97 if cmd == 'unbundle':
98 req.drain()
98 req.drain()
99 raise
99 raise
100 method = getattr(protocol, cmd)
100 method = getattr(protocol, cmd)
101 return method(self.repo, req)
101 return method(self.repo, req)
102 except ErrorResponse, inst:
102 except ErrorResponse, inst:
103 req.respond(inst.code, protocol.HGTYPE)
103 req.respond(inst.code, protocol.HGTYPE)
104 if not inst.message:
104 if not inst.message:
105 return []
105 return []
106 return '0\n%s\n' % inst.message,
106 return '0\n%s\n' % inst.message,
107
107
108 # work with CGI variables to create coherent structure
108 # work with CGI variables to create coherent structure
109 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
109 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
110
110
111 req.url = req.env['SCRIPT_NAME']
111 req.url = req.env['SCRIPT_NAME']
112 if not req.url.endswith('/'):
112 if not req.url.endswith('/'):
113 req.url += '/'
113 req.url += '/'
114 if 'REPO_NAME' in req.env:
114 if 'REPO_NAME' in req.env:
115 req.url += req.env['REPO_NAME'] + '/'
115 req.url += req.env['REPO_NAME'] + '/'
116
116
117 if 'PATH_INFO' in req.env:
117 if 'PATH_INFO' in req.env:
118 parts = req.env['PATH_INFO'].strip('/').split('/')
118 parts = req.env['PATH_INFO'].strip('/').split('/')
119 repo_parts = req.env.get('REPO_NAME', '').split('/')
119 repo_parts = req.env.get('REPO_NAME', '').split('/')
120 if parts[:len(repo_parts)] == repo_parts:
120 if parts[:len(repo_parts)] == repo_parts:
121 parts = parts[len(repo_parts):]
121 parts = parts[len(repo_parts):]
122 query = '/'.join(parts)
122 query = '/'.join(parts)
123 else:
123 else:
124 query = req.env['QUERY_STRING'].split('&', 1)[0]
124 query = req.env['QUERY_STRING'].split('&', 1)[0]
125 query = query.split(';', 1)[0]
125 query = query.split(';', 1)[0]
126
126
127 # translate user-visible url structure to internal structure
127 # translate user-visible url structure to internal structure
128
128
129 args = query.split('/', 2)
129 args = query.split('/', 2)
130 if 'cmd' not in req.form and args and args[0]:
130 if 'cmd' not in req.form and args and args[0]:
131
131
132 cmd = args.pop(0)
132 cmd = args.pop(0)
133 style = cmd.rfind('-')
133 style = cmd.rfind('-')
134 if style != -1:
134 if style != -1:
135 req.form['style'] = [cmd[:style]]
135 req.form['style'] = [cmd[:style]]
136 cmd = cmd[style+1:]
136 cmd = cmd[style+1:]
137
137
138 # avoid accepting e.g. style parameter as command
138 # avoid accepting e.g. style parameter as command
139 if hasattr(webcommands, cmd):
139 if hasattr(webcommands, cmd):
140 req.form['cmd'] = [cmd]
140 req.form['cmd'] = [cmd]
141 else:
141 else:
142 cmd = ''
142 cmd = ''
143
143
144 if cmd == 'static':
144 if cmd == 'static':
145 req.form['file'] = ['/'.join(args)]
145 req.form['file'] = ['/'.join(args)]
146 else:
146 else:
147 if args and args[0]:
147 if args and args[0]:
148 node = args.pop(0)
148 node = args.pop(0)
149 req.form['node'] = [node]
149 req.form['node'] = [node]
150 if args:
150 if args:
151 req.form['file'] = args
151 req.form['file'] = args
152
152
153 if cmd == 'archive':
153 if cmd == 'archive':
154 fn = req.form['node'][0]
154 fn = req.form['node'][0]
155 for type_, spec in self.archive_specs.iteritems():
155 for type_, spec in self.archive_specs.iteritems():
156 ext = spec[2]
156 ext = spec[2]
157 if fn.endswith(ext):
157 if fn.endswith(ext):
158 req.form['node'] = [fn[:-len(ext)]]
158 req.form['node'] = [fn[:-len(ext)]]
159 req.form['type'] = [type_]
159 req.form['type'] = [type_]
160
160
161 # process the web interface request
161 # process the web interface request
162
162
163 try:
163 try:
164
164
165 tmpl = self.templater(req)
165 tmpl = self.templater(req)
166 ctype = tmpl('mimetype', encoding=self.encoding)
166 ctype = tmpl('mimetype', encoding=self.encoding)
167 ctype = templater.stringify(ctype)
167 ctype = templater.stringify(ctype)
168
168
169 if cmd == '':
169 if cmd == '':
170 req.form['cmd'] = [tmpl.cache['default']]
170 req.form['cmd'] = [tmpl.cache['default']]
171 cmd = req.form['cmd'][0]
171 cmd = req.form['cmd'][0]
172
172
173 if cmd not in webcommands.__all__:
173 if cmd not in webcommands.__all__:
174 msg = 'no such method: %s' % cmd
174 msg = 'no such method: %s' % cmd
175 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
175 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
176 elif cmd == 'file' and 'raw' in req.form.get('style', []):
176 elif cmd == 'file' and 'raw' in req.form.get('style', []):
177 self.ctype = ctype
177 self.ctype = ctype
178 content = webcommands.rawfile(self, req, tmpl)
178 content = webcommands.rawfile(self, req, tmpl)
179 else:
179 else:
180 content = getattr(webcommands, cmd)(self, req, tmpl)
180 content = getattr(webcommands, cmd)(self, req, tmpl)
181 req.respond(HTTP_OK, ctype)
181 req.respond(HTTP_OK, ctype)
182
182
183 return ''.join(content),
183 return ''.join(content),
184
184
185 except revlog.LookupError, err:
185 except revlog.LookupError, err:
186 req.respond(HTTP_NOT_FOUND, ctype)
186 req.respond(HTTP_NOT_FOUND, ctype)
187 msg = str(err)
187 msg = str(err)
188 if 'manifest' not in msg:
188 if 'manifest' not in msg:
189 msg = 'revision not found: %s' % err.name
189 msg = 'revision not found: %s' % err.name
190 return ''.join(tmpl('error', error=msg)),
190 return ''.join(tmpl('error', error=msg)),
191 except (RepoError, revlog.RevlogError), inst:
191 except (RepoError, revlog.RevlogError), inst:
192 req.respond(HTTP_SERVER_ERROR, ctype)
192 req.respond(HTTP_SERVER_ERROR, ctype)
193 return ''.join(tmpl('error', error=str(inst))),
193 return ''.join(tmpl('error', error=str(inst))),
194 except ErrorResponse, inst:
194 except ErrorResponse, inst:
195 req.respond(inst.code, ctype)
195 req.respond(inst.code, ctype)
196 return ''.join(tmpl('error', error=inst.message)),
196 return ''.join(tmpl('error', error=inst.message)),
197
197
198 def templater(self, req):
198 def templater(self, req):
199
199
200 # determine scheme, port and server name
200 # determine scheme, port and server name
201 # this is needed to create absolute urls
201 # this is needed to create absolute urls
202
202
203 proto = req.env.get('wsgi.url_scheme')
203 proto = req.env.get('wsgi.url_scheme')
204 if proto == 'https':
204 if proto == 'https':
205 proto = 'https'
205 proto = 'https'
206 default_port = "443"
206 default_port = "443"
207 else:
207 else:
208 proto = 'http'
208 proto = 'http'
209 default_port = "80"
209 default_port = "80"
210
210
211 port = req.env["SERVER_PORT"]
211 port = req.env["SERVER_PORT"]
212 port = port != default_port and (":" + port) or ""
212 port = port != default_port and (":" + port) or ""
213 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
213 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
214 staticurl = self.config("web", "staticurl") or req.url + 'static/'
214 staticurl = self.config("web", "staticurl") or req.url + 'static/'
215 if not staticurl.endswith('/'):
215 if not staticurl.endswith('/'):
216 staticurl += '/'
216 staticurl += '/'
217
217
218 # some functions for the templater
218 # some functions for the templater
219
219
220 def header(**map):
220 def header(**map):
221 yield tmpl('header', encoding=self.encoding, **map)
221 yield tmpl('header', encoding=self.encoding, **map)
222
222
223 def footer(**map):
223 def footer(**map):
224 yield tmpl("footer", **map)
224 yield tmpl("footer", **map)
225
225
226 def motd(**map):
226 def motd(**map):
227 yield self.config("web", "motd", "")
227 yield self.config("web", "motd", "")
228
228
229 def sessionvars(**map):
229 def sessionvars(**map):
230 fields = []
230 fields = []
231 if 'style' in req.form:
231 if 'style' in req.form:
232 style = req.form['style'][0]
232 style = req.form['style'][0]
233 if style != self.config('web', 'style', ''):
233 if style != self.config('web', 'style', ''):
234 fields.append(('style', style))
234 fields.append(('style', style))
235
235
236 separator = req.url[-1] == '?' and ';' or '?'
236 separator = req.url[-1] == '?' and ';' or '?'
237 for name, value in fields:
237 for name, value in fields:
238 yield dict(name=name, value=value, separator=separator)
238 yield dict(name=name, value=value, separator=separator)
239 separator = ';'
239 separator = ';'
240
240
241 # figure out which style to use
241 # figure out which style to use
242
242
243 style = self.config("web", "style", "")
243 style = self.config("web", "style", "")
244 if 'style' in req.form:
244 if 'style' in req.form:
245 style = req.form['style'][0]
245 style = req.form['style'][0]
246 mapfile = style_map(self.templatepath, style)
246 mapfile = style_map(self.templatepath, style)
247
247
248 if not self.reponame:
248 if not self.reponame:
249 self.reponame = (self.config("web", "name")
249 self.reponame = (self.config("web", "name")
250 or req.env.get('REPO_NAME')
250 or req.env.get('REPO_NAME')
251 or req.url.strip('/') or self.repo.root)
251 or req.url.strip('/') or self.repo.root)
252
252
253 # create the templater
253 # create the templater
254
254
255 tmpl = templater.templater(mapfile, templatefilters.filters,
255 tmpl = templater.templater(mapfile, templatefilters.filters,
256 defaults={"url": req.url,
256 defaults={"url": req.url,
257 "staticurl": staticurl,
257 "staticurl": staticurl,
258 "urlbase": urlbase,
258 "urlbase": urlbase,
259 "repo": self.reponame,
259 "repo": self.reponame,
260 "header": header,
260 "header": header,
261 "footer": footer,
261 "footer": footer,
262 "motd": motd,
262 "motd": motd,
263 "sessionvars": sessionvars
263 "sessionvars": sessionvars
264 })
264 })
265 return tmpl
265 return tmpl
266
266
267 def archivelist(self, nodeid):
267 def archivelist(self, nodeid):
268 allowed = self.configlist("web", "allow_archive")
268 allowed = self.configlist("web", "allow_archive")
269 for i, spec in self.archive_specs.iteritems():
269 for i, spec in self.archive_specs.iteritems():
270 if i in allowed or self.configbool("web", "allow" + i):
270 if i in allowed or self.configbool("web", "allow" + i):
271 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
271 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
272
272
273 def listfilediffs(self, tmpl, files, changeset):
273 def listfilediffs(self, tmpl, files, changeset):
274 for f in files[:self.maxfiles]:
274 for f in files[:self.maxfiles]:
275 yield tmpl("filedifflink", node=hex(changeset), file=f)
275 yield tmpl("filedifflink", node=hex(changeset), file=f)
276 if len(files) > self.maxfiles:
276 if len(files) > self.maxfiles:
277 yield tmpl("fileellipses")
277 yield tmpl("fileellipses")
278
278
279 def diff(self, tmpl, node1, node2, files):
279 def diff(self, tmpl, node1, node2, files):
280 def filterfiles(filters, files):
281 l = [x for x in files if x in filters]
282
283 for t in filters:
284 if t and t[-1] != os.sep:
285 t += os.sep
286 l += [x for x in files if x.startswith(t)]
287 return l
288
289 parity = paritygen(self.stripecount)
290 def diffblock(diff, f, fn):
291 yield tmpl("diffblock",
292 lines=prettyprintlines(diff),
293 parity=parity.next(),
294 file=f,
295 filenode=hex(fn or nullid))
296
280
297 blockcount = countgen()
281 blockcount = countgen()
298 def prettyprintlines(diff):
282 def prettyprintlines(diff):
299 blockno = blockcount.next()
283 blockno = blockcount.next()
300 for lineno, l in enumerate(diff.splitlines(1)):
284 for lineno, l in enumerate(diff.splitlines(True)):
301 if blockno == 0:
285 if blockno == 0:
302 lineno = lineno + 1
286 lineno = lineno + 1
303 else:
287 else:
304 lineno = "%d.%d" % (blockno, lineno + 1)
288 lineno = "%d.%d" % (blockno, lineno + 1)
305 if l.startswith('+'):
289 if l.startswith('+'):
306 ltype = "difflineplus"
290 ltype = "difflineplus"
307 elif l.startswith('-'):
291 elif l.startswith('-'):
308 ltype = "difflineminus"
292 ltype = "difflineminus"
309 elif l.startswith('@'):
293 elif l.startswith('@'):
310 ltype = "difflineat"
294 ltype = "difflineat"
311 else:
295 else:
312 ltype = "diffline"
296 ltype = "diffline"
313 yield tmpl(ltype,
297 yield tmpl(ltype,
314 line=l,
298 line=l,
315 lineid="l%s" % lineno,
299 lineid="l%s" % lineno,
316 linenumber="% 8s" % lineno)
300 linenumber="% 8s" % lineno)
317
301
318 r = self.repo
302 if files:
319 c1 = r[node1]
303 m = match.exact(self.repo.root, self.repo.getcwd(), files)
320 c2 = r[node2]
304 else:
321 date1 = util.datestr(c1.date())
305 m = match.always(self.repo.root, self.repo.getcwd())
322 date2 = util.datestr(c2.date())
323
306
324 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
307 block = []
325 if files:
308 parity = paritygen(self.stripecount)
326 modified, added, removed = map(lambda x: filterfiles(files, x),
327 (modified, added, removed))
328
329 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
309 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
330 for f in modified:
310 for chunk in patch.diff(self.repo, node1, node2, m, opts=diffopts):
331 to = c1.filectx(f).data()
311 if chunk.startswith('diff') and block:
332 tn = c2.filectx(f).data()
312 yield tmpl('diffblock', parity=parity.next(),
333 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
313 lines=prettyprintlines(''.join(block)))
334 opts=diffopts), f, tn)
314 block = []
335 for f in added:
315 if chunk.startswith('diff'):
336 to = None
316 chunk = ''.join(chunk.splitlines(True)[1:])
337 tn = c2.filectx(f).data()
317 block.append(chunk)
338 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
318 yield tmpl('diffblock', parity=parity.next(),
339 opts=diffopts), f, tn)
319 lines=prettyprintlines(''.join(block)))
340 for f in removed:
341 to = c1.filectx(f).data()
342 tn = None
343 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
344 opts=diffopts), f, tn)
345
320
346 archive_specs = {
321 archive_specs = {
347 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
322 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
348 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
323 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
349 'zip': ('application/zip', 'zip', '.zip', None),
324 'zip': ('application/zip', 'zip', '.zip', None),
350 }
325 }
351
326
352 def check_perm(self, req, op):
327 def check_perm(self, req, op):
353 '''Check permission for operation based on request data (including
328 '''Check permission for operation based on request data (including
354 authentication info. Return true if op allowed, else false.'''
329 authentication info. Return true if op allowed, else false.'''
355
330
356 if op == 'pull' and not self.allowpull:
331 if op == 'pull' and not self.allowpull:
357 raise ErrorResponse(HTTP_OK, '')
332 raise ErrorResponse(HTTP_OK, '')
358 elif op == 'pull':
333 elif op == 'pull':
359 return
334 return
360
335
361 # enforce that you can only push using POST requests
336 # enforce that you can only push using POST requests
362 if req.env['REQUEST_METHOD'] != 'POST':
337 if req.env['REQUEST_METHOD'] != 'POST':
363 msg = 'push requires POST request'
338 msg = 'push requires POST request'
364 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
339 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
365
340
366 # require ssl by default for pushing, auth info cannot be sniffed
341 # require ssl by default for pushing, auth info cannot be sniffed
367 # and replayed
342 # and replayed
368 scheme = req.env.get('wsgi.url_scheme')
343 scheme = req.env.get('wsgi.url_scheme')
369 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
344 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
370 raise ErrorResponse(HTTP_OK, 'ssl required')
345 raise ErrorResponse(HTTP_OK, 'ssl required')
371
346
372 user = req.env.get('REMOTE_USER')
347 user = req.env.get('REMOTE_USER')
373
348
374 deny = self.configlist('web', 'deny_push')
349 deny = self.configlist('web', 'deny_push')
375 if deny and (not user or deny == ['*'] or user in deny):
350 if deny and (not user or deny == ['*'] or user in deny):
376 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
351 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
377
352
378 allow = self.configlist('web', 'allow_push')
353 allow = self.configlist('web', 'allow_push')
379 result = allow and (allow == ['*'] or user in allow)
354 result = allow and (allow == ['*'] or user in allow)
380 if not result:
355 if not result:
381 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
356 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
General Comments 0
You need to be logged in to leave comments. Login now