##// END OF EJS Templates
hgweb: use the given revision in the name of the archive...
Michael Gebetsroither -
r4164:5c1e18bb default
parent child Browse files
Show More
@@ -1,1156 +1,1159
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, 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, id, 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 cnode = self.repo.lookup(id)
612 arch_version = id
613 if cnode == id:
614 arch_version = short(cnode)
615 name = "%s-%s" % (reponame, arch_version)
612 mimetype, artype, extension, encoding = self.archive_specs[type_]
616 mimetype, artype, extension, encoding = self.archive_specs[type_]
613 headers = [('Content-type', mimetype),
617 headers = [('Content-type', mimetype),
614 ('Content-disposition', 'attachment; filename=%s%s' %
618 ('Content-disposition', 'attachment; filename=%s%s' %
615 (name, extension))]
619 (name, extension))]
616 if encoding:
620 if encoding:
617 headers.append(('Content-encoding', encoding))
621 headers.append(('Content-encoding', encoding))
618 req.header(headers)
622 req.header(headers)
619 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
623 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
620
624
621 # add tags to things
625 # add tags to things
622 # tags -> list of changesets corresponding to tags
626 # tags -> list of changesets corresponding to tags
623 # find tag, changeset, file
627 # find tag, changeset, file
624
628
625 def cleanpath(self, path):
629 def cleanpath(self, path):
626 path = path.lstrip('/')
630 path = path.lstrip('/')
627 return util.canonpath(self.repo.root, '', path)
631 return util.canonpath(self.repo.root, '', path)
628
632
629 def run(self):
633 def run(self):
630 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
634 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.")
635 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
632 import mercurial.hgweb.wsgicgi as wsgicgi
636 import mercurial.hgweb.wsgicgi as wsgicgi
633 from request import wsgiapplication
637 from request import wsgiapplication
634 def make_web_app():
638 def make_web_app():
635 return self
639 return self
636 wsgicgi.launch(wsgiapplication(make_web_app))
640 wsgicgi.launch(wsgiapplication(make_web_app))
637
641
638 def run_wsgi(self, req):
642 def run_wsgi(self, req):
639 def header(**map):
643 def header(**map):
640 header_file = cStringIO.StringIO(
644 header_file = cStringIO.StringIO(
641 ''.join(self.t("header", encoding=util._encoding, **map)))
645 ''.join(self.t("header", encoding=util._encoding, **map)))
642 msg = mimetools.Message(header_file, 0)
646 msg = mimetools.Message(header_file, 0)
643 req.header(msg.items())
647 req.header(msg.items())
644 yield header_file.read()
648 yield header_file.read()
645
649
646 def rawfileheader(**map):
650 def rawfileheader(**map):
647 req.header([('Content-type', map['mimetype']),
651 req.header([('Content-type', map['mimetype']),
648 ('Content-disposition', 'filename=%s' % map['file']),
652 ('Content-disposition', 'filename=%s' % map['file']),
649 ('Content-length', str(len(map['raw'])))])
653 ('Content-length', str(len(map['raw'])))])
650 yield ''
654 yield ''
651
655
652 def footer(**map):
656 def footer(**map):
653 yield self.t("footer", **map)
657 yield self.t("footer", **map)
654
658
655 def motd(**map):
659 def motd(**map):
656 yield self.config("web", "motd", "")
660 yield self.config("web", "motd", "")
657
661
658 def expand_form(form):
662 def expand_form(form):
659 shortcuts = {
663 shortcuts = {
660 'cl': [('cmd', ['changelog']), ('rev', None)],
664 'cl': [('cmd', ['changelog']), ('rev', None)],
661 'sl': [('cmd', ['shortlog']), ('rev', None)],
665 'sl': [('cmd', ['shortlog']), ('rev', None)],
662 'cs': [('cmd', ['changeset']), ('node', None)],
666 'cs': [('cmd', ['changeset']), ('node', None)],
663 'f': [('cmd', ['file']), ('filenode', None)],
667 'f': [('cmd', ['file']), ('filenode', None)],
664 'fl': [('cmd', ['filelog']), ('filenode', None)],
668 'fl': [('cmd', ['filelog']), ('filenode', None)],
665 'fd': [('cmd', ['filediff']), ('node', None)],
669 'fd': [('cmd', ['filediff']), ('node', None)],
666 'fa': [('cmd', ['annotate']), ('filenode', None)],
670 'fa': [('cmd', ['annotate']), ('filenode', None)],
667 'mf': [('cmd', ['manifest']), ('manifest', None)],
671 'mf': [('cmd', ['manifest']), ('manifest', None)],
668 'ca': [('cmd', ['archive']), ('node', None)],
672 'ca': [('cmd', ['archive']), ('node', None)],
669 'tags': [('cmd', ['tags'])],
673 'tags': [('cmd', ['tags'])],
670 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
674 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
671 'static': [('cmd', ['static']), ('file', None)]
675 'static': [('cmd', ['static']), ('file', None)]
672 }
676 }
673
677
674 for k in shortcuts.iterkeys():
678 for k in shortcuts.iterkeys():
675 if form.has_key(k):
679 if form.has_key(k):
676 for name, value in shortcuts[k]:
680 for name, value in shortcuts[k]:
677 if value is None:
681 if value is None:
678 value = form[k]
682 value = form[k]
679 form[name] = value
683 form[name] = value
680 del form[k]
684 del form[k]
681
685
682 def rewrite_request(req):
686 def rewrite_request(req):
683 '''translate new web interface to traditional format'''
687 '''translate new web interface to traditional format'''
684
688
685 def spliturl(req):
689 def spliturl(req):
686 def firstitem(query):
690 def firstitem(query):
687 return query.split('&', 1)[0].split(';', 1)[0]
691 return query.split('&', 1)[0].split(';', 1)[0]
688
692
689 def normurl(url):
693 def normurl(url):
690 inner = '/'.join([x for x in url.split('/') if x])
694 inner = '/'.join([x for x in url.split('/') if x])
691 tl = len(url) > 1 and url.endswith('/') and '/' or ''
695 tl = len(url) > 1 and url.endswith('/') and '/' or ''
692
696
693 return '%s%s%s' % (url.startswith('/') and '/' or '',
697 return '%s%s%s' % (url.startswith('/') and '/' or '',
694 inner, tl)
698 inner, tl)
695
699
696 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
700 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
697 pi = normurl(req.env.get('PATH_INFO', ''))
701 pi = normurl(req.env.get('PATH_INFO', ''))
698 if pi:
702 if pi:
699 # strip leading /
703 # strip leading /
700 pi = pi[1:]
704 pi = pi[1:]
701 if pi:
705 if pi:
702 root = root[:-len(pi)]
706 root = root[:-len(pi)]
703 if req.env.has_key('REPO_NAME'):
707 if req.env.has_key('REPO_NAME'):
704 rn = req.env['REPO_NAME'] + '/'
708 rn = req.env['REPO_NAME'] + '/'
705 root += rn
709 root += rn
706 query = pi[len(rn):]
710 query = pi[len(rn):]
707 else:
711 else:
708 query = pi
712 query = pi
709 else:
713 else:
710 root += '?'
714 root += '?'
711 query = firstitem(req.env['QUERY_STRING'])
715 query = firstitem(req.env['QUERY_STRING'])
712
716
713 return (root, query)
717 return (root, query)
714
718
715 req.url, query = spliturl(req)
719 req.url, query = spliturl(req)
716
720
717 if req.form.has_key('cmd'):
721 if req.form.has_key('cmd'):
718 # old style
722 # old style
719 return
723 return
720
724
721 args = query.split('/', 2)
725 args = query.split('/', 2)
722 if not args or not args[0]:
726 if not args or not args[0]:
723 return
727 return
724
728
725 cmd = args.pop(0)
729 cmd = args.pop(0)
726 style = cmd.rfind('-')
730 style = cmd.rfind('-')
727 if style != -1:
731 if style != -1:
728 req.form['style'] = [cmd[:style]]
732 req.form['style'] = [cmd[:style]]
729 cmd = cmd[style+1:]
733 cmd = cmd[style+1:]
730 # avoid accepting e.g. style parameter as command
734 # avoid accepting e.g. style parameter as command
731 if hasattr(self, 'do_' + cmd):
735 if hasattr(self, 'do_' + cmd):
732 req.form['cmd'] = [cmd]
736 req.form['cmd'] = [cmd]
733
737
734 if args and args[0]:
738 if args and args[0]:
735 node = args.pop(0)
739 node = args.pop(0)
736 req.form['node'] = [node]
740 req.form['node'] = [node]
737 if args:
741 if args:
738 req.form['file'] = args
742 req.form['file'] = args
739
743
740 if cmd == 'static':
744 if cmd == 'static':
741 req.form['file'] = req.form['node']
745 req.form['file'] = req.form['node']
742 elif cmd == 'archive':
746 elif cmd == 'archive':
743 fn = req.form['node'][0]
747 fn = req.form['node'][0]
744 for type_, spec in self.archive_specs.iteritems():
748 for type_, spec in self.archive_specs.iteritems():
745 ext = spec[2]
749 ext = spec[2]
746 if fn.endswith(ext):
750 if fn.endswith(ext):
747 req.form['node'] = [fn[:-len(ext)]]
751 req.form['node'] = [fn[:-len(ext)]]
748 req.form['type'] = [type_]
752 req.form['type'] = [type_]
749
753
750 def sessionvars(**map):
754 def sessionvars(**map):
751 fields = []
755 fields = []
752 if req.form.has_key('style'):
756 if req.form.has_key('style'):
753 style = req.form['style'][0]
757 style = req.form['style'][0]
754 if style != self.config('web', 'style', ''):
758 if style != self.config('web', 'style', ''):
755 fields.append(('style', style))
759 fields.append(('style', style))
756
760
757 separator = req.url[-1] == '?' and ';' or '?'
761 separator = req.url[-1] == '?' and ';' or '?'
758 for name, value in fields:
762 for name, value in fields:
759 yield dict(name=name, value=value, separator=separator)
763 yield dict(name=name, value=value, separator=separator)
760 separator = ';'
764 separator = ';'
761
765
762 self.refresh()
766 self.refresh()
763
767
764 expand_form(req.form)
768 expand_form(req.form)
765 rewrite_request(req)
769 rewrite_request(req)
766
770
767 style = self.config("web", "style", "")
771 style = self.config("web", "style", "")
768 if req.form.has_key('style'):
772 if req.form.has_key('style'):
769 style = req.form['style'][0]
773 style = req.form['style'][0]
770 mapfile = style_map(self.templatepath, style)
774 mapfile = style_map(self.templatepath, style)
771
775
772 port = req.env["SERVER_PORT"]
776 port = req.env["SERVER_PORT"]
773 port = port != "80" and (":" + port) or ""
777 port = port != "80" and (":" + port) or ""
774 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
778 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
775 staticurl = self.config("web", "staticurl") or req.url + 'static/'
779 staticurl = self.config("web", "staticurl") or req.url + 'static/'
776 if not staticurl.endswith('/'):
780 if not staticurl.endswith('/'):
777 staticurl += '/'
781 staticurl += '/'
778
782
779 if not self.reponame:
783 if not self.reponame:
780 self.reponame = (self.config("web", "name")
784 self.reponame = (self.config("web", "name")
781 or req.env.get('REPO_NAME')
785 or req.env.get('REPO_NAME')
782 or req.url.strip('/') or self.repo.root)
786 or req.url.strip('/') or self.repo.root)
783
787
784 self.t = templater.templater(mapfile, templater.common_filters,
788 self.t = templater.templater(mapfile, templater.common_filters,
785 defaults={"url": req.url,
789 defaults={"url": req.url,
786 "staticurl": staticurl,
790 "staticurl": staticurl,
787 "urlbase": urlbase,
791 "urlbase": urlbase,
788 "repo": self.reponame,
792 "repo": self.reponame,
789 "header": header,
793 "header": header,
790 "footer": footer,
794 "footer": footer,
791 "motd": motd,
795 "motd": motd,
792 "rawfileheader": rawfileheader,
796 "rawfileheader": rawfileheader,
793 "sessionvars": sessionvars
797 "sessionvars": sessionvars
794 })
798 })
795
799
796 if not req.form.has_key('cmd'):
800 if not req.form.has_key('cmd'):
797 req.form['cmd'] = [self.t.cache['default']]
801 req.form['cmd'] = [self.t.cache['default']]
798
802
799 cmd = req.form['cmd'][0]
803 cmd = req.form['cmd'][0]
800
804
801 method = getattr(self, 'do_' + cmd, None)
805 method = getattr(self, 'do_' + cmd, None)
802 if method:
806 if method:
803 try:
807 try:
804 method(req)
808 method(req)
805 except (hg.RepoError, revlog.RevlogError), inst:
809 except (hg.RepoError, revlog.RevlogError), inst:
806 req.write(self.t("error", error=str(inst)))
810 req.write(self.t("error", error=str(inst)))
807 else:
811 else:
808 req.write(self.t("error", error='No such method: ' + cmd))
812 req.write(self.t("error", error='No such method: ' + cmd))
809
813
810 def changectx(self, req):
814 def changectx(self, req):
811 if req.form.has_key('node'):
815 if req.form.has_key('node'):
812 changeid = req.form['node'][0]
816 changeid = req.form['node'][0]
813 elif req.form.has_key('manifest'):
817 elif req.form.has_key('manifest'):
814 changeid = req.form['manifest'][0]
818 changeid = req.form['manifest'][0]
815 else:
819 else:
816 changeid = self.repo.changelog.count() - 1
820 changeid = self.repo.changelog.count() - 1
817
821
818 try:
822 try:
819 ctx = self.repo.changectx(changeid)
823 ctx = self.repo.changectx(changeid)
820 except hg.RepoError:
824 except hg.RepoError:
821 man = self.repo.manifest
825 man = self.repo.manifest
822 mn = man.lookup(changeid)
826 mn = man.lookup(changeid)
823 ctx = self.repo.changectx(man.linkrev(mn))
827 ctx = self.repo.changectx(man.linkrev(mn))
824
828
825 return ctx
829 return ctx
826
830
827 def filectx(self, req):
831 def filectx(self, req):
828 path = self.cleanpath(req.form['file'][0])
832 path = self.cleanpath(req.form['file'][0])
829 if req.form.has_key('node'):
833 if req.form.has_key('node'):
830 changeid = req.form['node'][0]
834 changeid = req.form['node'][0]
831 else:
835 else:
832 changeid = req.form['filenode'][0]
836 changeid = req.form['filenode'][0]
833 try:
837 try:
834 ctx = self.repo.changectx(changeid)
838 ctx = self.repo.changectx(changeid)
835 fctx = ctx.filectx(path)
839 fctx = ctx.filectx(path)
836 except hg.RepoError:
840 except hg.RepoError:
837 fctx = self.repo.filectx(path, fileid=changeid)
841 fctx = self.repo.filectx(path, fileid=changeid)
838
842
839 return fctx
843 return fctx
840
844
841 def stripes(self, parity):
845 def stripes(self, parity):
842 "make horizontal stripes for easier reading"
846 "make horizontal stripes for easier reading"
843 if self.stripecount:
847 if self.stripecount:
844 return (1 + parity / self.stripecount) & 1
848 return (1 + parity / self.stripecount) & 1
845 else:
849 else:
846 return 0
850 return 0
847
851
848 def do_log(self, req):
852 def do_log(self, req):
849 if req.form.has_key('file') and req.form['file'][0]:
853 if req.form.has_key('file') and req.form['file'][0]:
850 self.do_filelog(req)
854 self.do_filelog(req)
851 else:
855 else:
852 self.do_changelog(req)
856 self.do_changelog(req)
853
857
854 def do_rev(self, req):
858 def do_rev(self, req):
855 self.do_changeset(req)
859 self.do_changeset(req)
856
860
857 def do_file(self, req):
861 def do_file(self, req):
858 path = self.cleanpath(req.form.get('file', [''])[0])
862 path = self.cleanpath(req.form.get('file', [''])[0])
859 if path:
863 if path:
860 try:
864 try:
861 req.write(self.filerevision(self.filectx(req)))
865 req.write(self.filerevision(self.filectx(req)))
862 return
866 return
863 except revlog.LookupError:
867 except revlog.LookupError:
864 pass
868 pass
865
869
866 req.write(self.manifest(self.changectx(req), path))
870 req.write(self.manifest(self.changectx(req), path))
867
871
868 def do_diff(self, req):
872 def do_diff(self, req):
869 self.do_filediff(req)
873 self.do_filediff(req)
870
874
871 def do_changelog(self, req, shortlog = False):
875 def do_changelog(self, req, shortlog = False):
872 if req.form.has_key('node'):
876 if req.form.has_key('node'):
873 ctx = self.changectx(req)
877 ctx = self.changectx(req)
874 else:
878 else:
875 if req.form.has_key('rev'):
879 if req.form.has_key('rev'):
876 hi = req.form['rev'][0]
880 hi = req.form['rev'][0]
877 else:
881 else:
878 hi = self.repo.changelog.count() - 1
882 hi = self.repo.changelog.count() - 1
879 try:
883 try:
880 ctx = self.repo.changectx(hi)
884 ctx = self.repo.changectx(hi)
881 except hg.RepoError:
885 except hg.RepoError:
882 req.write(self.search(hi)) # XXX redirect to 404 page?
886 req.write(self.search(hi)) # XXX redirect to 404 page?
883 return
887 return
884
888
885 req.write(self.changelog(ctx, shortlog = shortlog))
889 req.write(self.changelog(ctx, shortlog = shortlog))
886
890
887 def do_shortlog(self, req):
891 def do_shortlog(self, req):
888 self.do_changelog(req, shortlog = True)
892 self.do_changelog(req, shortlog = True)
889
893
890 def do_changeset(self, req):
894 def do_changeset(self, req):
891 req.write(self.changeset(self.changectx(req)))
895 req.write(self.changeset(self.changectx(req)))
892
896
893 def do_manifest(self, req):
897 def do_manifest(self, req):
894 req.write(self.manifest(self.changectx(req),
898 req.write(self.manifest(self.changectx(req),
895 self.cleanpath(req.form['path'][0])))
899 self.cleanpath(req.form['path'][0])))
896
900
897 def do_tags(self, req):
901 def do_tags(self, req):
898 req.write(self.tags())
902 req.write(self.tags())
899
903
900 def do_summary(self, req):
904 def do_summary(self, req):
901 req.write(self.summary())
905 req.write(self.summary())
902
906
903 def do_filediff(self, req):
907 def do_filediff(self, req):
904 req.write(self.filediff(self.filectx(req)))
908 req.write(self.filediff(self.filectx(req)))
905
909
906 def do_annotate(self, req):
910 def do_annotate(self, req):
907 req.write(self.fileannotate(self.filectx(req)))
911 req.write(self.fileannotate(self.filectx(req)))
908
912
909 def do_filelog(self, req):
913 def do_filelog(self, req):
910 req.write(self.filelog(self.filectx(req)))
914 req.write(self.filelog(self.filectx(req)))
911
915
912 def do_lookup(self, req):
916 def do_lookup(self, req):
913 try:
917 try:
914 r = hex(self.repo.lookup(req.form['key'][0]))
918 r = hex(self.repo.lookup(req.form['key'][0]))
915 success = 1
919 success = 1
916 except Exception,inst:
920 except Exception,inst:
917 r = str(inst)
921 r = str(inst)
918 success = 0
922 success = 0
919 resp = "%s %s\n" % (success, r)
923 resp = "%s %s\n" % (success, r)
920 req.httphdr("application/mercurial-0.1", length=len(resp))
924 req.httphdr("application/mercurial-0.1", length=len(resp))
921 req.write(resp)
925 req.write(resp)
922
926
923 def do_heads(self, req):
927 def do_heads(self, req):
924 resp = " ".join(map(hex, self.repo.heads())) + "\n"
928 resp = " ".join(map(hex, self.repo.heads())) + "\n"
925 req.httphdr("application/mercurial-0.1", length=len(resp))
929 req.httphdr("application/mercurial-0.1", length=len(resp))
926 req.write(resp)
930 req.write(resp)
927
931
928 def do_branches(self, req):
932 def do_branches(self, req):
929 nodes = []
933 nodes = []
930 if req.form.has_key('nodes'):
934 if req.form.has_key('nodes'):
931 nodes = map(bin, req.form['nodes'][0].split(" "))
935 nodes = map(bin, req.form['nodes'][0].split(" "))
932 resp = cStringIO.StringIO()
936 resp = cStringIO.StringIO()
933 for b in self.repo.branches(nodes):
937 for b in self.repo.branches(nodes):
934 resp.write(" ".join(map(hex, b)) + "\n")
938 resp.write(" ".join(map(hex, b)) + "\n")
935 resp = resp.getvalue()
939 resp = resp.getvalue()
936 req.httphdr("application/mercurial-0.1", length=len(resp))
940 req.httphdr("application/mercurial-0.1", length=len(resp))
937 req.write(resp)
941 req.write(resp)
938
942
939 def do_between(self, req):
943 def do_between(self, req):
940 if req.form.has_key('pairs'):
944 if req.form.has_key('pairs'):
941 pairs = [map(bin, p.split("-"))
945 pairs = [map(bin, p.split("-"))
942 for p in req.form['pairs'][0].split(" ")]
946 for p in req.form['pairs'][0].split(" ")]
943 resp = cStringIO.StringIO()
947 resp = cStringIO.StringIO()
944 for b in self.repo.between(pairs):
948 for b in self.repo.between(pairs):
945 resp.write(" ".join(map(hex, b)) + "\n")
949 resp.write(" ".join(map(hex, b)) + "\n")
946 resp = resp.getvalue()
950 resp = resp.getvalue()
947 req.httphdr("application/mercurial-0.1", length=len(resp))
951 req.httphdr("application/mercurial-0.1", length=len(resp))
948 req.write(resp)
952 req.write(resp)
949
953
950 def do_changegroup(self, req):
954 def do_changegroup(self, req):
951 req.httphdr("application/mercurial-0.1")
955 req.httphdr("application/mercurial-0.1")
952 nodes = []
956 nodes = []
953 if not self.allowpull:
957 if not self.allowpull:
954 return
958 return
955
959
956 if req.form.has_key('roots'):
960 if req.form.has_key('roots'):
957 nodes = map(bin, req.form['roots'][0].split(" "))
961 nodes = map(bin, req.form['roots'][0].split(" "))
958
962
959 z = zlib.compressobj()
963 z = zlib.compressobj()
960 f = self.repo.changegroup(nodes, 'serve')
964 f = self.repo.changegroup(nodes, 'serve')
961 while 1:
965 while 1:
962 chunk = f.read(4096)
966 chunk = f.read(4096)
963 if not chunk:
967 if not chunk:
964 break
968 break
965 req.write(z.compress(chunk))
969 req.write(z.compress(chunk))
966
970
967 req.write(z.flush())
971 req.write(z.flush())
968
972
969 def do_changegroupsubset(self, req):
973 def do_changegroupsubset(self, req):
970 req.httphdr("application/mercurial-0.1")
974 req.httphdr("application/mercurial-0.1")
971 bases = []
975 bases = []
972 heads = []
976 heads = []
973 if not self.allowpull:
977 if not self.allowpull:
974 return
978 return
975
979
976 if req.form.has_key('bases'):
980 if req.form.has_key('bases'):
977 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
981 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
978 if req.form.has_key('heads'):
982 if req.form.has_key('heads'):
979 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
983 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
980
984
981 z = zlib.compressobj()
985 z = zlib.compressobj()
982 f = self.repo.changegroupsubset(bases, heads, 'serve')
986 f = self.repo.changegroupsubset(bases, heads, 'serve')
983 while 1:
987 while 1:
984 chunk = f.read(4096)
988 chunk = f.read(4096)
985 if not chunk:
989 if not chunk:
986 break
990 break
987 req.write(z.compress(chunk))
991 req.write(z.compress(chunk))
988
992
989 req.write(z.flush())
993 req.write(z.flush())
990
994
991 def do_archive(self, req):
995 def do_archive(self, req):
992 changeset = self.repo.lookup(req.form['node'][0])
993 type_ = req.form['type'][0]
996 type_ = req.form['type'][0]
994 allowed = self.configlist("web", "allow_archive")
997 allowed = self.configlist("web", "allow_archive")
995 if (type_ in self.archives and (type_ in allowed or
998 if (type_ in self.archives and (type_ in allowed or
996 self.configbool("web", "allow" + type_, False))):
999 self.configbool("web", "allow" + type_, False))):
997 self.archive(req, changeset, type_)
1000 self.archive(req, req.form['node'][0], type_)
998 return
1001 return
999
1002
1000 req.write(self.t("error"))
1003 req.write(self.t("error"))
1001
1004
1002 def do_static(self, req):
1005 def do_static(self, req):
1003 fname = req.form['file'][0]
1006 fname = req.form['file'][0]
1004 # a repo owner may set web.static in .hg/hgrc to get any file
1007 # a repo owner may set web.static in .hg/hgrc to get any file
1005 # readable by the user running the CGI script
1008 # readable by the user running the CGI script
1006 static = self.config("web", "static",
1009 static = self.config("web", "static",
1007 os.path.join(self.templatepath, "static"),
1010 os.path.join(self.templatepath, "static"),
1008 untrusted=False)
1011 untrusted=False)
1009 req.write(staticfile(static, fname, req)
1012 req.write(staticfile(static, fname, req)
1010 or self.t("error", error="%r not found" % fname))
1013 or self.t("error", error="%r not found" % fname))
1011
1014
1012 def do_capabilities(self, req):
1015 def do_capabilities(self, req):
1013 caps = ['lookup', 'changegroupsubset']
1016 caps = ['lookup', 'changegroupsubset']
1014 if self.configbool('server', 'uncompressed'):
1017 if self.configbool('server', 'uncompressed'):
1015 caps.append('stream=%d' % self.repo.revlogversion)
1018 caps.append('stream=%d' % self.repo.revlogversion)
1016 # XXX: make configurable and/or share code with do_unbundle:
1019 # XXX: make configurable and/or share code with do_unbundle:
1017 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1020 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1018 if unbundleversions:
1021 if unbundleversions:
1019 caps.append('unbundle=%s' % ','.join(unbundleversions))
1022 caps.append('unbundle=%s' % ','.join(unbundleversions))
1020 resp = ' '.join(caps)
1023 resp = ' '.join(caps)
1021 req.httphdr("application/mercurial-0.1", length=len(resp))
1024 req.httphdr("application/mercurial-0.1", length=len(resp))
1022 req.write(resp)
1025 req.write(resp)
1023
1026
1024 def check_perm(self, req, op, default):
1027 def check_perm(self, req, op, default):
1025 '''check permission for operation based on user auth.
1028 '''check permission for operation based on user auth.
1026 return true if op allowed, else false.
1029 return true if op allowed, else false.
1027 default is policy to use if no config given.'''
1030 default is policy to use if no config given.'''
1028
1031
1029 user = req.env.get('REMOTE_USER')
1032 user = req.env.get('REMOTE_USER')
1030
1033
1031 deny = self.configlist('web', 'deny_' + op)
1034 deny = self.configlist('web', 'deny_' + op)
1032 if deny and (not user or deny == ['*'] or user in deny):
1035 if deny and (not user or deny == ['*'] or user in deny):
1033 return False
1036 return False
1034
1037
1035 allow = self.configlist('web', 'allow_' + op)
1038 allow = self.configlist('web', 'allow_' + op)
1036 return (allow and (allow == ['*'] or user in allow)) or default
1039 return (allow and (allow == ['*'] or user in allow)) or default
1037
1040
1038 def do_unbundle(self, req):
1041 def do_unbundle(self, req):
1039 def bail(response, headers={}):
1042 def bail(response, headers={}):
1040 length = int(req.env['CONTENT_LENGTH'])
1043 length = int(req.env['CONTENT_LENGTH'])
1041 for s in util.filechunkiter(req, limit=length):
1044 for s in util.filechunkiter(req, limit=length):
1042 # drain incoming bundle, else client will not see
1045 # drain incoming bundle, else client will not see
1043 # response when run outside cgi script
1046 # response when run outside cgi script
1044 pass
1047 pass
1045 req.httphdr("application/mercurial-0.1", headers=headers)
1048 req.httphdr("application/mercurial-0.1", headers=headers)
1046 req.write('0\n')
1049 req.write('0\n')
1047 req.write(response)
1050 req.write(response)
1048
1051
1049 # require ssl by default, auth info cannot be sniffed and
1052 # require ssl by default, auth info cannot be sniffed and
1050 # replayed
1053 # replayed
1051 ssl_req = self.configbool('web', 'push_ssl', True)
1054 ssl_req = self.configbool('web', 'push_ssl', True)
1052 if ssl_req:
1055 if ssl_req:
1053 if not req.env.get('HTTPS'):
1056 if not req.env.get('HTTPS'):
1054 bail(_('ssl required\n'))
1057 bail(_('ssl required\n'))
1055 return
1058 return
1056 proto = 'https'
1059 proto = 'https'
1057 else:
1060 else:
1058 proto = 'http'
1061 proto = 'http'
1059
1062
1060 # do not allow push unless explicitly allowed
1063 # do not allow push unless explicitly allowed
1061 if not self.check_perm(req, 'push', False):
1064 if not self.check_perm(req, 'push', False):
1062 bail(_('push not authorized\n'),
1065 bail(_('push not authorized\n'),
1063 headers={'status': '401 Unauthorized'})
1066 headers={'status': '401 Unauthorized'})
1064 return
1067 return
1065
1068
1066 req.httphdr("application/mercurial-0.1")
1069 req.httphdr("application/mercurial-0.1")
1067
1070
1068 their_heads = req.form['heads'][0].split(' ')
1071 their_heads = req.form['heads'][0].split(' ')
1069
1072
1070 def check_heads():
1073 def check_heads():
1071 heads = map(hex, self.repo.heads())
1074 heads = map(hex, self.repo.heads())
1072 return their_heads == [hex('force')] or their_heads == heads
1075 return their_heads == [hex('force')] or their_heads == heads
1073
1076
1074 # fail early if possible
1077 # fail early if possible
1075 if not check_heads():
1078 if not check_heads():
1076 bail(_('unsynced changes\n'))
1079 bail(_('unsynced changes\n'))
1077 return
1080 return
1078
1081
1079 # do not lock repo until all changegroup data is
1082 # do not lock repo until all changegroup data is
1080 # streamed. save to temporary file.
1083 # streamed. save to temporary file.
1081
1084
1082 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1085 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1083 fp = os.fdopen(fd, 'wb+')
1086 fp = os.fdopen(fd, 'wb+')
1084 try:
1087 try:
1085 length = int(req.env['CONTENT_LENGTH'])
1088 length = int(req.env['CONTENT_LENGTH'])
1086 for s in util.filechunkiter(req, limit=length):
1089 for s in util.filechunkiter(req, limit=length):
1087 fp.write(s)
1090 fp.write(s)
1088
1091
1089 lock = self.repo.lock()
1092 lock = self.repo.lock()
1090 try:
1093 try:
1091 if not check_heads():
1094 if not check_heads():
1092 req.write('0\n')
1095 req.write('0\n')
1093 req.write(_('unsynced changes\n'))
1096 req.write(_('unsynced changes\n'))
1094 return
1097 return
1095
1098
1096 fp.seek(0)
1099 fp.seek(0)
1097 header = fp.read(6)
1100 header = fp.read(6)
1098 if not header.startswith("HG"):
1101 if not header.startswith("HG"):
1099 # old client with uncompressed bundle
1102 # old client with uncompressed bundle
1100 def generator(f):
1103 def generator(f):
1101 yield header
1104 yield header
1102 for chunk in f:
1105 for chunk in f:
1103 yield chunk
1106 yield chunk
1104 elif not header.startswith("HG10"):
1107 elif not header.startswith("HG10"):
1105 req.write("0\n")
1108 req.write("0\n")
1106 req.write(_("unknown bundle version\n"))
1109 req.write(_("unknown bundle version\n"))
1107 return
1110 return
1108 elif header == "HG10GZ":
1111 elif header == "HG10GZ":
1109 def generator(f):
1112 def generator(f):
1110 zd = zlib.decompressobj()
1113 zd = zlib.decompressobj()
1111 for chunk in f:
1114 for chunk in f:
1112 yield zd.decompress(chunk)
1115 yield zd.decompress(chunk)
1113 elif header == "HG10BZ":
1116 elif header == "HG10BZ":
1114 def generator(f):
1117 def generator(f):
1115 zd = bz2.BZ2Decompressor()
1118 zd = bz2.BZ2Decompressor()
1116 zd.decompress("BZ")
1119 zd.decompress("BZ")
1117 for chunk in f:
1120 for chunk in f:
1118 yield zd.decompress(chunk)
1121 yield zd.decompress(chunk)
1119 elif header == "HG10UN":
1122 elif header == "HG10UN":
1120 def generator(f):
1123 def generator(f):
1121 for chunk in f:
1124 for chunk in f:
1122 yield chunk
1125 yield chunk
1123 else:
1126 else:
1124 req.write("0\n")
1127 req.write("0\n")
1125 req.write(_("unknown bundle compression type\n"))
1128 req.write(_("unknown bundle compression type\n"))
1126 return
1129 return
1127 gen = generator(util.filechunkiter(fp, 4096))
1130 gen = generator(util.filechunkiter(fp, 4096))
1128
1131
1129 # send addchangegroup output to client
1132 # send addchangegroup output to client
1130
1133
1131 old_stdout = sys.stdout
1134 old_stdout = sys.stdout
1132 sys.stdout = cStringIO.StringIO()
1135 sys.stdout = cStringIO.StringIO()
1133
1136
1134 try:
1137 try:
1135 url = 'remote:%s:%s' % (proto,
1138 url = 'remote:%s:%s' % (proto,
1136 req.env.get('REMOTE_HOST', ''))
1139 req.env.get('REMOTE_HOST', ''))
1137 try:
1140 try:
1138 ret = self.repo.addchangegroup(util.chunkbuffer(gen),
1141 ret = self.repo.addchangegroup(util.chunkbuffer(gen),
1139 'serve', url)
1142 'serve', url)
1140 except util.Abort, inst:
1143 except util.Abort, inst:
1141 sys.stdout.write("abort: %s\n" % inst)
1144 sys.stdout.write("abort: %s\n" % inst)
1142 ret = 0
1145 ret = 0
1143 finally:
1146 finally:
1144 val = sys.stdout.getvalue()
1147 val = sys.stdout.getvalue()
1145 sys.stdout = old_stdout
1148 sys.stdout = old_stdout
1146 req.write('%d\n' % ret)
1149 req.write('%d\n' % ret)
1147 req.write(val)
1150 req.write(val)
1148 finally:
1151 finally:
1149 lock.release()
1152 lock.release()
1150 finally:
1153 finally:
1151 fp.close()
1154 fp.close()
1152 os.unlink(tempname)
1155 os.unlink(tempname)
1153
1156
1154 def do_stream_out(self, req):
1157 def do_stream_out(self, req):
1155 req.httphdr("application/mercurial-0.1")
1158 req.httphdr("application/mercurial-0.1")
1156 streamclone.stream_out(self.repo, req)
1159 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now