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