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