##// END OF EJS Templates
hgweb: globally default to tip if no revision is specified
Brendan Cully -
r3333:8ec80c1b default
parent child Browse files
Show More
@@ -1,1042 +1,1044 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:templater")
16 demandload(globals(), "mercurial: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 = (0, 0), # FIXME
541 lastchange = (0, 0), # FIXME
542 tags = tagentries,
542 tags = tagentries,
543 shortlog = changelist,
543 shortlog = changelist,
544 node = hex(self.repo.changelog.tip()),
544 node = hex(self.repo.changelog.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 p = util.normpath(path)
588 if p[:2] == "..":
588 if p[:2] == "..":
589 raise Exception("suspicious path")
589 raise Exception("suspicious path")
590 return p
590 return p
591
591
592 def run(self):
592 def run(self):
593 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
593 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.")
594 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
595 import mercurial.hgweb.wsgicgi as wsgicgi
595 import mercurial.hgweb.wsgicgi as wsgicgi
596 from request import wsgiapplication
596 from request import wsgiapplication
597 def make_web_app():
597 def make_web_app():
598 return self
598 return self
599 wsgicgi.launch(wsgiapplication(make_web_app))
599 wsgicgi.launch(wsgiapplication(make_web_app))
600
600
601 def run_wsgi(self, req):
601 def run_wsgi(self, req):
602 def header(**map):
602 def header(**map):
603 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
603 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
604 msg = mimetools.Message(header_file, 0)
604 msg = mimetools.Message(header_file, 0)
605 req.header(msg.items())
605 req.header(msg.items())
606 yield header_file.read()
606 yield header_file.read()
607
607
608 def rawfileheader(**map):
608 def rawfileheader(**map):
609 req.header([('Content-type', map['mimetype']),
609 req.header([('Content-type', map['mimetype']),
610 ('Content-disposition', 'filename=%s' % map['file']),
610 ('Content-disposition', 'filename=%s' % map['file']),
611 ('Content-length', str(len(map['raw'])))])
611 ('Content-length', str(len(map['raw'])))])
612 yield ''
612 yield ''
613
613
614 def footer(**map):
614 def footer(**map):
615 yield self.t("footer",
615 yield self.t("footer",
616 motd=self.repo.ui.config("web", "motd", ""),
616 motd=self.repo.ui.config("web", "motd", ""),
617 **map)
617 **map)
618
618
619 def expand_form(form):
619 def expand_form(form):
620 shortcuts = {
620 shortcuts = {
621 'cl': [('cmd', ['changelog']), ('rev', None)],
621 'cl': [('cmd', ['changelog']), ('rev', None)],
622 'sl': [('cmd', ['shortlog']), ('rev', None)],
622 'sl': [('cmd', ['shortlog']), ('rev', None)],
623 'cs': [('cmd', ['changeset']), ('node', None)],
623 'cs': [('cmd', ['changeset']), ('node', None)],
624 'f': [('cmd', ['file']), ('filenode', None)],
624 'f': [('cmd', ['file']), ('filenode', None)],
625 'fl': [('cmd', ['filelog']), ('filenode', None)],
625 'fl': [('cmd', ['filelog']), ('filenode', None)],
626 'fd': [('cmd', ['filediff']), ('node', None)],
626 'fd': [('cmd', ['filediff']), ('node', None)],
627 'fa': [('cmd', ['annotate']), ('filenode', None)],
627 'fa': [('cmd', ['annotate']), ('filenode', None)],
628 'mf': [('cmd', ['manifest']), ('manifest', None)],
628 'mf': [('cmd', ['manifest']), ('manifest', None)],
629 'ca': [('cmd', ['archive']), ('node', None)],
629 'ca': [('cmd', ['archive']), ('node', None)],
630 'tags': [('cmd', ['tags'])],
630 'tags': [('cmd', ['tags'])],
631 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
631 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
632 'static': [('cmd', ['static']), ('file', None)]
632 'static': [('cmd', ['static']), ('file', None)]
633 }
633 }
634
634
635 for k in shortcuts.iterkeys():
635 for k in shortcuts.iterkeys():
636 if form.has_key(k):
636 if form.has_key(k):
637 for name, value in shortcuts[k]:
637 for name, value in shortcuts[k]:
638 if value is None:
638 if value is None:
639 value = form[k]
639 value = form[k]
640 form[name] = value
640 form[name] = value
641 del form[k]
641 del form[k]
642
642
643 def rewrite_request(req):
643 def rewrite_request(req):
644 '''translate new web interface to traditional format'''
644 '''translate new web interface to traditional format'''
645
645
646 def spliturl(req):
646 def spliturl(req):
647 def firstitem(query):
647 def firstitem(query):
648 return query.split('&', 1)[0].split(';', 1)[0]
648 return query.split('&', 1)[0].split(';', 1)[0]
649
649
650 def normurl(url):
650 def normurl(url):
651 inner = '/'.join([x for x in url.split('/') if x])
651 inner = '/'.join([x for x in url.split('/') if x])
652 tl = len(url) > 1 and url.endswith('/') and '/' or ''
652 tl = len(url) > 1 and url.endswith('/') and '/' or ''
653
653
654 return '%s%s%s' % (url.startswith('/') and '/' or '',
654 return '%s%s%s' % (url.startswith('/') and '/' or '',
655 inner, tl)
655 inner, tl)
656
656
657 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
657 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
658 pi = normurl(req.env.get('PATH_INFO', ''))
658 pi = normurl(req.env.get('PATH_INFO', ''))
659 if pi:
659 if pi:
660 # strip leading /
660 # strip leading /
661 pi = pi[1:]
661 pi = pi[1:]
662 if pi:
662 if pi:
663 root = root[:-len(pi)]
663 root = root[:-len(pi)]
664 if req.env.has_key('REPO_NAME'):
664 if req.env.has_key('REPO_NAME'):
665 rn = req.env['REPO_NAME'] + '/'
665 rn = req.env['REPO_NAME'] + '/'
666 root += rn
666 root += rn
667 query = pi[len(rn):]
667 query = pi[len(rn):]
668 else:
668 else:
669 query = pi
669 query = pi
670 else:
670 else:
671 root += '?'
671 root += '?'
672 query = firstitem(req.env['QUERY_STRING'])
672 query = firstitem(req.env['QUERY_STRING'])
673
673
674 return (root, query)
674 return (root, query)
675
675
676 req.url, query = spliturl(req)
676 req.url, query = spliturl(req)
677
677
678 if req.form.has_key('cmd'):
678 if req.form.has_key('cmd'):
679 # old style
679 # old style
680 return
680 return
681
681
682 args = query.split('/', 2)
682 args = query.split('/', 2)
683 if not args or not args[0]:
683 if not args or not args[0]:
684 return
684 return
685
685
686 cmd = args.pop(0)
686 cmd = args.pop(0)
687 style = cmd.rfind('-')
687 style = cmd.rfind('-')
688 if style != -1:
688 if style != -1:
689 req.form['style'] = [cmd[:style]]
689 req.form['style'] = [cmd[:style]]
690 cmd = cmd[style+1:]
690 cmd = cmd[style+1:]
691 # avoid accepting e.g. style parameter as command
691 # avoid accepting e.g. style parameter as command
692 if hasattr(self, 'do_' + cmd):
692 if hasattr(self, 'do_' + cmd):
693 req.form['cmd'] = [cmd]
693 req.form['cmd'] = [cmd]
694
694
695 if args and args[0]:
695 if args and args[0]:
696 node = args.pop(0)
696 node = args.pop(0)
697 req.form['node'] = [node]
697 req.form['node'] = [node]
698 if args:
698 if args:
699 req.form['file'] = args
699 req.form['file'] = args
700
700
701 if cmd == 'static':
701 if cmd == 'static':
702 req.form['file'] = req.form['node']
702 req.form['file'] = req.form['node']
703 elif cmd == 'archive':
703 elif cmd == 'archive':
704 fn = req.form['node'][0]
704 fn = req.form['node'][0]
705 for type_, spec in self.archive_specs.iteritems():
705 for type_, spec in self.archive_specs.iteritems():
706 ext = spec[2]
706 ext = spec[2]
707 if fn.endswith(ext):
707 if fn.endswith(ext):
708 req.form['node'] = [fn[:-len(ext)]]
708 req.form['node'] = [fn[:-len(ext)]]
709 req.form['type'] = [type_]
709 req.form['type'] = [type_]
710
710
711 def queryprefix(**map):
711 def queryprefix(**map):
712 return req.url[-1] == '?' and ';' or '?'
712 return req.url[-1] == '?' and ';' or '?'
713
713
714 def getentries(**map):
714 def getentries(**map):
715 fields = {}
715 fields = {}
716 if req.form.has_key('style'):
716 if req.form.has_key('style'):
717 style = req.form['style'][0]
717 style = req.form['style'][0]
718 if style != self.repo.ui.config('web', 'style', ''):
718 if style != self.repo.ui.config('web', 'style', ''):
719 fields['style'] = style
719 fields['style'] = style
720
720
721 if fields:
721 if fields:
722 fields = ['%s=%s' % (k, urllib.quote(v))
722 fields = ['%s=%s' % (k, urllib.quote(v))
723 for k, v in fields.iteritems()]
723 for k, v in fields.iteritems()]
724 yield '%s%s' % (queryprefix(), ';'.join(fields))
724 yield '%s%s' % (queryprefix(), ';'.join(fields))
725 else:
725 else:
726 yield ''
726 yield ''
727
727
728 self.refresh()
728 self.refresh()
729
729
730 expand_form(req.form)
730 expand_form(req.form)
731 rewrite_request(req)
731 rewrite_request(req)
732
732
733 style = self.repo.ui.config("web", "style", "")
733 style = self.repo.ui.config("web", "style", "")
734 if req.form.has_key('style'):
734 if req.form.has_key('style'):
735 style = req.form['style'][0]
735 style = req.form['style'][0]
736 mapfile = style_map(self.templatepath, style)
736 mapfile = style_map(self.templatepath, style)
737
737
738 if not req.url:
738 if not req.url:
739 port = req.env["SERVER_PORT"]
739 port = req.env["SERVER_PORT"]
740 port = port != "80" and (":" + port) or ""
740 port = port != "80" and (":" + port) or ""
741 uri = req.env["REQUEST_URI"]
741 uri = req.env["REQUEST_URI"]
742 if "?" in uri:
742 if "?" in uri:
743 uri = uri.split("?")[0]
743 uri = uri.split("?")[0]
744 req.url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
744 req.url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
745
745
746 if not self.reponame:
746 if not self.reponame:
747 self.reponame = (self.repo.ui.config("web", "name")
747 self.reponame = (self.repo.ui.config("web", "name")
748 or req.env.get('REPO_NAME')
748 or req.env.get('REPO_NAME')
749 or req.url.strip('/') or self.repo.root)
749 or req.url.strip('/') or self.repo.root)
750
750
751 self.t = templater.templater(mapfile, templater.common_filters,
751 self.t = templater.templater(mapfile, templater.common_filters,
752 defaults={"url": req.url,
752 defaults={"url": req.url,
753 "repo": self.reponame,
753 "repo": self.reponame,
754 "header": header,
754 "header": header,
755 "footer": footer,
755 "footer": footer,
756 "rawfileheader": rawfileheader,
756 "rawfileheader": rawfileheader,
757 "queryprefix": queryprefix,
757 "queryprefix": queryprefix,
758 "getentries": getentries
758 "getentries": getentries
759 })
759 })
760
760
761 if not req.form.has_key('cmd'):
761 if not req.form.has_key('cmd'):
762 req.form['cmd'] = [self.t.cache['default'],]
762 req.form['cmd'] = [self.t.cache['default'],]
763
763
764 cmd = req.form['cmd'][0]
764 cmd = req.form['cmd'][0]
765
765
766 method = getattr(self, 'do_' + cmd, None)
766 method = getattr(self, 'do_' + cmd, None)
767 if method:
767 if method:
768 method(req)
768 method(req)
769 else:
769 else:
770 req.write(self.t("error"))
770 req.write(self.t("error"))
771
771
772 def changectx(self, req):
772 def changectx(self, req):
773 if req.form.has_key('node'):
773 if req.form.has_key('node'):
774 changeid = req.form['node'][0]
774 changeid = req.form['node'][0]
775 elif req.form.has_key('manifest'):
776 changeid = req.form['manifest'][0]
775 else:
777 else:
776 changeid = req.form['manifest'][0]
778 changeid = self.repo.changelog.count() - 1
779
777 try:
780 try:
778 ctx = self.repo.changectx(changeid)
781 ctx = self.repo.changectx(changeid)
779 except hg.RepoError:
782 except hg.RepoError:
780 man = self.repo.manifest
783 man = self.repo.manifest
781 mn = man.lookup(changeid)
784 mn = man.lookup(changeid)
782 ctx = self.repo.changectx(man.linkrev(mn))
785 ctx = self.repo.changectx(man.linkrev(mn))
783
786
784 return ctx
787 return ctx
785
788
786 def filectx(self, req):
789 def filectx(self, req):
787 path = self.cleanpath(req.form['file'][0])
790 path = self.cleanpath(req.form['file'][0])
788 if req.form.has_key('node'):
791 if req.form.has_key('node'):
789 changeid = req.form['node'][0]
792 changeid = req.form['node'][0]
790 else:
793 else:
791 changeid = req.form['filenode'][0]
794 changeid = req.form['filenode'][0]
792 try:
795 try:
793 ctx = self.repo.changectx(changeid)
796 ctx = self.repo.changectx(changeid)
794 fctx = ctx.filectx(path)
797 fctx = ctx.filectx(path)
795 except hg.RepoError:
798 except hg.RepoError:
796 fctx = self.repo.filectx(path, fileid=changeid)
799 fctx = self.repo.filectx(path, fileid=changeid)
797
800
798 return fctx
801 return fctx
799
802
800 def stripes(self, parity):
803 def stripes(self, parity):
801 "make horizontal stripes for easier reading"
804 "make horizontal stripes for easier reading"
802 if self.stripecount:
805 if self.stripecount:
803 return (1 + parity / self.stripecount) & 1
806 return (1 + parity / self.stripecount) & 1
804 else:
807 else:
805 return 0
808 return 0
806
809
807 def do_log(self, req):
810 def do_log(self, req):
808 if req.form.has_key('file') and req.form['file'][0]:
811 if req.form.has_key('file') and req.form['file'][0]:
809 self.do_filelog(req)
812 self.do_filelog(req)
810 else:
813 else:
811 self.do_changelog(req)
814 self.do_changelog(req)
812
815
813 def do_rev(self, req):
816 def do_rev(self, req):
814 self.do_changeset(req)
817 self.do_changeset(req)
815
818
816 def do_file(self, req):
819 def do_file(self, req):
817 path = req.form.get('file', [''])[0]
820 path = req.form.get('file', [''])[0]
818 if path:
821 if path:
819 try:
822 try:
820 req.write(self.filerevision(self.filectx(req)))
823 req.write(self.filerevision(self.filectx(req)))
821 return
824 return
822 except hg.RepoError:
825 except hg.RepoError:
823 pass
826 pass
824 path = self.cleanpath(path)
827 path = self.cleanpath(path)
825
828
826 req.write(self.manifest(self.changectx(req), '/' + path))
829 req.write(self.manifest(self.changectx(req), '/' + path))
827
830
828 def do_diff(self, req):
831 def do_diff(self, req):
829 self.do_filediff(req)
832 self.do_filediff(req)
830
833
831 def do_changelog(self, req, shortlog = False):
834 def do_changelog(self, req, shortlog = False):
832 if req.form.has_key('node'):
835 if req.form.has_key('node'):
833 ctx = self.changectx(req)
836 ctx = self.changectx(req)
834 else:
837 else:
835 if req.form.has_key('rev'):
838 if req.form.has_key('rev'):
836 hi = req.form['rev'][0]
839 hi = req.form['rev'][0]
837 else:
840 else:
838 hi = self.repo.changelog.count() - 1
841 hi = self.repo.changelog.count() - 1
839 try:
842 try:
840 ctx = self.repo.changectx(hi)
843 ctx = self.repo.changectx(hi)
841 except hg.RepoError:
844 except hg.RepoError:
842 req.write(self.search(hi)) # XXX redirect to 404 page?
845 req.write(self.search(hi)) # XXX redirect to 404 page?
843 return
846 return
844
847
845 req.write(self.changelog(ctx, shortlog = shortlog))
848 req.write(self.changelog(ctx, shortlog = shortlog))
846
849
847 def do_shortlog(self, req):
850 def do_shortlog(self, req):
848 self.do_changelog(req, shortlog = True)
851 self.do_changelog(req, shortlog = True)
849
852
850 def do_changeset(self, req):
853 def do_changeset(self, req):
851 ctx = self.repo.changectx(req.form['node'][0])
854 req.write(self.changeset(self.changectx(req)))
852 req.write(self.changeset(ctx))
853
855
854 def do_manifest(self, req):
856 def do_manifest(self, req):
855 req.write(self.manifest(self.changectx(req),
857 req.write(self.manifest(self.changectx(req),
856 self.cleanpath(req.form['path'][0])))
858 self.cleanpath(req.form['path'][0])))
857
859
858 def do_tags(self, req):
860 def do_tags(self, req):
859 req.write(self.tags())
861 req.write(self.tags())
860
862
861 def do_summary(self, req):
863 def do_summary(self, req):
862 req.write(self.summary())
864 req.write(self.summary())
863
865
864 def do_filediff(self, req):
866 def do_filediff(self, req):
865 req.write(self.filediff(self.filectx(req)))
867 req.write(self.filediff(self.filectx(req)))
866
868
867 def do_annotate(self, req):
869 def do_annotate(self, req):
868 req.write(self.fileannotate(self.filectx(req)))
870 req.write(self.fileannotate(self.filectx(req)))
869
871
870 def do_filelog(self, req):
872 def do_filelog(self, req):
871 req.write(self.filelog(self.filectx(req)))
873 req.write(self.filelog(self.filectx(req)))
872
874
873 def do_heads(self, req):
875 def do_heads(self, req):
874 resp = " ".join(map(hex, self.repo.heads())) + "\n"
876 resp = " ".join(map(hex, self.repo.heads())) + "\n"
875 req.httphdr("application/mercurial-0.1", length=len(resp))
877 req.httphdr("application/mercurial-0.1", length=len(resp))
876 req.write(resp)
878 req.write(resp)
877
879
878 def do_branches(self, req):
880 def do_branches(self, req):
879 nodes = []
881 nodes = []
880 if req.form.has_key('nodes'):
882 if req.form.has_key('nodes'):
881 nodes = map(bin, req.form['nodes'][0].split(" "))
883 nodes = map(bin, req.form['nodes'][0].split(" "))
882 resp = cStringIO.StringIO()
884 resp = cStringIO.StringIO()
883 for b in self.repo.branches(nodes):
885 for b in self.repo.branches(nodes):
884 resp.write(" ".join(map(hex, b)) + "\n")
886 resp.write(" ".join(map(hex, b)) + "\n")
885 resp = resp.getvalue()
887 resp = resp.getvalue()
886 req.httphdr("application/mercurial-0.1", length=len(resp))
888 req.httphdr("application/mercurial-0.1", length=len(resp))
887 req.write(resp)
889 req.write(resp)
888
890
889 def do_between(self, req):
891 def do_between(self, req):
890 if req.form.has_key('pairs'):
892 if req.form.has_key('pairs'):
891 pairs = [map(bin, p.split("-"))
893 pairs = [map(bin, p.split("-"))
892 for p in req.form['pairs'][0].split(" ")]
894 for p in req.form['pairs'][0].split(" ")]
893 resp = cStringIO.StringIO()
895 resp = cStringIO.StringIO()
894 for b in self.repo.between(pairs):
896 for b in self.repo.between(pairs):
895 resp.write(" ".join(map(hex, b)) + "\n")
897 resp.write(" ".join(map(hex, b)) + "\n")
896 resp = resp.getvalue()
898 resp = resp.getvalue()
897 req.httphdr("application/mercurial-0.1", length=len(resp))
899 req.httphdr("application/mercurial-0.1", length=len(resp))
898 req.write(resp)
900 req.write(resp)
899
901
900 def do_changegroup(self, req):
902 def do_changegroup(self, req):
901 req.httphdr("application/mercurial-0.1")
903 req.httphdr("application/mercurial-0.1")
902 nodes = []
904 nodes = []
903 if not self.allowpull:
905 if not self.allowpull:
904 return
906 return
905
907
906 if req.form.has_key('roots'):
908 if req.form.has_key('roots'):
907 nodes = map(bin, req.form['roots'][0].split(" "))
909 nodes = map(bin, req.form['roots'][0].split(" "))
908
910
909 z = zlib.compressobj()
911 z = zlib.compressobj()
910 f = self.repo.changegroup(nodes, 'serve')
912 f = self.repo.changegroup(nodes, 'serve')
911 while 1:
913 while 1:
912 chunk = f.read(4096)
914 chunk = f.read(4096)
913 if not chunk:
915 if not chunk:
914 break
916 break
915 req.write(z.compress(chunk))
917 req.write(z.compress(chunk))
916
918
917 req.write(z.flush())
919 req.write(z.flush())
918
920
919 def do_archive(self, req):
921 def do_archive(self, req):
920 changeset = self.repo.lookup(req.form['node'][0])
922 changeset = self.repo.lookup(req.form['node'][0])
921 type_ = req.form['type'][0]
923 type_ = req.form['type'][0]
922 allowed = self.repo.ui.configlist("web", "allow_archive")
924 allowed = self.repo.ui.configlist("web", "allow_archive")
923 if (type_ in self.archives and (type_ in allowed or
925 if (type_ in self.archives and (type_ in allowed or
924 self.repo.ui.configbool("web", "allow" + type_, False))):
926 self.repo.ui.configbool("web", "allow" + type_, False))):
925 self.archive(req, changeset, type_)
927 self.archive(req, changeset, type_)
926 return
928 return
927
929
928 req.write(self.t("error"))
930 req.write(self.t("error"))
929
931
930 def do_static(self, req):
932 def do_static(self, req):
931 fname = req.form['file'][0]
933 fname = req.form['file'][0]
932 static = self.repo.ui.config("web", "static",
934 static = self.repo.ui.config("web", "static",
933 os.path.join(self.templatepath,
935 os.path.join(self.templatepath,
934 "static"))
936 "static"))
935 req.write(staticfile(static, fname, req)
937 req.write(staticfile(static, fname, req)
936 or self.t("error", error="%r not found" % fname))
938 or self.t("error", error="%r not found" % fname))
937
939
938 def do_capabilities(self, req):
940 def do_capabilities(self, req):
939 caps = ['unbundle']
941 caps = ['unbundle']
940 if self.repo.ui.configbool('server', 'uncompressed'):
942 if self.repo.ui.configbool('server', 'uncompressed'):
941 caps.append('stream=%d' % self.repo.revlogversion)
943 caps.append('stream=%d' % self.repo.revlogversion)
942 resp = ' '.join(caps)
944 resp = ' '.join(caps)
943 req.httphdr("application/mercurial-0.1", length=len(resp))
945 req.httphdr("application/mercurial-0.1", length=len(resp))
944 req.write(resp)
946 req.write(resp)
945
947
946 def check_perm(self, req, op, default):
948 def check_perm(self, req, op, default):
947 '''check permission for operation based on user auth.
949 '''check permission for operation based on user auth.
948 return true if op allowed, else false.
950 return true if op allowed, else false.
949 default is policy to use if no config given.'''
951 default is policy to use if no config given.'''
950
952
951 user = req.env.get('REMOTE_USER')
953 user = req.env.get('REMOTE_USER')
952
954
953 deny = self.repo.ui.configlist('web', 'deny_' + op)
955 deny = self.repo.ui.configlist('web', 'deny_' + op)
954 if deny and (not user or deny == ['*'] or user in deny):
956 if deny and (not user or deny == ['*'] or user in deny):
955 return False
957 return False
956
958
957 allow = self.repo.ui.configlist('web', 'allow_' + op)
959 allow = self.repo.ui.configlist('web', 'allow_' + op)
958 return (allow and (allow == ['*'] or user in allow)) or default
960 return (allow and (allow == ['*'] or user in allow)) or default
959
961
960 def do_unbundle(self, req):
962 def do_unbundle(self, req):
961 def bail(response, headers={}):
963 def bail(response, headers={}):
962 length = int(req.env['CONTENT_LENGTH'])
964 length = int(req.env['CONTENT_LENGTH'])
963 for s in util.filechunkiter(req, limit=length):
965 for s in util.filechunkiter(req, limit=length):
964 # drain incoming bundle, else client will not see
966 # drain incoming bundle, else client will not see
965 # response when run outside cgi script
967 # response when run outside cgi script
966 pass
968 pass
967 req.httphdr("application/mercurial-0.1", headers=headers)
969 req.httphdr("application/mercurial-0.1", headers=headers)
968 req.write('0\n')
970 req.write('0\n')
969 req.write(response)
971 req.write(response)
970
972
971 # require ssl by default, auth info cannot be sniffed and
973 # require ssl by default, auth info cannot be sniffed and
972 # replayed
974 # replayed
973 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
975 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
974 if ssl_req:
976 if ssl_req:
975 if not req.env.get('HTTPS'):
977 if not req.env.get('HTTPS'):
976 bail(_('ssl required\n'))
978 bail(_('ssl required\n'))
977 return
979 return
978 proto = 'https'
980 proto = 'https'
979 else:
981 else:
980 proto = 'http'
982 proto = 'http'
981
983
982 # do not allow push unless explicitly allowed
984 # do not allow push unless explicitly allowed
983 if not self.check_perm(req, 'push', False):
985 if not self.check_perm(req, 'push', False):
984 bail(_('push not authorized\n'),
986 bail(_('push not authorized\n'),
985 headers={'status': '401 Unauthorized'})
987 headers={'status': '401 Unauthorized'})
986 return
988 return
987
989
988 req.httphdr("application/mercurial-0.1")
990 req.httphdr("application/mercurial-0.1")
989
991
990 their_heads = req.form['heads'][0].split(' ')
992 their_heads = req.form['heads'][0].split(' ')
991
993
992 def check_heads():
994 def check_heads():
993 heads = map(hex, self.repo.heads())
995 heads = map(hex, self.repo.heads())
994 return their_heads == [hex('force')] or their_heads == heads
996 return their_heads == [hex('force')] or their_heads == heads
995
997
996 # fail early if possible
998 # fail early if possible
997 if not check_heads():
999 if not check_heads():
998 bail(_('unsynced changes\n'))
1000 bail(_('unsynced changes\n'))
999 return
1001 return
1000
1002
1001 # do not lock repo until all changegroup data is
1003 # do not lock repo until all changegroup data is
1002 # streamed. save to temporary file.
1004 # streamed. save to temporary file.
1003
1005
1004 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1006 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1005 fp = os.fdopen(fd, 'wb+')
1007 fp = os.fdopen(fd, 'wb+')
1006 try:
1008 try:
1007 length = int(req.env['CONTENT_LENGTH'])
1009 length = int(req.env['CONTENT_LENGTH'])
1008 for s in util.filechunkiter(req, limit=length):
1010 for s in util.filechunkiter(req, limit=length):
1009 fp.write(s)
1011 fp.write(s)
1010
1012
1011 lock = self.repo.lock()
1013 lock = self.repo.lock()
1012 try:
1014 try:
1013 if not check_heads():
1015 if not check_heads():
1014 req.write('0\n')
1016 req.write('0\n')
1015 req.write(_('unsynced changes\n'))
1017 req.write(_('unsynced changes\n'))
1016 return
1018 return
1017
1019
1018 fp.seek(0)
1020 fp.seek(0)
1019
1021
1020 # send addchangegroup output to client
1022 # send addchangegroup output to client
1021
1023
1022 old_stdout = sys.stdout
1024 old_stdout = sys.stdout
1023 sys.stdout = cStringIO.StringIO()
1025 sys.stdout = cStringIO.StringIO()
1024
1026
1025 try:
1027 try:
1026 url = 'remote:%s:%s' % (proto,
1028 url = 'remote:%s:%s' % (proto,
1027 req.env.get('REMOTE_HOST', ''))
1029 req.env.get('REMOTE_HOST', ''))
1028 ret = self.repo.addchangegroup(fp, 'serve', url)
1030 ret = self.repo.addchangegroup(fp, 'serve', url)
1029 finally:
1031 finally:
1030 val = sys.stdout.getvalue()
1032 val = sys.stdout.getvalue()
1031 sys.stdout = old_stdout
1033 sys.stdout = old_stdout
1032 req.write('%d\n' % ret)
1034 req.write('%d\n' % ret)
1033 req.write(val)
1035 req.write(val)
1034 finally:
1036 finally:
1035 lock.release()
1037 lock.release()
1036 finally:
1038 finally:
1037 fp.close()
1039 fp.close()
1038 os.unlink(tempname)
1040 os.unlink(tempname)
1039
1041
1040 def do_stream_out(self, req):
1042 def do_stream_out(self, req):
1041 req.httphdr("application/mercurial-0.1")
1043 req.httphdr("application/mercurial-0.1")
1042 streamclone.stream_out(self.repo, req)
1044 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now