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