##// END OF EJS Templates
Teach hgweb about revlog.LookupError
Brendan Cully -
r3936:ea238a4e default
parent child Browse files
Show More
@@ -1,1161 +1,1161
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, 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, mimetypes, re, zlib, ConfigParser, mimetools, cStringIO, sys
9 import os, mimetypes, re, zlib, ConfigParser, 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 get_mtime, staticfile, style_map
15 from common import get_mtime, staticfile, style_map
16
16
17 def _up(p):
17 def _up(p):
18 if p[0] != "/":
18 if p[0] != "/":
19 p = "/" + p
19 p = "/" + p
20 if p[-1] == "/":
20 if p[-1] == "/":
21 p = p[:-1]
21 p = p[:-1]
22 up = os.path.dirname(p)
22 up = os.path.dirname(p)
23 if up == "/":
23 if up == "/":
24 return "/"
24 return "/"
25 return up + "/"
25 return up + "/"
26
26
27 def revnavgen(pos, pagelen, limit, nodefunc):
27 def revnavgen(pos, pagelen, limit, nodefunc):
28 def seq(factor, limit=None):
28 def seq(factor, limit=None):
29 if limit:
29 if limit:
30 yield limit
30 yield limit
31 if limit >= 20 and limit <= 40:
31 if limit >= 20 and limit <= 40:
32 yield 50
32 yield 50
33 else:
33 else:
34 yield 1 * factor
34 yield 1 * factor
35 yield 3 * factor
35 yield 3 * factor
36 for f in seq(factor * 10):
36 for f in seq(factor * 10):
37 yield f
37 yield f
38
38
39 def nav(**map):
39 def nav(**map):
40 l = []
40 l = []
41 last = 0
41 last = 0
42 for f in seq(1, pagelen):
42 for f in seq(1, pagelen):
43 if f < pagelen or f <= last:
43 if f < pagelen or f <= last:
44 continue
44 continue
45 if f > limit:
45 if f > limit:
46 break
46 break
47 last = f
47 last = f
48 if pos + f < limit:
48 if pos + f < limit:
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 if pos - f >= 0:
50 if pos - f >= 0:
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52
52
53 try:
53 try:
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55
55
56 for label, node in l:
56 for label, node in l:
57 yield {"label": label, "node": node}
57 yield {"label": label, "node": node}
58
58
59 yield {"label": "tip", "node": "tip"}
59 yield {"label": "tip", "node": "tip"}
60 except hg.RepoError:
60 except hg.RepoError:
61 pass
61 pass
62
62
63 return nav
63 return nav
64
64
65 class hgweb(object):
65 class hgweb(object):
66 def __init__(self, repo, name=None):
66 def __init__(self, repo, name=None):
67 if type(repo) == type(""):
67 if type(repo) == type(""):
68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
68 self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
69 else:
69 else:
70 self.repo = repo
70 self.repo = repo
71
71
72 self.mtime = -1
72 self.mtime = -1
73 self.reponame = name
73 self.reponame = name
74 self.archives = 'zip', 'gz', 'bz2'
74 self.archives = 'zip', 'gz', 'bz2'
75 self.stripecount = 1
75 self.stripecount = 1
76 # a repo owner may set web.templates in .hg/hgrc to get any file
76 # a repo owner may set web.templates in .hg/hgrc to get any file
77 # readable by the user running the CGI script
77 # readable by the user running the CGI script
78 self.templatepath = self.config("web", "templates",
78 self.templatepath = self.config("web", "templates",
79 templater.templatepath(),
79 templater.templatepath(),
80 untrusted=False)
80 untrusted=False)
81
81
82 # The CGI scripts are often run by a user different from the repo owner.
82 # The CGI scripts are often run by a user different from the repo owner.
83 # Trust the settings from the .hg/hgrc files by default.
83 # Trust the settings from the .hg/hgrc files by default.
84 def config(self, section, name, default=None, untrusted=True):
84 def config(self, section, name, default=None, untrusted=True):
85 return self.repo.ui.config(section, name, default,
85 return self.repo.ui.config(section, name, default,
86 untrusted=untrusted)
86 untrusted=untrusted)
87
87
88 def configbool(self, section, name, default=False, untrusted=True):
88 def configbool(self, section, name, default=False, untrusted=True):
89 return self.repo.ui.configbool(section, name, default,
89 return self.repo.ui.configbool(section, name, default,
90 untrusted=untrusted)
90 untrusted=untrusted)
91
91
92 def configlist(self, section, name, default=None, untrusted=True):
92 def configlist(self, section, name, default=None, untrusted=True):
93 return self.repo.ui.configlist(section, name, default,
93 return self.repo.ui.configlist(section, name, default,
94 untrusted=untrusted)
94 untrusted=untrusted)
95
95
96 def refresh(self):
96 def refresh(self):
97 mtime = get_mtime(self.repo.root)
97 mtime = get_mtime(self.repo.root)
98 if mtime != self.mtime:
98 if mtime != self.mtime:
99 self.mtime = mtime
99 self.mtime = mtime
100 self.repo = hg.repository(self.repo.ui, self.repo.root)
100 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 self.maxchanges = int(self.config("web", "maxchanges", 10))
101 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 self.stripecount = int(self.config("web", "stripes", 1))
102 self.stripecount = int(self.config("web", "stripes", 1))
103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
103 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 self.maxfiles = int(self.config("web", "maxfiles", 10))
104 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 self.allowpull = self.configbool("web", "allowpull", True)
105 self.allowpull = self.configbool("web", "allowpull", True)
106
106
107 def archivelist(self, nodeid):
107 def archivelist(self, nodeid):
108 allowed = self.configlist("web", "allow_archive")
108 allowed = self.configlist("web", "allow_archive")
109 for i, spec in self.archive_specs.iteritems():
109 for i, spec in self.archive_specs.iteritems():
110 if i in allowed or self.configbool("web", "allow" + i):
110 if i in allowed or self.configbool("web", "allow" + i):
111 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
111 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
112
112
113 def listfilediffs(self, files, changeset):
113 def listfilediffs(self, files, changeset):
114 for f in files[:self.maxfiles]:
114 for f in files[:self.maxfiles]:
115 yield self.t("filedifflink", node=hex(changeset), file=f)
115 yield self.t("filedifflink", node=hex(changeset), file=f)
116 if len(files) > self.maxfiles:
116 if len(files) > self.maxfiles:
117 yield self.t("fileellipses")
117 yield self.t("fileellipses")
118
118
119 def siblings(self, siblings=[], hiderev=None, **args):
119 def siblings(self, siblings=[], hiderev=None, **args):
120 siblings = [s for s in siblings if s.node() != nullid]
120 siblings = [s for s in siblings if s.node() != nullid]
121 if len(siblings) == 1 and siblings[0].rev() == hiderev:
121 if len(siblings) == 1 and siblings[0].rev() == hiderev:
122 return
122 return
123 for s in siblings:
123 for s in siblings:
124 d = {'node': hex(s.node()), 'rev': s.rev()}
124 d = {'node': hex(s.node()), 'rev': s.rev()}
125 if hasattr(s, 'path'):
125 if hasattr(s, 'path'):
126 d['file'] = s.path()
126 d['file'] = s.path()
127 d.update(args)
127 d.update(args)
128 yield d
128 yield d
129
129
130 def renamelink(self, fl, node):
130 def renamelink(self, fl, node):
131 r = fl.renamed(node)
131 r = fl.renamed(node)
132 if r:
132 if r:
133 return [dict(file=r[0], node=hex(r[1]))]
133 return [dict(file=r[0], node=hex(r[1]))]
134 return []
134 return []
135
135
136 def showtag(self, t1, node=nullid, **args):
136 def showtag(self, t1, node=nullid, **args):
137 for t in self.repo.nodetags(node):
137 for t in self.repo.nodetags(node):
138 yield self.t(t1, tag=t, **args)
138 yield self.t(t1, tag=t, **args)
139
139
140 def diff(self, node1, node2, files):
140 def diff(self, node1, node2, files):
141 def filterfiles(filters, files):
141 def filterfiles(filters, files):
142 l = [x for x in files if x in filters]
142 l = [x for x in files if x in filters]
143
143
144 for t in filters:
144 for t in filters:
145 if t and t[-1] != os.sep:
145 if t and t[-1] != os.sep:
146 t += os.sep
146 t += os.sep
147 l += [x for x in files if x.startswith(t)]
147 l += [x for x in files if x.startswith(t)]
148 return l
148 return l
149
149
150 parity = [0]
150 parity = [0]
151 def diffblock(diff, f, fn):
151 def diffblock(diff, f, fn):
152 yield self.t("diffblock",
152 yield self.t("diffblock",
153 lines=prettyprintlines(diff),
153 lines=prettyprintlines(diff),
154 parity=parity[0],
154 parity=parity[0],
155 file=f,
155 file=f,
156 filenode=hex(fn or nullid))
156 filenode=hex(fn or nullid))
157 parity[0] = 1 - parity[0]
157 parity[0] = 1 - parity[0]
158
158
159 def prettyprintlines(diff):
159 def prettyprintlines(diff):
160 for l in diff.splitlines(1):
160 for l in diff.splitlines(1):
161 if l.startswith('+'):
161 if l.startswith('+'):
162 yield self.t("difflineplus", line=l)
162 yield self.t("difflineplus", line=l)
163 elif l.startswith('-'):
163 elif l.startswith('-'):
164 yield self.t("difflineminus", line=l)
164 yield self.t("difflineminus", line=l)
165 elif l.startswith('@'):
165 elif l.startswith('@'):
166 yield self.t("difflineat", line=l)
166 yield self.t("difflineat", line=l)
167 else:
167 else:
168 yield self.t("diffline", line=l)
168 yield self.t("diffline", line=l)
169
169
170 r = self.repo
170 r = self.repo
171 cl = r.changelog
171 cl = r.changelog
172 mf = r.manifest
172 mf = r.manifest
173 change1 = cl.read(node1)
173 change1 = cl.read(node1)
174 change2 = cl.read(node2)
174 change2 = cl.read(node2)
175 mmap1 = mf.read(change1[0])
175 mmap1 = mf.read(change1[0])
176 mmap2 = mf.read(change2[0])
176 mmap2 = mf.read(change2[0])
177 date1 = util.datestr(change1[2])
177 date1 = util.datestr(change1[2])
178 date2 = util.datestr(change2[2])
178 date2 = util.datestr(change2[2])
179
179
180 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
180 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
181 if files:
181 if files:
182 modified, added, removed = map(lambda x: filterfiles(files, x),
182 modified, added, removed = map(lambda x: filterfiles(files, x),
183 (modified, added, removed))
183 (modified, added, removed))
184
184
185 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
185 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
186 for f in modified:
186 for f in modified:
187 to = r.file(f).read(mmap1[f])
187 to = r.file(f).read(mmap1[f])
188 tn = r.file(f).read(mmap2[f])
188 tn = r.file(f).read(mmap2[f])
189 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
189 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
190 opts=diffopts), f, tn)
190 opts=diffopts), f, tn)
191 for f in added:
191 for f in added:
192 to = None
192 to = None
193 tn = r.file(f).read(mmap2[f])
193 tn = r.file(f).read(mmap2[f])
194 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
194 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
195 opts=diffopts), f, tn)
195 opts=diffopts), f, tn)
196 for f in removed:
196 for f in removed:
197 to = r.file(f).read(mmap1[f])
197 to = r.file(f).read(mmap1[f])
198 tn = None
198 tn = None
199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
200 opts=diffopts), f, tn)
200 opts=diffopts), f, tn)
201
201
202 def changelog(self, ctx, shortlog=False):
202 def changelog(self, ctx, shortlog=False):
203 def changelist(**map):
203 def changelist(**map):
204 parity = (start - end) & 1
204 parity = (start - end) & 1
205 cl = self.repo.changelog
205 cl = self.repo.changelog
206 l = [] # build a list in forward order for efficiency
206 l = [] # build a list in forward order for efficiency
207 for i in xrange(start, end):
207 for i in xrange(start, end):
208 ctx = self.repo.changectx(i)
208 ctx = self.repo.changectx(i)
209 n = ctx.node()
209 n = ctx.node()
210
210
211 l.insert(0, {"parity": parity,
211 l.insert(0, {"parity": parity,
212 "author": ctx.user(),
212 "author": ctx.user(),
213 "parent": self.siblings(ctx.parents(), i - 1),
213 "parent": self.siblings(ctx.parents(), i - 1),
214 "child": self.siblings(ctx.children(), i + 1),
214 "child": self.siblings(ctx.children(), i + 1),
215 "changelogtag": self.showtag("changelogtag",n),
215 "changelogtag": self.showtag("changelogtag",n),
216 "desc": ctx.description(),
216 "desc": ctx.description(),
217 "date": ctx.date(),
217 "date": ctx.date(),
218 "files": self.listfilediffs(ctx.files(), n),
218 "files": self.listfilediffs(ctx.files(), n),
219 "rev": i,
219 "rev": i,
220 "node": hex(n)})
220 "node": hex(n)})
221 parity = 1 - parity
221 parity = 1 - parity
222
222
223 for e in l:
223 for e in l:
224 yield e
224 yield e
225
225
226 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
226 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
227 cl = self.repo.changelog
227 cl = self.repo.changelog
228 count = cl.count()
228 count = cl.count()
229 pos = ctx.rev()
229 pos = ctx.rev()
230 start = max(0, pos - maxchanges + 1)
230 start = max(0, pos - maxchanges + 1)
231 end = min(count, start + maxchanges)
231 end = min(count, start + maxchanges)
232 pos = end - 1
232 pos = end - 1
233
233
234 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
234 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
235
235
236 yield self.t(shortlog and 'shortlog' or 'changelog',
236 yield self.t(shortlog and 'shortlog' or 'changelog',
237 changenav=changenav,
237 changenav=changenav,
238 node=hex(cl.tip()),
238 node=hex(cl.tip()),
239 rev=pos, changesets=count, entries=changelist,
239 rev=pos, changesets=count, entries=changelist,
240 archives=self.archivelist("tip"))
240 archives=self.archivelist("tip"))
241
241
242 def search(self, query):
242 def search(self, query):
243
243
244 def changelist(**map):
244 def changelist(**map):
245 cl = self.repo.changelog
245 cl = self.repo.changelog
246 count = 0
246 count = 0
247 qw = query.lower().split()
247 qw = query.lower().split()
248
248
249 def revgen():
249 def revgen():
250 for i in xrange(cl.count() - 1, 0, -100):
250 for i in xrange(cl.count() - 1, 0, -100):
251 l = []
251 l = []
252 for j in xrange(max(0, i - 100), i):
252 for j in xrange(max(0, i - 100), i):
253 ctx = self.repo.changectx(j)
253 ctx = self.repo.changectx(j)
254 l.append(ctx)
254 l.append(ctx)
255 l.reverse()
255 l.reverse()
256 for e in l:
256 for e in l:
257 yield e
257 yield e
258
258
259 for ctx in revgen():
259 for ctx in revgen():
260 miss = 0
260 miss = 0
261 for q in qw:
261 for q in qw:
262 if not (q in ctx.user().lower() or
262 if not (q in ctx.user().lower() or
263 q in ctx.description().lower() or
263 q in ctx.description().lower() or
264 q in " ".join(ctx.files()[:20]).lower()):
264 q in " ".join(ctx.files()[:20]).lower()):
265 miss = 1
265 miss = 1
266 break
266 break
267 if miss:
267 if miss:
268 continue
268 continue
269
269
270 count += 1
270 count += 1
271 n = ctx.node()
271 n = ctx.node()
272
272
273 yield self.t('searchentry',
273 yield self.t('searchentry',
274 parity=self.stripes(count),
274 parity=self.stripes(count),
275 author=ctx.user(),
275 author=ctx.user(),
276 parent=self.siblings(ctx.parents()),
276 parent=self.siblings(ctx.parents()),
277 child=self.siblings(ctx.children()),
277 child=self.siblings(ctx.children()),
278 changelogtag=self.showtag("changelogtag",n),
278 changelogtag=self.showtag("changelogtag",n),
279 desc=ctx.description(),
279 desc=ctx.description(),
280 date=ctx.date(),
280 date=ctx.date(),
281 files=self.listfilediffs(ctx.files(), n),
281 files=self.listfilediffs(ctx.files(), n),
282 rev=ctx.rev(),
282 rev=ctx.rev(),
283 node=hex(n))
283 node=hex(n))
284
284
285 if count >= self.maxchanges:
285 if count >= self.maxchanges:
286 break
286 break
287
287
288 cl = self.repo.changelog
288 cl = self.repo.changelog
289
289
290 yield self.t('search',
290 yield self.t('search',
291 query=query,
291 query=query,
292 node=hex(cl.tip()),
292 node=hex(cl.tip()),
293 entries=changelist)
293 entries=changelist)
294
294
295 def changeset(self, ctx):
295 def changeset(self, ctx):
296 n = ctx.node()
296 n = ctx.node()
297 parents = ctx.parents()
297 parents = ctx.parents()
298 p1 = parents[0].node()
298 p1 = parents[0].node()
299
299
300 files = []
300 files = []
301 parity = 0
301 parity = 0
302 for f in ctx.files():
302 for f in ctx.files():
303 files.append(self.t("filenodelink",
303 files.append(self.t("filenodelink",
304 node=hex(n), file=f,
304 node=hex(n), file=f,
305 parity=parity))
305 parity=parity))
306 parity = 1 - parity
306 parity = 1 - parity
307
307
308 def diff(**map):
308 def diff(**map):
309 yield self.diff(p1, n, None)
309 yield self.diff(p1, n, None)
310
310
311 yield self.t('changeset',
311 yield self.t('changeset',
312 diff=diff,
312 diff=diff,
313 rev=ctx.rev(),
313 rev=ctx.rev(),
314 node=hex(n),
314 node=hex(n),
315 parent=self.siblings(parents),
315 parent=self.siblings(parents),
316 child=self.siblings(ctx.children()),
316 child=self.siblings(ctx.children()),
317 changesettag=self.showtag("changesettag",n),
317 changesettag=self.showtag("changesettag",n),
318 author=ctx.user(),
318 author=ctx.user(),
319 desc=ctx.description(),
319 desc=ctx.description(),
320 date=ctx.date(),
320 date=ctx.date(),
321 files=files,
321 files=files,
322 archives=self.archivelist(hex(n)))
322 archives=self.archivelist(hex(n)))
323
323
324 def filelog(self, fctx):
324 def filelog(self, fctx):
325 f = fctx.path()
325 f = fctx.path()
326 fl = fctx.filelog()
326 fl = fctx.filelog()
327 count = fl.count()
327 count = fl.count()
328 pagelen = self.maxshortchanges
328 pagelen = self.maxshortchanges
329 pos = fctx.filerev()
329 pos = fctx.filerev()
330 start = max(0, pos - pagelen + 1)
330 start = max(0, pos - pagelen + 1)
331 end = min(count, start + pagelen)
331 end = min(count, start + pagelen)
332 pos = end - 1
332 pos = end - 1
333
333
334 def entries(**map):
334 def entries(**map):
335 l = []
335 l = []
336 parity = (count - 1) & 1
336 parity = (count - 1) & 1
337
337
338 for i in xrange(start, end):
338 for i in xrange(start, end):
339 ctx = fctx.filectx(i)
339 ctx = fctx.filectx(i)
340 n = fl.node(i)
340 n = fl.node(i)
341
341
342 l.insert(0, {"parity": parity,
342 l.insert(0, {"parity": parity,
343 "filerev": i,
343 "filerev": i,
344 "file": f,
344 "file": f,
345 "node": hex(ctx.node()),
345 "node": hex(ctx.node()),
346 "author": ctx.user(),
346 "author": ctx.user(),
347 "date": ctx.date(),
347 "date": ctx.date(),
348 "rename": self.renamelink(fl, n),
348 "rename": self.renamelink(fl, n),
349 "parent": self.siblings(fctx.parents()),
349 "parent": self.siblings(fctx.parents()),
350 "child": self.siblings(fctx.children()),
350 "child": self.siblings(fctx.children()),
351 "desc": ctx.description()})
351 "desc": ctx.description()})
352 parity = 1 - parity
352 parity = 1 - parity
353
353
354 for e in l:
354 for e in l:
355 yield e
355 yield e
356
356
357 nodefunc = lambda x: fctx.filectx(fileid=x)
357 nodefunc = lambda x: fctx.filectx(fileid=x)
358 nav = revnavgen(pos, pagelen, count, nodefunc)
358 nav = revnavgen(pos, pagelen, count, nodefunc)
359 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
359 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
360 entries=entries)
360 entries=entries)
361
361
362 def filerevision(self, fctx):
362 def filerevision(self, fctx):
363 f = fctx.path()
363 f = fctx.path()
364 text = fctx.data()
364 text = fctx.data()
365 fl = fctx.filelog()
365 fl = fctx.filelog()
366 n = fctx.filenode()
366 n = fctx.filenode()
367
367
368 mt = mimetypes.guess_type(f)[0]
368 mt = mimetypes.guess_type(f)[0]
369 rawtext = text
369 rawtext = text
370 if util.binary(text):
370 if util.binary(text):
371 mt = mt or 'application/octet-stream'
371 mt = mt or 'application/octet-stream'
372 text = "(binary:%s)" % mt
372 text = "(binary:%s)" % mt
373 mt = mt or 'text/plain'
373 mt = mt or 'text/plain'
374
374
375 def lines():
375 def lines():
376 for l, t in enumerate(text.splitlines(1)):
376 for l, t in enumerate(text.splitlines(1)):
377 yield {"line": t,
377 yield {"line": t,
378 "linenumber": "% 6d" % (l + 1),
378 "linenumber": "% 6d" % (l + 1),
379 "parity": self.stripes(l)}
379 "parity": self.stripes(l)}
380
380
381 yield self.t("filerevision",
381 yield self.t("filerevision",
382 file=f,
382 file=f,
383 path=_up(f),
383 path=_up(f),
384 text=lines(),
384 text=lines(),
385 raw=rawtext,
385 raw=rawtext,
386 mimetype=mt,
386 mimetype=mt,
387 rev=fctx.rev(),
387 rev=fctx.rev(),
388 node=hex(fctx.node()),
388 node=hex(fctx.node()),
389 author=fctx.user(),
389 author=fctx.user(),
390 date=fctx.date(),
390 date=fctx.date(),
391 desc=fctx.description(),
391 desc=fctx.description(),
392 parent=self.siblings(fctx.parents()),
392 parent=self.siblings(fctx.parents()),
393 child=self.siblings(fctx.children()),
393 child=self.siblings(fctx.children()),
394 rename=self.renamelink(fl, n),
394 rename=self.renamelink(fl, n),
395 permissions=fctx.manifest().execf(f))
395 permissions=fctx.manifest().execf(f))
396
396
397 def fileannotate(self, fctx):
397 def fileannotate(self, fctx):
398 f = fctx.path()
398 f = fctx.path()
399 n = fctx.filenode()
399 n = fctx.filenode()
400 fl = fctx.filelog()
400 fl = fctx.filelog()
401
401
402 def annotate(**map):
402 def annotate(**map):
403 parity = 0
403 parity = 0
404 last = None
404 last = None
405 for f, l in fctx.annotate(follow=True):
405 for f, l in fctx.annotate(follow=True):
406 fnode = f.filenode()
406 fnode = f.filenode()
407 name = self.repo.ui.shortuser(f.user())
407 name = self.repo.ui.shortuser(f.user())
408
408
409 if last != fnode:
409 if last != fnode:
410 parity = 1 - parity
410 parity = 1 - parity
411 last = fnode
411 last = fnode
412
412
413 yield {"parity": parity,
413 yield {"parity": parity,
414 "node": hex(f.node()),
414 "node": hex(f.node()),
415 "rev": f.rev(),
415 "rev": f.rev(),
416 "author": name,
416 "author": name,
417 "file": f.path(),
417 "file": f.path(),
418 "line": l}
418 "line": l}
419
419
420 yield self.t("fileannotate",
420 yield self.t("fileannotate",
421 file=f,
421 file=f,
422 annotate=annotate,
422 annotate=annotate,
423 path=_up(f),
423 path=_up(f),
424 rev=fctx.rev(),
424 rev=fctx.rev(),
425 node=hex(fctx.node()),
425 node=hex(fctx.node()),
426 author=fctx.user(),
426 author=fctx.user(),
427 date=fctx.date(),
427 date=fctx.date(),
428 desc=fctx.description(),
428 desc=fctx.description(),
429 rename=self.renamelink(fl, n),
429 rename=self.renamelink(fl, n),
430 parent=self.siblings(fctx.parents()),
430 parent=self.siblings(fctx.parents()),
431 child=self.siblings(fctx.children()),
431 child=self.siblings(fctx.children()),
432 permissions=fctx.manifest().execf(f))
432 permissions=fctx.manifest().execf(f))
433
433
434 def manifest(self, ctx, path):
434 def manifest(self, ctx, path):
435 mf = ctx.manifest()
435 mf = ctx.manifest()
436 node = ctx.node()
436 node = ctx.node()
437
437
438 files = {}
438 files = {}
439
439
440 if path and path[-1] != "/":
440 if path and path[-1] != "/":
441 path += "/"
441 path += "/"
442 l = len(path)
442 l = len(path)
443 abspath = "/" + path
443 abspath = "/" + path
444
444
445 for f, n in mf.items():
445 for f, n in mf.items():
446 if f[:l] != path:
446 if f[:l] != path:
447 continue
447 continue
448 remain = f[l:]
448 remain = f[l:]
449 if "/" in remain:
449 if "/" in remain:
450 short = remain[:remain.index("/") + 1] # bleah
450 short = remain[:remain.index("/") + 1] # bleah
451 files[short] = (f, None)
451 files[short] = (f, None)
452 else:
452 else:
453 short = os.path.basename(remain)
453 short = os.path.basename(remain)
454 files[short] = (f, n)
454 files[short] = (f, n)
455
455
456 def filelist(**map):
456 def filelist(**map):
457 parity = 0
457 parity = 0
458 fl = files.keys()
458 fl = files.keys()
459 fl.sort()
459 fl.sort()
460 for f in fl:
460 for f in fl:
461 full, fnode = files[f]
461 full, fnode = files[f]
462 if not fnode:
462 if not fnode:
463 continue
463 continue
464
464
465 yield {"file": full,
465 yield {"file": full,
466 "parity": self.stripes(parity),
466 "parity": self.stripes(parity),
467 "basename": f,
467 "basename": f,
468 "size": ctx.filectx(full).size(),
468 "size": ctx.filectx(full).size(),
469 "permissions": mf.execf(full)}
469 "permissions": mf.execf(full)}
470 parity += 1
470 parity += 1
471
471
472 def dirlist(**map):
472 def dirlist(**map):
473 parity = 0
473 parity = 0
474 fl = files.keys()
474 fl = files.keys()
475 fl.sort()
475 fl.sort()
476 for f in fl:
476 for f in fl:
477 full, fnode = files[f]
477 full, fnode = files[f]
478 if fnode:
478 if fnode:
479 continue
479 continue
480
480
481 yield {"parity": self.stripes(parity),
481 yield {"parity": self.stripes(parity),
482 "path": os.path.join(abspath, f),
482 "path": os.path.join(abspath, f),
483 "basename": f[:-1]}
483 "basename": f[:-1]}
484 parity += 1
484 parity += 1
485
485
486 yield self.t("manifest",
486 yield self.t("manifest",
487 rev=ctx.rev(),
487 rev=ctx.rev(),
488 node=hex(node),
488 node=hex(node),
489 path=abspath,
489 path=abspath,
490 up=_up(abspath),
490 up=_up(abspath),
491 fentries=filelist,
491 fentries=filelist,
492 dentries=dirlist,
492 dentries=dirlist,
493 archives=self.archivelist(hex(node)))
493 archives=self.archivelist(hex(node)))
494
494
495 def tags(self):
495 def tags(self):
496 cl = self.repo.changelog
496 cl = self.repo.changelog
497
497
498 i = self.repo.tagslist()
498 i = self.repo.tagslist()
499 i.reverse()
499 i.reverse()
500
500
501 def entries(notip=False, **map):
501 def entries(notip=False, **map):
502 parity = 0
502 parity = 0
503 for k, n in i:
503 for k, n in i:
504 if notip and k == "tip":
504 if notip and k == "tip":
505 continue
505 continue
506 yield {"parity": self.stripes(parity),
506 yield {"parity": self.stripes(parity),
507 "tag": k,
507 "tag": k,
508 "date": cl.read(n)[2],
508 "date": cl.read(n)[2],
509 "node": hex(n)}
509 "node": hex(n)}
510 parity += 1
510 parity += 1
511
511
512 yield self.t("tags",
512 yield self.t("tags",
513 node=hex(self.repo.changelog.tip()),
513 node=hex(self.repo.changelog.tip()),
514 entries=lambda **x: entries(False, **x),
514 entries=lambda **x: entries(False, **x),
515 entriesnotip=lambda **x: entries(True, **x))
515 entriesnotip=lambda **x: entries(True, **x))
516
516
517 def summary(self):
517 def summary(self):
518 cl = self.repo.changelog
518 cl = self.repo.changelog
519
519
520 i = self.repo.tagslist()
520 i = self.repo.tagslist()
521 i.reverse()
521 i.reverse()
522
522
523 def tagentries(**map):
523 def tagentries(**map):
524 parity = 0
524 parity = 0
525 count = 0
525 count = 0
526 for k, n in i:
526 for k, n in i:
527 if k == "tip": # skip tip
527 if k == "tip": # skip tip
528 continue;
528 continue;
529
529
530 count += 1
530 count += 1
531 if count > 10: # limit to 10 tags
531 if count > 10: # limit to 10 tags
532 break;
532 break;
533
533
534 c = cl.read(n)
534 c = cl.read(n)
535 t = c[2]
535 t = c[2]
536
536
537 yield self.t("tagentry",
537 yield self.t("tagentry",
538 parity = self.stripes(parity),
538 parity = self.stripes(parity),
539 tag = k,
539 tag = k,
540 node = hex(n),
540 node = hex(n),
541 date = t)
541 date = t)
542 parity += 1
542 parity += 1
543
543
544 def heads(**map):
544 def heads(**map):
545 parity = 0
545 parity = 0
546 count = 0
546 count = 0
547
547
548 for node in self.repo.heads():
548 for node in self.repo.heads():
549 count += 1
549 count += 1
550 if count > 10:
550 if count > 10:
551 break;
551 break;
552
552
553 ctx = self.repo.changectx(node)
553 ctx = self.repo.changectx(node)
554
554
555 yield {'parity': self.stripes(parity),
555 yield {'parity': self.stripes(parity),
556 'branch': ctx.branch(),
556 'branch': ctx.branch(),
557 'node': hex(node),
557 'node': hex(node),
558 'date': ctx.date()}
558 'date': ctx.date()}
559 parity += 1
559 parity += 1
560
560
561 def changelist(**map):
561 def changelist(**map):
562 parity = 0
562 parity = 0
563 cl = self.repo.changelog
563 cl = self.repo.changelog
564 l = [] # build a list in forward order for efficiency
564 l = [] # build a list in forward order for efficiency
565 for i in xrange(start, end):
565 for i in xrange(start, end):
566 n = cl.node(i)
566 n = cl.node(i)
567 changes = cl.read(n)
567 changes = cl.read(n)
568 hn = hex(n)
568 hn = hex(n)
569 t = changes[2]
569 t = changes[2]
570
570
571 l.insert(0, self.t(
571 l.insert(0, self.t(
572 'shortlogentry',
572 'shortlogentry',
573 parity = parity,
573 parity = parity,
574 author = changes[1],
574 author = changes[1],
575 desc = changes[4],
575 desc = changes[4],
576 date = t,
576 date = t,
577 rev = i,
577 rev = i,
578 node = hn))
578 node = hn))
579 parity = 1 - parity
579 parity = 1 - parity
580
580
581 yield l
581 yield l
582
582
583 count = cl.count()
583 count = cl.count()
584 start = max(0, count - self.maxchanges)
584 start = max(0, count - self.maxchanges)
585 end = min(count, start + self.maxchanges)
585 end = min(count, start + self.maxchanges)
586
586
587 yield self.t("summary",
587 yield self.t("summary",
588 desc = self.config("web", "description", "unknown"),
588 desc = self.config("web", "description", "unknown"),
589 owner = (self.config("ui", "username") or # preferred
589 owner = (self.config("ui", "username") or # preferred
590 self.config("web", "contact") or # deprecated
590 self.config("web", "contact") or # deprecated
591 self.config("web", "author", "unknown")), # also
591 self.config("web", "author", "unknown")), # also
592 lastchange = cl.read(cl.tip())[2],
592 lastchange = cl.read(cl.tip())[2],
593 tags = tagentries,
593 tags = tagentries,
594 heads = heads,
594 heads = heads,
595 shortlog = changelist,
595 shortlog = changelist,
596 node = hex(cl.tip()),
596 node = hex(cl.tip()),
597 archives=self.archivelist("tip"))
597 archives=self.archivelist("tip"))
598
598
599 def filediff(self, fctx):
599 def filediff(self, fctx):
600 n = fctx.node()
600 n = fctx.node()
601 path = fctx.path()
601 path = fctx.path()
602 parents = fctx.parents()
602 parents = fctx.parents()
603 p1 = parents and parents[0].node() or nullid
603 p1 = parents and parents[0].node() or nullid
604
604
605 def diff(**map):
605 def diff(**map):
606 yield self.diff(p1, n, [path])
606 yield self.diff(p1, n, [path])
607
607
608 yield self.t("filediff",
608 yield self.t("filediff",
609 file=path,
609 file=path,
610 node=hex(n),
610 node=hex(n),
611 rev=fctx.rev(),
611 rev=fctx.rev(),
612 parent=self.siblings(parents),
612 parent=self.siblings(parents),
613 child=self.siblings(fctx.children()),
613 child=self.siblings(fctx.children()),
614 diff=diff)
614 diff=diff)
615
615
616 archive_specs = {
616 archive_specs = {
617 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
617 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
618 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
618 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
619 'zip': ('application/zip', 'zip', '.zip', None),
619 'zip': ('application/zip', 'zip', '.zip', None),
620 }
620 }
621
621
622 def archive(self, req, cnode, type_):
622 def archive(self, req, cnode, type_):
623 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
623 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
624 name = "%s-%s" % (reponame, short(cnode))
624 name = "%s-%s" % (reponame, short(cnode))
625 mimetype, artype, extension, encoding = self.archive_specs[type_]
625 mimetype, artype, extension, encoding = self.archive_specs[type_]
626 headers = [('Content-type', mimetype),
626 headers = [('Content-type', mimetype),
627 ('Content-disposition', 'attachment; filename=%s%s' %
627 ('Content-disposition', 'attachment; filename=%s%s' %
628 (name, extension))]
628 (name, extension))]
629 if encoding:
629 if encoding:
630 headers.append(('Content-encoding', encoding))
630 headers.append(('Content-encoding', encoding))
631 req.header(headers)
631 req.header(headers)
632 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
632 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
633
633
634 # add tags to things
634 # add tags to things
635 # tags -> list of changesets corresponding to tags
635 # tags -> list of changesets corresponding to tags
636 # find tag, changeset, file
636 # find tag, changeset, file
637
637
638 def cleanpath(self, path):
638 def cleanpath(self, path):
639 path = path.lstrip('/')
639 path = path.lstrip('/')
640 return util.canonpath(self.repo.root, '', path)
640 return util.canonpath(self.repo.root, '', path)
641
641
642 def run(self):
642 def run(self):
643 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
643 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
644 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
644 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
645 import mercurial.hgweb.wsgicgi as wsgicgi
645 import mercurial.hgweb.wsgicgi as wsgicgi
646 from request import wsgiapplication
646 from request import wsgiapplication
647 def make_web_app():
647 def make_web_app():
648 return self
648 return self
649 wsgicgi.launch(wsgiapplication(make_web_app))
649 wsgicgi.launch(wsgiapplication(make_web_app))
650
650
651 def run_wsgi(self, req):
651 def run_wsgi(self, req):
652 def header(**map):
652 def header(**map):
653 header_file = cStringIO.StringIO(
653 header_file = cStringIO.StringIO(
654 ''.join(self.t("header", encoding=util._encoding, **map)))
654 ''.join(self.t("header", encoding=util._encoding, **map)))
655 msg = mimetools.Message(header_file, 0)
655 msg = mimetools.Message(header_file, 0)
656 req.header(msg.items())
656 req.header(msg.items())
657 yield header_file.read()
657 yield header_file.read()
658
658
659 def rawfileheader(**map):
659 def rawfileheader(**map):
660 req.header([('Content-type', map['mimetype']),
660 req.header([('Content-type', map['mimetype']),
661 ('Content-disposition', 'filename=%s' % map['file']),
661 ('Content-disposition', 'filename=%s' % map['file']),
662 ('Content-length', str(len(map['raw'])))])
662 ('Content-length', str(len(map['raw'])))])
663 yield ''
663 yield ''
664
664
665 def footer(**map):
665 def footer(**map):
666 yield self.t("footer", **map)
666 yield self.t("footer", **map)
667
667
668 def motd(**map):
668 def motd(**map):
669 yield self.config("web", "motd", "")
669 yield self.config("web", "motd", "")
670
670
671 def expand_form(form):
671 def expand_form(form):
672 shortcuts = {
672 shortcuts = {
673 'cl': [('cmd', ['changelog']), ('rev', None)],
673 'cl': [('cmd', ['changelog']), ('rev', None)],
674 'sl': [('cmd', ['shortlog']), ('rev', None)],
674 'sl': [('cmd', ['shortlog']), ('rev', None)],
675 'cs': [('cmd', ['changeset']), ('node', None)],
675 'cs': [('cmd', ['changeset']), ('node', None)],
676 'f': [('cmd', ['file']), ('filenode', None)],
676 'f': [('cmd', ['file']), ('filenode', None)],
677 'fl': [('cmd', ['filelog']), ('filenode', None)],
677 'fl': [('cmd', ['filelog']), ('filenode', None)],
678 'fd': [('cmd', ['filediff']), ('node', None)],
678 'fd': [('cmd', ['filediff']), ('node', None)],
679 'fa': [('cmd', ['annotate']), ('filenode', None)],
679 'fa': [('cmd', ['annotate']), ('filenode', None)],
680 'mf': [('cmd', ['manifest']), ('manifest', None)],
680 'mf': [('cmd', ['manifest']), ('manifest', None)],
681 'ca': [('cmd', ['archive']), ('node', None)],
681 'ca': [('cmd', ['archive']), ('node', None)],
682 'tags': [('cmd', ['tags'])],
682 'tags': [('cmd', ['tags'])],
683 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
683 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
684 'static': [('cmd', ['static']), ('file', None)]
684 'static': [('cmd', ['static']), ('file', None)]
685 }
685 }
686
686
687 for k in shortcuts.iterkeys():
687 for k in shortcuts.iterkeys():
688 if form.has_key(k):
688 if form.has_key(k):
689 for name, value in shortcuts[k]:
689 for name, value in shortcuts[k]:
690 if value is None:
690 if value is None:
691 value = form[k]
691 value = form[k]
692 form[name] = value
692 form[name] = value
693 del form[k]
693 del form[k]
694
694
695 def rewrite_request(req):
695 def rewrite_request(req):
696 '''translate new web interface to traditional format'''
696 '''translate new web interface to traditional format'''
697
697
698 def spliturl(req):
698 def spliturl(req):
699 def firstitem(query):
699 def firstitem(query):
700 return query.split('&', 1)[0].split(';', 1)[0]
700 return query.split('&', 1)[0].split(';', 1)[0]
701
701
702 def normurl(url):
702 def normurl(url):
703 inner = '/'.join([x for x in url.split('/') if x])
703 inner = '/'.join([x for x in url.split('/') if x])
704 tl = len(url) > 1 and url.endswith('/') and '/' or ''
704 tl = len(url) > 1 and url.endswith('/') and '/' or ''
705
705
706 return '%s%s%s' % (url.startswith('/') and '/' or '',
706 return '%s%s%s' % (url.startswith('/') and '/' or '',
707 inner, tl)
707 inner, tl)
708
708
709 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
709 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
710 pi = normurl(req.env.get('PATH_INFO', ''))
710 pi = normurl(req.env.get('PATH_INFO', ''))
711 if pi:
711 if pi:
712 # strip leading /
712 # strip leading /
713 pi = pi[1:]
713 pi = pi[1:]
714 if pi:
714 if pi:
715 root = root[:-len(pi)]
715 root = root[:-len(pi)]
716 if req.env.has_key('REPO_NAME'):
716 if req.env.has_key('REPO_NAME'):
717 rn = req.env['REPO_NAME'] + '/'
717 rn = req.env['REPO_NAME'] + '/'
718 root += rn
718 root += rn
719 query = pi[len(rn):]
719 query = pi[len(rn):]
720 else:
720 else:
721 query = pi
721 query = pi
722 else:
722 else:
723 root += '?'
723 root += '?'
724 query = firstitem(req.env['QUERY_STRING'])
724 query = firstitem(req.env['QUERY_STRING'])
725
725
726 return (root, query)
726 return (root, query)
727
727
728 req.url, query = spliturl(req)
728 req.url, query = spliturl(req)
729
729
730 if req.form.has_key('cmd'):
730 if req.form.has_key('cmd'):
731 # old style
731 # old style
732 return
732 return
733
733
734 args = query.split('/', 2)
734 args = query.split('/', 2)
735 if not args or not args[0]:
735 if not args or not args[0]:
736 return
736 return
737
737
738 cmd = args.pop(0)
738 cmd = args.pop(0)
739 style = cmd.rfind('-')
739 style = cmd.rfind('-')
740 if style != -1:
740 if style != -1:
741 req.form['style'] = [cmd[:style]]
741 req.form['style'] = [cmd[:style]]
742 cmd = cmd[style+1:]
742 cmd = cmd[style+1:]
743 # avoid accepting e.g. style parameter as command
743 # avoid accepting e.g. style parameter as command
744 if hasattr(self, 'do_' + cmd):
744 if hasattr(self, 'do_' + cmd):
745 req.form['cmd'] = [cmd]
745 req.form['cmd'] = [cmd]
746
746
747 if args and args[0]:
747 if args and args[0]:
748 node = args.pop(0)
748 node = args.pop(0)
749 req.form['node'] = [node]
749 req.form['node'] = [node]
750 if args:
750 if args:
751 req.form['file'] = args
751 req.form['file'] = args
752
752
753 if cmd == 'static':
753 if cmd == 'static':
754 req.form['file'] = req.form['node']
754 req.form['file'] = req.form['node']
755 elif cmd == 'archive':
755 elif cmd == 'archive':
756 fn = req.form['node'][0]
756 fn = req.form['node'][0]
757 for type_, spec in self.archive_specs.iteritems():
757 for type_, spec in self.archive_specs.iteritems():
758 ext = spec[2]
758 ext = spec[2]
759 if fn.endswith(ext):
759 if fn.endswith(ext):
760 req.form['node'] = [fn[:-len(ext)]]
760 req.form['node'] = [fn[:-len(ext)]]
761 req.form['type'] = [type_]
761 req.form['type'] = [type_]
762
762
763 def sessionvars(**map):
763 def sessionvars(**map):
764 fields = []
764 fields = []
765 if req.form.has_key('style'):
765 if req.form.has_key('style'):
766 style = req.form['style'][0]
766 style = req.form['style'][0]
767 if style != self.config('web', 'style', ''):
767 if style != self.config('web', 'style', ''):
768 fields.append(('style', style))
768 fields.append(('style', style))
769
769
770 separator = req.url[-1] == '?' and ';' or '?'
770 separator = req.url[-1] == '?' and ';' or '?'
771 for name, value in fields:
771 for name, value in fields:
772 yield dict(name=name, value=value, separator=separator)
772 yield dict(name=name, value=value, separator=separator)
773 separator = ';'
773 separator = ';'
774
774
775 self.refresh()
775 self.refresh()
776
776
777 expand_form(req.form)
777 expand_form(req.form)
778 rewrite_request(req)
778 rewrite_request(req)
779
779
780 style = self.config("web", "style", "")
780 style = self.config("web", "style", "")
781 if req.form.has_key('style'):
781 if req.form.has_key('style'):
782 style = req.form['style'][0]
782 style = req.form['style'][0]
783 mapfile = style_map(self.templatepath, style)
783 mapfile = style_map(self.templatepath, style)
784
784
785 port = req.env["SERVER_PORT"]
785 port = req.env["SERVER_PORT"]
786 port = port != "80" and (":" + port) or ""
786 port = port != "80" and (":" + port) or ""
787 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
787 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
788
788
789 if not self.reponame:
789 if not self.reponame:
790 self.reponame = (self.config("web", "name")
790 self.reponame = (self.config("web", "name")
791 or req.env.get('REPO_NAME')
791 or req.env.get('REPO_NAME')
792 or req.url.strip('/') or self.repo.root)
792 or req.url.strip('/') or self.repo.root)
793
793
794 self.t = templater.templater(mapfile, templater.common_filters,
794 self.t = templater.templater(mapfile, templater.common_filters,
795 defaults={"url": req.url,
795 defaults={"url": req.url,
796 "urlbase": urlbase,
796 "urlbase": urlbase,
797 "repo": self.reponame,
797 "repo": self.reponame,
798 "header": header,
798 "header": header,
799 "footer": footer,
799 "footer": footer,
800 "motd": motd,
800 "motd": motd,
801 "rawfileheader": rawfileheader,
801 "rawfileheader": rawfileheader,
802 "sessionvars": sessionvars
802 "sessionvars": sessionvars
803 })
803 })
804
804
805 if not req.form.has_key('cmd'):
805 if not req.form.has_key('cmd'):
806 req.form['cmd'] = [self.t.cache['default']]
806 req.form['cmd'] = [self.t.cache['default']]
807
807
808 cmd = req.form['cmd'][0]
808 cmd = req.form['cmd'][0]
809
809
810 method = getattr(self, 'do_' + cmd, None)
810 method = getattr(self, 'do_' + cmd, None)
811 if method:
811 if method:
812 try:
812 try:
813 method(req)
813 method(req)
814 except (hg.RepoError, revlog.RevlogError), inst:
814 except (hg.RepoError, revlog.RevlogError), inst:
815 req.write(self.t("error", error=str(inst)))
815 req.write(self.t("error", error=str(inst)))
816 else:
816 else:
817 req.write(self.t("error", error='No such method: ' + cmd))
817 req.write(self.t("error", error='No such method: ' + cmd))
818
818
819 def changectx(self, req):
819 def changectx(self, req):
820 if req.form.has_key('node'):
820 if req.form.has_key('node'):
821 changeid = req.form['node'][0]
821 changeid = req.form['node'][0]
822 elif req.form.has_key('manifest'):
822 elif req.form.has_key('manifest'):
823 changeid = req.form['manifest'][0]
823 changeid = req.form['manifest'][0]
824 else:
824 else:
825 changeid = self.repo.changelog.count() - 1
825 changeid = self.repo.changelog.count() - 1
826
826
827 try:
827 try:
828 ctx = self.repo.changectx(changeid)
828 ctx = self.repo.changectx(changeid)
829 except hg.RepoError:
829 except hg.RepoError:
830 man = self.repo.manifest
830 man = self.repo.manifest
831 mn = man.lookup(changeid)
831 mn = man.lookup(changeid)
832 ctx = self.repo.changectx(man.linkrev(mn))
832 ctx = self.repo.changectx(man.linkrev(mn))
833
833
834 return ctx
834 return ctx
835
835
836 def filectx(self, req):
836 def filectx(self, req):
837 path = self.cleanpath(req.form['file'][0])
837 path = self.cleanpath(req.form['file'][0])
838 if req.form.has_key('node'):
838 if req.form.has_key('node'):
839 changeid = req.form['node'][0]
839 changeid = req.form['node'][0]
840 else:
840 else:
841 changeid = req.form['filenode'][0]
841 changeid = req.form['filenode'][0]
842 try:
842 try:
843 ctx = self.repo.changectx(changeid)
843 ctx = self.repo.changectx(changeid)
844 fctx = ctx.filectx(path)
844 fctx = ctx.filectx(path)
845 except hg.RepoError:
845 except hg.RepoError:
846 fctx = self.repo.filectx(path, fileid=changeid)
846 fctx = self.repo.filectx(path, fileid=changeid)
847
847
848 return fctx
848 return fctx
849
849
850 def stripes(self, parity):
850 def stripes(self, parity):
851 "make horizontal stripes for easier reading"
851 "make horizontal stripes for easier reading"
852 if self.stripecount:
852 if self.stripecount:
853 return (1 + parity / self.stripecount) & 1
853 return (1 + parity / self.stripecount) & 1
854 else:
854 else:
855 return 0
855 return 0
856
856
857 def do_log(self, req):
857 def do_log(self, req):
858 if req.form.has_key('file') and req.form['file'][0]:
858 if req.form.has_key('file') and req.form['file'][0]:
859 self.do_filelog(req)
859 self.do_filelog(req)
860 else:
860 else:
861 self.do_changelog(req)
861 self.do_changelog(req)
862
862
863 def do_rev(self, req):
863 def do_rev(self, req):
864 self.do_changeset(req)
864 self.do_changeset(req)
865
865
866 def do_file(self, req):
866 def do_file(self, req):
867 path = self.cleanpath(req.form.get('file', [''])[0])
867 path = self.cleanpath(req.form.get('file', [''])[0])
868 if path:
868 if path:
869 try:
869 try:
870 req.write(self.filerevision(self.filectx(req)))
870 req.write(self.filerevision(self.filectx(req)))
871 return
871 return
872 except hg.RepoError:
872 except revlog.LookupError:
873 pass
873 pass
874
874
875 req.write(self.manifest(self.changectx(req), path))
875 req.write(self.manifest(self.changectx(req), path))
876
876
877 def do_diff(self, req):
877 def do_diff(self, req):
878 self.do_filediff(req)
878 self.do_filediff(req)
879
879
880 def do_changelog(self, req, shortlog = False):
880 def do_changelog(self, req, shortlog = False):
881 if req.form.has_key('node'):
881 if req.form.has_key('node'):
882 ctx = self.changectx(req)
882 ctx = self.changectx(req)
883 else:
883 else:
884 if req.form.has_key('rev'):
884 if req.form.has_key('rev'):
885 hi = req.form['rev'][0]
885 hi = req.form['rev'][0]
886 else:
886 else:
887 hi = self.repo.changelog.count() - 1
887 hi = self.repo.changelog.count() - 1
888 try:
888 try:
889 ctx = self.repo.changectx(hi)
889 ctx = self.repo.changectx(hi)
890 except hg.RepoError:
890 except hg.RepoError:
891 req.write(self.search(hi)) # XXX redirect to 404 page?
891 req.write(self.search(hi)) # XXX redirect to 404 page?
892 return
892 return
893
893
894 req.write(self.changelog(ctx, shortlog = shortlog))
894 req.write(self.changelog(ctx, shortlog = shortlog))
895
895
896 def do_shortlog(self, req):
896 def do_shortlog(self, req):
897 self.do_changelog(req, shortlog = True)
897 self.do_changelog(req, shortlog = True)
898
898
899 def do_changeset(self, req):
899 def do_changeset(self, req):
900 req.write(self.changeset(self.changectx(req)))
900 req.write(self.changeset(self.changectx(req)))
901
901
902 def do_manifest(self, req):
902 def do_manifest(self, req):
903 req.write(self.manifest(self.changectx(req),
903 req.write(self.manifest(self.changectx(req),
904 self.cleanpath(req.form['path'][0])))
904 self.cleanpath(req.form['path'][0])))
905
905
906 def do_tags(self, req):
906 def do_tags(self, req):
907 req.write(self.tags())
907 req.write(self.tags())
908
908
909 def do_summary(self, req):
909 def do_summary(self, req):
910 req.write(self.summary())
910 req.write(self.summary())
911
911
912 def do_filediff(self, req):
912 def do_filediff(self, req):
913 req.write(self.filediff(self.filectx(req)))
913 req.write(self.filediff(self.filectx(req)))
914
914
915 def do_annotate(self, req):
915 def do_annotate(self, req):
916 req.write(self.fileannotate(self.filectx(req)))
916 req.write(self.fileannotate(self.filectx(req)))
917
917
918 def do_filelog(self, req):
918 def do_filelog(self, req):
919 req.write(self.filelog(self.filectx(req)))
919 req.write(self.filelog(self.filectx(req)))
920
920
921 def do_lookup(self, req):
921 def do_lookup(self, req):
922 try:
922 try:
923 r = hex(self.repo.lookup(req.form['key'][0]))
923 r = hex(self.repo.lookup(req.form['key'][0]))
924 success = 1
924 success = 1
925 except Exception,inst:
925 except Exception,inst:
926 r = str(inst)
926 r = str(inst)
927 success = 0
927 success = 0
928 resp = "%s %s\n" % (success, r)
928 resp = "%s %s\n" % (success, r)
929 req.httphdr("application/mercurial-0.1", length=len(resp))
929 req.httphdr("application/mercurial-0.1", length=len(resp))
930 req.write(resp)
930 req.write(resp)
931
931
932 def do_heads(self, req):
932 def do_heads(self, req):
933 resp = " ".join(map(hex, self.repo.heads())) + "\n"
933 resp = " ".join(map(hex, self.repo.heads())) + "\n"
934 req.httphdr("application/mercurial-0.1", length=len(resp))
934 req.httphdr("application/mercurial-0.1", length=len(resp))
935 req.write(resp)
935 req.write(resp)
936
936
937 def do_branches(self, req):
937 def do_branches(self, req):
938 nodes = []
938 nodes = []
939 if req.form.has_key('nodes'):
939 if req.form.has_key('nodes'):
940 nodes = map(bin, req.form['nodes'][0].split(" "))
940 nodes = map(bin, req.form['nodes'][0].split(" "))
941 resp = cStringIO.StringIO()
941 resp = cStringIO.StringIO()
942 for b in self.repo.branches(nodes):
942 for b in self.repo.branches(nodes):
943 resp.write(" ".join(map(hex, b)) + "\n")
943 resp.write(" ".join(map(hex, b)) + "\n")
944 resp = resp.getvalue()
944 resp = resp.getvalue()
945 req.httphdr("application/mercurial-0.1", length=len(resp))
945 req.httphdr("application/mercurial-0.1", length=len(resp))
946 req.write(resp)
946 req.write(resp)
947
947
948 def do_between(self, req):
948 def do_between(self, req):
949 if req.form.has_key('pairs'):
949 if req.form.has_key('pairs'):
950 pairs = [map(bin, p.split("-"))
950 pairs = [map(bin, p.split("-"))
951 for p in req.form['pairs'][0].split(" ")]
951 for p in req.form['pairs'][0].split(" ")]
952 resp = cStringIO.StringIO()
952 resp = cStringIO.StringIO()
953 for b in self.repo.between(pairs):
953 for b in self.repo.between(pairs):
954 resp.write(" ".join(map(hex, b)) + "\n")
954 resp.write(" ".join(map(hex, b)) + "\n")
955 resp = resp.getvalue()
955 resp = resp.getvalue()
956 req.httphdr("application/mercurial-0.1", length=len(resp))
956 req.httphdr("application/mercurial-0.1", length=len(resp))
957 req.write(resp)
957 req.write(resp)
958
958
959 def do_changegroup(self, req):
959 def do_changegroup(self, req):
960 req.httphdr("application/mercurial-0.1")
960 req.httphdr("application/mercurial-0.1")
961 nodes = []
961 nodes = []
962 if not self.allowpull:
962 if not self.allowpull:
963 return
963 return
964
964
965 if req.form.has_key('roots'):
965 if req.form.has_key('roots'):
966 nodes = map(bin, req.form['roots'][0].split(" "))
966 nodes = map(bin, req.form['roots'][0].split(" "))
967
967
968 z = zlib.compressobj()
968 z = zlib.compressobj()
969 f = self.repo.changegroup(nodes, 'serve')
969 f = self.repo.changegroup(nodes, 'serve')
970 while 1:
970 while 1:
971 chunk = f.read(4096)
971 chunk = f.read(4096)
972 if not chunk:
972 if not chunk:
973 break
973 break
974 req.write(z.compress(chunk))
974 req.write(z.compress(chunk))
975
975
976 req.write(z.flush())
976 req.write(z.flush())
977
977
978 def do_changegroupsubset(self, req):
978 def do_changegroupsubset(self, req):
979 req.httphdr("application/mercurial-0.1")
979 req.httphdr("application/mercurial-0.1")
980 bases = []
980 bases = []
981 heads = []
981 heads = []
982 if not self.allowpull:
982 if not self.allowpull:
983 return
983 return
984
984
985 if req.form.has_key('bases'):
985 if req.form.has_key('bases'):
986 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
986 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
987 if req.form.has_key('heads'):
987 if req.form.has_key('heads'):
988 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
988 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
989
989
990 z = zlib.compressobj()
990 z = zlib.compressobj()
991 f = self.repo.changegroupsubset(bases, heads, 'serve')
991 f = self.repo.changegroupsubset(bases, heads, 'serve')
992 while 1:
992 while 1:
993 chunk = f.read(4096)
993 chunk = f.read(4096)
994 if not chunk:
994 if not chunk:
995 break
995 break
996 req.write(z.compress(chunk))
996 req.write(z.compress(chunk))
997
997
998 req.write(z.flush())
998 req.write(z.flush())
999
999
1000 def do_archive(self, req):
1000 def do_archive(self, req):
1001 changeset = self.repo.lookup(req.form['node'][0])
1001 changeset = self.repo.lookup(req.form['node'][0])
1002 type_ = req.form['type'][0]
1002 type_ = req.form['type'][0]
1003 allowed = self.configlist("web", "allow_archive")
1003 allowed = self.configlist("web", "allow_archive")
1004 if (type_ in self.archives and (type_ in allowed or
1004 if (type_ in self.archives and (type_ in allowed or
1005 self.configbool("web", "allow" + type_, False))):
1005 self.configbool("web", "allow" + type_, False))):
1006 self.archive(req, changeset, type_)
1006 self.archive(req, changeset, type_)
1007 return
1007 return
1008
1008
1009 req.write(self.t("error"))
1009 req.write(self.t("error"))
1010
1010
1011 def do_static(self, req):
1011 def do_static(self, req):
1012 fname = req.form['file'][0]
1012 fname = req.form['file'][0]
1013 # a repo owner may set web.static in .hg/hgrc to get any file
1013 # a repo owner may set web.static in .hg/hgrc to get any file
1014 # readable by the user running the CGI script
1014 # readable by the user running the CGI script
1015 static = self.config("web", "static",
1015 static = self.config("web", "static",
1016 os.path.join(self.templatepath, "static"),
1016 os.path.join(self.templatepath, "static"),
1017 untrusted=False)
1017 untrusted=False)
1018 req.write(staticfile(static, fname, req)
1018 req.write(staticfile(static, fname, req)
1019 or self.t("error", error="%r not found" % fname))
1019 or self.t("error", error="%r not found" % fname))
1020
1020
1021 def do_capabilities(self, req):
1021 def do_capabilities(self, req):
1022 caps = ['lookup', 'changegroupsubset']
1022 caps = ['lookup', 'changegroupsubset']
1023 if self.configbool('server', 'uncompressed'):
1023 if self.configbool('server', 'uncompressed'):
1024 caps.append('stream=%d' % self.repo.revlogversion)
1024 caps.append('stream=%d' % self.repo.revlogversion)
1025 # XXX: make configurable and/or share code with do_unbundle:
1025 # XXX: make configurable and/or share code with do_unbundle:
1026 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1026 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1027 if unbundleversions:
1027 if unbundleversions:
1028 caps.append('unbundle=%s' % ','.join(unbundleversions))
1028 caps.append('unbundle=%s' % ','.join(unbundleversions))
1029 resp = ' '.join(caps)
1029 resp = ' '.join(caps)
1030 req.httphdr("application/mercurial-0.1", length=len(resp))
1030 req.httphdr("application/mercurial-0.1", length=len(resp))
1031 req.write(resp)
1031 req.write(resp)
1032
1032
1033 def check_perm(self, req, op, default):
1033 def check_perm(self, req, op, default):
1034 '''check permission for operation based on user auth.
1034 '''check permission for operation based on user auth.
1035 return true if op allowed, else false.
1035 return true if op allowed, else false.
1036 default is policy to use if no config given.'''
1036 default is policy to use if no config given.'''
1037
1037
1038 user = req.env.get('REMOTE_USER')
1038 user = req.env.get('REMOTE_USER')
1039
1039
1040 deny = self.configlist('web', 'deny_' + op)
1040 deny = self.configlist('web', 'deny_' + op)
1041 if deny and (not user or deny == ['*'] or user in deny):
1041 if deny and (not user or deny == ['*'] or user in deny):
1042 return False
1042 return False
1043
1043
1044 allow = self.configlist('web', 'allow_' + op)
1044 allow = self.configlist('web', 'allow_' + op)
1045 return (allow and (allow == ['*'] or user in allow)) or default
1045 return (allow and (allow == ['*'] or user in allow)) or default
1046
1046
1047 def do_unbundle(self, req):
1047 def do_unbundle(self, req):
1048 def bail(response, headers={}):
1048 def bail(response, headers={}):
1049 length = int(req.env['CONTENT_LENGTH'])
1049 length = int(req.env['CONTENT_LENGTH'])
1050 for s in util.filechunkiter(req, limit=length):
1050 for s in util.filechunkiter(req, limit=length):
1051 # drain incoming bundle, else client will not see
1051 # drain incoming bundle, else client will not see
1052 # response when run outside cgi script
1052 # response when run outside cgi script
1053 pass
1053 pass
1054 req.httphdr("application/mercurial-0.1", headers=headers)
1054 req.httphdr("application/mercurial-0.1", headers=headers)
1055 req.write('0\n')
1055 req.write('0\n')
1056 req.write(response)
1056 req.write(response)
1057
1057
1058 # require ssl by default, auth info cannot be sniffed and
1058 # require ssl by default, auth info cannot be sniffed and
1059 # replayed
1059 # replayed
1060 ssl_req = self.configbool('web', 'push_ssl', True)
1060 ssl_req = self.configbool('web', 'push_ssl', True)
1061 if ssl_req:
1061 if ssl_req:
1062 if not req.env.get('HTTPS'):
1062 if not req.env.get('HTTPS'):
1063 bail(_('ssl required\n'))
1063 bail(_('ssl required\n'))
1064 return
1064 return
1065 proto = 'https'
1065 proto = 'https'
1066 else:
1066 else:
1067 proto = 'http'
1067 proto = 'http'
1068
1068
1069 # do not allow push unless explicitly allowed
1069 # do not allow push unless explicitly allowed
1070 if not self.check_perm(req, 'push', False):
1070 if not self.check_perm(req, 'push', False):
1071 bail(_('push not authorized\n'),
1071 bail(_('push not authorized\n'),
1072 headers={'status': '401 Unauthorized'})
1072 headers={'status': '401 Unauthorized'})
1073 return
1073 return
1074
1074
1075 req.httphdr("application/mercurial-0.1")
1075 req.httphdr("application/mercurial-0.1")
1076
1076
1077 their_heads = req.form['heads'][0].split(' ')
1077 their_heads = req.form['heads'][0].split(' ')
1078
1078
1079 def check_heads():
1079 def check_heads():
1080 heads = map(hex, self.repo.heads())
1080 heads = map(hex, self.repo.heads())
1081 return their_heads == [hex('force')] or their_heads == heads
1081 return their_heads == [hex('force')] or their_heads == heads
1082
1082
1083 # fail early if possible
1083 # fail early if possible
1084 if not check_heads():
1084 if not check_heads():
1085 bail(_('unsynced changes\n'))
1085 bail(_('unsynced changes\n'))
1086 return
1086 return
1087
1087
1088 # do not lock repo until all changegroup data is
1088 # do not lock repo until all changegroup data is
1089 # streamed. save to temporary file.
1089 # streamed. save to temporary file.
1090
1090
1091 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1091 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1092 fp = os.fdopen(fd, 'wb+')
1092 fp = os.fdopen(fd, 'wb+')
1093 try:
1093 try:
1094 length = int(req.env['CONTENT_LENGTH'])
1094 length = int(req.env['CONTENT_LENGTH'])
1095 for s in util.filechunkiter(req, limit=length):
1095 for s in util.filechunkiter(req, limit=length):
1096 fp.write(s)
1096 fp.write(s)
1097
1097
1098 lock = self.repo.lock()
1098 lock = self.repo.lock()
1099 try:
1099 try:
1100 if not check_heads():
1100 if not check_heads():
1101 req.write('0\n')
1101 req.write('0\n')
1102 req.write(_('unsynced changes\n'))
1102 req.write(_('unsynced changes\n'))
1103 return
1103 return
1104
1104
1105 fp.seek(0)
1105 fp.seek(0)
1106 header = fp.read(6)
1106 header = fp.read(6)
1107 if not header.startswith("HG"):
1107 if not header.startswith("HG"):
1108 # old client with uncompressed bundle
1108 # old client with uncompressed bundle
1109 def generator(f):
1109 def generator(f):
1110 yield header
1110 yield header
1111 for chunk in f:
1111 for chunk in f:
1112 yield chunk
1112 yield chunk
1113 elif not header.startswith("HG10"):
1113 elif not header.startswith("HG10"):
1114 req.write("0\n")
1114 req.write("0\n")
1115 req.write(_("unknown bundle version\n"))
1115 req.write(_("unknown bundle version\n"))
1116 return
1116 return
1117 elif header == "HG10GZ":
1117 elif header == "HG10GZ":
1118 def generator(f):
1118 def generator(f):
1119 zd = zlib.decompressobj()
1119 zd = zlib.decompressobj()
1120 for chunk in f:
1120 for chunk in f:
1121 yield zd.decompress(chunk)
1121 yield zd.decompress(chunk)
1122 elif header == "HG10BZ":
1122 elif header == "HG10BZ":
1123 def generator(f):
1123 def generator(f):
1124 zd = bz2.BZ2Decompressor()
1124 zd = bz2.BZ2Decompressor()
1125 zd.decompress("BZ")
1125 zd.decompress("BZ")
1126 for chunk in f:
1126 for chunk in f:
1127 yield zd.decompress(chunk)
1127 yield zd.decompress(chunk)
1128 elif header == "HG10UN":
1128 elif header == "HG10UN":
1129 def generator(f):
1129 def generator(f):
1130 for chunk in f:
1130 for chunk in f:
1131 yield chunk
1131 yield chunk
1132 else:
1132 else:
1133 req.write("0\n")
1133 req.write("0\n")
1134 req.write(_("unknown bundle compression type\n"))
1134 req.write(_("unknown bundle compression type\n"))
1135 return
1135 return
1136 gen = generator(util.filechunkiter(fp, 4096))
1136 gen = generator(util.filechunkiter(fp, 4096))
1137
1137
1138 # send addchangegroup output to client
1138 # send addchangegroup output to client
1139
1139
1140 old_stdout = sys.stdout
1140 old_stdout = sys.stdout
1141 sys.stdout = cStringIO.StringIO()
1141 sys.stdout = cStringIO.StringIO()
1142
1142
1143 try:
1143 try:
1144 url = 'remote:%s:%s' % (proto,
1144 url = 'remote:%s:%s' % (proto,
1145 req.env.get('REMOTE_HOST', ''))
1145 req.env.get('REMOTE_HOST', ''))
1146 ret = self.repo.addchangegroup(util.chunkbuffer(gen),
1146 ret = self.repo.addchangegroup(util.chunkbuffer(gen),
1147 'serve', url)
1147 'serve', url)
1148 finally:
1148 finally:
1149 val = sys.stdout.getvalue()
1149 val = sys.stdout.getvalue()
1150 sys.stdout = old_stdout
1150 sys.stdout = old_stdout
1151 req.write('%d\n' % ret)
1151 req.write('%d\n' % ret)
1152 req.write(val)
1152 req.write(val)
1153 finally:
1153 finally:
1154 lock.release()
1154 lock.release()
1155 finally:
1155 finally:
1156 fp.close()
1156 fp.close()
1157 os.unlink(tempname)
1157 os.unlink(tempname)
1158
1158
1159 def do_stream_out(self, req):
1159 def do_stream_out(self, req):
1160 req.httphdr("application/mercurial-0.1")
1160 req.httphdr("application/mercurial-0.1")
1161 streamclone.stream_out(self.repo, req)
1161 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now