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