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