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