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