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