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