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