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