##// END OF EJS Templates
hgweb: fix breaking tests on Python < 2.5
Bryan O'Sullivan -
r5563:d61fea13 default
parent child Browse files
Show More
@@ -1,92 +1,99
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
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, mimetypes, os
9 import errno, mimetypes, os
10
10
11 class ErrorResponse(Exception):
11 class ErrorResponse(Exception):
12 def __init__(self, code, message=None):
12 def __init__(self, code, message=None):
13 Exception.__init__(self)
13 Exception.__init__(self)
14 self.code = code
14 self.code = code
15 if message is None:
15 if message:
16 from httplib import responses
16 self.message = message
17 self.message = responses.get(code, 'Error')
18 else:
17 else:
19 self.message = message
18 self.message = _statusmessage(code)
19
20 def _statusmessage(code):
21 from BaseHTTPServer import BaseHTTPRequestHandler
22 responses = BaseHTTPRequestHandler.responses
23 return responses.get(code, ('Error', 'Unknown error'))[0]
24
25 def statusmessage(code):
26 return '%d %s' % (code, _statusmessage(code))
20
27
21 def get_mtime(repo_path):
28 def get_mtime(repo_path):
22 store_path = os.path.join(repo_path, ".hg")
29 store_path = os.path.join(repo_path, ".hg")
23 if not os.path.isdir(os.path.join(store_path, "data")):
30 if not os.path.isdir(os.path.join(store_path, "data")):
24 store_path = os.path.join(store_path, "store")
31 store_path = os.path.join(store_path, "store")
25 cl_path = os.path.join(store_path, "00changelog.i")
32 cl_path = os.path.join(store_path, "00changelog.i")
26 if os.path.exists(cl_path):
33 if os.path.exists(cl_path):
27 return os.stat(cl_path).st_mtime
34 return os.stat(cl_path).st_mtime
28 else:
35 else:
29 return os.stat(store_path).st_mtime
36 return os.stat(store_path).st_mtime
30
37
31 def staticfile(directory, fname, req):
38 def staticfile(directory, fname, req):
32 """return a file inside directory with guessed content-type header
39 """return a file inside directory with guessed content-type header
33
40
34 fname always uses '/' as directory separator and isn't allowed to
41 fname always uses '/' as directory separator and isn't allowed to
35 contain unusual path components.
42 contain unusual path components.
36 Content-type is guessed using the mimetypes module.
43 Content-type is guessed using the mimetypes module.
37 Return an empty string if fname is illegal or file not found.
44 Return an empty string if fname is illegal or file not found.
38
45
39 """
46 """
40 parts = fname.split('/')
47 parts = fname.split('/')
41 path = directory
48 path = directory
42 for part in parts:
49 for part in parts:
43 if (part in ('', os.curdir, os.pardir) or
50 if (part in ('', os.curdir, os.pardir) or
44 os.sep in part or os.altsep is not None and os.altsep in part):
51 os.sep in part or os.altsep is not None and os.altsep in part):
45 return ""
52 return ""
46 path = os.path.join(path, part)
53 path = os.path.join(path, part)
47 try:
54 try:
48 os.stat(path)
55 os.stat(path)
49 ct = mimetypes.guess_type(path)[0] or "text/plain"
56 ct = mimetypes.guess_type(path)[0] or "text/plain"
50 req.header([('Content-type', ct),
57 req.header([('Content-type', ct),
51 ('Content-length', str(os.path.getsize(path)))])
58 ('Content-length', str(os.path.getsize(path)))])
52 return file(path, 'rb').read()
59 return file(path, 'rb').read()
53 except TypeError:
60 except TypeError:
54 raise ErrorResponse(500, 'illegal file name')
61 raise ErrorResponse(500, 'illegal file name')
55 except OSError, err:
62 except OSError, err:
56 if err.errno == errno.ENOENT:
63 if err.errno == errno.ENOENT:
57 raise ErrorResponse(404)
64 raise ErrorResponse(404)
58 else:
65 else:
59 raise ErrorResponse(500, err.strerror)
66 raise ErrorResponse(500, err.strerror)
60
67
61 def style_map(templatepath, style):
68 def style_map(templatepath, style):
62 """Return path to mapfile for a given style.
69 """Return path to mapfile for a given style.
63
70
64 Searches mapfile in the following locations:
71 Searches mapfile in the following locations:
65 1. templatepath/style/map
72 1. templatepath/style/map
66 2. templatepath/map-style
73 2. templatepath/map-style
67 3. templatepath/map
74 3. templatepath/map
68 """
75 """
69 locations = style and [os.path.join(style, "map"), "map-"+style] or []
76 locations = style and [os.path.join(style, "map"), "map-"+style] or []
70 locations.append("map")
77 locations.append("map")
71 for location in locations:
78 for location in locations:
72 mapfile = os.path.join(templatepath, location)
79 mapfile = os.path.join(templatepath, location)
73 if os.path.isfile(mapfile):
80 if os.path.isfile(mapfile):
74 return mapfile
81 return mapfile
75 raise RuntimeError("No hgweb templates found in %r" % templatepath)
82 raise RuntimeError("No hgweb templates found in %r" % templatepath)
76
83
77 def paritygen(stripecount, offset=0):
84 def paritygen(stripecount, offset=0):
78 """count parity of horizontal stripes for easier reading"""
85 """count parity of horizontal stripes for easier reading"""
79 if stripecount and offset:
86 if stripecount and offset:
80 # account for offset, e.g. due to building the list in reverse
87 # account for offset, e.g. due to building the list in reverse
81 count = (stripecount + offset) % stripecount
88 count = (stripecount + offset) % stripecount
82 parity = (stripecount + offset) / stripecount & 1
89 parity = (stripecount + offset) / stripecount & 1
83 else:
90 else:
84 count = 0
91 count = 0
85 parity = 0
92 parity = 0
86 while True:
93 while True:
87 yield parity
94 yield parity
88 count += 1
95 count += 1
89 if stripecount and count >= stripecount:
96 if stripecount and count >= stripecount:
90 parity = 1 - parity
97 parity = 1 - parity
91 count = 0
98 count = 0
92
99
@@ -1,1221 +1,1222
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 common import statusmsg
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 from request import wsgiapplication
675 def make_web_app():
676 def make_web_app():
676 return self
677 return self
677 wsgicgi.launch(wsgiapplication(make_web_app))
678 wsgicgi.launch(wsgiapplication(make_web_app))
678
679
679 def run_wsgi(self, req):
680 def run_wsgi(self, req):
680 def header(**map):
681 def header(**map):
681 header_file = cStringIO.StringIO(
682 header_file = cStringIO.StringIO(
682 ''.join(self.t("header", encoding=self.encoding, **map)))
683 ''.join(self.t("header", encoding=self.encoding, **map)))
683 msg = mimetools.Message(header_file, 0)
684 msg = mimetools.Message(header_file, 0)
684 req.header(msg.items())
685 req.header(msg.items())
685 yield header_file.read()
686 yield header_file.read()
686
687
687 def rawfileheader(**map):
688 def rawfileheader(**map):
688 req.header([('Content-type', map['mimetype']),
689 req.header([('Content-type', map['mimetype']),
689 ('Content-disposition', 'filename=%s' % map['file']),
690 ('Content-disposition', 'filename=%s' % map['file']),
690 ('Content-length', str(len(map['raw'])))])
691 ('Content-length', str(len(map['raw'])))])
691 yield ''
692 yield ''
692
693
693 def footer(**map):
694 def footer(**map):
694 yield self.t("footer", **map)
695 yield self.t("footer", **map)
695
696
696 def motd(**map):
697 def motd(**map):
697 yield self.config("web", "motd", "")
698 yield self.config("web", "motd", "")
698
699
699 def expand_form(form):
700 def expand_form(form):
700 shortcuts = {
701 shortcuts = {
701 'cl': [('cmd', ['changelog']), ('rev', None)],
702 'cl': [('cmd', ['changelog']), ('rev', None)],
702 'sl': [('cmd', ['shortlog']), ('rev', None)],
703 'sl': [('cmd', ['shortlog']), ('rev', None)],
703 'cs': [('cmd', ['changeset']), ('node', None)],
704 'cs': [('cmd', ['changeset']), ('node', None)],
704 'f': [('cmd', ['file']), ('filenode', None)],
705 'f': [('cmd', ['file']), ('filenode', None)],
705 'fl': [('cmd', ['filelog']), ('filenode', None)],
706 'fl': [('cmd', ['filelog']), ('filenode', None)],
706 'fd': [('cmd', ['filediff']), ('node', None)],
707 'fd': [('cmd', ['filediff']), ('node', None)],
707 'fa': [('cmd', ['annotate']), ('filenode', None)],
708 'fa': [('cmd', ['annotate']), ('filenode', None)],
708 'mf': [('cmd', ['manifest']), ('manifest', None)],
709 'mf': [('cmd', ['manifest']), ('manifest', None)],
709 'ca': [('cmd', ['archive']), ('node', None)],
710 'ca': [('cmd', ['archive']), ('node', None)],
710 'tags': [('cmd', ['tags'])],
711 'tags': [('cmd', ['tags'])],
711 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
712 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
712 'static': [('cmd', ['static']), ('file', None)]
713 'static': [('cmd', ['static']), ('file', None)]
713 }
714 }
714
715
715 for k in shortcuts.iterkeys():
716 for k in shortcuts.iterkeys():
716 if form.has_key(k):
717 if form.has_key(k):
717 for name, value in shortcuts[k]:
718 for name, value in shortcuts[k]:
718 if value is None:
719 if value is None:
719 value = form[k]
720 value = form[k]
720 form[name] = value
721 form[name] = value
721 del form[k]
722 del form[k]
722
723
723 def rewrite_request(req):
724 def rewrite_request(req):
724 '''translate new web interface to traditional format'''
725 '''translate new web interface to traditional format'''
725
726
726 def spliturl(req):
727 def spliturl(req):
727 def firstitem(query):
728 def firstitem(query):
728 return query.split('&', 1)[0].split(';', 1)[0]
729 return query.split('&', 1)[0].split(';', 1)[0]
729
730
730 def normurl(url):
731 def normurl(url):
731 inner = '/'.join([x for x in url.split('/') if x])
732 inner = '/'.join([x for x in url.split('/') if x])
732 tl = len(url) > 1 and url.endswith('/') and '/' or ''
733 tl = len(url) > 1 and url.endswith('/') and '/' or ''
733
734
734 return '%s%s%s' % (url.startswith('/') and '/' or '',
735 return '%s%s%s' % (url.startswith('/') and '/' or '',
735 inner, tl)
736 inner, tl)
736
737
737 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
738 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
738 pi = normurl(req.env.get('PATH_INFO', ''))
739 pi = normurl(req.env.get('PATH_INFO', ''))
739 if pi:
740 if pi:
740 # strip leading /
741 # strip leading /
741 pi = pi[1:]
742 pi = pi[1:]
742 if pi:
743 if pi:
743 root = root[:root.rfind(pi)]
744 root = root[:root.rfind(pi)]
744 if req.env.has_key('REPO_NAME'):
745 if req.env.has_key('REPO_NAME'):
745 rn = req.env['REPO_NAME'] + '/'
746 rn = req.env['REPO_NAME'] + '/'
746 root += rn
747 root += rn
747 query = pi[len(rn):]
748 query = pi[len(rn):]
748 else:
749 else:
749 query = pi
750 query = pi
750 else:
751 else:
751 root += '?'
752 root += '?'
752 query = firstitem(req.env['QUERY_STRING'])
753 query = firstitem(req.env['QUERY_STRING'])
753
754
754 return (root, query)
755 return (root, query)
755
756
756 req.url, query = spliturl(req)
757 req.url, query = spliturl(req)
757
758
758 if req.form.has_key('cmd'):
759 if req.form.has_key('cmd'):
759 # old style
760 # old style
760 return
761 return
761
762
762 args = query.split('/', 2)
763 args = query.split('/', 2)
763 if not args or not args[0]:
764 if not args or not args[0]:
764 return
765 return
765
766
766 cmd = args.pop(0)
767 cmd = args.pop(0)
767 style = cmd.rfind('-')
768 style = cmd.rfind('-')
768 if style != -1:
769 if style != -1:
769 req.form['style'] = [cmd[:style]]
770 req.form['style'] = [cmd[:style]]
770 cmd = cmd[style+1:]
771 cmd = cmd[style+1:]
771 # avoid accepting e.g. style parameter as command
772 # avoid accepting e.g. style parameter as command
772 if hasattr(self, 'do_' + cmd):
773 if hasattr(self, 'do_' + cmd):
773 req.form['cmd'] = [cmd]
774 req.form['cmd'] = [cmd]
774
775
775 if args and args[0]:
776 if args and args[0]:
776 node = args.pop(0)
777 node = args.pop(0)
777 req.form['node'] = [node]
778 req.form['node'] = [node]
778 if args:
779 if args:
779 req.form['file'] = args
780 req.form['file'] = args
780
781
781 if cmd == 'static':
782 if cmd == 'static':
782 req.form['file'] = req.form['node']
783 req.form['file'] = req.form['node']
783 elif cmd == 'archive':
784 elif cmd == 'archive':
784 fn = req.form['node'][0]
785 fn = req.form['node'][0]
785 for type_, spec in self.archive_specs.iteritems():
786 for type_, spec in self.archive_specs.iteritems():
786 ext = spec[2]
787 ext = spec[2]
787 if fn.endswith(ext):
788 if fn.endswith(ext):
788 req.form['node'] = [fn[:-len(ext)]]
789 req.form['node'] = [fn[:-len(ext)]]
789 req.form['type'] = [type_]
790 req.form['type'] = [type_]
790
791
791 def sessionvars(**map):
792 def sessionvars(**map):
792 fields = []
793 fields = []
793 if req.form.has_key('style'):
794 if req.form.has_key('style'):
794 style = req.form['style'][0]
795 style = req.form['style'][0]
795 if style != self.config('web', 'style', ''):
796 if style != self.config('web', 'style', ''):
796 fields.append(('style', style))
797 fields.append(('style', style))
797
798
798 separator = req.url[-1] == '?' and ';' or '?'
799 separator = req.url[-1] == '?' and ';' or '?'
799 for name, value in fields:
800 for name, value in fields:
800 yield dict(name=name, value=value, separator=separator)
801 yield dict(name=name, value=value, separator=separator)
801 separator = ';'
802 separator = ';'
802
803
803 self.refresh()
804 self.refresh()
804
805
805 expand_form(req.form)
806 expand_form(req.form)
806 rewrite_request(req)
807 rewrite_request(req)
807
808
808 style = self.config("web", "style", "")
809 style = self.config("web", "style", "")
809 if req.form.has_key('style'):
810 if req.form.has_key('style'):
810 style = req.form['style'][0]
811 style = req.form['style'][0]
811 mapfile = style_map(self.templatepath, style)
812 mapfile = style_map(self.templatepath, style)
812
813
813 proto = req.env.get('wsgi.url_scheme')
814 proto = req.env.get('wsgi.url_scheme')
814 if proto == 'https':
815 if proto == 'https':
815 proto = 'https'
816 proto = 'https'
816 default_port = "443"
817 default_port = "443"
817 else:
818 else:
818 proto = 'http'
819 proto = 'http'
819 default_port = "80"
820 default_port = "80"
820
821
821 port = req.env["SERVER_PORT"]
822 port = req.env["SERVER_PORT"]
822 port = port != default_port and (":" + port) or ""
823 port = port != default_port and (":" + port) or ""
823 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
824 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
824 staticurl = self.config("web", "staticurl") or req.url + 'static/'
825 staticurl = self.config("web", "staticurl") or req.url + 'static/'
825 if not staticurl.endswith('/'):
826 if not staticurl.endswith('/'):
826 staticurl += '/'
827 staticurl += '/'
827
828
828 if not self.reponame:
829 if not self.reponame:
829 self.reponame = (self.config("web", "name")
830 self.reponame = (self.config("web", "name")
830 or req.env.get('REPO_NAME')
831 or req.env.get('REPO_NAME')
831 or req.url.strip('/') or self.repo.root)
832 or req.url.strip('/') or self.repo.root)
832
833
833 self.t = templater.templater(mapfile, templater.common_filters,
834 self.t = templater.templater(mapfile, templater.common_filters,
834 defaults={"url": req.url,
835 defaults={"url": req.url,
835 "staticurl": staticurl,
836 "staticurl": staticurl,
836 "urlbase": urlbase,
837 "urlbase": urlbase,
837 "repo": self.reponame,
838 "repo": self.reponame,
838 "header": header,
839 "header": header,
839 "footer": footer,
840 "footer": footer,
840 "motd": motd,
841 "motd": motd,
841 "rawfileheader": rawfileheader,
842 "rawfileheader": rawfileheader,
842 "sessionvars": sessionvars
843 "sessionvars": sessionvars
843 })
844 })
844
845
845 try:
846 try:
846 if not req.form.has_key('cmd'):
847 if not req.form.has_key('cmd'):
847 req.form['cmd'] = [self.t.cache['default']]
848 req.form['cmd'] = [self.t.cache['default']]
848
849
849 cmd = req.form['cmd'][0]
850 cmd = req.form['cmd'][0]
850
851
851 try:
852 try:
852 method = getattr(self, 'do_' + cmd)
853 method = getattr(self, 'do_' + cmd)
853 method(req)
854 method(req)
854 except revlog.LookupError, err:
855 except revlog.LookupError, err:
855 req.respond(404, self.t(
856 req.respond(404, self.t(
856 'error', error='revision not found: %s' % err.name))
857 'error', error='revision not found: %s' % err.name))
857 except (hg.RepoError, revlog.RevlogError), inst:
858 except (hg.RepoError, revlog.RevlogError), inst:
858 req.respond('500 Internal Server Error',
859 req.respond('500 Internal Server Error',
859 self.t('error', error=str(inst)))
860 self.t('error', error=str(inst)))
860 except ErrorResponse, inst:
861 except ErrorResponse, inst:
861 req.respond(inst.code, self.t('error', error=inst.message))
862 req.respond(inst.code, self.t('error', error=inst.message))
862 except AttributeError:
863 except AttributeError:
863 req.respond(400,
864 req.respond(400,
864 self.t('error', error='No such method: ' + cmd))
865 self.t('error', error='No such method: ' + cmd))
865 finally:
866 finally:
866 self.t = None
867 self.t = None
867
868
868 def changectx(self, req):
869 def changectx(self, req):
869 if req.form.has_key('node'):
870 if req.form.has_key('node'):
870 changeid = req.form['node'][0]
871 changeid = req.form['node'][0]
871 elif req.form.has_key('manifest'):
872 elif req.form.has_key('manifest'):
872 changeid = req.form['manifest'][0]
873 changeid = req.form['manifest'][0]
873 else:
874 else:
874 changeid = self.repo.changelog.count() - 1
875 changeid = self.repo.changelog.count() - 1
875
876
876 try:
877 try:
877 ctx = self.repo.changectx(changeid)
878 ctx = self.repo.changectx(changeid)
878 except hg.RepoError:
879 except hg.RepoError:
879 man = self.repo.manifest
880 man = self.repo.manifest
880 mn = man.lookup(changeid)
881 mn = man.lookup(changeid)
881 ctx = self.repo.changectx(man.linkrev(mn))
882 ctx = self.repo.changectx(man.linkrev(mn))
882
883
883 return ctx
884 return ctx
884
885
885 def filectx(self, req):
886 def filectx(self, req):
886 path = self.cleanpath(req.form['file'][0])
887 path = self.cleanpath(req.form['file'][0])
887 if req.form.has_key('node'):
888 if req.form.has_key('node'):
888 changeid = req.form['node'][0]
889 changeid = req.form['node'][0]
889 else:
890 else:
890 changeid = req.form['filenode'][0]
891 changeid = req.form['filenode'][0]
891 try:
892 try:
892 ctx = self.repo.changectx(changeid)
893 ctx = self.repo.changectx(changeid)
893 fctx = ctx.filectx(path)
894 fctx = ctx.filectx(path)
894 except hg.RepoError:
895 except hg.RepoError:
895 fctx = self.repo.filectx(path, fileid=changeid)
896 fctx = self.repo.filectx(path, fileid=changeid)
896
897
897 return fctx
898 return fctx
898
899
899 def do_log(self, req):
900 def do_log(self, req):
900 if req.form.has_key('file') and req.form['file'][0]:
901 if req.form.has_key('file') and req.form['file'][0]:
901 self.do_filelog(req)
902 self.do_filelog(req)
902 else:
903 else:
903 self.do_changelog(req)
904 self.do_changelog(req)
904
905
905 def do_rev(self, req):
906 def do_rev(self, req):
906 self.do_changeset(req)
907 self.do_changeset(req)
907
908
908 def do_file(self, req):
909 def do_file(self, req):
909 path = self.cleanpath(req.form.get('file', [''])[0])
910 path = self.cleanpath(req.form.get('file', [''])[0])
910 if path:
911 if path:
911 try:
912 try:
912 req.write(self.filerevision(self.filectx(req)))
913 req.write(self.filerevision(self.filectx(req)))
913 return
914 return
914 except revlog.LookupError:
915 except revlog.LookupError:
915 pass
916 pass
916
917
917 req.write(self.manifest(self.changectx(req), path))
918 req.write(self.manifest(self.changectx(req), path))
918
919
919 def do_diff(self, req):
920 def do_diff(self, req):
920 self.do_filediff(req)
921 self.do_filediff(req)
921
922
922 def do_changelog(self, req, shortlog = False):
923 def do_changelog(self, req, shortlog = False):
923 if req.form.has_key('node'):
924 if req.form.has_key('node'):
924 ctx = self.changectx(req)
925 ctx = self.changectx(req)
925 else:
926 else:
926 if req.form.has_key('rev'):
927 if req.form.has_key('rev'):
927 hi = req.form['rev'][0]
928 hi = req.form['rev'][0]
928 else:
929 else:
929 hi = self.repo.changelog.count() - 1
930 hi = self.repo.changelog.count() - 1
930 try:
931 try:
931 ctx = self.repo.changectx(hi)
932 ctx = self.repo.changectx(hi)
932 except hg.RepoError:
933 except hg.RepoError:
933 req.write(self.search(hi)) # XXX redirect to 404 page?
934 req.write(self.search(hi)) # XXX redirect to 404 page?
934 return
935 return
935
936
936 req.write(self.changelog(ctx, shortlog = shortlog))
937 req.write(self.changelog(ctx, shortlog = shortlog))
937
938
938 def do_shortlog(self, req):
939 def do_shortlog(self, req):
939 self.do_changelog(req, shortlog = True)
940 self.do_changelog(req, shortlog = True)
940
941
941 def do_changeset(self, req):
942 def do_changeset(self, req):
942 req.write(self.changeset(self.changectx(req)))
943 req.write(self.changeset(self.changectx(req)))
943
944
944 def do_manifest(self, req):
945 def do_manifest(self, req):
945 req.write(self.manifest(self.changectx(req),
946 req.write(self.manifest(self.changectx(req),
946 self.cleanpath(req.form['path'][0])))
947 self.cleanpath(req.form['path'][0])))
947
948
948 def do_tags(self, req):
949 def do_tags(self, req):
949 req.write(self.tags())
950 req.write(self.tags())
950
951
951 def do_summary(self, req):
952 def do_summary(self, req):
952 req.write(self.summary())
953 req.write(self.summary())
953
954
954 def do_filediff(self, req):
955 def do_filediff(self, req):
955 req.write(self.filediff(self.filectx(req)))
956 req.write(self.filediff(self.filectx(req)))
956
957
957 def do_annotate(self, req):
958 def do_annotate(self, req):
958 req.write(self.fileannotate(self.filectx(req)))
959 req.write(self.fileannotate(self.filectx(req)))
959
960
960 def do_filelog(self, req):
961 def do_filelog(self, req):
961 req.write(self.filelog(self.filectx(req)))
962 req.write(self.filelog(self.filectx(req)))
962
963
963 def do_lookup(self, req):
964 def do_lookup(self, req):
964 try:
965 try:
965 r = hex(self.repo.lookup(req.form['key'][0]))
966 r = hex(self.repo.lookup(req.form['key'][0]))
966 success = 1
967 success = 1
967 except Exception,inst:
968 except Exception,inst:
968 r = str(inst)
969 r = str(inst)
969 success = 0
970 success = 0
970 resp = "%s %s\n" % (success, r)
971 resp = "%s %s\n" % (success, r)
971 req.httphdr("application/mercurial-0.1", length=len(resp))
972 req.httphdr("application/mercurial-0.1", length=len(resp))
972 req.write(resp)
973 req.write(resp)
973
974
974 def do_heads(self, req):
975 def do_heads(self, req):
975 resp = " ".join(map(hex, self.repo.heads())) + "\n"
976 resp = " ".join(map(hex, self.repo.heads())) + "\n"
976 req.httphdr("application/mercurial-0.1", length=len(resp))
977 req.httphdr("application/mercurial-0.1", length=len(resp))
977 req.write(resp)
978 req.write(resp)
978
979
979 def do_branches(self, req):
980 def do_branches(self, req):
980 nodes = []
981 nodes = []
981 if req.form.has_key('nodes'):
982 if req.form.has_key('nodes'):
982 nodes = map(bin, req.form['nodes'][0].split(" "))
983 nodes = map(bin, req.form['nodes'][0].split(" "))
983 resp = cStringIO.StringIO()
984 resp = cStringIO.StringIO()
984 for b in self.repo.branches(nodes):
985 for b in self.repo.branches(nodes):
985 resp.write(" ".join(map(hex, b)) + "\n")
986 resp.write(" ".join(map(hex, b)) + "\n")
986 resp = resp.getvalue()
987 resp = resp.getvalue()
987 req.httphdr("application/mercurial-0.1", length=len(resp))
988 req.httphdr("application/mercurial-0.1", length=len(resp))
988 req.write(resp)
989 req.write(resp)
989
990
990 def do_between(self, req):
991 def do_between(self, req):
991 if req.form.has_key('pairs'):
992 if req.form.has_key('pairs'):
992 pairs = [map(bin, p.split("-"))
993 pairs = [map(bin, p.split("-"))
993 for p in req.form['pairs'][0].split(" ")]
994 for p in req.form['pairs'][0].split(" ")]
994 resp = cStringIO.StringIO()
995 resp = cStringIO.StringIO()
995 for b in self.repo.between(pairs):
996 for b in self.repo.between(pairs):
996 resp.write(" ".join(map(hex, b)) + "\n")
997 resp.write(" ".join(map(hex, b)) + "\n")
997 resp = resp.getvalue()
998 resp = resp.getvalue()
998 req.httphdr("application/mercurial-0.1", length=len(resp))
999 req.httphdr("application/mercurial-0.1", length=len(resp))
999 req.write(resp)
1000 req.write(resp)
1000
1001
1001 def do_changegroup(self, req):
1002 def do_changegroup(self, req):
1002 req.httphdr("application/mercurial-0.1")
1003 req.httphdr("application/mercurial-0.1")
1003 nodes = []
1004 nodes = []
1004 if not self.allowpull:
1005 if not self.allowpull:
1005 return
1006 return
1006
1007
1007 if req.form.has_key('roots'):
1008 if req.form.has_key('roots'):
1008 nodes = map(bin, req.form['roots'][0].split(" "))
1009 nodes = map(bin, req.form['roots'][0].split(" "))
1009
1010
1010 z = zlib.compressobj()
1011 z = zlib.compressobj()
1011 f = self.repo.changegroup(nodes, 'serve')
1012 f = self.repo.changegroup(nodes, 'serve')
1012 while 1:
1013 while 1:
1013 chunk = f.read(4096)
1014 chunk = f.read(4096)
1014 if not chunk:
1015 if not chunk:
1015 break
1016 break
1016 req.write(z.compress(chunk))
1017 req.write(z.compress(chunk))
1017
1018
1018 req.write(z.flush())
1019 req.write(z.flush())
1019
1020
1020 def do_changegroupsubset(self, req):
1021 def do_changegroupsubset(self, req):
1021 req.httphdr("application/mercurial-0.1")
1022 req.httphdr("application/mercurial-0.1")
1022 bases = []
1023 bases = []
1023 heads = []
1024 heads = []
1024 if not self.allowpull:
1025 if not self.allowpull:
1025 return
1026 return
1026
1027
1027 if req.form.has_key('bases'):
1028 if req.form.has_key('bases'):
1028 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1029 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1029 if req.form.has_key('heads'):
1030 if req.form.has_key('heads'):
1030 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1031 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1031
1032
1032 z = zlib.compressobj()
1033 z = zlib.compressobj()
1033 f = self.repo.changegroupsubset(bases, heads, 'serve')
1034 f = self.repo.changegroupsubset(bases, heads, 'serve')
1034 while 1:
1035 while 1:
1035 chunk = f.read(4096)
1036 chunk = f.read(4096)
1036 if not chunk:
1037 if not chunk:
1037 break
1038 break
1038 req.write(z.compress(chunk))
1039 req.write(z.compress(chunk))
1039
1040
1040 req.write(z.flush())
1041 req.write(z.flush())
1041
1042
1042 def do_archive(self, req):
1043 def do_archive(self, req):
1043 type_ = req.form['type'][0]
1044 type_ = req.form['type'][0]
1044 allowed = self.configlist("web", "allow_archive")
1045 allowed = self.configlist("web", "allow_archive")
1045 if (type_ in self.archives and (type_ in allowed or
1046 if (type_ in self.archives and (type_ in allowed or
1046 self.configbool("web", "allow" + type_, False))):
1047 self.configbool("web", "allow" + type_, False))):
1047 self.archive(req, req.form['node'][0], type_)
1048 self.archive(req, req.form['node'][0], type_)
1048 return
1049 return
1049
1050
1050 req.respond(400, self.t('error',
1051 req.respond(400, self.t('error',
1051 error='Unsupported archive type: %s' % type_))
1052 error='Unsupported archive type: %s' % type_))
1052
1053
1053 def do_static(self, req):
1054 def do_static(self, req):
1054 fname = req.form['file'][0]
1055 fname = req.form['file'][0]
1055 # a repo owner may set web.static in .hg/hgrc to get any file
1056 # a repo owner may set web.static in .hg/hgrc to get any file
1056 # readable by the user running the CGI script
1057 # readable by the user running the CGI script
1057 static = self.config("web", "static",
1058 static = self.config("web", "static",
1058 os.path.join(self.templatepath, "static"),
1059 os.path.join(self.templatepath, "static"),
1059 untrusted=False)
1060 untrusted=False)
1060 req.write(staticfile(static, fname, req))
1061 req.write(staticfile(static, fname, req))
1061
1062
1062 def do_capabilities(self, req):
1063 def do_capabilities(self, req):
1063 caps = ['lookup', 'changegroupsubset']
1064 caps = ['lookup', 'changegroupsubset']
1064 if self.configbool('server', 'uncompressed'):
1065 if self.configbool('server', 'uncompressed'):
1065 caps.append('stream=%d' % self.repo.changelog.version)
1066 caps.append('stream=%d' % self.repo.changelog.version)
1066 # XXX: make configurable and/or share code with do_unbundle:
1067 # XXX: make configurable and/or share code with do_unbundle:
1067 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1068 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1068 if unbundleversions:
1069 if unbundleversions:
1069 caps.append('unbundle=%s' % ','.join(unbundleversions))
1070 caps.append('unbundle=%s' % ','.join(unbundleversions))
1070 resp = ' '.join(caps)
1071 resp = ' '.join(caps)
1071 req.httphdr("application/mercurial-0.1", length=len(resp))
1072 req.httphdr("application/mercurial-0.1", length=len(resp))
1072 req.write(resp)
1073 req.write(resp)
1073
1074
1074 def check_perm(self, req, op, default):
1075 def check_perm(self, req, op, default):
1075 '''check permission for operation based on user auth.
1076 '''check permission for operation based on user auth.
1076 return true if op allowed, else false.
1077 return true if op allowed, else false.
1077 default is policy to use if no config given.'''
1078 default is policy to use if no config given.'''
1078
1079
1079 user = req.env.get('REMOTE_USER')
1080 user = req.env.get('REMOTE_USER')
1080
1081
1081 deny = self.configlist('web', 'deny_' + op)
1082 deny = self.configlist('web', 'deny_' + op)
1082 if deny and (not user or deny == ['*'] or user in deny):
1083 if deny and (not user or deny == ['*'] or user in deny):
1083 return False
1084 return False
1084
1085
1085 allow = self.configlist('web', 'allow_' + op)
1086 allow = self.configlist('web', 'allow_' + op)
1086 return (allow and (allow == ['*'] or user in allow)) or default
1087 return (allow and (allow == ['*'] or user in allow)) or default
1087
1088
1088 def do_unbundle(self, req):
1089 def do_unbundle(self, req):
1089 def bail(response, headers={}):
1090 def bail(response, headers={}):
1090 length = int(req.env['CONTENT_LENGTH'])
1091 length = int(req.env['CONTENT_LENGTH'])
1091 for s in util.filechunkiter(req, limit=length):
1092 for s in util.filechunkiter(req, limit=length):
1092 # drain incoming bundle, else client will not see
1093 # drain incoming bundle, else client will not see
1093 # response when run outside cgi script
1094 # response when run outside cgi script
1094 pass
1095 pass
1095 req.httphdr("application/mercurial-0.1", headers=headers)
1096 req.httphdr("application/mercurial-0.1", headers=headers)
1096 req.write('0\n')
1097 req.write('0\n')
1097 req.write(response)
1098 req.write(response)
1098
1099
1099 # require ssl by default, auth info cannot be sniffed and
1100 # require ssl by default, auth info cannot be sniffed and
1100 # replayed
1101 # replayed
1101 ssl_req = self.configbool('web', 'push_ssl', True)
1102 ssl_req = self.configbool('web', 'push_ssl', True)
1102 if ssl_req:
1103 if ssl_req:
1103 if req.env.get('wsgi.url_scheme') != 'https':
1104 if req.env.get('wsgi.url_scheme') != 'https':
1104 bail(_('ssl required\n'))
1105 bail(_('ssl required\n'))
1105 return
1106 return
1106 proto = 'https'
1107 proto = 'https'
1107 else:
1108 else:
1108 proto = 'http'
1109 proto = 'http'
1109
1110
1110 # do not allow push unless explicitly allowed
1111 # do not allow push unless explicitly allowed
1111 if not self.check_perm(req, 'push', False):
1112 if not self.check_perm(req, 'push', False):
1112 bail(_('push not authorized\n'),
1113 bail(_('push not authorized\n'),
1113 headers={'status': '401 Unauthorized'})
1114 headers={'status': '401 Unauthorized'})
1114 return
1115 return
1115
1116
1116 their_heads = req.form['heads'][0].split(' ')
1117 their_heads = req.form['heads'][0].split(' ')
1117
1118
1118 def check_heads():
1119 def check_heads():
1119 heads = map(hex, self.repo.heads())
1120 heads = map(hex, self.repo.heads())
1120 return their_heads == [hex('force')] or their_heads == heads
1121 return their_heads == [hex('force')] or their_heads == heads
1121
1122
1122 # fail early if possible
1123 # fail early if possible
1123 if not check_heads():
1124 if not check_heads():
1124 bail(_('unsynced changes\n'))
1125 bail(_('unsynced changes\n'))
1125 return
1126 return
1126
1127
1127 req.httphdr("application/mercurial-0.1")
1128 req.httphdr("application/mercurial-0.1")
1128
1129
1129 # do not lock repo until all changegroup data is
1130 # do not lock repo until all changegroup data is
1130 # streamed. save to temporary file.
1131 # streamed. save to temporary file.
1131
1132
1132 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1133 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1133 fp = os.fdopen(fd, 'wb+')
1134 fp = os.fdopen(fd, 'wb+')
1134 try:
1135 try:
1135 length = int(req.env['CONTENT_LENGTH'])
1136 length = int(req.env['CONTENT_LENGTH'])
1136 for s in util.filechunkiter(req, limit=length):
1137 for s in util.filechunkiter(req, limit=length):
1137 fp.write(s)
1138 fp.write(s)
1138
1139
1139 try:
1140 try:
1140 lock = self.repo.lock()
1141 lock = self.repo.lock()
1141 try:
1142 try:
1142 if not check_heads():
1143 if not check_heads():
1143 req.write('0\n')
1144 req.write('0\n')
1144 req.write(_('unsynced changes\n'))
1145 req.write(_('unsynced changes\n'))
1145 return
1146 return
1146
1147
1147 fp.seek(0)
1148 fp.seek(0)
1148 header = fp.read(6)
1149 header = fp.read(6)
1149 if not header.startswith("HG"):
1150 if not header.startswith("HG"):
1150 # old client with uncompressed bundle
1151 # old client with uncompressed bundle
1151 def generator(f):
1152 def generator(f):
1152 yield header
1153 yield header
1153 for chunk in f:
1154 for chunk in f:
1154 yield chunk
1155 yield chunk
1155 elif not header.startswith("HG10"):
1156 elif not header.startswith("HG10"):
1156 req.write("0\n")
1157 req.write("0\n")
1157 req.write(_("unknown bundle version\n"))
1158 req.write(_("unknown bundle version\n"))
1158 return
1159 return
1159 elif header == "HG10GZ":
1160 elif header == "HG10GZ":
1160 def generator(f):
1161 def generator(f):
1161 zd = zlib.decompressobj()
1162 zd = zlib.decompressobj()
1162 for chunk in f:
1163 for chunk in f:
1163 yield zd.decompress(chunk)
1164 yield zd.decompress(chunk)
1164 elif header == "HG10BZ":
1165 elif header == "HG10BZ":
1165 def generator(f):
1166 def generator(f):
1166 zd = bz2.BZ2Decompressor()
1167 zd = bz2.BZ2Decompressor()
1167 zd.decompress("BZ")
1168 zd.decompress("BZ")
1168 for chunk in f:
1169 for chunk in f:
1169 yield zd.decompress(chunk)
1170 yield zd.decompress(chunk)
1170 elif header == "HG10UN":
1171 elif header == "HG10UN":
1171 def generator(f):
1172 def generator(f):
1172 for chunk in f:
1173 for chunk in f:
1173 yield chunk
1174 yield chunk
1174 else:
1175 else:
1175 req.write("0\n")
1176 req.write("0\n")
1176 req.write(_("unknown bundle compression type\n"))
1177 req.write(_("unknown bundle compression type\n"))
1177 return
1178 return
1178 gen = generator(util.filechunkiter(fp, 4096))
1179 gen = generator(util.filechunkiter(fp, 4096))
1179
1180
1180 # send addchangegroup output to client
1181 # send addchangegroup output to client
1181
1182
1182 old_stdout = sys.stdout
1183 old_stdout = sys.stdout
1183 sys.stdout = cStringIO.StringIO()
1184 sys.stdout = cStringIO.StringIO()
1184
1185
1185 try:
1186 try:
1186 url = 'remote:%s:%s' % (proto,
1187 url = 'remote:%s:%s' % (proto,
1187 req.env.get('REMOTE_HOST', ''))
1188 req.env.get('REMOTE_HOST', ''))
1188 try:
1189 try:
1189 ret = self.repo.addchangegroup(
1190 ret = self.repo.addchangegroup(
1190 util.chunkbuffer(gen), 'serve', url)
1191 util.chunkbuffer(gen), 'serve', url)
1191 except util.Abort, inst:
1192 except util.Abort, inst:
1192 sys.stdout.write("abort: %s\n" % inst)
1193 sys.stdout.write("abort: %s\n" % inst)
1193 ret = 0
1194 ret = 0
1194 finally:
1195 finally:
1195 val = sys.stdout.getvalue()
1196 val = sys.stdout.getvalue()
1196 sys.stdout = old_stdout
1197 sys.stdout = old_stdout
1197 req.write('%d\n' % ret)
1198 req.write('%d\n' % ret)
1198 req.write(val)
1199 req.write(val)
1199 finally:
1200 finally:
1200 del lock
1201 del lock
1201 except (OSError, IOError), inst:
1202 except (OSError, IOError), inst:
1202 req.write('0\n')
1203 req.write('0\n')
1203 filename = getattr(inst, 'filename', '')
1204 filename = getattr(inst, 'filename', '')
1204 # Don't send our filesystem layout to the client
1205 # Don't send our filesystem layout to the client
1205 if filename.startswith(self.repo.root):
1206 if filename.startswith(self.repo.root):
1206 filename = filename[len(self.repo.root)+1:]
1207 filename = filename[len(self.repo.root)+1:]
1207 else:
1208 else:
1208 filename = ''
1209 filename = ''
1209 error = getattr(inst, 'strerror', 'Unknown error')
1210 error = getattr(inst, 'strerror', 'Unknown error')
1210 if inst.errno == errno.ENOENT:
1211 if inst.errno == errno.ENOENT:
1211 code = 404
1212 code = 404
1212 else:
1213 else:
1213 code = 500
1214 code = 500
1214 req.respond(code, '%s: %s\n' % (error, filename))
1215 req.respond(code, '%s: %s\n' % (error, filename))
1215 finally:
1216 finally:
1216 fp.close()
1217 fp.close()
1217 os.unlink(tempname)
1218 os.unlink(tempname)
1218
1219
1219 def do_stream_out(self, req):
1220 def do_stream_out(self, req):
1220 req.httphdr("application/mercurial-0.1")
1221 req.httphdr("application/mercurial-0.1")
1221 streamclone.stream_out(self.repo, req, untrusted=True)
1222 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,99 +1,94
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
11 from common import ErrorResponse, statusmessage
12
12
13 class wsgiapplication(object):
13 class wsgiapplication(object):
14 def __init__(self, destmaker):
14 def __init__(self, destmaker):
15 self.destmaker = destmaker
15 self.destmaker = destmaker
16
16
17 def __call__(self, wsgienv, start_response):
17 def __call__(self, wsgienv, start_response):
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19
19
20 class _wsgirequest(object):
20 class _wsgirequest(object):
21 def __init__(self, destination, wsgienv, start_response):
21 def __init__(self, destination, wsgienv, start_response):
22 version = wsgienv['wsgi.version']
22 version = wsgienv['wsgi.version']
23 if (version < (1, 0)) or (version >= (2, 0)):
23 if (version < (1, 0)) or (version >= (2, 0)):
24 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
24 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
25 % version)
25 % version)
26 self.inp = wsgienv['wsgi.input']
26 self.inp = wsgienv['wsgi.input']
27 self.server_write = None
27 self.server_write = None
28 self.err = wsgienv['wsgi.errors']
28 self.err = wsgienv['wsgi.errors']
29 self.threaded = wsgienv['wsgi.multithread']
29 self.threaded = wsgienv['wsgi.multithread']
30 self.multiprocess = wsgienv['wsgi.multiprocess']
30 self.multiprocess = wsgienv['wsgi.multiprocess']
31 self.run_once = wsgienv['wsgi.run_once']
31 self.run_once = wsgienv['wsgi.run_once']
32 self.env = wsgienv
32 self.env = wsgienv
33 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
33 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
34 self.start_response = start_response
34 self.start_response = start_response
35 self.headers = []
35 self.headers = []
36 destination.run_wsgi(self)
36 destination.run_wsgi(self)
37
37
38 out = property(lambda self: self)
38 out = property(lambda self: self)
39
39
40 def __iter__(self):
40 def __iter__(self):
41 return iter([])
41 return iter([])
42
42
43 def read(self, count=-1):
43 def read(self, count=-1):
44 return self.inp.read(count)
44 return self.inp.read(count)
45
45
46 def respond(self, status, *things):
46 def respond(self, status, *things):
47 for thing in things:
47 for thing in things:
48 if hasattr(thing, "__iter__"):
48 if hasattr(thing, "__iter__"):
49 for part in thing:
49 for part in thing:
50 self.respond(status, part)
50 self.respond(status, part)
51 else:
51 else:
52 thing = str(thing)
52 thing = str(thing)
53 if self.server_write is None:
53 if self.server_write is None:
54 if not self.headers:
54 if not self.headers:
55 raise RuntimeError("request.write called before headers sent (%s)." % thing)
55 raise RuntimeError("request.write called before headers sent (%s)." % thing)
56 code = None
57 if isinstance(status, ErrorResponse):
56 if isinstance(status, ErrorResponse):
58 code = status.code
57 status = statusmessage(status.code)
59 elif isinstance(status, int):
58 elif isinstance(status, int):
60 code = status
59 status = statusmessage(status)
61 if code:
62 from httplib import responses
63 status = '%d %s' % (
64 code, responses.get(code, 'Error'))
65 self.server_write = self.start_response(status,
60 self.server_write = self.start_response(status,
66 self.headers)
61 self.headers)
67 self.start_response = None
62 self.start_response = None
68 self.headers = []
63 self.headers = []
69 try:
64 try:
70 self.server_write(thing)
65 self.server_write(thing)
71 except socket.error, inst:
66 except socket.error, inst:
72 if inst[0] != errno.ECONNRESET:
67 if inst[0] != errno.ECONNRESET:
73 raise
68 raise
74
69
75 def write(self, *things):
70 def write(self, *things):
76 self.respond('200 Script output follows', *things)
71 self.respond('200 Script output follows', *things)
77
72
78 def writelines(self, lines):
73 def writelines(self, lines):
79 for line in lines:
74 for line in lines:
80 self.write(line)
75 self.write(line)
81
76
82 def flush(self):
77 def flush(self):
83 return None
78 return None
84
79
85 def close(self):
80 def close(self):
86 return None
81 return None
87
82
88 def header(self, headers=[('Content-type','text/html')]):
83 def header(self, headers=[('Content-type','text/html')]):
89 self.headers.extend(headers)
84 self.headers.extend(headers)
90
85
91 def httphdr(self, type, filename=None, length=0, headers={}):
86 def httphdr(self, type, filename=None, length=0, headers={}):
92 headers = headers.items()
87 headers = headers.items()
93 headers.append(('Content-type', type))
88 headers.append(('Content-type', type))
94 if filename:
89 if filename:
95 headers.append(('Content-disposition', 'attachment; filename=%s' %
90 headers.append(('Content-disposition', 'attachment; filename=%s' %
96 filename))
91 filename))
97 if length:
92 if length:
98 headers.append(('Content-length', str(length)))
93 headers.append(('Content-length', str(length)))
99 self.header(headers)
94 self.header(headers)
General Comments 0
You need to be logged in to leave comments. Login now