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