##// END OF EJS Templates
hgweb: add methods to get contexts from request
Brendan Cully -
r3226:5c602877 default
parent child Browse files
Show More
@@ -1,943 +1,943 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(), "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, ctx, shortlog=False):
152 def changelog(self, ctx, shortlog=False):
153 pos = ctx.rev()
153 pos = ctx.rev()
154 def changenav(**map):
154 def changenav(**map):
155 def seq(factor, maxchanges=None):
155 def seq(factor, maxchanges=None):
156 if maxchanges:
156 if maxchanges:
157 yield maxchanges
157 yield maxchanges
158 if maxchanges >= 20 and maxchanges <= 40:
158 if maxchanges >= 20 and maxchanges <= 40:
159 yield 50
159 yield 50
160 else:
160 else:
161 yield 1 * factor
161 yield 1 * factor
162 yield 3 * factor
162 yield 3 * factor
163 for f in seq(factor * 10):
163 for f in seq(factor * 10):
164 yield f
164 yield f
165
165
166 l = []
166 l = []
167 last = 0
167 last = 0
168 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
168 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
169 for f in seq(1, maxchanges):
169 for f in seq(1, maxchanges):
170 if f < maxchanges or f <= last:
170 if f < maxchanges or f <= last:
171 continue
171 continue
172 if f > count:
172 if f > count:
173 break
173 break
174 last = f
174 last = f
175 r = "%d" % f
175 r = "%d" % f
176 if pos + f < count:
176 if pos + f < count:
177 l.append(("+" + r, pos + f))
177 l.append(("+" + r, pos + f))
178 if pos - f >= 0:
178 if pos - f >= 0:
179 l.insert(0, ("-" + r, pos - f))
179 l.insert(0, ("-" + r, pos - f))
180
180
181 yield {"rev": 0, "label": "(0)"}
181 yield {"rev": 0, "label": "(0)"}
182
182
183 for label, rev in l:
183 for label, rev in l:
184 yield {"label": label, "rev": rev}
184 yield {"label": label, "rev": rev}
185
185
186 yield {"label": "tip", "rev": "tip"}
186 yield {"label": "tip", "rev": "tip"}
187
187
188 def changelist(**map):
188 def changelist(**map):
189 parity = (start - end) & 1
189 parity = (start - end) & 1
190 cl = self.repo.changelog
190 cl = self.repo.changelog
191 l = [] # build a list in forward order for efficiency
191 l = [] # build a list in forward order for efficiency
192 for i in range(start, end):
192 for i in range(start, end):
193 ctx = self.repo.changectx(i)
193 ctx = self.repo.changectx(i)
194 n = ctx.node()
194 n = ctx.node()
195
195
196 l.insert(0, {"parity": parity,
196 l.insert(0, {"parity": parity,
197 "author": ctx.user(),
197 "author": ctx.user(),
198 "parent": self.siblings(ctx.parents(), i - 1),
198 "parent": self.siblings(ctx.parents(), i - 1),
199 "child": self.siblings(ctx.children(), i + 1),
199 "child": self.siblings(ctx.children(), i + 1),
200 "changelogtag": self.showtag("changelogtag",n),
200 "changelogtag": self.showtag("changelogtag",n),
201 "desc": ctx.description(),
201 "desc": ctx.description(),
202 "date": ctx.date(),
202 "date": ctx.date(),
203 "files": self.listfilediffs(ctx.files(), n),
203 "files": self.listfilediffs(ctx.files(), n),
204 "rev": i,
204 "rev": i,
205 "node": hex(n)})
205 "node": hex(n)})
206 parity = 1 - parity
206 parity = 1 - parity
207
207
208 for e in l:
208 for e in l:
209 yield e
209 yield e
210
210
211 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
211 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
212 cl = self.repo.changelog
212 cl = self.repo.changelog
213 mf = cl.read(cl.tip())[0]
213 mf = cl.read(cl.tip())[0]
214 count = cl.count()
214 count = cl.count()
215 start = max(0, pos - maxchanges + 1)
215 start = max(0, pos - maxchanges + 1)
216 end = min(count, start + maxchanges)
216 end = min(count, start + maxchanges)
217 pos = end - 1
217 pos = end - 1
218
218
219 yield self.t(shortlog and 'shortlog' or 'changelog',
219 yield self.t(shortlog and 'shortlog' or 'changelog',
220 changenav=changenav,
220 changenav=changenav,
221 node=hex(cl.tip()),
221 node=hex(cl.tip()),
222 rev=pos, changesets=count, entries=changelist,
222 rev=pos, changesets=count, entries=changelist,
223 archives=self.archivelist("tip"))
223 archives=self.archivelist("tip"))
224
224
225 def search(self, query):
225 def search(self, query):
226
226
227 def changelist(**map):
227 def changelist(**map):
228 cl = self.repo.changelog
228 cl = self.repo.changelog
229 count = 0
229 count = 0
230 qw = query.lower().split()
230 qw = query.lower().split()
231
231
232 def revgen():
232 def revgen():
233 for i in range(cl.count() - 1, 0, -100):
233 for i in range(cl.count() - 1, 0, -100):
234 l = []
234 l = []
235 for j in range(max(0, i - 100), i):
235 for j in range(max(0, i - 100), i):
236 ctx = self.repo.changectx(j)
236 ctx = self.repo.changectx(j)
237 l.append(ctx)
237 l.append(ctx)
238 l.reverse()
238 l.reverse()
239 for e in l:
239 for e in l:
240 yield e
240 yield e
241
241
242 for ctx in revgen():
242 for ctx in revgen():
243 miss = 0
243 miss = 0
244 for q in qw:
244 for q in qw:
245 if not (q in ctx.user().lower() or
245 if not (q in ctx.user().lower() or
246 q in ctx.description().lower() or
246 q in ctx.description().lower() or
247 q in " ".join(ctx.files()[:20]).lower()):
247 q in " ".join(ctx.files()[:20]).lower()):
248 miss = 1
248 miss = 1
249 break
249 break
250 if miss:
250 if miss:
251 continue
251 continue
252
252
253 count += 1
253 count += 1
254 n = ctx.node()
254 n = ctx.node()
255
255
256 yield self.t('searchentry',
256 yield self.t('searchentry',
257 parity=self.stripes(count),
257 parity=self.stripes(count),
258 author=ctx.user(),
258 author=ctx.user(),
259 parent=self.siblings(ctx.parents()),
259 parent=self.siblings(ctx.parents()),
260 child=self.siblings(ctx.children()),
260 child=self.siblings(ctx.children()),
261 changelogtag=self.showtag("changelogtag",n),
261 changelogtag=self.showtag("changelogtag",n),
262 desc=ctx.description(),
262 desc=ctx.description(),
263 date=ctx.date(),
263 date=ctx.date(),
264 files=self.listfilediffs(ctx.files(), n),
264 files=self.listfilediffs(ctx.files(), n),
265 rev=ctx.rev(),
265 rev=ctx.rev(),
266 node=hex(n))
266 node=hex(n))
267
267
268 if count >= self.maxchanges:
268 if count >= self.maxchanges:
269 break
269 break
270
270
271 cl = self.repo.changelog
271 cl = self.repo.changelog
272
272
273 yield self.t('search',
273 yield self.t('search',
274 query=query,
274 query=query,
275 node=hex(cl.tip()),
275 node=hex(cl.tip()),
276 entries=changelist)
276 entries=changelist)
277
277
278 def changeset(self, ctx):
278 def changeset(self, ctx):
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(hex(n)))
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, fctx):
555 def filediff(self, fctx):
556 n = fctx.node()
556 n = fctx.node()
557 path = fctx.path()
557 path = fctx.path()
558 parents = fctx.changectx().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, [path])
562 yield self.diff(p1, n, [path])
563
563
564 yield self.t("filediff",
564 yield self.t("filediff",
565 file=path,
565 file=path,
566 node=hex(n),
566 node=hex(n),
567 rev=fctx.rev(),
567 rev=fctx.rev(),
568 parent=self.siblings(parents),
568 parent=self.siblings(parents),
569 child=self.siblings(fctx.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'):
652 changeid = form['manifest'][0]
653 try:
654 req.changectx = self.repo.changectx(changeid)
655 except hg.RepoError:
656 man = self.repo.manifest
657 mn = man.lookup(changeid)
658 req.changectx = self.repo.changectx(man.linkrev(mn))
659
660 if form.has_key('filenode'):
661 changeid = form['filenode'][0]
662 path = self.cleanpath(form['file'][0])
663 try:
664 req.changectx = self.repo.changectx(changeid)
665 req.filectx = req.changectx.filectx(path)
666 except hg.RepoError:
667 req.filectx = self.repo.filectx(path, fileid=changeid)
668 req.changectx = req.filectx.changectx()
669
670 self.refresh()
651 self.refresh()
671
652
672 expand_form(req.form)
653 expand_form(req.form)
673
654
674 m = os.path.join(self.templatepath, "map")
655 m = os.path.join(self.templatepath, "map")
675 style = self.repo.ui.config("web", "style", "")
656 style = self.repo.ui.config("web", "style", "")
676 if req.form.has_key('style'):
657 if req.form.has_key('style'):
677 style = req.form['style'][0]
658 style = req.form['style'][0]
678 if style:
659 if style:
679 b = os.path.basename("map-" + style)
660 b = os.path.basename("map-" + style)
680 p = os.path.join(self.templatepath, b)
661 p = os.path.join(self.templatepath, b)
681 if os.path.isfile(p):
662 if os.path.isfile(p):
682 m = p
663 m = p
683
664
684 port = req.env["SERVER_PORT"]
665 port = req.env["SERVER_PORT"]
685 port = port != "80" and (":" + port) or ""
666 port = port != "80" and (":" + port) or ""
686 uri = req.env["REQUEST_URI"]
667 uri = req.env["REQUEST_URI"]
687 if "?" in uri:
668 if "?" in uri:
688 uri = uri.split("?")[0]
669 uri = uri.split("?")[0]
689 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
670 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
690 if not self.reponame:
671 if not self.reponame:
691 self.reponame = (self.repo.ui.config("web", "name")
672 self.reponame = (self.repo.ui.config("web", "name")
692 or uri.strip('/') or self.repo.root)
673 or uri.strip('/') or self.repo.root)
693
674
694 self.t = templater.templater(m, templater.common_filters,
675 self.t = templater.templater(m, templater.common_filters,
695 defaults={"url": url,
676 defaults={"url": url,
696 "repo": self.reponame,
677 "repo": self.reponame,
697 "header": header,
678 "header": header,
698 "footer": footer,
679 "footer": footer,
699 "rawfileheader": rawfileheader,
680 "rawfileheader": rawfileheader,
700 })
681 })
701
682
702 if not req.form.has_key('cmd'):
683 if not req.form.has_key('cmd'):
703 req.form['cmd'] = [self.t.cache['default'],]
684 req.form['cmd'] = [self.t.cache['default'],]
704
685
705 cmd = req.form['cmd'][0]
686 cmd = req.form['cmd'][0]
706
687
707 method = getattr(self, 'do_' + cmd, None)
688 method = getattr(self, 'do_' + cmd, None)
708 if method:
689 if method:
709 method(req)
690 method(req)
710 else:
691 else:
711 req.write(self.t("error"))
692 req.write(self.t("error"))
712
693
694 def changectx(self, req):
695 if req.form.has_key('node'):
696 changeid = req.form['node'][0]
697 else:
698 changeid = req.form['manifest'][0]
699 try:
700 ctx = self.repo.changectx(changeid)
701 except hg.RepoError:
702 man = self.repo.manifest
703 mn = man.lookup(changeid)
704 ctx = self.repo.changectx(man.linkrev(mn))
705
706 return ctx
707
708 def filectx(self, req):
709 path = self.cleanpath(req.form['file'][0])
710 if req.form.has_key('node'):
711 changeid = req.form['node'][0]
712 else:
713 changeid = req.form['filenode'][0]
714 try:
715 ctx = self.repo.changectx(changeid)
716 fctx = ctx.filectx(path)
717 except hg.RepoError:
718 fctx = self.repo.filectx(path, fileid=changeid)
719
720 return fctx
721
713 def stripes(self, parity):
722 def stripes(self, parity):
714 "make horizontal stripes for easier reading"
723 "make horizontal stripes for easier reading"
715 if self.stripecount:
724 if self.stripecount:
716 return (1 + parity / self.stripecount) & 1
725 return (1 + parity / self.stripecount) & 1
717 else:
726 else:
718 return 0
727 return 0
719
728
720 def do_changelog(self, req):
729 def do_changelog(self, req, shortlog = False):
721 if req.form.has_key('rev'):
730 if req.form.has_key('node'):
722 hi = req.form['rev'][0]
731 ctx = self.changectx(req)
723 else:
732 else:
724 hi = self.repo.changelog.count() - 1
733 if req.form.has_key('rev'):
725 try:
734 hi = req.form['rev'][0]
726 ctx = self.repo.changectx(hi)
735 else:
727 except hg.RepoError:
736 hi = self.repo.changelog.count() - 1
728 req.write(self.search(hi)) # XXX redirect to 404 page?
737 try:
729 return
738 ctx = self.repo.changectx(hi)
739 except hg.RepoError:
740 req.write(self.search(hi)) # XXX redirect to 404 page?
741 return
730
742
731 req.write(self.changelog(ctx))
743 req.write(self.changelog(ctx, shortlog = shortlog))
732
744
733 def do_shortlog(self, req):
745 def do_shortlog(self, req):
734 if req.form.has_key('rev'):
746 self.do_changelog(req, shortlog = True)
735 hi = req.form['rev'][0]
736 else:
737 hi = self.repo.changelog.count() - 1
738 try:
739 hi = self.repo.changectx(hi)
740 except hg.RepoError:
741 req.write(self.search(hi)) # XXX redirect to 404 page?
742 return
743
744 req.write(self.changelog(ctx, shortlog = True))
745
747
746 def do_changeset(self, req):
748 def do_changeset(self, req):
747 ctx = self.repo.changectx(req.form['node'][0])
749 ctx = self.repo.changectx(req.form['node'][0])
748 req.write(self.changeset(ctx))
750 req.write(self.changeset(ctx))
749
751
750 def do_manifest(self, req):
752 def do_manifest(self, req):
751 req.write(self.manifest(req.changectx,
753 req.write(self.manifest(self.changectx(req),
752 self.cleanpath(req.form['path'][0])))
754 self.cleanpath(req.form['path'][0])))
753
755
754 def do_tags(self, req):
756 def do_tags(self, req):
755 req.write(self.tags())
757 req.write(self.tags())
756
758
757 def do_summary(self, req):
759 def do_summary(self, req):
758 req.write(self.summary())
760 req.write(self.summary())
759
761
760 def do_filediff(self, req):
762 def do_filediff(self, req):
761 ctx = self.repo.changectx(req.form['node'][0])
763 req.write(self.filediff(self.filectx(req)))
762 fctx = ctx.filectx(self.cleanpath(req.form['file'][0]))
763 req.write(self.filediff(fctx))
764
764
765 def do_file(self, req):
765 def do_file(self, req):
766 req.write(self.filerevision(req.filectx))
766 req.write(self.filerevision(self.filectx(req)))
767
767
768 def do_annotate(self, req):
768 def do_annotate(self, req):
769 req.write(self.fileannotate(req.filectx))
769 req.write(self.fileannotate(self.filectx(req)))
770
770
771 def do_filelog(self, req):
771 def do_filelog(self, req):
772 req.write(self.filelog(req.filectx))
772 req.write(self.filelog(self.filectx(req)))
773
773
774 def do_heads(self, req):
774 def do_heads(self, req):
775 resp = " ".join(map(hex, self.repo.heads())) + "\n"
775 resp = " ".join(map(hex, self.repo.heads())) + "\n"
776 req.httphdr("application/mercurial-0.1", length=len(resp))
776 req.httphdr("application/mercurial-0.1", length=len(resp))
777 req.write(resp)
777 req.write(resp)
778
778
779 def do_branches(self, req):
779 def do_branches(self, req):
780 nodes = []
780 nodes = []
781 if req.form.has_key('nodes'):
781 if req.form.has_key('nodes'):
782 nodes = map(bin, req.form['nodes'][0].split(" "))
782 nodes = map(bin, req.form['nodes'][0].split(" "))
783 resp = cStringIO.StringIO()
783 resp = cStringIO.StringIO()
784 for b in self.repo.branches(nodes):
784 for b in self.repo.branches(nodes):
785 resp.write(" ".join(map(hex, b)) + "\n")
785 resp.write(" ".join(map(hex, b)) + "\n")
786 resp = resp.getvalue()
786 resp = resp.getvalue()
787 req.httphdr("application/mercurial-0.1", length=len(resp))
787 req.httphdr("application/mercurial-0.1", length=len(resp))
788 req.write(resp)
788 req.write(resp)
789
789
790 def do_between(self, req):
790 def do_between(self, req):
791 if req.form.has_key('pairs'):
791 if req.form.has_key('pairs'):
792 pairs = [map(bin, p.split("-"))
792 pairs = [map(bin, p.split("-"))
793 for p in req.form['pairs'][0].split(" ")]
793 for p in req.form['pairs'][0].split(" ")]
794 resp = cStringIO.StringIO()
794 resp = cStringIO.StringIO()
795 for b in self.repo.between(pairs):
795 for b in self.repo.between(pairs):
796 resp.write(" ".join(map(hex, b)) + "\n")
796 resp.write(" ".join(map(hex, b)) + "\n")
797 resp = resp.getvalue()
797 resp = resp.getvalue()
798 req.httphdr("application/mercurial-0.1", length=len(resp))
798 req.httphdr("application/mercurial-0.1", length=len(resp))
799 req.write(resp)
799 req.write(resp)
800
800
801 def do_changegroup(self, req):
801 def do_changegroup(self, req):
802 req.httphdr("application/mercurial-0.1")
802 req.httphdr("application/mercurial-0.1")
803 nodes = []
803 nodes = []
804 if not self.allowpull:
804 if not self.allowpull:
805 return
805 return
806
806
807 if req.form.has_key('roots'):
807 if req.form.has_key('roots'):
808 nodes = map(bin, req.form['roots'][0].split(" "))
808 nodes = map(bin, req.form['roots'][0].split(" "))
809
809
810 z = zlib.compressobj()
810 z = zlib.compressobj()
811 f = self.repo.changegroup(nodes, 'serve')
811 f = self.repo.changegroup(nodes, 'serve')
812 while 1:
812 while 1:
813 chunk = f.read(4096)
813 chunk = f.read(4096)
814 if not chunk:
814 if not chunk:
815 break
815 break
816 req.write(z.compress(chunk))
816 req.write(z.compress(chunk))
817
817
818 req.write(z.flush())
818 req.write(z.flush())
819
819
820 def do_archive(self, req):
820 def do_archive(self, req):
821 changeset = self.repo.lookup(req.form['node'][0])
821 changeset = self.repo.lookup(req.form['node'][0])
822 type_ = req.form['type'][0]
822 type_ = req.form['type'][0]
823 allowed = self.repo.ui.configlist("web", "allow_archive")
823 allowed = self.repo.ui.configlist("web", "allow_archive")
824 if (type_ in self.archives and (type_ in allowed or
824 if (type_ in self.archives and (type_ in allowed or
825 self.repo.ui.configbool("web", "allow" + type_, False))):
825 self.repo.ui.configbool("web", "allow" + type_, False))):
826 self.archive(req, changeset, type_)
826 self.archive(req, changeset, type_)
827 return
827 return
828
828
829 req.write(self.t("error"))
829 req.write(self.t("error"))
830
830
831 def do_static(self, req):
831 def do_static(self, req):
832 fname = req.form['file'][0]
832 fname = req.form['file'][0]
833 static = self.repo.ui.config("web", "static",
833 static = self.repo.ui.config("web", "static",
834 os.path.join(self.templatepath,
834 os.path.join(self.templatepath,
835 "static"))
835 "static"))
836 req.write(staticfile(static, fname, req)
836 req.write(staticfile(static, fname, req)
837 or self.t("error", error="%r not found" % fname))
837 or self.t("error", error="%r not found" % fname))
838
838
839 def do_capabilities(self, req):
839 def do_capabilities(self, req):
840 caps = ['unbundle']
840 caps = ['unbundle']
841 if self.repo.ui.configbool('server', 'uncompressed'):
841 if self.repo.ui.configbool('server', 'uncompressed'):
842 caps.append('stream=%d' % self.repo.revlogversion)
842 caps.append('stream=%d' % self.repo.revlogversion)
843 resp = ' '.join(caps)
843 resp = ' '.join(caps)
844 req.httphdr("application/mercurial-0.1", length=len(resp))
844 req.httphdr("application/mercurial-0.1", length=len(resp))
845 req.write(resp)
845 req.write(resp)
846
846
847 def check_perm(self, req, op, default):
847 def check_perm(self, req, op, default):
848 '''check permission for operation based on user auth.
848 '''check permission for operation based on user auth.
849 return true if op allowed, else false.
849 return true if op allowed, else false.
850 default is policy to use if no config given.'''
850 default is policy to use if no config given.'''
851
851
852 user = req.env.get('REMOTE_USER')
852 user = req.env.get('REMOTE_USER')
853
853
854 deny = self.repo.ui.configlist('web', 'deny_' + op)
854 deny = self.repo.ui.configlist('web', 'deny_' + op)
855 if deny and (not user or deny == ['*'] or user in deny):
855 if deny and (not user or deny == ['*'] or user in deny):
856 return False
856 return False
857
857
858 allow = self.repo.ui.configlist('web', 'allow_' + op)
858 allow = self.repo.ui.configlist('web', 'allow_' + op)
859 return (allow and (allow == ['*'] or user in allow)) or default
859 return (allow and (allow == ['*'] or user in allow)) or default
860
860
861 def do_unbundle(self, req):
861 def do_unbundle(self, req):
862 def bail(response, headers={}):
862 def bail(response, headers={}):
863 length = int(req.env['CONTENT_LENGTH'])
863 length = int(req.env['CONTENT_LENGTH'])
864 for s in util.filechunkiter(req, limit=length):
864 for s in util.filechunkiter(req, limit=length):
865 # drain incoming bundle, else client will not see
865 # drain incoming bundle, else client will not see
866 # response when run outside cgi script
866 # response when run outside cgi script
867 pass
867 pass
868 req.httphdr("application/mercurial-0.1", headers=headers)
868 req.httphdr("application/mercurial-0.1", headers=headers)
869 req.write('0\n')
869 req.write('0\n')
870 req.write(response)
870 req.write(response)
871
871
872 # require ssl by default, auth info cannot be sniffed and
872 # require ssl by default, auth info cannot be sniffed and
873 # replayed
873 # replayed
874 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
874 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
875 if ssl_req:
875 if ssl_req:
876 if not req.env.get('HTTPS'):
876 if not req.env.get('HTTPS'):
877 bail(_('ssl required\n'))
877 bail(_('ssl required\n'))
878 return
878 return
879 proto = 'https'
879 proto = 'https'
880 else:
880 else:
881 proto = 'http'
881 proto = 'http'
882
882
883 # do not allow push unless explicitly allowed
883 # do not allow push unless explicitly allowed
884 if not self.check_perm(req, 'push', False):
884 if not self.check_perm(req, 'push', False):
885 bail(_('push not authorized\n'),
885 bail(_('push not authorized\n'),
886 headers={'status': '401 Unauthorized'})
886 headers={'status': '401 Unauthorized'})
887 return
887 return
888
888
889 req.httphdr("application/mercurial-0.1")
889 req.httphdr("application/mercurial-0.1")
890
890
891 their_heads = req.form['heads'][0].split(' ')
891 their_heads = req.form['heads'][0].split(' ')
892
892
893 def check_heads():
893 def check_heads():
894 heads = map(hex, self.repo.heads())
894 heads = map(hex, self.repo.heads())
895 return their_heads == [hex('force')] or their_heads == heads
895 return their_heads == [hex('force')] or their_heads == heads
896
896
897 # fail early if possible
897 # fail early if possible
898 if not check_heads():
898 if not check_heads():
899 bail(_('unsynced changes\n'))
899 bail(_('unsynced changes\n'))
900 return
900 return
901
901
902 # do not lock repo until all changegroup data is
902 # do not lock repo until all changegroup data is
903 # streamed. save to temporary file.
903 # streamed. save to temporary file.
904
904
905 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
905 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
906 fp = os.fdopen(fd, 'wb+')
906 fp = os.fdopen(fd, 'wb+')
907 try:
907 try:
908 length = int(req.env['CONTENT_LENGTH'])
908 length = int(req.env['CONTENT_LENGTH'])
909 for s in util.filechunkiter(req, limit=length):
909 for s in util.filechunkiter(req, limit=length):
910 fp.write(s)
910 fp.write(s)
911
911
912 lock = self.repo.lock()
912 lock = self.repo.lock()
913 try:
913 try:
914 if not check_heads():
914 if not check_heads():
915 req.write('0\n')
915 req.write('0\n')
916 req.write(_('unsynced changes\n'))
916 req.write(_('unsynced changes\n'))
917 return
917 return
918
918
919 fp.seek(0)
919 fp.seek(0)
920
920
921 # send addchangegroup output to client
921 # send addchangegroup output to client
922
922
923 old_stdout = sys.stdout
923 old_stdout = sys.stdout
924 sys.stdout = cStringIO.StringIO()
924 sys.stdout = cStringIO.StringIO()
925
925
926 try:
926 try:
927 url = 'remote:%s:%s' % (proto,
927 url = 'remote:%s:%s' % (proto,
928 req.env.get('REMOTE_HOST', ''))
928 req.env.get('REMOTE_HOST', ''))
929 ret = self.repo.addchangegroup(fp, 'serve', url)
929 ret = self.repo.addchangegroup(fp, 'serve', url)
930 finally:
930 finally:
931 val = sys.stdout.getvalue()
931 val = sys.stdout.getvalue()
932 sys.stdout = old_stdout
932 sys.stdout = old_stdout
933 req.write('%d\n' % ret)
933 req.write('%d\n' % ret)
934 req.write(val)
934 req.write(val)
935 finally:
935 finally:
936 lock.release()
936 lock.release()
937 finally:
937 finally:
938 fp.close()
938 fp.close()
939 os.unlink(tempname)
939 os.unlink(tempname)
940
940
941 def do_stream_out(self, req):
941 def do_stream_out(self, req):
942 req.httphdr("application/mercurial-0.1")
942 req.httphdr("application/mercurial-0.1")
943 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