##// END OF EJS Templates
Less indirection in the WSGI web interface. This simplifies some code, and makes it more compliant with WSGI.
Dirkjan Ochtman -
r5566:d74fc8de default
parent child Browse files
Show More
@@ -1,31 +1,28 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # An example CGI script to use hgweb, edit as necessary
3 # An example CGI script to use hgweb, edit as necessary
4
4
5 # adjust python path if not a system-wide install:
5 # adjust python path if not a system-wide install:
6 #import sys
6 #import sys
7 #sys.path.insert(0, "/path/to/python/lib")
7 #sys.path.insert(0, "/path/to/python/lib")
8
8
9 # enable importing on demand to reduce startup time
9 # enable importing on demand to reduce startup time
10 from mercurial import demandimport; demandimport.enable()
10 from mercurial import demandimport; demandimport.enable()
11
11
12 # send python tracebacks to the browser if an error occurs:
12 # send python tracebacks to the browser if an error occurs:
13 import cgitb
13 import cgitb
14 cgitb.enable()
14 cgitb.enable()
15
15
16 # If you'd like to serve pages with UTF-8 instead of your default
16 # If you'd like to serve pages with UTF-8 instead of your default
17 # locale charset, you can do so by uncommenting the following lines.
17 # locale charset, you can do so by uncommenting the following lines.
18 # Note that this will cause your .hgrc files to be interpreted in
18 # Note that this will cause your .hgrc files to be interpreted in
19 # UTF-8 and all your repo files to be displayed using UTF-8.
19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 #
20 #
21 #import os
21 #import os
22 #os.environ["HGENCODING"] = "UTF-8"
22 #os.environ["HGENCODING"] = "UTF-8"
23
23
24 from mercurial.hgweb.hgweb_mod import hgweb
24 from mercurial.hgweb.hgweb_mod import hgweb
25 from mercurial.hgweb.request import wsgiapplication
26 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
27
26
28 def make_web_app():
27 application = hgweb("/path/to/repo", "repository name")
29 return hgweb("/path/to/repo", "repository name")
28 wsgicgi.launch(application)
30
31 wsgicgi.launch(wsgiapplication(make_web_app))
@@ -1,50 +1,47 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # An example CGI script to export multiple hgweb repos, edit as necessary
3 # An example CGI script to export multiple hgweb repos, edit as necessary
4
4
5 # adjust python path if not a system-wide install:
5 # adjust python path if not a system-wide install:
6 #import sys
6 #import sys
7 #sys.path.insert(0, "/path/to/python/lib")
7 #sys.path.insert(0, "/path/to/python/lib")
8
8
9 # enable importing on demand to reduce startup time
9 # enable importing on demand to reduce startup time
10 from mercurial import demandimport; demandimport.enable()
10 from mercurial import demandimport; demandimport.enable()
11
11
12 # send python tracebacks to the browser if an error occurs:
12 # send python tracebacks to the browser if an error occurs:
13 import cgitb
13 import cgitb
14 cgitb.enable()
14 cgitb.enable()
15
15
16 # If you'd like to serve pages with UTF-8 instead of your default
16 # If you'd like to serve pages with UTF-8 instead of your default
17 # locale charset, you can do so by uncommenting the following lines.
17 # locale charset, you can do so by uncommenting the following lines.
18 # Note that this will cause your .hgrc files to be interpreted in
18 # Note that this will cause your .hgrc files to be interpreted in
19 # UTF-8 and all your repo files to be displayed using UTF-8.
19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 #
20 #
21 #import os
21 #import os
22 #os.environ["HGENCODING"] = "UTF-8"
22 #os.environ["HGENCODING"] = "UTF-8"
23
23
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
25 from mercurial.hgweb.request import wsgiapplication
26 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
27
26
28 # The config file looks like this. You can have paths to individual
27 # The config file looks like this. You can have paths to individual
29 # repos, collections of repos in a directory tree, or both.
28 # repos, collections of repos in a directory tree, or both.
30 #
29 #
31 # [paths]
30 # [paths]
32 # virtual/path = /real/path
31 # virtual/path = /real/path
33 # virtual/path = /real/path
32 # virtual/path = /real/path
34 #
33 #
35 # [collections]
34 # [collections]
36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
35 # /prefix/to/strip/off = /root/of/tree/full/of/repos
37 #
36 #
38 # collections example: say directory tree /foo contains repos /foo/bar,
37 # collections example: say directory tree /foo contains repos /foo/bar,
39 # /foo/quux/baz. Give this config section:
38 # /foo/quux/baz. Give this config section:
40 # [collections]
39 # [collections]
41 # /foo = /foo
40 # /foo = /foo
42 # Then repos will list as bar and quux/baz.
41 # Then repos will list as bar and quux/baz.
43 #
42 #
44 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
43 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
45 # or use a dictionary with entries like 'virtual/path': '/real/path'
44 # or use a dictionary with entries like 'virtual/path': '/real/path'
46
45
47 def make_web_app():
46 application = hgwebdir('hgweb.config')
48 return hgwebdir("hgweb.config")
47 wsgicgi.launch(application)
49
50 wsgicgi.launch(wsgiapplication(make_web_app))
@@ -1,1221 +1,1224 b''
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 errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
9 import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
10 import tempfile, urllib, bz2
11 from mercurial.node import *
11 from mercurial.node import *
12 from mercurial.i18n import gettext as _
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 from mercurial import revlog, templater
14 from mercurial import revlog, templater
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
16 from request import wsgirequest
16
17
17 def _up(p):
18 def _up(p):
18 if p[0] != "/":
19 if p[0] != "/":
19 p = "/" + p
20 p = "/" + p
20 if p[-1] == "/":
21 if p[-1] == "/":
21 p = p[:-1]
22 p = p[:-1]
22 up = os.path.dirname(p)
23 up = os.path.dirname(p)
23 if up == "/":
24 if up == "/":
24 return "/"
25 return "/"
25 return up + "/"
26 return up + "/"
26
27
27 def revnavgen(pos, pagelen, limit, nodefunc):
28 def revnavgen(pos, pagelen, limit, nodefunc):
28 def seq(factor, limit=None):
29 def seq(factor, limit=None):
29 if limit:
30 if limit:
30 yield limit
31 yield limit
31 if limit >= 20 and limit <= 40:
32 if limit >= 20 and limit <= 40:
32 yield 50
33 yield 50
33 else:
34 else:
34 yield 1 * factor
35 yield 1 * factor
35 yield 3 * factor
36 yield 3 * factor
36 for f in seq(factor * 10):
37 for f in seq(factor * 10):
37 yield f
38 yield f
38
39
39 def nav(**map):
40 def nav(**map):
40 l = []
41 l = []
41 last = 0
42 last = 0
42 for f in seq(1, pagelen):
43 for f in seq(1, pagelen):
43 if f < pagelen or f <= last:
44 if f < pagelen or f <= last:
44 continue
45 continue
45 if f > limit:
46 if f > limit:
46 break
47 break
47 last = f
48 last = f
48 if pos + f < limit:
49 if pos + f < limit:
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 if pos - f >= 0:
51 if pos - f >= 0:
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52
53
53 try:
54 try:
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55
56
56 for label, node in l:
57 for label, node in l:
57 yield {"label": label, "node": node}
58 yield {"label": label, "node": node}
58
59
59 yield {"label": "tip", "node": "tip"}
60 yield {"label": "tip", "node": "tip"}
60 except hg.RepoError:
61 except hg.RepoError:
61 pass
62 pass
62
63
63 return nav
64 return nav
64
65
65 class hgweb(object):
66 class hgweb(object):
66 def __init__(self, repo, name=None):
67 def __init__(self, repo, name=None):
67 if isinstance(repo, str):
68 if isinstance(repo, str):
68 parentui = ui.ui(report_untrusted=False, interactive=False)
69 parentui = ui.ui(report_untrusted=False, interactive=False)
69 self.repo = hg.repository(parentui, repo)
70 self.repo = hg.repository(parentui, repo)
70 else:
71 else:
71 self.repo = repo
72 self.repo = repo
72
73
73 self.mtime = -1
74 self.mtime = -1
74 self.reponame = name
75 self.reponame = name
75 self.archives = 'zip', 'gz', 'bz2'
76 self.archives = 'zip', 'gz', 'bz2'
76 self.stripecount = 1
77 self.stripecount = 1
77 # a repo owner may set web.templates in .hg/hgrc to get any file
78 # a repo owner may set web.templates in .hg/hgrc to get any file
78 # readable by the user running the CGI script
79 # readable by the user running the CGI script
79 self.templatepath = self.config("web", "templates",
80 self.templatepath = self.config("web", "templates",
80 templater.templatepath(),
81 templater.templatepath(),
81 untrusted=False)
82 untrusted=False)
82
83
83 # The CGI scripts are often run by a user different from the repo owner.
84 # The CGI scripts are often run by a user different from the repo owner.
84 # Trust the settings from the .hg/hgrc files by default.
85 # Trust the settings from the .hg/hgrc files by default.
85 def config(self, section, name, default=None, untrusted=True):
86 def config(self, section, name, default=None, untrusted=True):
86 return self.repo.ui.config(section, name, default,
87 return self.repo.ui.config(section, name, default,
87 untrusted=untrusted)
88 untrusted=untrusted)
88
89
89 def configbool(self, section, name, default=False, untrusted=True):
90 def configbool(self, section, name, default=False, untrusted=True):
90 return self.repo.ui.configbool(section, name, default,
91 return self.repo.ui.configbool(section, name, default,
91 untrusted=untrusted)
92 untrusted=untrusted)
92
93
93 def configlist(self, section, name, default=None, untrusted=True):
94 def configlist(self, section, name, default=None, untrusted=True):
94 return self.repo.ui.configlist(section, name, default,
95 return self.repo.ui.configlist(section, name, default,
95 untrusted=untrusted)
96 untrusted=untrusted)
96
97
97 def refresh(self):
98 def refresh(self):
98 mtime = get_mtime(self.repo.root)
99 mtime = get_mtime(self.repo.root)
99 if mtime != self.mtime:
100 if mtime != self.mtime:
100 self.mtime = mtime
101 self.mtime = mtime
101 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 self.stripecount = int(self.config("web", "stripes", 1))
104 self.stripecount = int(self.config("web", "stripes", 1))
104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 self.allowpull = self.configbool("web", "allowpull", True)
107 self.allowpull = self.configbool("web", "allowpull", True)
107 self.encoding = self.config("web", "encoding", util._encoding)
108 self.encoding = self.config("web", "encoding", util._encoding)
108
109
109 def archivelist(self, nodeid):
110 def archivelist(self, nodeid):
110 allowed = self.configlist("web", "allow_archive")
111 allowed = self.configlist("web", "allow_archive")
111 for i, spec in self.archive_specs.iteritems():
112 for i, spec in self.archive_specs.iteritems():
112 if i in allowed or self.configbool("web", "allow" + i):
113 if i in allowed or self.configbool("web", "allow" + i):
113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114
115
115 def listfilediffs(self, files, changeset):
116 def listfilediffs(self, files, changeset):
116 for f in files[:self.maxfiles]:
117 for f in files[:self.maxfiles]:
117 yield self.t("filedifflink", node=hex(changeset), file=f)
118 yield self.t("filedifflink", node=hex(changeset), file=f)
118 if len(files) > self.maxfiles:
119 if len(files) > self.maxfiles:
119 yield self.t("fileellipses")
120 yield self.t("fileellipses")
120
121
121 def siblings(self, siblings=[], hiderev=None, **args):
122 def siblings(self, siblings=[], hiderev=None, **args):
122 siblings = [s for s in siblings if s.node() != nullid]
123 siblings = [s for s in siblings if s.node() != nullid]
123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 return
125 return
125 for s in siblings:
126 for s in siblings:
126 d = {'node': hex(s.node()), 'rev': s.rev()}
127 d = {'node': hex(s.node()), 'rev': s.rev()}
127 if hasattr(s, 'path'):
128 if hasattr(s, 'path'):
128 d['file'] = s.path()
129 d['file'] = s.path()
129 d.update(args)
130 d.update(args)
130 yield d
131 yield d
131
132
132 def renamelink(self, fl, node):
133 def renamelink(self, fl, node):
133 r = fl.renamed(node)
134 r = fl.renamed(node)
134 if r:
135 if r:
135 return [dict(file=r[0], node=hex(r[1]))]
136 return [dict(file=r[0], node=hex(r[1]))]
136 return []
137 return []
137
138
138 def nodetagsdict(self, node):
139 def nodetagsdict(self, node):
139 return [{"name": i} for i in self.repo.nodetags(node)]
140 return [{"name": i} for i in self.repo.nodetags(node)]
140
141
141 def nodebranchdict(self, ctx):
142 def nodebranchdict(self, ctx):
142 branches = []
143 branches = []
143 branch = ctx.branch()
144 branch = ctx.branch()
144 # If this is an empty repo, ctx.node() == nullid,
145 # If this is an empty repo, ctx.node() == nullid,
145 # ctx.branch() == 'default', but branchtags() is
146 # ctx.branch() == 'default', but branchtags() is
146 # an empty dict. Using dict.get avoids a traceback.
147 # an empty dict. Using dict.get avoids a traceback.
147 if self.repo.branchtags().get(branch) == ctx.node():
148 if self.repo.branchtags().get(branch) == ctx.node():
148 branches.append({"name": branch})
149 branches.append({"name": branch})
149 return branches
150 return branches
150
151
151 def showtag(self, t1, node=nullid, **args):
152 def showtag(self, t1, node=nullid, **args):
152 for t in self.repo.nodetags(node):
153 for t in self.repo.nodetags(node):
153 yield self.t(t1, tag=t, **args)
154 yield self.t(t1, tag=t, **args)
154
155
155 def diff(self, node1, node2, files):
156 def diff(self, node1, node2, files):
156 def filterfiles(filters, files):
157 def filterfiles(filters, files):
157 l = [x for x in files if x in filters]
158 l = [x for x in files if x in filters]
158
159
159 for t in filters:
160 for t in filters:
160 if t and t[-1] != os.sep:
161 if t and t[-1] != os.sep:
161 t += os.sep
162 t += os.sep
162 l += [x for x in files if x.startswith(t)]
163 l += [x for x in files if x.startswith(t)]
163 return l
164 return l
164
165
165 parity = paritygen(self.stripecount)
166 parity = paritygen(self.stripecount)
166 def diffblock(diff, f, fn):
167 def diffblock(diff, f, fn):
167 yield self.t("diffblock",
168 yield self.t("diffblock",
168 lines=prettyprintlines(diff),
169 lines=prettyprintlines(diff),
169 parity=parity.next(),
170 parity=parity.next(),
170 file=f,
171 file=f,
171 filenode=hex(fn or nullid))
172 filenode=hex(fn or nullid))
172
173
173 def prettyprintlines(diff):
174 def prettyprintlines(diff):
174 for l in diff.splitlines(1):
175 for l in diff.splitlines(1):
175 if l.startswith('+'):
176 if l.startswith('+'):
176 yield self.t("difflineplus", line=l)
177 yield self.t("difflineplus", line=l)
177 elif l.startswith('-'):
178 elif l.startswith('-'):
178 yield self.t("difflineminus", line=l)
179 yield self.t("difflineminus", line=l)
179 elif l.startswith('@'):
180 elif l.startswith('@'):
180 yield self.t("difflineat", line=l)
181 yield self.t("difflineat", line=l)
181 else:
182 else:
182 yield self.t("diffline", line=l)
183 yield self.t("diffline", line=l)
183
184
184 r = self.repo
185 r = self.repo
185 c1 = r.changectx(node1)
186 c1 = r.changectx(node1)
186 c2 = r.changectx(node2)
187 c2 = r.changectx(node2)
187 date1 = util.datestr(c1.date())
188 date1 = util.datestr(c1.date())
188 date2 = util.datestr(c2.date())
189 date2 = util.datestr(c2.date())
189
190
190 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 if files:
192 if files:
192 modified, added, removed = map(lambda x: filterfiles(files, x),
193 modified, added, removed = map(lambda x: filterfiles(files, x),
193 (modified, added, removed))
194 (modified, added, removed))
194
195
195 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 for f in modified:
197 for f in modified:
197 to = c1.filectx(f).data()
198 to = c1.filectx(f).data()
198 tn = c2.filectx(f).data()
199 tn = c2.filectx(f).data()
199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
200 opts=diffopts), f, tn)
201 opts=diffopts), f, tn)
201 for f in added:
202 for f in added:
202 to = None
203 to = None
203 tn = c2.filectx(f).data()
204 tn = c2.filectx(f).data()
204 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
205 opts=diffopts), f, tn)
206 opts=diffopts), f, tn)
206 for f in removed:
207 for f in removed:
207 to = c1.filectx(f).data()
208 to = c1.filectx(f).data()
208 tn = None
209 tn = None
209 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
210 opts=diffopts), f, tn)
211 opts=diffopts), f, tn)
211
212
212 def changelog(self, ctx, shortlog=False):
213 def changelog(self, ctx, shortlog=False):
213 def changelist(limit=0,**map):
214 def changelist(limit=0,**map):
214 cl = self.repo.changelog
215 cl = self.repo.changelog
215 l = [] # build a list in forward order for efficiency
216 l = [] # build a list in forward order for efficiency
216 for i in xrange(start, end):
217 for i in xrange(start, end):
217 ctx = self.repo.changectx(i)
218 ctx = self.repo.changectx(i)
218 n = ctx.node()
219 n = ctx.node()
219
220
220 l.insert(0, {"parity": parity.next(),
221 l.insert(0, {"parity": parity.next(),
221 "author": ctx.user(),
222 "author": ctx.user(),
222 "parent": self.siblings(ctx.parents(), i - 1),
223 "parent": self.siblings(ctx.parents(), i - 1),
223 "child": self.siblings(ctx.children(), i + 1),
224 "child": self.siblings(ctx.children(), i + 1),
224 "changelogtag": self.showtag("changelogtag",n),
225 "changelogtag": self.showtag("changelogtag",n),
225 "desc": ctx.description(),
226 "desc": ctx.description(),
226 "date": ctx.date(),
227 "date": ctx.date(),
227 "files": self.listfilediffs(ctx.files(), n),
228 "files": self.listfilediffs(ctx.files(), n),
228 "rev": i,
229 "rev": i,
229 "node": hex(n),
230 "node": hex(n),
230 "tags": self.nodetagsdict(n),
231 "tags": self.nodetagsdict(n),
231 "branches": self.nodebranchdict(ctx)})
232 "branches": self.nodebranchdict(ctx)})
232
233
233 if limit > 0:
234 if limit > 0:
234 l = l[:limit]
235 l = l[:limit]
235
236
236 for e in l:
237 for e in l:
237 yield e
238 yield e
238
239
239 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 cl = self.repo.changelog
241 cl = self.repo.changelog
241 count = cl.count()
242 count = cl.count()
242 pos = ctx.rev()
243 pos = ctx.rev()
243 start = max(0, pos - maxchanges + 1)
244 start = max(0, pos - maxchanges + 1)
244 end = min(count, start + maxchanges)
245 end = min(count, start + maxchanges)
245 pos = end - 1
246 pos = end - 1
246 parity = paritygen(self.stripecount, offset=start-end)
247 parity = paritygen(self.stripecount, offset=start-end)
247
248
248 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249
250
250 yield self.t(shortlog and 'shortlog' or 'changelog',
251 yield self.t(shortlog and 'shortlog' or 'changelog',
251 changenav=changenav,
252 changenav=changenav,
252 node=hex(cl.tip()),
253 node=hex(cl.tip()),
253 rev=pos, changesets=count,
254 rev=pos, changesets=count,
254 entries=lambda **x: changelist(limit=0,**x),
255 entries=lambda **x: changelist(limit=0,**x),
255 latestentry=lambda **x: changelist(limit=1,**x),
256 latestentry=lambda **x: changelist(limit=1,**x),
256 archives=self.archivelist("tip"))
257 archives=self.archivelist("tip"))
257
258
258 def search(self, query):
259 def search(self, query):
259
260
260 def changelist(**map):
261 def changelist(**map):
261 cl = self.repo.changelog
262 cl = self.repo.changelog
262 count = 0
263 count = 0
263 qw = query.lower().split()
264 qw = query.lower().split()
264
265
265 def revgen():
266 def revgen():
266 for i in xrange(cl.count() - 1, 0, -100):
267 for i in xrange(cl.count() - 1, 0, -100):
267 l = []
268 l = []
268 for j in xrange(max(0, i - 100), i):
269 for j in xrange(max(0, i - 100), i):
269 ctx = self.repo.changectx(j)
270 ctx = self.repo.changectx(j)
270 l.append(ctx)
271 l.append(ctx)
271 l.reverse()
272 l.reverse()
272 for e in l:
273 for e in l:
273 yield e
274 yield e
274
275
275 for ctx in revgen():
276 for ctx in revgen():
276 miss = 0
277 miss = 0
277 for q in qw:
278 for q in qw:
278 if not (q in ctx.user().lower() or
279 if not (q in ctx.user().lower() or
279 q in ctx.description().lower() or
280 q in ctx.description().lower() or
280 q in " ".join(ctx.files()).lower()):
281 q in " ".join(ctx.files()).lower()):
281 miss = 1
282 miss = 1
282 break
283 break
283 if miss:
284 if miss:
284 continue
285 continue
285
286
286 count += 1
287 count += 1
287 n = ctx.node()
288 n = ctx.node()
288
289
289 yield self.t('searchentry',
290 yield self.t('searchentry',
290 parity=parity.next(),
291 parity=parity.next(),
291 author=ctx.user(),
292 author=ctx.user(),
292 parent=self.siblings(ctx.parents()),
293 parent=self.siblings(ctx.parents()),
293 child=self.siblings(ctx.children()),
294 child=self.siblings(ctx.children()),
294 changelogtag=self.showtag("changelogtag",n),
295 changelogtag=self.showtag("changelogtag",n),
295 desc=ctx.description(),
296 desc=ctx.description(),
296 date=ctx.date(),
297 date=ctx.date(),
297 files=self.listfilediffs(ctx.files(), n),
298 files=self.listfilediffs(ctx.files(), n),
298 rev=ctx.rev(),
299 rev=ctx.rev(),
299 node=hex(n),
300 node=hex(n),
300 tags=self.nodetagsdict(n),
301 tags=self.nodetagsdict(n),
301 branches=self.nodebranchdict(ctx))
302 branches=self.nodebranchdict(ctx))
302
303
303 if count >= self.maxchanges:
304 if count >= self.maxchanges:
304 break
305 break
305
306
306 cl = self.repo.changelog
307 cl = self.repo.changelog
307 parity = paritygen(self.stripecount)
308 parity = paritygen(self.stripecount)
308
309
309 yield self.t('search',
310 yield self.t('search',
310 query=query,
311 query=query,
311 node=hex(cl.tip()),
312 node=hex(cl.tip()),
312 entries=changelist,
313 entries=changelist,
313 archives=self.archivelist("tip"))
314 archives=self.archivelist("tip"))
314
315
315 def changeset(self, ctx):
316 def changeset(self, ctx):
316 n = ctx.node()
317 n = ctx.node()
317 parents = ctx.parents()
318 parents = ctx.parents()
318 p1 = parents[0].node()
319 p1 = parents[0].node()
319
320
320 files = []
321 files = []
321 parity = paritygen(self.stripecount)
322 parity = paritygen(self.stripecount)
322 for f in ctx.files():
323 for f in ctx.files():
323 files.append(self.t("filenodelink",
324 files.append(self.t("filenodelink",
324 node=hex(n), file=f,
325 node=hex(n), file=f,
325 parity=parity.next()))
326 parity=parity.next()))
326
327
327 def diff(**map):
328 def diff(**map):
328 yield self.diff(p1, n, None)
329 yield self.diff(p1, n, None)
329
330
330 yield self.t('changeset',
331 yield self.t('changeset',
331 diff=diff,
332 diff=diff,
332 rev=ctx.rev(),
333 rev=ctx.rev(),
333 node=hex(n),
334 node=hex(n),
334 parent=self.siblings(parents),
335 parent=self.siblings(parents),
335 child=self.siblings(ctx.children()),
336 child=self.siblings(ctx.children()),
336 changesettag=self.showtag("changesettag",n),
337 changesettag=self.showtag("changesettag",n),
337 author=ctx.user(),
338 author=ctx.user(),
338 desc=ctx.description(),
339 desc=ctx.description(),
339 date=ctx.date(),
340 date=ctx.date(),
340 files=files,
341 files=files,
341 archives=self.archivelist(hex(n)),
342 archives=self.archivelist(hex(n)),
342 tags=self.nodetagsdict(n),
343 tags=self.nodetagsdict(n),
343 branches=self.nodebranchdict(ctx))
344 branches=self.nodebranchdict(ctx))
344
345
345 def filelog(self, fctx):
346 def filelog(self, fctx):
346 f = fctx.path()
347 f = fctx.path()
347 fl = fctx.filelog()
348 fl = fctx.filelog()
348 count = fl.count()
349 count = fl.count()
349 pagelen = self.maxshortchanges
350 pagelen = self.maxshortchanges
350 pos = fctx.filerev()
351 pos = fctx.filerev()
351 start = max(0, pos - pagelen + 1)
352 start = max(0, pos - pagelen + 1)
352 end = min(count, start + pagelen)
353 end = min(count, start + pagelen)
353 pos = end - 1
354 pos = end - 1
354 parity = paritygen(self.stripecount, offset=start-end)
355 parity = paritygen(self.stripecount, offset=start-end)
355
356
356 def entries(limit=0, **map):
357 def entries(limit=0, **map):
357 l = []
358 l = []
358
359
359 for i in xrange(start, end):
360 for i in xrange(start, end):
360 ctx = fctx.filectx(i)
361 ctx = fctx.filectx(i)
361 n = fl.node(i)
362 n = fl.node(i)
362
363
363 l.insert(0, {"parity": parity.next(),
364 l.insert(0, {"parity": parity.next(),
364 "filerev": i,
365 "filerev": i,
365 "file": f,
366 "file": f,
366 "node": hex(ctx.node()),
367 "node": hex(ctx.node()),
367 "author": ctx.user(),
368 "author": ctx.user(),
368 "date": ctx.date(),
369 "date": ctx.date(),
369 "rename": self.renamelink(fl, n),
370 "rename": self.renamelink(fl, n),
370 "parent": self.siblings(fctx.parents()),
371 "parent": self.siblings(fctx.parents()),
371 "child": self.siblings(fctx.children()),
372 "child": self.siblings(fctx.children()),
372 "desc": ctx.description()})
373 "desc": ctx.description()})
373
374
374 if limit > 0:
375 if limit > 0:
375 l = l[:limit]
376 l = l[:limit]
376
377
377 for e in l:
378 for e in l:
378 yield e
379 yield e
379
380
380 nodefunc = lambda x: fctx.filectx(fileid=x)
381 nodefunc = lambda x: fctx.filectx(fileid=x)
381 nav = revnavgen(pos, pagelen, count, nodefunc)
382 nav = revnavgen(pos, pagelen, count, nodefunc)
382 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 entries=lambda **x: entries(limit=0, **x),
384 entries=lambda **x: entries(limit=0, **x),
384 latestentry=lambda **x: entries(limit=1, **x))
385 latestentry=lambda **x: entries(limit=1, **x))
385
386
386 def filerevision(self, fctx):
387 def filerevision(self, fctx):
387 f = fctx.path()
388 f = fctx.path()
388 text = fctx.data()
389 text = fctx.data()
389 fl = fctx.filelog()
390 fl = fctx.filelog()
390 n = fctx.filenode()
391 n = fctx.filenode()
391 parity = paritygen(self.stripecount)
392 parity = paritygen(self.stripecount)
392
393
393 mt = mimetypes.guess_type(f)[0]
394 mt = mimetypes.guess_type(f)[0]
394 rawtext = text
395 rawtext = text
395 if util.binary(text):
396 if util.binary(text):
396 mt = mt or 'application/octet-stream'
397 mt = mt or 'application/octet-stream'
397 text = "(binary:%s)" % mt
398 text = "(binary:%s)" % mt
398 mt = mt or 'text/plain'
399 mt = mt or 'text/plain'
399
400
400 def lines():
401 def lines():
401 for l, t in enumerate(text.splitlines(1)):
402 for l, t in enumerate(text.splitlines(1)):
402 yield {"line": t,
403 yield {"line": t,
403 "linenumber": "% 6d" % (l + 1),
404 "linenumber": "% 6d" % (l + 1),
404 "parity": parity.next()}
405 "parity": parity.next()}
405
406
406 yield self.t("filerevision",
407 yield self.t("filerevision",
407 file=f,
408 file=f,
408 path=_up(f),
409 path=_up(f),
409 text=lines(),
410 text=lines(),
410 raw=rawtext,
411 raw=rawtext,
411 mimetype=mt,
412 mimetype=mt,
412 rev=fctx.rev(),
413 rev=fctx.rev(),
413 node=hex(fctx.node()),
414 node=hex(fctx.node()),
414 author=fctx.user(),
415 author=fctx.user(),
415 date=fctx.date(),
416 date=fctx.date(),
416 desc=fctx.description(),
417 desc=fctx.description(),
417 parent=self.siblings(fctx.parents()),
418 parent=self.siblings(fctx.parents()),
418 child=self.siblings(fctx.children()),
419 child=self.siblings(fctx.children()),
419 rename=self.renamelink(fl, n),
420 rename=self.renamelink(fl, n),
420 permissions=fctx.manifest().flags(f))
421 permissions=fctx.manifest().flags(f))
421
422
422 def fileannotate(self, fctx):
423 def fileannotate(self, fctx):
423 f = fctx.path()
424 f = fctx.path()
424 n = fctx.filenode()
425 n = fctx.filenode()
425 fl = fctx.filelog()
426 fl = fctx.filelog()
426 parity = paritygen(self.stripecount)
427 parity = paritygen(self.stripecount)
427
428
428 def annotate(**map):
429 def annotate(**map):
429 last = None
430 last = None
430 for f, l in fctx.annotate(follow=True):
431 for f, l in fctx.annotate(follow=True):
431 fnode = f.filenode()
432 fnode = f.filenode()
432 name = self.repo.ui.shortuser(f.user())
433 name = self.repo.ui.shortuser(f.user())
433
434
434 if last != fnode:
435 if last != fnode:
435 last = fnode
436 last = fnode
436
437
437 yield {"parity": parity.next(),
438 yield {"parity": parity.next(),
438 "node": hex(f.node()),
439 "node": hex(f.node()),
439 "rev": f.rev(),
440 "rev": f.rev(),
440 "author": name,
441 "author": name,
441 "file": f.path(),
442 "file": f.path(),
442 "line": l}
443 "line": l}
443
444
444 yield self.t("fileannotate",
445 yield self.t("fileannotate",
445 file=f,
446 file=f,
446 annotate=annotate,
447 annotate=annotate,
447 path=_up(f),
448 path=_up(f),
448 rev=fctx.rev(),
449 rev=fctx.rev(),
449 node=hex(fctx.node()),
450 node=hex(fctx.node()),
450 author=fctx.user(),
451 author=fctx.user(),
451 date=fctx.date(),
452 date=fctx.date(),
452 desc=fctx.description(),
453 desc=fctx.description(),
453 rename=self.renamelink(fl, n),
454 rename=self.renamelink(fl, n),
454 parent=self.siblings(fctx.parents()),
455 parent=self.siblings(fctx.parents()),
455 child=self.siblings(fctx.children()),
456 child=self.siblings(fctx.children()),
456 permissions=fctx.manifest().flags(f))
457 permissions=fctx.manifest().flags(f))
457
458
458 def manifest(self, ctx, path):
459 def manifest(self, ctx, path):
459 mf = ctx.manifest()
460 mf = ctx.manifest()
460 node = ctx.node()
461 node = ctx.node()
461
462
462 files = {}
463 files = {}
463 parity = paritygen(self.stripecount)
464 parity = paritygen(self.stripecount)
464
465
465 if path and path[-1] != "/":
466 if path and path[-1] != "/":
466 path += "/"
467 path += "/"
467 l = len(path)
468 l = len(path)
468 abspath = "/" + path
469 abspath = "/" + path
469
470
470 for f, n in mf.items():
471 for f, n in mf.items():
471 if f[:l] != path:
472 if f[:l] != path:
472 continue
473 continue
473 remain = f[l:]
474 remain = f[l:]
474 if "/" in remain:
475 if "/" in remain:
475 short = remain[:remain.index("/") + 1] # bleah
476 short = remain[:remain.index("/") + 1] # bleah
476 files[short] = (f, None)
477 files[short] = (f, None)
477 else:
478 else:
478 short = os.path.basename(remain)
479 short = os.path.basename(remain)
479 files[short] = (f, n)
480 files[short] = (f, n)
480
481
481 if not files:
482 if not files:
482 raise ErrorResponse(404, 'Path not found: ' + path)
483 raise ErrorResponse(404, 'Path not found: ' + path)
483
484
484 def filelist(**map):
485 def filelist(**map):
485 fl = files.keys()
486 fl = files.keys()
486 fl.sort()
487 fl.sort()
487 for f in fl:
488 for f in fl:
488 full, fnode = files[f]
489 full, fnode = files[f]
489 if not fnode:
490 if not fnode:
490 continue
491 continue
491
492
492 fctx = ctx.filectx(full)
493 fctx = ctx.filectx(full)
493 yield {"file": full,
494 yield {"file": full,
494 "parity": parity.next(),
495 "parity": parity.next(),
495 "basename": f,
496 "basename": f,
496 "date": fctx.changectx().date(),
497 "date": fctx.changectx().date(),
497 "size": fctx.size(),
498 "size": fctx.size(),
498 "permissions": mf.flags(full)}
499 "permissions": mf.flags(full)}
499
500
500 def dirlist(**map):
501 def dirlist(**map):
501 fl = files.keys()
502 fl = files.keys()
502 fl.sort()
503 fl.sort()
503 for f in fl:
504 for f in fl:
504 full, fnode = files[f]
505 full, fnode = files[f]
505 if fnode:
506 if fnode:
506 continue
507 continue
507
508
508 yield {"parity": parity.next(),
509 yield {"parity": parity.next(),
509 "path": "%s%s" % (abspath, f),
510 "path": "%s%s" % (abspath, f),
510 "basename": f[:-1]}
511 "basename": f[:-1]}
511
512
512 yield self.t("manifest",
513 yield self.t("manifest",
513 rev=ctx.rev(),
514 rev=ctx.rev(),
514 node=hex(node),
515 node=hex(node),
515 path=abspath,
516 path=abspath,
516 up=_up(abspath),
517 up=_up(abspath),
517 upparity=parity.next(),
518 upparity=parity.next(),
518 fentries=filelist,
519 fentries=filelist,
519 dentries=dirlist,
520 dentries=dirlist,
520 archives=self.archivelist(hex(node)),
521 archives=self.archivelist(hex(node)),
521 tags=self.nodetagsdict(node),
522 tags=self.nodetagsdict(node),
522 branches=self.nodebranchdict(ctx))
523 branches=self.nodebranchdict(ctx))
523
524
524 def tags(self):
525 def tags(self):
525 i = self.repo.tagslist()
526 i = self.repo.tagslist()
526 i.reverse()
527 i.reverse()
527 parity = paritygen(self.stripecount)
528 parity = paritygen(self.stripecount)
528
529
529 def entries(notip=False,limit=0, **map):
530 def entries(notip=False,limit=0, **map):
530 count = 0
531 count = 0
531 for k, n in i:
532 for k, n in i:
532 if notip and k == "tip":
533 if notip and k == "tip":
533 continue
534 continue
534 if limit > 0 and count >= limit:
535 if limit > 0 and count >= limit:
535 continue
536 continue
536 count = count + 1
537 count = count + 1
537 yield {"parity": parity.next(),
538 yield {"parity": parity.next(),
538 "tag": k,
539 "tag": k,
539 "date": self.repo.changectx(n).date(),
540 "date": self.repo.changectx(n).date(),
540 "node": hex(n)}
541 "node": hex(n)}
541
542
542 yield self.t("tags",
543 yield self.t("tags",
543 node=hex(self.repo.changelog.tip()),
544 node=hex(self.repo.changelog.tip()),
544 entries=lambda **x: entries(False,0, **x),
545 entries=lambda **x: entries(False,0, **x),
545 entriesnotip=lambda **x: entries(True,0, **x),
546 entriesnotip=lambda **x: entries(True,0, **x),
546 latestentry=lambda **x: entries(True,1, **x))
547 latestentry=lambda **x: entries(True,1, **x))
547
548
548 def summary(self):
549 def summary(self):
549 i = self.repo.tagslist()
550 i = self.repo.tagslist()
550 i.reverse()
551 i.reverse()
551
552
552 def tagentries(**map):
553 def tagentries(**map):
553 parity = paritygen(self.stripecount)
554 parity = paritygen(self.stripecount)
554 count = 0
555 count = 0
555 for k, n in i:
556 for k, n in i:
556 if k == "tip": # skip tip
557 if k == "tip": # skip tip
557 continue;
558 continue;
558
559
559 count += 1
560 count += 1
560 if count > 10: # limit to 10 tags
561 if count > 10: # limit to 10 tags
561 break;
562 break;
562
563
563 yield self.t("tagentry",
564 yield self.t("tagentry",
564 parity=parity.next(),
565 parity=parity.next(),
565 tag=k,
566 tag=k,
566 node=hex(n),
567 node=hex(n),
567 date=self.repo.changectx(n).date())
568 date=self.repo.changectx(n).date())
568
569
569
570
570 def branches(**map):
571 def branches(**map):
571 parity = paritygen(self.stripecount)
572 parity = paritygen(self.stripecount)
572
573
573 b = self.repo.branchtags()
574 b = self.repo.branchtags()
574 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
575 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
575 l.sort()
576 l.sort()
576
577
577 for r,n,t in l:
578 for r,n,t in l:
578 ctx = self.repo.changectx(n)
579 ctx = self.repo.changectx(n)
579
580
580 yield {'parity': parity.next(),
581 yield {'parity': parity.next(),
581 'branch': t,
582 'branch': t,
582 'node': hex(n),
583 'node': hex(n),
583 'date': ctx.date()}
584 'date': ctx.date()}
584
585
585 def changelist(**map):
586 def changelist(**map):
586 parity = paritygen(self.stripecount, offset=start-end)
587 parity = paritygen(self.stripecount, offset=start-end)
587 l = [] # build a list in forward order for efficiency
588 l = [] # build a list in forward order for efficiency
588 for i in xrange(start, end):
589 for i in xrange(start, end):
589 ctx = self.repo.changectx(i)
590 ctx = self.repo.changectx(i)
590 n = ctx.node()
591 n = ctx.node()
591 hn = hex(n)
592 hn = hex(n)
592
593
593 l.insert(0, self.t(
594 l.insert(0, self.t(
594 'shortlogentry',
595 'shortlogentry',
595 parity=parity.next(),
596 parity=parity.next(),
596 author=ctx.user(),
597 author=ctx.user(),
597 desc=ctx.description(),
598 desc=ctx.description(),
598 date=ctx.date(),
599 date=ctx.date(),
599 rev=i,
600 rev=i,
600 node=hn,
601 node=hn,
601 tags=self.nodetagsdict(n),
602 tags=self.nodetagsdict(n),
602 branches=self.nodebranchdict(ctx)))
603 branches=self.nodebranchdict(ctx)))
603
604
604 yield l
605 yield l
605
606
606 cl = self.repo.changelog
607 cl = self.repo.changelog
607 count = cl.count()
608 count = cl.count()
608 start = max(0, count - self.maxchanges)
609 start = max(0, count - self.maxchanges)
609 end = min(count, start + self.maxchanges)
610 end = min(count, start + self.maxchanges)
610
611
611 yield self.t("summary",
612 yield self.t("summary",
612 desc=self.config("web", "description", "unknown"),
613 desc=self.config("web", "description", "unknown"),
613 owner=(self.config("ui", "username") or # preferred
614 owner=(self.config("ui", "username") or # preferred
614 self.config("web", "contact") or # deprecated
615 self.config("web", "contact") or # deprecated
615 self.config("web", "author", "unknown")), # also
616 self.config("web", "author", "unknown")), # also
616 lastchange=cl.read(cl.tip())[2],
617 lastchange=cl.read(cl.tip())[2],
617 tags=tagentries,
618 tags=tagentries,
618 branches=branches,
619 branches=branches,
619 shortlog=changelist,
620 shortlog=changelist,
620 node=hex(cl.tip()),
621 node=hex(cl.tip()),
621 archives=self.archivelist("tip"))
622 archives=self.archivelist("tip"))
622
623
623 def filediff(self, fctx):
624 def filediff(self, fctx):
624 n = fctx.node()
625 n = fctx.node()
625 path = fctx.path()
626 path = fctx.path()
626 parents = fctx.parents()
627 parents = fctx.parents()
627 p1 = parents and parents[0].node() or nullid
628 p1 = parents and parents[0].node() or nullid
628
629
629 def diff(**map):
630 def diff(**map):
630 yield self.diff(p1, n, [path])
631 yield self.diff(p1, n, [path])
631
632
632 yield self.t("filediff",
633 yield self.t("filediff",
633 file=path,
634 file=path,
634 node=hex(n),
635 node=hex(n),
635 rev=fctx.rev(),
636 rev=fctx.rev(),
636 parent=self.siblings(parents),
637 parent=self.siblings(parents),
637 child=self.siblings(fctx.children()),
638 child=self.siblings(fctx.children()),
638 diff=diff)
639 diff=diff)
639
640
640 archive_specs = {
641 archive_specs = {
641 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
642 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
642 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
643 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
643 'zip': ('application/zip', 'zip', '.zip', None),
644 'zip': ('application/zip', 'zip', '.zip', None),
644 }
645 }
645
646
646 def archive(self, req, key, type_):
647 def archive(self, req, key, type_):
647 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
648 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
648 cnode = self.repo.lookup(key)
649 cnode = self.repo.lookup(key)
649 arch_version = key
650 arch_version = key
650 if cnode == key or key == 'tip':
651 if cnode == key or key == 'tip':
651 arch_version = short(cnode)
652 arch_version = short(cnode)
652 name = "%s-%s" % (reponame, arch_version)
653 name = "%s-%s" % (reponame, arch_version)
653 mimetype, artype, extension, encoding = self.archive_specs[type_]
654 mimetype, artype, extension, encoding = self.archive_specs[type_]
654 headers = [('Content-type', mimetype),
655 headers = [('Content-type', mimetype),
655 ('Content-disposition', 'attachment; filename=%s%s' %
656 ('Content-disposition', 'attachment; filename=%s%s' %
656 (name, extension))]
657 (name, extension))]
657 if encoding:
658 if encoding:
658 headers.append(('Content-encoding', encoding))
659 headers.append(('Content-encoding', encoding))
659 req.header(headers)
660 req.header(headers)
660 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
661 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
661
662
662 # add tags to things
663 # add tags to things
663 # tags -> list of changesets corresponding to tags
664 # tags -> list of changesets corresponding to tags
664 # find tag, changeset, file
665 # find tag, changeset, file
665
666
666 def cleanpath(self, path):
667 def cleanpath(self, path):
667 path = path.lstrip('/')
668 path = path.lstrip('/')
668 return util.canonpath(self.repo.root, '', path)
669 return util.canonpath(self.repo.root, '', path)
669
670
670 def run(self):
671 def run(self):
671 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
672 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
673 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
673 import mercurial.hgweb.wsgicgi as wsgicgi
674 import mercurial.hgweb.wsgicgi as wsgicgi
674 from request import wsgiapplication
675 wsgicgi.launch(self)
675 def make_web_app():
676
676 return self
677 def __call__(self, env, respond):
677 wsgicgi.launch(wsgiapplication(make_web_app))
678 req = wsgirequest(env, respond)
679 self.run_wsgi(req)
680 return req
678
681
679 def run_wsgi(self, req):
682 def run_wsgi(self, req):
680 def header(**map):
683 def header(**map):
681 header_file = cStringIO.StringIO(
684 header_file = cStringIO.StringIO(
682 ''.join(self.t("header", encoding=self.encoding, **map)))
685 ''.join(self.t("header", encoding=self.encoding, **map)))
683 msg = mimetools.Message(header_file, 0)
686 msg = mimetools.Message(header_file, 0)
684 req.header(msg.items())
687 req.header(msg.items())
685 yield header_file.read()
688 yield header_file.read()
686
689
687 def rawfileheader(**map):
690 def rawfileheader(**map):
688 req.header([('Content-type', map['mimetype']),
691 req.header([('Content-type', map['mimetype']),
689 ('Content-disposition', 'filename=%s' % map['file']),
692 ('Content-disposition', 'filename=%s' % map['file']),
690 ('Content-length', str(len(map['raw'])))])
693 ('Content-length', str(len(map['raw'])))])
691 yield ''
694 yield ''
692
695
693 def footer(**map):
696 def footer(**map):
694 yield self.t("footer", **map)
697 yield self.t("footer", **map)
695
698
696 def motd(**map):
699 def motd(**map):
697 yield self.config("web", "motd", "")
700 yield self.config("web", "motd", "")
698
701
699 def expand_form(form):
702 def expand_form(form):
700 shortcuts = {
703 shortcuts = {
701 'cl': [('cmd', ['changelog']), ('rev', None)],
704 'cl': [('cmd', ['changelog']), ('rev', None)],
702 'sl': [('cmd', ['shortlog']), ('rev', None)],
705 'sl': [('cmd', ['shortlog']), ('rev', None)],
703 'cs': [('cmd', ['changeset']), ('node', None)],
706 'cs': [('cmd', ['changeset']), ('node', None)],
704 'f': [('cmd', ['file']), ('filenode', None)],
707 'f': [('cmd', ['file']), ('filenode', None)],
705 'fl': [('cmd', ['filelog']), ('filenode', None)],
708 'fl': [('cmd', ['filelog']), ('filenode', None)],
706 'fd': [('cmd', ['filediff']), ('node', None)],
709 'fd': [('cmd', ['filediff']), ('node', None)],
707 'fa': [('cmd', ['annotate']), ('filenode', None)],
710 'fa': [('cmd', ['annotate']), ('filenode', None)],
708 'mf': [('cmd', ['manifest']), ('manifest', None)],
711 'mf': [('cmd', ['manifest']), ('manifest', None)],
709 'ca': [('cmd', ['archive']), ('node', None)],
712 'ca': [('cmd', ['archive']), ('node', None)],
710 'tags': [('cmd', ['tags'])],
713 'tags': [('cmd', ['tags'])],
711 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
714 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
712 'static': [('cmd', ['static']), ('file', None)]
715 'static': [('cmd', ['static']), ('file', None)]
713 }
716 }
714
717
715 for k in shortcuts.iterkeys():
718 for k in shortcuts.iterkeys():
716 if form.has_key(k):
719 if form.has_key(k):
717 for name, value in shortcuts[k]:
720 for name, value in shortcuts[k]:
718 if value is None:
721 if value is None:
719 value = form[k]
722 value = form[k]
720 form[name] = value
723 form[name] = value
721 del form[k]
724 del form[k]
722
725
723 def rewrite_request(req):
726 def rewrite_request(req):
724 '''translate new web interface to traditional format'''
727 '''translate new web interface to traditional format'''
725
728
726 def spliturl(req):
729 def spliturl(req):
727 def firstitem(query):
730 def firstitem(query):
728 return query.split('&', 1)[0].split(';', 1)[0]
731 return query.split('&', 1)[0].split(';', 1)[0]
729
732
730 def normurl(url):
733 def normurl(url):
731 inner = '/'.join([x for x in url.split('/') if x])
734 inner = '/'.join([x for x in url.split('/') if x])
732 tl = len(url) > 1 and url.endswith('/') and '/' or ''
735 tl = len(url) > 1 and url.endswith('/') and '/' or ''
733
736
734 return '%s%s%s' % (url.startswith('/') and '/' or '',
737 return '%s%s%s' % (url.startswith('/') and '/' or '',
735 inner, tl)
738 inner, tl)
736
739
737 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
740 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
738 pi = normurl(req.env.get('PATH_INFO', ''))
741 pi = normurl(req.env.get('PATH_INFO', ''))
739 if pi:
742 if pi:
740 # strip leading /
743 # strip leading /
741 pi = pi[1:]
744 pi = pi[1:]
742 if pi:
745 if pi:
743 root = root[:root.rfind(pi)]
746 root = root[:root.rfind(pi)]
744 if req.env.has_key('REPO_NAME'):
747 if req.env.has_key('REPO_NAME'):
745 rn = req.env['REPO_NAME'] + '/'
748 rn = req.env['REPO_NAME'] + '/'
746 root += rn
749 root += rn
747 query = pi[len(rn):]
750 query = pi[len(rn):]
748 else:
751 else:
749 query = pi
752 query = pi
750 else:
753 else:
751 root += '?'
754 root += '?'
752 query = firstitem(req.env['QUERY_STRING'])
755 query = firstitem(req.env['QUERY_STRING'])
753
756
754 return (root, query)
757 return (root, query)
755
758
756 req.url, query = spliturl(req)
759 req.url, query = spliturl(req)
757
760
758 if req.form.has_key('cmd'):
761 if req.form.has_key('cmd'):
759 # old style
762 # old style
760 return
763 return
761
764
762 args = query.split('/', 2)
765 args = query.split('/', 2)
763 if not args or not args[0]:
766 if not args or not args[0]:
764 return
767 return
765
768
766 cmd = args.pop(0)
769 cmd = args.pop(0)
767 style = cmd.rfind('-')
770 style = cmd.rfind('-')
768 if style != -1:
771 if style != -1:
769 req.form['style'] = [cmd[:style]]
772 req.form['style'] = [cmd[:style]]
770 cmd = cmd[style+1:]
773 cmd = cmd[style+1:]
771 # avoid accepting e.g. style parameter as command
774 # avoid accepting e.g. style parameter as command
772 if hasattr(self, 'do_' + cmd):
775 if hasattr(self, 'do_' + cmd):
773 req.form['cmd'] = [cmd]
776 req.form['cmd'] = [cmd]
774
777
775 if args and args[0]:
778 if args and args[0]:
776 node = args.pop(0)
779 node = args.pop(0)
777 req.form['node'] = [node]
780 req.form['node'] = [node]
778 if args:
781 if args:
779 req.form['file'] = args
782 req.form['file'] = args
780
783
781 if cmd == 'static':
784 if cmd == 'static':
782 req.form['file'] = req.form['node']
785 req.form['file'] = req.form['node']
783 elif cmd == 'archive':
786 elif cmd == 'archive':
784 fn = req.form['node'][0]
787 fn = req.form['node'][0]
785 for type_, spec in self.archive_specs.iteritems():
788 for type_, spec in self.archive_specs.iteritems():
786 ext = spec[2]
789 ext = spec[2]
787 if fn.endswith(ext):
790 if fn.endswith(ext):
788 req.form['node'] = [fn[:-len(ext)]]
791 req.form['node'] = [fn[:-len(ext)]]
789 req.form['type'] = [type_]
792 req.form['type'] = [type_]
790
793
791 def sessionvars(**map):
794 def sessionvars(**map):
792 fields = []
795 fields = []
793 if req.form.has_key('style'):
796 if req.form.has_key('style'):
794 style = req.form['style'][0]
797 style = req.form['style'][0]
795 if style != self.config('web', 'style', ''):
798 if style != self.config('web', 'style', ''):
796 fields.append(('style', style))
799 fields.append(('style', style))
797
800
798 separator = req.url[-1] == '?' and ';' or '?'
801 separator = req.url[-1] == '?' and ';' or '?'
799 for name, value in fields:
802 for name, value in fields:
800 yield dict(name=name, value=value, separator=separator)
803 yield dict(name=name, value=value, separator=separator)
801 separator = ';'
804 separator = ';'
802
805
803 self.refresh()
806 self.refresh()
804
807
805 expand_form(req.form)
808 expand_form(req.form)
806 rewrite_request(req)
809 rewrite_request(req)
807
810
808 style = self.config("web", "style", "")
811 style = self.config("web", "style", "")
809 if req.form.has_key('style'):
812 if req.form.has_key('style'):
810 style = req.form['style'][0]
813 style = req.form['style'][0]
811 mapfile = style_map(self.templatepath, style)
814 mapfile = style_map(self.templatepath, style)
812
815
813 proto = req.env.get('wsgi.url_scheme')
816 proto = req.env.get('wsgi.url_scheme')
814 if proto == 'https':
817 if proto == 'https':
815 proto = 'https'
818 proto = 'https'
816 default_port = "443"
819 default_port = "443"
817 else:
820 else:
818 proto = 'http'
821 proto = 'http'
819 default_port = "80"
822 default_port = "80"
820
823
821 port = req.env["SERVER_PORT"]
824 port = req.env["SERVER_PORT"]
822 port = port != default_port and (":" + port) or ""
825 port = port != default_port and (":" + port) or ""
823 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
826 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
824 staticurl = self.config("web", "staticurl") or req.url + 'static/'
827 staticurl = self.config("web", "staticurl") or req.url + 'static/'
825 if not staticurl.endswith('/'):
828 if not staticurl.endswith('/'):
826 staticurl += '/'
829 staticurl += '/'
827
830
828 if not self.reponame:
831 if not self.reponame:
829 self.reponame = (self.config("web", "name")
832 self.reponame = (self.config("web", "name")
830 or req.env.get('REPO_NAME')
833 or req.env.get('REPO_NAME')
831 or req.url.strip('/') or self.repo.root)
834 or req.url.strip('/') or self.repo.root)
832
835
833 self.t = templater.templater(mapfile, templater.common_filters,
836 self.t = templater.templater(mapfile, templater.common_filters,
834 defaults={"url": req.url,
837 defaults={"url": req.url,
835 "staticurl": staticurl,
838 "staticurl": staticurl,
836 "urlbase": urlbase,
839 "urlbase": urlbase,
837 "repo": self.reponame,
840 "repo": self.reponame,
838 "header": header,
841 "header": header,
839 "footer": footer,
842 "footer": footer,
840 "motd": motd,
843 "motd": motd,
841 "rawfileheader": rawfileheader,
844 "rawfileheader": rawfileheader,
842 "sessionvars": sessionvars
845 "sessionvars": sessionvars
843 })
846 })
844
847
845 try:
848 try:
846 if not req.form.has_key('cmd'):
849 if not req.form.has_key('cmd'):
847 req.form['cmd'] = [self.t.cache['default']]
850 req.form['cmd'] = [self.t.cache['default']]
848
851
849 cmd = req.form['cmd'][0]
852 cmd = req.form['cmd'][0]
850
853
851 try:
854 try:
852 method = getattr(self, 'do_' + cmd)
855 method = getattr(self, 'do_' + cmd)
853 method(req)
856 method(req)
854 except revlog.LookupError, err:
857 except revlog.LookupError, err:
855 req.respond(404, self.t(
858 req.respond(404, self.t(
856 'error', error='revision not found: %s' % err.name))
859 'error', error='revision not found: %s' % err.name))
857 except (hg.RepoError, revlog.RevlogError), inst:
860 except (hg.RepoError, revlog.RevlogError), inst:
858 req.respond('500 Internal Server Error',
861 req.respond('500 Internal Server Error',
859 self.t('error', error=str(inst)))
862 self.t('error', error=str(inst)))
860 except ErrorResponse, inst:
863 except ErrorResponse, inst:
861 req.respond(inst.code, self.t('error', error=inst.message))
864 req.respond(inst.code, self.t('error', error=inst.message))
862 except AttributeError:
865 except AttributeError:
863 req.respond(400,
866 req.respond(400,
864 self.t('error', error='No such method: ' + cmd))
867 self.t('error', error='No such method: ' + cmd))
865 finally:
868 finally:
866 self.t = None
869 self.t = None
867
870
868 def changectx(self, req):
871 def changectx(self, req):
869 if req.form.has_key('node'):
872 if req.form.has_key('node'):
870 changeid = req.form['node'][0]
873 changeid = req.form['node'][0]
871 elif req.form.has_key('manifest'):
874 elif req.form.has_key('manifest'):
872 changeid = req.form['manifest'][0]
875 changeid = req.form['manifest'][0]
873 else:
876 else:
874 changeid = self.repo.changelog.count() - 1
877 changeid = self.repo.changelog.count() - 1
875
878
876 try:
879 try:
877 ctx = self.repo.changectx(changeid)
880 ctx = self.repo.changectx(changeid)
878 except hg.RepoError:
881 except hg.RepoError:
879 man = self.repo.manifest
882 man = self.repo.manifest
880 mn = man.lookup(changeid)
883 mn = man.lookup(changeid)
881 ctx = self.repo.changectx(man.linkrev(mn))
884 ctx = self.repo.changectx(man.linkrev(mn))
882
885
883 return ctx
886 return ctx
884
887
885 def filectx(self, req):
888 def filectx(self, req):
886 path = self.cleanpath(req.form['file'][0])
889 path = self.cleanpath(req.form['file'][0])
887 if req.form.has_key('node'):
890 if req.form.has_key('node'):
888 changeid = req.form['node'][0]
891 changeid = req.form['node'][0]
889 else:
892 else:
890 changeid = req.form['filenode'][0]
893 changeid = req.form['filenode'][0]
891 try:
894 try:
892 ctx = self.repo.changectx(changeid)
895 ctx = self.repo.changectx(changeid)
893 fctx = ctx.filectx(path)
896 fctx = ctx.filectx(path)
894 except hg.RepoError:
897 except hg.RepoError:
895 fctx = self.repo.filectx(path, fileid=changeid)
898 fctx = self.repo.filectx(path, fileid=changeid)
896
899
897 return fctx
900 return fctx
898
901
899 def do_log(self, req):
902 def do_log(self, req):
900 if req.form.has_key('file') and req.form['file'][0]:
903 if req.form.has_key('file') and req.form['file'][0]:
901 self.do_filelog(req)
904 self.do_filelog(req)
902 else:
905 else:
903 self.do_changelog(req)
906 self.do_changelog(req)
904
907
905 def do_rev(self, req):
908 def do_rev(self, req):
906 self.do_changeset(req)
909 self.do_changeset(req)
907
910
908 def do_file(self, req):
911 def do_file(self, req):
909 path = self.cleanpath(req.form.get('file', [''])[0])
912 path = self.cleanpath(req.form.get('file', [''])[0])
910 if path:
913 if path:
911 try:
914 try:
912 req.write(self.filerevision(self.filectx(req)))
915 req.write(self.filerevision(self.filectx(req)))
913 return
916 return
914 except revlog.LookupError:
917 except revlog.LookupError:
915 pass
918 pass
916
919
917 req.write(self.manifest(self.changectx(req), path))
920 req.write(self.manifest(self.changectx(req), path))
918
921
919 def do_diff(self, req):
922 def do_diff(self, req):
920 self.do_filediff(req)
923 self.do_filediff(req)
921
924
922 def do_changelog(self, req, shortlog = False):
925 def do_changelog(self, req, shortlog = False):
923 if req.form.has_key('node'):
926 if req.form.has_key('node'):
924 ctx = self.changectx(req)
927 ctx = self.changectx(req)
925 else:
928 else:
926 if req.form.has_key('rev'):
929 if req.form.has_key('rev'):
927 hi = req.form['rev'][0]
930 hi = req.form['rev'][0]
928 else:
931 else:
929 hi = self.repo.changelog.count() - 1
932 hi = self.repo.changelog.count() - 1
930 try:
933 try:
931 ctx = self.repo.changectx(hi)
934 ctx = self.repo.changectx(hi)
932 except hg.RepoError:
935 except hg.RepoError:
933 req.write(self.search(hi)) # XXX redirect to 404 page?
936 req.write(self.search(hi)) # XXX redirect to 404 page?
934 return
937 return
935
938
936 req.write(self.changelog(ctx, shortlog = shortlog))
939 req.write(self.changelog(ctx, shortlog = shortlog))
937
940
938 def do_shortlog(self, req):
941 def do_shortlog(self, req):
939 self.do_changelog(req, shortlog = True)
942 self.do_changelog(req, shortlog = True)
940
943
941 def do_changeset(self, req):
944 def do_changeset(self, req):
942 req.write(self.changeset(self.changectx(req)))
945 req.write(self.changeset(self.changectx(req)))
943
946
944 def do_manifest(self, req):
947 def do_manifest(self, req):
945 req.write(self.manifest(self.changectx(req),
948 req.write(self.manifest(self.changectx(req),
946 self.cleanpath(req.form['path'][0])))
949 self.cleanpath(req.form['path'][0])))
947
950
948 def do_tags(self, req):
951 def do_tags(self, req):
949 req.write(self.tags())
952 req.write(self.tags())
950
953
951 def do_summary(self, req):
954 def do_summary(self, req):
952 req.write(self.summary())
955 req.write(self.summary())
953
956
954 def do_filediff(self, req):
957 def do_filediff(self, req):
955 req.write(self.filediff(self.filectx(req)))
958 req.write(self.filediff(self.filectx(req)))
956
959
957 def do_annotate(self, req):
960 def do_annotate(self, req):
958 req.write(self.fileannotate(self.filectx(req)))
961 req.write(self.fileannotate(self.filectx(req)))
959
962
960 def do_filelog(self, req):
963 def do_filelog(self, req):
961 req.write(self.filelog(self.filectx(req)))
964 req.write(self.filelog(self.filectx(req)))
962
965
963 def do_lookup(self, req):
966 def do_lookup(self, req):
964 try:
967 try:
965 r = hex(self.repo.lookup(req.form['key'][0]))
968 r = hex(self.repo.lookup(req.form['key'][0]))
966 success = 1
969 success = 1
967 except Exception,inst:
970 except Exception,inst:
968 r = str(inst)
971 r = str(inst)
969 success = 0
972 success = 0
970 resp = "%s %s\n" % (success, r)
973 resp = "%s %s\n" % (success, r)
971 req.httphdr("application/mercurial-0.1", length=len(resp))
974 req.httphdr("application/mercurial-0.1", length=len(resp))
972 req.write(resp)
975 req.write(resp)
973
976
974 def do_heads(self, req):
977 def do_heads(self, req):
975 resp = " ".join(map(hex, self.repo.heads())) + "\n"
978 resp = " ".join(map(hex, self.repo.heads())) + "\n"
976 req.httphdr("application/mercurial-0.1", length=len(resp))
979 req.httphdr("application/mercurial-0.1", length=len(resp))
977 req.write(resp)
980 req.write(resp)
978
981
979 def do_branches(self, req):
982 def do_branches(self, req):
980 nodes = []
983 nodes = []
981 if req.form.has_key('nodes'):
984 if req.form.has_key('nodes'):
982 nodes = map(bin, req.form['nodes'][0].split(" "))
985 nodes = map(bin, req.form['nodes'][0].split(" "))
983 resp = cStringIO.StringIO()
986 resp = cStringIO.StringIO()
984 for b in self.repo.branches(nodes):
987 for b in self.repo.branches(nodes):
985 resp.write(" ".join(map(hex, b)) + "\n")
988 resp.write(" ".join(map(hex, b)) + "\n")
986 resp = resp.getvalue()
989 resp = resp.getvalue()
987 req.httphdr("application/mercurial-0.1", length=len(resp))
990 req.httphdr("application/mercurial-0.1", length=len(resp))
988 req.write(resp)
991 req.write(resp)
989
992
990 def do_between(self, req):
993 def do_between(self, req):
991 if req.form.has_key('pairs'):
994 if req.form.has_key('pairs'):
992 pairs = [map(bin, p.split("-"))
995 pairs = [map(bin, p.split("-"))
993 for p in req.form['pairs'][0].split(" ")]
996 for p in req.form['pairs'][0].split(" ")]
994 resp = cStringIO.StringIO()
997 resp = cStringIO.StringIO()
995 for b in self.repo.between(pairs):
998 for b in self.repo.between(pairs):
996 resp.write(" ".join(map(hex, b)) + "\n")
999 resp.write(" ".join(map(hex, b)) + "\n")
997 resp = resp.getvalue()
1000 resp = resp.getvalue()
998 req.httphdr("application/mercurial-0.1", length=len(resp))
1001 req.httphdr("application/mercurial-0.1", length=len(resp))
999 req.write(resp)
1002 req.write(resp)
1000
1003
1001 def do_changegroup(self, req):
1004 def do_changegroup(self, req):
1002 req.httphdr("application/mercurial-0.1")
1005 req.httphdr("application/mercurial-0.1")
1003 nodes = []
1006 nodes = []
1004 if not self.allowpull:
1007 if not self.allowpull:
1005 return
1008 return
1006
1009
1007 if req.form.has_key('roots'):
1010 if req.form.has_key('roots'):
1008 nodes = map(bin, req.form['roots'][0].split(" "))
1011 nodes = map(bin, req.form['roots'][0].split(" "))
1009
1012
1010 z = zlib.compressobj()
1013 z = zlib.compressobj()
1011 f = self.repo.changegroup(nodes, 'serve')
1014 f = self.repo.changegroup(nodes, 'serve')
1012 while 1:
1015 while 1:
1013 chunk = f.read(4096)
1016 chunk = f.read(4096)
1014 if not chunk:
1017 if not chunk:
1015 break
1018 break
1016 req.write(z.compress(chunk))
1019 req.write(z.compress(chunk))
1017
1020
1018 req.write(z.flush())
1021 req.write(z.flush())
1019
1022
1020 def do_changegroupsubset(self, req):
1023 def do_changegroupsubset(self, req):
1021 req.httphdr("application/mercurial-0.1")
1024 req.httphdr("application/mercurial-0.1")
1022 bases = []
1025 bases = []
1023 heads = []
1026 heads = []
1024 if not self.allowpull:
1027 if not self.allowpull:
1025 return
1028 return
1026
1029
1027 if req.form.has_key('bases'):
1030 if req.form.has_key('bases'):
1028 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1031 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1029 if req.form.has_key('heads'):
1032 if req.form.has_key('heads'):
1030 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1033 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1031
1034
1032 z = zlib.compressobj()
1035 z = zlib.compressobj()
1033 f = self.repo.changegroupsubset(bases, heads, 'serve')
1036 f = self.repo.changegroupsubset(bases, heads, 'serve')
1034 while 1:
1037 while 1:
1035 chunk = f.read(4096)
1038 chunk = f.read(4096)
1036 if not chunk:
1039 if not chunk:
1037 break
1040 break
1038 req.write(z.compress(chunk))
1041 req.write(z.compress(chunk))
1039
1042
1040 req.write(z.flush())
1043 req.write(z.flush())
1041
1044
1042 def do_archive(self, req):
1045 def do_archive(self, req):
1043 type_ = req.form['type'][0]
1046 type_ = req.form['type'][0]
1044 allowed = self.configlist("web", "allow_archive")
1047 allowed = self.configlist("web", "allow_archive")
1045 if (type_ in self.archives and (type_ in allowed or
1048 if (type_ in self.archives and (type_ in allowed or
1046 self.configbool("web", "allow" + type_, False))):
1049 self.configbool("web", "allow" + type_, False))):
1047 self.archive(req, req.form['node'][0], type_)
1050 self.archive(req, req.form['node'][0], type_)
1048 return
1051 return
1049
1052
1050 req.respond(400, self.t('error',
1053 req.respond(400, self.t('error',
1051 error='Unsupported archive type: %s' % type_))
1054 error='Unsupported archive type: %s' % type_))
1052
1055
1053 def do_static(self, req):
1056 def do_static(self, req):
1054 fname = req.form['file'][0]
1057 fname = req.form['file'][0]
1055 # a repo owner may set web.static in .hg/hgrc to get any file
1058 # a repo owner may set web.static in .hg/hgrc to get any file
1056 # readable by the user running the CGI script
1059 # readable by the user running the CGI script
1057 static = self.config("web", "static",
1060 static = self.config("web", "static",
1058 os.path.join(self.templatepath, "static"),
1061 os.path.join(self.templatepath, "static"),
1059 untrusted=False)
1062 untrusted=False)
1060 req.write(staticfile(static, fname, req))
1063 req.write(staticfile(static, fname, req))
1061
1064
1062 def do_capabilities(self, req):
1065 def do_capabilities(self, req):
1063 caps = ['lookup', 'changegroupsubset']
1066 caps = ['lookup', 'changegroupsubset']
1064 if self.configbool('server', 'uncompressed'):
1067 if self.configbool('server', 'uncompressed'):
1065 caps.append('stream=%d' % self.repo.changelog.version)
1068 caps.append('stream=%d' % self.repo.changelog.version)
1066 # XXX: make configurable and/or share code with do_unbundle:
1069 # XXX: make configurable and/or share code with do_unbundle:
1067 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1070 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1068 if unbundleversions:
1071 if unbundleversions:
1069 caps.append('unbundle=%s' % ','.join(unbundleversions))
1072 caps.append('unbundle=%s' % ','.join(unbundleversions))
1070 resp = ' '.join(caps)
1073 resp = ' '.join(caps)
1071 req.httphdr("application/mercurial-0.1", length=len(resp))
1074 req.httphdr("application/mercurial-0.1", length=len(resp))
1072 req.write(resp)
1075 req.write(resp)
1073
1076
1074 def check_perm(self, req, op, default):
1077 def check_perm(self, req, op, default):
1075 '''check permission for operation based on user auth.
1078 '''check permission for operation based on user auth.
1076 return true if op allowed, else false.
1079 return true if op allowed, else false.
1077 default is policy to use if no config given.'''
1080 default is policy to use if no config given.'''
1078
1081
1079 user = req.env.get('REMOTE_USER')
1082 user = req.env.get('REMOTE_USER')
1080
1083
1081 deny = self.configlist('web', 'deny_' + op)
1084 deny = self.configlist('web', 'deny_' + op)
1082 if deny and (not user or deny == ['*'] or user in deny):
1085 if deny and (not user or deny == ['*'] or user in deny):
1083 return False
1086 return False
1084
1087
1085 allow = self.configlist('web', 'allow_' + op)
1088 allow = self.configlist('web', 'allow_' + op)
1086 return (allow and (allow == ['*'] or user in allow)) or default
1089 return (allow and (allow == ['*'] or user in allow)) or default
1087
1090
1088 def do_unbundle(self, req):
1091 def do_unbundle(self, req):
1089 def bail(response, headers={}):
1092 def bail(response, headers={}):
1090 length = int(req.env['CONTENT_LENGTH'])
1093 length = int(req.env['CONTENT_LENGTH'])
1091 for s in util.filechunkiter(req, limit=length):
1094 for s in util.filechunkiter(req, limit=length):
1092 # drain incoming bundle, else client will not see
1095 # drain incoming bundle, else client will not see
1093 # response when run outside cgi script
1096 # response when run outside cgi script
1094 pass
1097 pass
1095 req.httphdr("application/mercurial-0.1", headers=headers)
1098 req.httphdr("application/mercurial-0.1", headers=headers)
1096 req.write('0\n')
1099 req.write('0\n')
1097 req.write(response)
1100 req.write(response)
1098
1101
1099 # require ssl by default, auth info cannot be sniffed and
1102 # require ssl by default, auth info cannot be sniffed and
1100 # replayed
1103 # replayed
1101 ssl_req = self.configbool('web', 'push_ssl', True)
1104 ssl_req = self.configbool('web', 'push_ssl', True)
1102 if ssl_req:
1105 if ssl_req:
1103 if req.env.get('wsgi.url_scheme') != 'https':
1106 if req.env.get('wsgi.url_scheme') != 'https':
1104 bail(_('ssl required\n'))
1107 bail(_('ssl required\n'))
1105 return
1108 return
1106 proto = 'https'
1109 proto = 'https'
1107 else:
1110 else:
1108 proto = 'http'
1111 proto = 'http'
1109
1112
1110 # do not allow push unless explicitly allowed
1113 # do not allow push unless explicitly allowed
1111 if not self.check_perm(req, 'push', False):
1114 if not self.check_perm(req, 'push', False):
1112 bail(_('push not authorized\n'),
1115 bail(_('push not authorized\n'),
1113 headers={'status': '401 Unauthorized'})
1116 headers={'status': '401 Unauthorized'})
1114 return
1117 return
1115
1118
1116 their_heads = req.form['heads'][0].split(' ')
1119 their_heads = req.form['heads'][0].split(' ')
1117
1120
1118 def check_heads():
1121 def check_heads():
1119 heads = map(hex, self.repo.heads())
1122 heads = map(hex, self.repo.heads())
1120 return their_heads == [hex('force')] or their_heads == heads
1123 return their_heads == [hex('force')] or their_heads == heads
1121
1124
1122 # fail early if possible
1125 # fail early if possible
1123 if not check_heads():
1126 if not check_heads():
1124 bail(_('unsynced changes\n'))
1127 bail(_('unsynced changes\n'))
1125 return
1128 return
1126
1129
1127 req.httphdr("application/mercurial-0.1")
1130 req.httphdr("application/mercurial-0.1")
1128
1131
1129 # do not lock repo until all changegroup data is
1132 # do not lock repo until all changegroup data is
1130 # streamed. save to temporary file.
1133 # streamed. save to temporary file.
1131
1134
1132 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1135 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1133 fp = os.fdopen(fd, 'wb+')
1136 fp = os.fdopen(fd, 'wb+')
1134 try:
1137 try:
1135 length = int(req.env['CONTENT_LENGTH'])
1138 length = int(req.env['CONTENT_LENGTH'])
1136 for s in util.filechunkiter(req, limit=length):
1139 for s in util.filechunkiter(req, limit=length):
1137 fp.write(s)
1140 fp.write(s)
1138
1141
1139 try:
1142 try:
1140 lock = self.repo.lock()
1143 lock = self.repo.lock()
1141 try:
1144 try:
1142 if not check_heads():
1145 if not check_heads():
1143 req.write('0\n')
1146 req.write('0\n')
1144 req.write(_('unsynced changes\n'))
1147 req.write(_('unsynced changes\n'))
1145 return
1148 return
1146
1149
1147 fp.seek(0)
1150 fp.seek(0)
1148 header = fp.read(6)
1151 header = fp.read(6)
1149 if not header.startswith("HG"):
1152 if not header.startswith("HG"):
1150 # old client with uncompressed bundle
1153 # old client with uncompressed bundle
1151 def generator(f):
1154 def generator(f):
1152 yield header
1155 yield header
1153 for chunk in f:
1156 for chunk in f:
1154 yield chunk
1157 yield chunk
1155 elif not header.startswith("HG10"):
1158 elif not header.startswith("HG10"):
1156 req.write("0\n")
1159 req.write("0\n")
1157 req.write(_("unknown bundle version\n"))
1160 req.write(_("unknown bundle version\n"))
1158 return
1161 return
1159 elif header == "HG10GZ":
1162 elif header == "HG10GZ":
1160 def generator(f):
1163 def generator(f):
1161 zd = zlib.decompressobj()
1164 zd = zlib.decompressobj()
1162 for chunk in f:
1165 for chunk in f:
1163 yield zd.decompress(chunk)
1166 yield zd.decompress(chunk)
1164 elif header == "HG10BZ":
1167 elif header == "HG10BZ":
1165 def generator(f):
1168 def generator(f):
1166 zd = bz2.BZ2Decompressor()
1169 zd = bz2.BZ2Decompressor()
1167 zd.decompress("BZ")
1170 zd.decompress("BZ")
1168 for chunk in f:
1171 for chunk in f:
1169 yield zd.decompress(chunk)
1172 yield zd.decompress(chunk)
1170 elif header == "HG10UN":
1173 elif header == "HG10UN":
1171 def generator(f):
1174 def generator(f):
1172 for chunk in f:
1175 for chunk in f:
1173 yield chunk
1176 yield chunk
1174 else:
1177 else:
1175 req.write("0\n")
1178 req.write("0\n")
1176 req.write(_("unknown bundle compression type\n"))
1179 req.write(_("unknown bundle compression type\n"))
1177 return
1180 return
1178 gen = generator(util.filechunkiter(fp, 4096))
1181 gen = generator(util.filechunkiter(fp, 4096))
1179
1182
1180 # send addchangegroup output to client
1183 # send addchangegroup output to client
1181
1184
1182 old_stdout = sys.stdout
1185 old_stdout = sys.stdout
1183 sys.stdout = cStringIO.StringIO()
1186 sys.stdout = cStringIO.StringIO()
1184
1187
1185 try:
1188 try:
1186 url = 'remote:%s:%s' % (proto,
1189 url = 'remote:%s:%s' % (proto,
1187 req.env.get('REMOTE_HOST', ''))
1190 req.env.get('REMOTE_HOST', ''))
1188 try:
1191 try:
1189 ret = self.repo.addchangegroup(
1192 ret = self.repo.addchangegroup(
1190 util.chunkbuffer(gen), 'serve', url)
1193 util.chunkbuffer(gen), 'serve', url)
1191 except util.Abort, inst:
1194 except util.Abort, inst:
1192 sys.stdout.write("abort: %s\n" % inst)
1195 sys.stdout.write("abort: %s\n" % inst)
1193 ret = 0
1196 ret = 0
1194 finally:
1197 finally:
1195 val = sys.stdout.getvalue()
1198 val = sys.stdout.getvalue()
1196 sys.stdout = old_stdout
1199 sys.stdout = old_stdout
1197 req.write('%d\n' % ret)
1200 req.write('%d\n' % ret)
1198 req.write(val)
1201 req.write(val)
1199 finally:
1202 finally:
1200 del lock
1203 del lock
1201 except (OSError, IOError), inst:
1204 except (OSError, IOError), inst:
1202 req.write('0\n')
1205 req.write('0\n')
1203 filename = getattr(inst, 'filename', '')
1206 filename = getattr(inst, 'filename', '')
1204 # Don't send our filesystem layout to the client
1207 # Don't send our filesystem layout to the client
1205 if filename.startswith(self.repo.root):
1208 if filename.startswith(self.repo.root):
1206 filename = filename[len(self.repo.root)+1:]
1209 filename = filename[len(self.repo.root)+1:]
1207 else:
1210 else:
1208 filename = ''
1211 filename = ''
1209 error = getattr(inst, 'strerror', 'Unknown error')
1212 error = getattr(inst, 'strerror', 'Unknown error')
1210 if inst.errno == errno.ENOENT:
1213 if inst.errno == errno.ENOENT:
1211 code = 404
1214 code = 404
1212 else:
1215 else:
1213 code = 500
1216 code = 500
1214 req.respond(code, '%s: %s\n' % (error, filename))
1217 req.respond(code, '%s: %s\n' % (error, filename))
1215 finally:
1218 finally:
1216 fp.close()
1219 fp.close()
1217 os.unlink(tempname)
1220 os.unlink(tempname)
1218
1221
1219 def do_stream_out(self, req):
1222 def do_stream_out(self, req):
1220 req.httphdr("application/mercurial-0.1")
1223 req.httphdr("application/mercurial-0.1")
1221 streamclone.stream_out(self.repo, req, untrusted=True)
1224 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,261 +1,264 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
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, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14 from request import wsgirequest
14
15
15 # This is a stopgap
16 # This is a stopgap
16 class hgwebdir(object):
17 class hgwebdir(object):
17 def __init__(self, config, parentui=None):
18 def __init__(self, config, parentui=None):
18 def cleannames(items):
19 def cleannames(items):
19 return [(util.pconvert(name.strip(os.sep)), path)
20 return [(util.pconvert(name.strip(os.sep)), path)
20 for name, path in items]
21 for name, path in items]
21
22
22 self.parentui = parentui
23 self.parentui = parentui
23 self.motd = None
24 self.motd = None
24 self.style = None
25 self.style = None
25 self.stripecount = None
26 self.stripecount = None
26 self.repos_sorted = ('name', False)
27 self.repos_sorted = ('name', False)
27 if isinstance(config, (list, tuple)):
28 if isinstance(config, (list, tuple)):
28 self.repos = cleannames(config)
29 self.repos = cleannames(config)
29 self.repos_sorted = ('', False)
30 self.repos_sorted = ('', False)
30 elif isinstance(config, dict):
31 elif isinstance(config, dict):
31 self.repos = cleannames(config.items())
32 self.repos = cleannames(config.items())
32 self.repos.sort()
33 self.repos.sort()
33 else:
34 else:
34 if isinstance(config, util.configparser):
35 if isinstance(config, util.configparser):
35 cp = config
36 cp = config
36 else:
37 else:
37 cp = util.configparser()
38 cp = util.configparser()
38 cp.read(config)
39 cp.read(config)
39 self.repos = []
40 self.repos = []
40 if cp.has_section('web'):
41 if cp.has_section('web'):
41 if cp.has_option('web', 'motd'):
42 if cp.has_option('web', 'motd'):
42 self.motd = cp.get('web', 'motd')
43 self.motd = cp.get('web', 'motd')
43 if cp.has_option('web', 'style'):
44 if cp.has_option('web', 'style'):
44 self.style = cp.get('web', 'style')
45 self.style = cp.get('web', 'style')
45 if cp.has_option('web', 'stripes'):
46 if cp.has_option('web', 'stripes'):
46 self.stripecount = int(cp.get('web', 'stripes'))
47 self.stripecount = int(cp.get('web', 'stripes'))
47 if cp.has_section('paths'):
48 if cp.has_section('paths'):
48 self.repos.extend(cleannames(cp.items('paths')))
49 self.repos.extend(cleannames(cp.items('paths')))
49 if cp.has_section('collections'):
50 if cp.has_section('collections'):
50 for prefix, root in cp.items('collections'):
51 for prefix, root in cp.items('collections'):
51 for path in util.walkrepos(root):
52 for path in util.walkrepos(root):
52 repo = os.path.normpath(path)
53 repo = os.path.normpath(path)
53 name = repo
54 name = repo
54 if name.startswith(prefix):
55 if name.startswith(prefix):
55 name = name[len(prefix):]
56 name = name[len(prefix):]
56 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.sort()
58 self.repos.sort()
58
59
59 def run(self):
60 def run(self):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 import mercurial.hgweb.wsgicgi as wsgicgi
63 import mercurial.hgweb.wsgicgi as wsgicgi
63 from request import wsgiapplication
64 wsgicgi.launch(self)
64 def make_web_app():
65
65 return self
66 def __call__(self, env, respond):
66 wsgicgi.launch(wsgiapplication(make_web_app))
67 req = wsgirequest(env, respond)
68 self.run_wsgi(req)
69 return req
67
70
68 def run_wsgi(self, req):
71 def run_wsgi(self, req):
69 def header(**map):
72 def header(**map):
70 header_file = cStringIO.StringIO(
73 header_file = cStringIO.StringIO(
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
74 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 msg = mimetools.Message(header_file, 0)
75 msg = mimetools.Message(header_file, 0)
73 req.header(msg.items())
76 req.header(msg.items())
74 yield header_file.read()
77 yield header_file.read()
75
78
76 def footer(**map):
79 def footer(**map):
77 yield tmpl("footer", **map)
80 yield tmpl("footer", **map)
78
81
79 def motd(**map):
82 def motd(**map):
80 if self.motd is not None:
83 if self.motd is not None:
81 yield self.motd
84 yield self.motd
82 else:
85 else:
83 yield config('web', 'motd', '')
86 yield config('web', 'motd', '')
84
87
85 parentui = self.parentui or ui.ui(report_untrusted=False,
88 parentui = self.parentui or ui.ui(report_untrusted=False,
86 interactive=False)
89 interactive=False)
87
90
88 def config(section, name, default=None, untrusted=True):
91 def config(section, name, default=None, untrusted=True):
89 return parentui.config(section, name, default, untrusted)
92 return parentui.config(section, name, default, untrusted)
90
93
91 url = req.env['REQUEST_URI'].split('?')[0]
94 url = req.env['REQUEST_URI'].split('?')[0]
92 if not url.endswith('/'):
95 if not url.endswith('/'):
93 url += '/'
96 url += '/'
94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
97 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
95 base = url[:len(url) - len(pathinfo)]
98 base = url[:len(url) - len(pathinfo)]
96 if not base.endswith('/'):
99 if not base.endswith('/'):
97 base += '/'
100 base += '/'
98
101
99 staticurl = config('web', 'staticurl') or base + 'static/'
102 staticurl = config('web', 'staticurl') or base + 'static/'
100 if not staticurl.endswith('/'):
103 if not staticurl.endswith('/'):
101 staticurl += '/'
104 staticurl += '/'
102
105
103 style = self.style
106 style = self.style
104 if style is None:
107 if style is None:
105 style = config('web', 'style', '')
108 style = config('web', 'style', '')
106 if req.form.has_key('style'):
109 if req.form.has_key('style'):
107 style = req.form['style'][0]
110 style = req.form['style'][0]
108 if self.stripecount is None:
111 if self.stripecount is None:
109 self.stripecount = int(config('web', 'stripes', 1))
112 self.stripecount = int(config('web', 'stripes', 1))
110 mapfile = style_map(templater.templatepath(), style)
113 mapfile = style_map(templater.templatepath(), style)
111 tmpl = templater.templater(mapfile, templater.common_filters,
114 tmpl = templater.templater(mapfile, templater.common_filters,
112 defaults={"header": header,
115 defaults={"header": header,
113 "footer": footer,
116 "footer": footer,
114 "motd": motd,
117 "motd": motd,
115 "url": url,
118 "url": url,
116 "staticurl": staticurl})
119 "staticurl": staticurl})
117
120
118 def archivelist(ui, nodeid, url):
121 def archivelist(ui, nodeid, url):
119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
122 allowed = ui.configlist("web", "allow_archive", untrusted=True)
120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
123 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
124 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
122 untrusted=True):
125 untrusted=True):
123 yield {"type" : i[0], "extension": i[1],
126 yield {"type" : i[0], "extension": i[1],
124 "node": nodeid, "url": url}
127 "node": nodeid, "url": url}
125
128
126 def entries(sortcolumn="", descending=False, subdir="", **map):
129 def entries(sortcolumn="", descending=False, subdir="", **map):
127 def sessionvars(**map):
130 def sessionvars(**map):
128 fields = []
131 fields = []
129 if req.form.has_key('style'):
132 if req.form.has_key('style'):
130 style = req.form['style'][0]
133 style = req.form['style'][0]
131 if style != get('web', 'style', ''):
134 if style != get('web', 'style', ''):
132 fields.append(('style', style))
135 fields.append(('style', style))
133
136
134 separator = url[-1] == '?' and ';' or '?'
137 separator = url[-1] == '?' and ';' or '?'
135 for name, value in fields:
138 for name, value in fields:
136 yield dict(name=name, value=value, separator=separator)
139 yield dict(name=name, value=value, separator=separator)
137 separator = ';'
140 separator = ';'
138
141
139 rows = []
142 rows = []
140 parity = paritygen(self.stripecount)
143 parity = paritygen(self.stripecount)
141 for name, path in self.repos:
144 for name, path in self.repos:
142 if not name.startswith(subdir):
145 if not name.startswith(subdir):
143 continue
146 continue
144 name = name[len(subdir):]
147 name = name[len(subdir):]
145
148
146 u = ui.ui(parentui=parentui)
149 u = ui.ui(parentui=parentui)
147 try:
150 try:
148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
151 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
149 except Exception, e:
152 except Exception, e:
150 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
153 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
151 continue
154 continue
152 def get(section, name, default=None):
155 def get(section, name, default=None):
153 return u.config(section, name, default, untrusted=True)
156 return u.config(section, name, default, untrusted=True)
154
157
155 if u.configbool("web", "hidden", untrusted=True):
158 if u.configbool("web", "hidden", untrusted=True):
156 continue
159 continue
157
160
158 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
161 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
159 .replace("//", "/")) + '/'
162 .replace("//", "/")) + '/'
160
163
161 # update time with local timezone
164 # update time with local timezone
162 try:
165 try:
163 d = (get_mtime(path), util.makedate()[1])
166 d = (get_mtime(path), util.makedate()[1])
164 except OSError:
167 except OSError:
165 continue
168 continue
166
169
167 contact = (get("ui", "username") or # preferred
170 contact = (get("ui", "username") or # preferred
168 get("web", "contact") or # deprecated
171 get("web", "contact") or # deprecated
169 get("web", "author", "")) # also
172 get("web", "author", "")) # also
170 description = get("web", "description", "")
173 description = get("web", "description", "")
171 name = get("web", "name", name)
174 name = get("web", "name", name)
172 row = dict(contact=contact or "unknown",
175 row = dict(contact=contact or "unknown",
173 contact_sort=contact.upper() or "unknown",
176 contact_sort=contact.upper() or "unknown",
174 name=name,
177 name=name,
175 name_sort=name,
178 name_sort=name,
176 url=url,
179 url=url,
177 description=description or "unknown",
180 description=description or "unknown",
178 description_sort=description.upper() or "unknown",
181 description_sort=description.upper() or "unknown",
179 lastchange=d,
182 lastchange=d,
180 lastchange_sort=d[1]-d[0],
183 lastchange_sort=d[1]-d[0],
181 sessionvars=sessionvars,
184 sessionvars=sessionvars,
182 archives=archivelist(u, "tip", url))
185 archives=archivelist(u, "tip", url))
183 if (not sortcolumn
186 if (not sortcolumn
184 or (sortcolumn, descending) == self.repos_sorted):
187 or (sortcolumn, descending) == self.repos_sorted):
185 # fast path for unsorted output
188 # fast path for unsorted output
186 row['parity'] = parity.next()
189 row['parity'] = parity.next()
187 yield row
190 yield row
188 else:
191 else:
189 rows.append((row["%s_sort" % sortcolumn], row))
192 rows.append((row["%s_sort" % sortcolumn], row))
190 if rows:
193 if rows:
191 rows.sort()
194 rows.sort()
192 if descending:
195 if descending:
193 rows.reverse()
196 rows.reverse()
194 for key, row in rows:
197 for key, row in rows:
195 row['parity'] = parity.next()
198 row['parity'] = parity.next()
196 yield row
199 yield row
197
200
198 def makeindex(req, subdir=""):
201 def makeindex(req, subdir=""):
199 sortable = ["name", "description", "contact", "lastchange"]
202 sortable = ["name", "description", "contact", "lastchange"]
200 sortcolumn, descending = self.repos_sorted
203 sortcolumn, descending = self.repos_sorted
201 if req.form.has_key('sort'):
204 if req.form.has_key('sort'):
202 sortcolumn = req.form['sort'][0]
205 sortcolumn = req.form['sort'][0]
203 descending = sortcolumn.startswith('-')
206 descending = sortcolumn.startswith('-')
204 if descending:
207 if descending:
205 sortcolumn = sortcolumn[1:]
208 sortcolumn = sortcolumn[1:]
206 if sortcolumn not in sortable:
209 if sortcolumn not in sortable:
207 sortcolumn = ""
210 sortcolumn = ""
208
211
209 sort = [("sort_%s" % column,
212 sort = [("sort_%s" % column,
210 "%s%s" % ((not descending and column == sortcolumn)
213 "%s%s" % ((not descending and column == sortcolumn)
211 and "-" or "", column))
214 and "-" or "", column))
212 for column in sortable]
215 for column in sortable]
213 req.write(tmpl("index", entries=entries, subdir=subdir,
216 req.write(tmpl("index", entries=entries, subdir=subdir,
214 sortcolumn=sortcolumn, descending=descending,
217 sortcolumn=sortcolumn, descending=descending,
215 **dict(sort)))
218 **dict(sort)))
216
219
217 try:
220 try:
218 try:
221 try:
219 virtual = req.env.get("PATH_INFO", "").strip('/')
222 virtual = req.env.get("PATH_INFO", "").strip('/')
220 if virtual.startswith('static/'):
223 if virtual.startswith('static/'):
221 static = os.path.join(templater.templatepath(), 'static')
224 static = os.path.join(templater.templatepath(), 'static')
222 fname = virtual[7:]
225 fname = virtual[7:]
223 req.write(staticfile(static, fname, req))
226 req.write(staticfile(static, fname, req))
224 elif virtual:
227 elif virtual:
225 repos = dict(self.repos)
228 repos = dict(self.repos)
226 while virtual:
229 while virtual:
227 real = repos.get(virtual)
230 real = repos.get(virtual)
228 if real:
231 if real:
229 req.env['REPO_NAME'] = virtual
232 req.env['REPO_NAME'] = virtual
230 try:
233 try:
231 repo = hg.repository(parentui, real)
234 repo = hg.repository(parentui, real)
232 hgweb(repo).run_wsgi(req)
235 hgweb(repo).run_wsgi(req)
233 return
236 return
234 except IOError, inst:
237 except IOError, inst:
235 raise ErrorResponse(500, inst.strerror)
238 raise ErrorResponse(500, inst.strerror)
236 except hg.RepoError, inst:
239 except hg.RepoError, inst:
237 raise ErrorResponse(500, str(inst))
240 raise ErrorResponse(500, str(inst))
238
241
239 # browse subdirectories
242 # browse subdirectories
240 subdir = virtual + '/'
243 subdir = virtual + '/'
241 if [r for r in repos if r.startswith(subdir)]:
244 if [r for r in repos if r.startswith(subdir)]:
242 makeindex(req, subdir)
245 makeindex(req, subdir)
243 return
246 return
244
247
245 up = virtual.rfind('/')
248 up = virtual.rfind('/')
246 if up < 0:
249 if up < 0:
247 break
250 break
248 virtual = virtual[:up]
251 virtual = virtual[:up]
249
252
250 req.respond(404, tmpl("notfound", repo=virtual))
253 req.respond(404, tmpl("notfound", repo=virtual))
251 else:
254 else:
252 if req.form.has_key('static'):
255 if req.form.has_key('static'):
253 static = os.path.join(templater.templatepath(), "static")
256 static = os.path.join(templater.templatepath(), "static")
254 fname = req.form['static'][0]
257 fname = req.form['static'][0]
255 req.write(staticfile(static, fname, req))
258 req.write(staticfile(static, fname, req))
256 else:
259 else:
257 makeindex(req)
260 makeindex(req)
258 except ErrorResponse, err:
261 except ErrorResponse, err:
259 req.respond(err.code, tmpl('error', error=err.message or ''))
262 req.respond(err.code, tmpl('error', error=err.message or ''))
260 finally:
263 finally:
261 tmpl = None
264 tmpl = None
@@ -1,94 +1,92 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
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
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 socket, cgi, errno
9 import socket, cgi, errno
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from common import ErrorResponse, statusmessage
11 from common import ErrorResponse, statusmessage
12
12
13 class wsgiapplication(object):
13 class wsgirequest(object):
14 def __init__(self, destmaker):
14 def __init__(self, wsgienv, start_response):
15 self.destmaker = destmaker
16
17 def __call__(self, wsgienv, start_response):
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19
20 class _wsgirequest(object):
21 def __init__(self, destination, wsgienv, start_response):
22 version = wsgienv['wsgi.version']
15 version = wsgienv['wsgi.version']
23 if (version < (1, 0)) or (version >= (2, 0)):
16 if (version < (1, 0)) or (version >= (2, 0)):
24 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
17 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
25 % version)
18 % version)
26 self.inp = wsgienv['wsgi.input']
19 self.inp = wsgienv['wsgi.input']
27 self.server_write = None
20 self.server_write = None
28 self.err = wsgienv['wsgi.errors']
21 self.err = wsgienv['wsgi.errors']
29 self.threaded = wsgienv['wsgi.multithread']
22 self.threaded = wsgienv['wsgi.multithread']
30 self.multiprocess = wsgienv['wsgi.multiprocess']
23 self.multiprocess = wsgienv['wsgi.multiprocess']
31 self.run_once = wsgienv['wsgi.run_once']
24 self.run_once = wsgienv['wsgi.run_once']
32 self.env = wsgienv
25 self.env = wsgienv
33 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
26 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
34 self.start_response = start_response
27 self.start_response = start_response
35 self.headers = []
28 self.headers = []
36 destination.run_wsgi(self)
37
29
38 out = property(lambda self: self)
30 out = property(lambda self: self)
39
31
40 def __iter__(self):
32 def __iter__(self):
41 return iter([])
33 return iter([])
42
34
43 def read(self, count=-1):
35 def read(self, count=-1):
44 return self.inp.read(count)
36 return self.inp.read(count)
45
37
46 def respond(self, status, *things):
38 def respond(self, status, *things):
47 for thing in things:
39 for thing in things:
48 if hasattr(thing, "__iter__"):
40 if hasattr(thing, "__iter__"):
49 for part in thing:
41 for part in thing:
50 self.respond(status, part)
42 self.respond(status, part)
51 else:
43 else:
52 thing = str(thing)
44 thing = str(thing)
53 if self.server_write is None:
45 if self.server_write is None:
54 if not self.headers:
46 if not self.headers:
55 raise RuntimeError("request.write called before headers sent (%s)." % thing)
47 raise RuntimeError("request.write called before headers sent (%s)." % thing)
56 if isinstance(status, ErrorResponse):
48 if isinstance(status, ErrorResponse):
57 status = statusmessage(status.code)
49 status = statusmessage(status.code)
58 elif isinstance(status, int):
50 elif isinstance(status, int):
59 status = statusmessage(status)
51 status = statusmessage(status)
60 self.server_write = self.start_response(status,
52 self.server_write = self.start_response(status,
61 self.headers)
53 self.headers)
62 self.start_response = None
54 self.start_response = None
63 self.headers = []
55 self.headers = []
64 try:
56 try:
65 self.server_write(thing)
57 self.server_write(thing)
66 except socket.error, inst:
58 except socket.error, inst:
67 if inst[0] != errno.ECONNRESET:
59 if inst[0] != errno.ECONNRESET:
68 raise
60 raise
69
61
70 def write(self, *things):
62 def write(self, *things):
71 self.respond('200 Script output follows', *things)
63 self.respond('200 Script output follows', *things)
72
64
73 def writelines(self, lines):
65 def writelines(self, lines):
74 for line in lines:
66 for line in lines:
75 self.write(line)
67 self.write(line)
76
68
77 def flush(self):
69 def flush(self):
78 return None
70 return None
79
71
80 def close(self):
72 def close(self):
81 return None
73 return None
82
74
83 def header(self, headers=[('Content-type','text/html')]):
75 def header(self, headers=[('Content-type','text/html')]):
84 self.headers.extend(headers)
76 self.headers.extend(headers)
85
77
86 def httphdr(self, type, filename=None, length=0, headers={}):
78 def httphdr(self, type, filename=None, length=0, headers={}):
87 headers = headers.items()
79 headers = headers.items()
88 headers.append(('Content-type', type))
80 headers.append(('Content-type', type))
89 if filename:
81 if filename:
90 headers.append(('Content-disposition', 'attachment; filename=%s' %
82 headers.append(('Content-disposition', 'attachment; filename=%s' %
91 filename))
83 filename))
92 if length:
84 if length:
93 headers.append(('Content-length', str(length)))
85 headers.append(('Content-length', str(length)))
94 self.header(headers)
86 self.header(headers)
87
88 def wsgiapplication(app_maker):
89 application = app_maker()
90 def run_wsgi(env, respond):
91 application(env, respond)
92 return run_wsgi
@@ -1,293 +1,289 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
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, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import ui, hg, util, templater
10 from mercurial import ui, hg, util, templater
11 from hgweb_mod import hgweb
11 from hgweb_mod import hgweb
12 from hgwebdir_mod import hgwebdir
12 from hgwebdir_mod import hgwebdir
13 from request import wsgiapplication
14 from mercurial.i18n import gettext as _
13 from mercurial.i18n import gettext as _
15
14
16 def _splitURI(uri):
15 def _splitURI(uri):
17 """ Return path and query splited from uri
16 """ Return path and query splited from uri
18
17
19 Just like CGI environment, the path is unquoted, the query is
18 Just like CGI environment, the path is unquoted, the query is
20 not.
19 not.
21 """
20 """
22 if '?' in uri:
21 if '?' in uri:
23 path, query = uri.split('?', 1)
22 path, query = uri.split('?', 1)
24 else:
23 else:
25 path, query = uri, ''
24 path, query = uri, ''
26 return urllib.unquote(path), query
25 return urllib.unquote(path), query
27
26
28 class _error_logger(object):
27 class _error_logger(object):
29 def __init__(self, handler):
28 def __init__(self, handler):
30 self.handler = handler
29 self.handler = handler
31 def flush(self):
30 def flush(self):
32 pass
31 pass
33 def write(self, str):
32 def write(self, str):
34 self.writelines(str.split('\n'))
33 self.writelines(str.split('\n'))
35 def writelines(self, seq):
34 def writelines(self, seq):
36 for msg in seq:
35 for msg in seq:
37 self.handler.log_error("HG error: %s", msg)
36 self.handler.log_error("HG error: %s", msg)
38
37
39 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
40
39
41 url_scheme = 'http'
40 url_scheme = 'http'
42
41
43 def __init__(self, *args, **kargs):
42 def __init__(self, *args, **kargs):
44 self.protocol_version = 'HTTP/1.1'
43 self.protocol_version = 'HTTP/1.1'
45 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
46
45
47 def _log_any(self, fp, format, *args):
46 def _log_any(self, fp, format, *args):
48 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
49 self.log_date_time_string(),
48 self.log_date_time_string(),
50 format % args))
49 format % args))
51 fp.flush()
50 fp.flush()
52
51
53 def log_error(self, format, *args):
52 def log_error(self, format, *args):
54 self._log_any(self.server.errorlog, format, *args)
53 self._log_any(self.server.errorlog, format, *args)
55
54
56 def log_message(self, format, *args):
55 def log_message(self, format, *args):
57 self._log_any(self.server.accesslog, format, *args)
56 self._log_any(self.server.accesslog, format, *args)
58
57
59 def do_write(self):
58 def do_write(self):
60 try:
59 try:
61 self.do_hgweb()
60 self.do_hgweb()
62 except socket.error, inst:
61 except socket.error, inst:
63 if inst[0] != errno.EPIPE:
62 if inst[0] != errno.EPIPE:
64 raise
63 raise
65
64
66 def do_POST(self):
65 def do_POST(self):
67 try:
66 try:
68 self.do_write()
67 self.do_write()
69 except StandardError, inst:
68 except StandardError, inst:
70 self._start_response("500 Internal Server Error", [])
69 self._start_response("500 Internal Server Error", [])
71 self._write("Internal Server Error")
70 self._write("Internal Server Error")
72 tb = "".join(traceback.format_exception(*sys.exc_info()))
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
73 self.log_error("Exception happened during processing request '%s':\n%s",
72 self.log_error("Exception happened during processing request '%s':\n%s",
74 self.path, tb)
73 self.path, tb)
75
74
76 def do_GET(self):
75 def do_GET(self):
77 self.do_POST()
76 self.do_POST()
78
77
79 def do_hgweb(self):
78 def do_hgweb(self):
80 path_info, query = _splitURI(self.path)
79 path_info, query = _splitURI(self.path)
81
80
82 env = {}
81 env = {}
83 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
84 env['REQUEST_METHOD'] = self.command
83 env['REQUEST_METHOD'] = self.command
85 env['SERVER_NAME'] = self.server.server_name
84 env['SERVER_NAME'] = self.server.server_name
86 env['SERVER_PORT'] = str(self.server.server_port)
85 env['SERVER_PORT'] = str(self.server.server_port)
87 env['REQUEST_URI'] = self.path
86 env['REQUEST_URI'] = self.path
88 env['PATH_INFO'] = path_info
87 env['PATH_INFO'] = path_info
89 env['REMOTE_HOST'] = self.client_address[0]
88 env['REMOTE_HOST'] = self.client_address[0]
90 env['REMOTE_ADDR'] = self.client_address[0]
89 env['REMOTE_ADDR'] = self.client_address[0]
91 if query:
90 if query:
92 env['QUERY_STRING'] = query
91 env['QUERY_STRING'] = query
93
92
94 if self.headers.typeheader is None:
93 if self.headers.typeheader is None:
95 env['CONTENT_TYPE'] = self.headers.type
94 env['CONTENT_TYPE'] = self.headers.type
96 else:
95 else:
97 env['CONTENT_TYPE'] = self.headers.typeheader
96 env['CONTENT_TYPE'] = self.headers.typeheader
98 length = self.headers.getheader('content-length')
97 length = self.headers.getheader('content-length')
99 if length:
98 if length:
100 env['CONTENT_LENGTH'] = length
99 env['CONTENT_LENGTH'] = length
101 for header in [h for h in self.headers.keys()
100 for header in [h for h in self.headers.keys()
102 if h not in ('content-type', 'content-length')]:
101 if h not in ('content-type', 'content-length')]:
103 hkey = 'HTTP_' + header.replace('-', '_').upper()
102 hkey = 'HTTP_' + header.replace('-', '_').upper()
104 hval = self.headers.getheader(header)
103 hval = self.headers.getheader(header)
105 hval = hval.replace('\n', '').strip()
104 hval = hval.replace('\n', '').strip()
106 if hval:
105 if hval:
107 env[hkey] = hval
106 env[hkey] = hval
108 env['SERVER_PROTOCOL'] = self.request_version
107 env['SERVER_PROTOCOL'] = self.request_version
109 env['wsgi.version'] = (1, 0)
108 env['wsgi.version'] = (1, 0)
110 env['wsgi.url_scheme'] = self.url_scheme
109 env['wsgi.url_scheme'] = self.url_scheme
111 env['wsgi.input'] = self.rfile
110 env['wsgi.input'] = self.rfile
112 env['wsgi.errors'] = _error_logger(self)
111 env['wsgi.errors'] = _error_logger(self)
113 env['wsgi.multithread'] = isinstance(self.server,
112 env['wsgi.multithread'] = isinstance(self.server,
114 SocketServer.ThreadingMixIn)
113 SocketServer.ThreadingMixIn)
115 env['wsgi.multiprocess'] = isinstance(self.server,
114 env['wsgi.multiprocess'] = isinstance(self.server,
116 SocketServer.ForkingMixIn)
115 SocketServer.ForkingMixIn)
117 env['wsgi.run_once'] = 0
116 env['wsgi.run_once'] = 0
118
117
119 self.close_connection = True
118 self.close_connection = True
120 self.saved_status = None
119 self.saved_status = None
121 self.saved_headers = []
120 self.saved_headers = []
122 self.sent_headers = False
121 self.sent_headers = False
123 self.length = None
122 self.length = None
124 req = self.server.reqmaker(env, self._start_response)
123 self.server.application(env, self._start_response)
125 for data in req:
126 if data:
127 self._write(data)
128
124
129 def send_headers(self):
125 def send_headers(self):
130 if not self.saved_status:
126 if not self.saved_status:
131 raise AssertionError("Sending headers before start_response() called")
127 raise AssertionError("Sending headers before start_response() called")
132 saved_status = self.saved_status.split(None, 1)
128 saved_status = self.saved_status.split(None, 1)
133 saved_status[0] = int(saved_status[0])
129 saved_status[0] = int(saved_status[0])
134 self.send_response(*saved_status)
130 self.send_response(*saved_status)
135 should_close = True
131 should_close = True
136 for h in self.saved_headers:
132 for h in self.saved_headers:
137 self.send_header(*h)
133 self.send_header(*h)
138 if h[0].lower() == 'content-length':
134 if h[0].lower() == 'content-length':
139 should_close = False
135 should_close = False
140 self.length = int(h[1])
136 self.length = int(h[1])
141 # The value of the Connection header is a list of case-insensitive
137 # The value of the Connection header is a list of case-insensitive
142 # tokens separated by commas and optional whitespace.
138 # tokens separated by commas and optional whitespace.
143 if 'close' in [token.strip().lower() for token in
139 if 'close' in [token.strip().lower() for token in
144 self.headers.get('connection', '').split(',')]:
140 self.headers.get('connection', '').split(',')]:
145 should_close = True
141 should_close = True
146 if should_close:
142 if should_close:
147 self.send_header('Connection', 'close')
143 self.send_header('Connection', 'close')
148 self.close_connection = should_close
144 self.close_connection = should_close
149 self.end_headers()
145 self.end_headers()
150 self.sent_headers = True
146 self.sent_headers = True
151
147
152 def _start_response(self, http_status, headers, exc_info=None):
148 def _start_response(self, http_status, headers, exc_info=None):
153 code, msg = http_status.split(None, 1)
149 code, msg = http_status.split(None, 1)
154 code = int(code)
150 code = int(code)
155 self.saved_status = http_status
151 self.saved_status = http_status
156 bad_headers = ('connection', 'transfer-encoding')
152 bad_headers = ('connection', 'transfer-encoding')
157 self.saved_headers = [h for h in headers
153 self.saved_headers = [h for h in headers
158 if h[0].lower() not in bad_headers]
154 if h[0].lower() not in bad_headers]
159 return self._write
155 return self._write
160
156
161 def _write(self, data):
157 def _write(self, data):
162 if not self.saved_status:
158 if not self.saved_status:
163 raise AssertionError("data written before start_response() called")
159 raise AssertionError("data written before start_response() called")
164 elif not self.sent_headers:
160 elif not self.sent_headers:
165 self.send_headers()
161 self.send_headers()
166 if self.length is not None:
162 if self.length is not None:
167 if len(data) > self.length:
163 if len(data) > self.length:
168 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
169 self.length = self.length - len(data)
165 self.length = self.length - len(data)
170 self.wfile.write(data)
166 self.wfile.write(data)
171 self.wfile.flush()
167 self.wfile.flush()
172
168
173 class _shgwebhandler(_hgwebhandler):
169 class _shgwebhandler(_hgwebhandler):
174
170
175 url_scheme = 'https'
171 url_scheme = 'https'
176
172
177 def setup(self):
173 def setup(self):
178 self.connection = self.request
174 self.connection = self.request
179 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
175 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
180 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
176 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
181
177
182 def do_write(self):
178 def do_write(self):
183 from OpenSSL.SSL import SysCallError
179 from OpenSSL.SSL import SysCallError
184 try:
180 try:
185 super(_shgwebhandler, self).do_write()
181 super(_shgwebhandler, self).do_write()
186 except SysCallError, inst:
182 except SysCallError, inst:
187 if inst.args[0] != errno.EPIPE:
183 if inst.args[0] != errno.EPIPE:
188 raise
184 raise
189
185
190 def handle_one_request(self):
186 def handle_one_request(self):
191 from OpenSSL.SSL import SysCallError, ZeroReturnError
187 from OpenSSL.SSL import SysCallError, ZeroReturnError
192 try:
188 try:
193 super(_shgwebhandler, self).handle_one_request()
189 super(_shgwebhandler, self).handle_one_request()
194 except (SysCallError, ZeroReturnError):
190 except (SysCallError, ZeroReturnError):
195 self.close_connection = True
191 self.close_connection = True
196 pass
192 pass
197
193
198 def create_server(ui, repo):
194 def create_server(ui, repo):
199 use_threads = True
195 use_threads = True
200
196
201 def openlog(opt, default):
197 def openlog(opt, default):
202 if opt and opt != '-':
198 if opt and opt != '-':
203 return open(opt, 'w')
199 return open(opt, 'w')
204 return default
200 return default
205
201
206 if repo is None:
202 if repo is None:
207 myui = ui
203 myui = ui
208 else:
204 else:
209 myui = repo.ui
205 myui = repo.ui
210 address = myui.config("web", "address", "")
206 address = myui.config("web", "address", "")
211 port = int(myui.config("web", "port", 8000))
207 port = int(myui.config("web", "port", 8000))
212 use_ipv6 = myui.configbool("web", "ipv6")
208 use_ipv6 = myui.configbool("web", "ipv6")
213 webdir_conf = myui.config("web", "webdir_conf")
209 webdir_conf = myui.config("web", "webdir_conf")
214 ssl_cert = myui.config("web", "certificate")
210 ssl_cert = myui.config("web", "certificate")
215 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
211 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
216 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
212 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
217
213
218 if use_threads:
214 if use_threads:
219 try:
215 try:
220 from threading import activeCount
216 from threading import activeCount
221 except ImportError:
217 except ImportError:
222 use_threads = False
218 use_threads = False
223
219
224 if use_threads:
220 if use_threads:
225 _mixin = SocketServer.ThreadingMixIn
221 _mixin = SocketServer.ThreadingMixIn
226 else:
222 else:
227 if hasattr(os, "fork"):
223 if hasattr(os, "fork"):
228 _mixin = SocketServer.ForkingMixIn
224 _mixin = SocketServer.ForkingMixIn
229 else:
225 else:
230 class _mixin:
226 class _mixin:
231 pass
227 pass
232
228
233 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
229 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
234
230
235 # SO_REUSEADDR has broken semantics on windows
231 # SO_REUSEADDR has broken semantics on windows
236 if os.name == 'nt':
232 if os.name == 'nt':
237 allow_reuse_address = 0
233 allow_reuse_address = 0
238
234
239 def __init__(self, *args, **kargs):
235 def __init__(self, *args, **kargs):
240 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
236 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
241 self.accesslog = accesslog
237 self.accesslog = accesslog
242 self.errorlog = errorlog
238 self.errorlog = errorlog
243 self.daemon_threads = True
239 self.daemon_threads = True
244 def make_handler():
240 def make_handler():
245 if webdir_conf:
241 if webdir_conf:
246 hgwebobj = hgwebdir(webdir_conf, ui)
242 hgwebobj = hgwebdir(webdir_conf, ui)
247 elif repo is not None:
243 elif repo is not None:
248 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
244 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
249 else:
245 else:
250 raise hg.RepoError(_("There is no Mercurial repository here"
246 raise hg.RepoError(_("There is no Mercurial repository here"
251 " (.hg not found)"))
247 " (.hg not found)"))
252 return hgwebobj
248 return hgwebobj
253 self.reqmaker = wsgiapplication(make_handler)
249 self.application = make_handler()
254
250
255 addr = address
251 addr = address
256 if addr in ('', '::'):
252 if addr in ('', '::'):
257 addr = socket.gethostname()
253 addr = socket.gethostname()
258
254
259 self.addr, self.port = addr, port
255 self.addr, self.port = addr, port
260
256
261 if ssl_cert:
257 if ssl_cert:
262 try:
258 try:
263 from OpenSSL import SSL
259 from OpenSSL import SSL
264 ctx = SSL.Context(SSL.SSLv23_METHOD)
260 ctx = SSL.Context(SSL.SSLv23_METHOD)
265 except ImportError:
261 except ImportError:
266 raise util.Abort("SSL support is unavailable")
262 raise util.Abort("SSL support is unavailable")
267 ctx.use_privatekey_file(ssl_cert)
263 ctx.use_privatekey_file(ssl_cert)
268 ctx.use_certificate_file(ssl_cert)
264 ctx.use_certificate_file(ssl_cert)
269 sock = socket.socket(self.address_family, self.socket_type)
265 sock = socket.socket(self.address_family, self.socket_type)
270 self.socket = SSL.Connection(ctx, sock)
266 self.socket = SSL.Connection(ctx, sock)
271 self.server_bind()
267 self.server_bind()
272 self.server_activate()
268 self.server_activate()
273
269
274 class IPv6HTTPServer(MercurialHTTPServer):
270 class IPv6HTTPServer(MercurialHTTPServer):
275 address_family = getattr(socket, 'AF_INET6', None)
271 address_family = getattr(socket, 'AF_INET6', None)
276
272
277 def __init__(self, *args, **kwargs):
273 def __init__(self, *args, **kwargs):
278 if self.address_family is None:
274 if self.address_family is None:
279 raise hg.RepoError(_('IPv6 not available on this system'))
275 raise hg.RepoError(_('IPv6 not available on this system'))
280 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
276 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
281
277
282 if ssl_cert:
278 if ssl_cert:
283 handler = _shgwebhandler
279 handler = _shgwebhandler
284 else:
280 else:
285 handler = _hgwebhandler
281 handler = _hgwebhandler
286
282
287 try:
283 try:
288 if use_ipv6:
284 if use_ipv6:
289 return IPv6HTTPServer((address, port), handler)
285 return IPv6HTTPServer((address, port), handler)
290 else:
286 else:
291 return MercurialHTTPServer((address, port), handler)
287 return MercurialHTTPServer((address, port), handler)
292 except socket.error, inst:
288 except socket.error, inst:
293 raise util.Abort(_('cannot start server: %s') % inst.args[1])
289 raise util.Abort(_('cannot start server: %s') % inst.args[1])
@@ -1,70 +1,69 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir repo
3 mkdir repo
4 cd repo
4 cd repo
5 hg init
5 hg init
6 echo foo > bar
6 echo foo > bar
7 hg add bar
7 hg add bar
8 hg commit -m "test" -d "0 0"
8 hg commit -m "test" -d "0 0"
9 hg tip
9 hg tip
10
10
11 cat > request.py <<EOF
11 cat > request.py <<EOF
12 from mercurial import dispatch
12 from mercurial import dispatch
13 from mercurial.hgweb.hgweb_mod import hgweb
13 from mercurial.hgweb.hgweb_mod import hgweb
14 from mercurial.hgweb.request import _wsgirequest
15 from mercurial.ui import ui
14 from mercurial.ui import ui
16 from mercurial import hg
15 from mercurial import hg
17 from StringIO import StringIO
16 from StringIO import StringIO
18 import os, sys
17 import os, sys
19
18
20 class FileLike(object):
19 class FileLike(object):
21 def __init__(self, real):
20 def __init__(self, real):
22 self.real = real
21 self.real = real
23 def fileno(self):
22 def fileno(self):
24 print >> sys.__stdout__, 'FILENO'
23 print >> sys.__stdout__, 'FILENO'
25 return self.real.fileno()
24 return self.real.fileno()
26 def read(self):
25 def read(self):
27 print >> sys.__stdout__, 'READ'
26 print >> sys.__stdout__, 'READ'
28 return self.real.read()
27 return self.real.read()
29 def readline(self):
28 def readline(self):
30 print >> sys.__stdout__, 'READLINE'
29 print >> sys.__stdout__, 'READLINE'
31 return self.real.readline()
30 return self.real.readline()
32 def isatty(self):
31 def isatty(self):
33 print >> sys.__stdout__, 'ISATTY'
32 print >> sys.__stdout__, 'ISATTY'
34 return False
33 return False
35
34
36 sys.stdin = FileLike(sys.stdin)
35 sys.stdin = FileLike(sys.stdin)
37 errors = StringIO()
36 errors = StringIO()
38 input = StringIO()
37 input = StringIO()
39 output = StringIO()
38 output = StringIO()
40
39
41 def startrsp(headers, data):
40 def startrsp(headers, data):
42 print '---- HEADERS'
41 print '---- HEADERS'
43 print headers
42 print headers
44 print '---- DATA'
43 print '---- DATA'
45 print data
44 print data
46 return output.write
45 return output.write
47
46
48 env = {
47 env = {
49 'wsgi.version': (1, 0),
48 'wsgi.version': (1, 0),
50 'wsgi.url_scheme': 'http',
49 'wsgi.url_scheme': 'http',
51 'wsgi.errors': errors,
50 'wsgi.errors': errors,
52 'wsgi.input': input,
51 'wsgi.input': input,
53 'wsgi.multithread': False,
52 'wsgi.multithread': False,
54 'wsgi.multiprocess': False,
53 'wsgi.multiprocess': False,
55 'wsgi.run_once': False,
54 'wsgi.run_once': False,
56 'REQUEST_METHOD': 'GET',
55 'REQUEST_METHOD': 'GET',
57 'SCRIPT_NAME': '',
56 'SCRIPT_NAME': '',
58 'PATH_INFO': '',
57 'PATH_INFO': '',
59 'QUERY_STRING': '',
58 'QUERY_STRING': '',
60 'SERVER_NAME': '127.0.0.1',
59 'SERVER_NAME': '127.0.0.1',
61 'SERVER_PORT': os.environ['HGPORT'],
60 'SERVER_PORT': os.environ['HGPORT'],
62 'SERVER_PROTOCOL': 'HTTP/1.0'
61 'SERVER_PROTOCOL': 'HTTP/1.0'
63 }
62 }
64
63
65 _wsgirequest(hgweb('.'), env, startrsp)
64 hgweb('.')(env, startrsp)
66 print '---- ERRORS'
65 print '---- ERRORS'
67 print errors.getvalue()
66 print errors.getvalue()
68 EOF
67 EOF
69
68
70 python request.py
69 python request.py
General Comments 0
You need to be logged in to leave comments. Login now