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