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