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