##// END OF EJS Templates
http: query server for capabilities
Vadim Gelfer -
r2442:c660691f default
parent child Browse files
Show More
@@ -1,835 +1,841 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 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 cStringIO")
13 demandload(globals(), "re zlib ConfigParser cStringIO")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
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.templatepath = self.repo.ui.config("web", "templates",
40 self.templatepath = self.repo.ui.config("web", "templates",
41 templater.templatepath())
41 templater.templatepath())
42
42
43 def refresh(self):
43 def refresh(self):
44 mtime = get_mtime(self.repo.root)
44 mtime = get_mtime(self.repo.root)
45 if mtime != self.mtime:
45 if mtime != self.mtime:
46 self.mtime = mtime
46 self.mtime = mtime
47 self.repo = hg.repository(self.repo.ui, self.repo.root)
47 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51
51
52 def archivelist(self, nodeid):
52 def archivelist(self, nodeid):
53 allowed = (self.repo.ui.config("web", "allow_archive", "")
53 allowed = (self.repo.ui.config("web", "allow_archive", "")
54 .replace(",", " ").split())
54 .replace(",", " ").split())
55 for i in self.archives:
55 for i in self.archives:
56 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
56 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
57 yield {"type" : i, "node" : nodeid, "url": ""}
57 yield {"type" : i, "node" : nodeid, "url": ""}
58
58
59 def listfiles(self, files, mf):
59 def listfiles(self, files, mf):
60 for f in files[:self.maxfiles]:
60 for f in files[:self.maxfiles]:
61 yield self.t("filenodelink", node=hex(mf[f]), file=f)
61 yield self.t("filenodelink", node=hex(mf[f]), file=f)
62 if len(files) > self.maxfiles:
62 if len(files) > self.maxfiles:
63 yield self.t("fileellipses")
63 yield self.t("fileellipses")
64
64
65 def listfilediffs(self, files, changeset):
65 def listfilediffs(self, files, changeset):
66 for f in files[:self.maxfiles]:
66 for f in files[:self.maxfiles]:
67 yield self.t("filedifflink", node=hex(changeset), file=f)
67 yield self.t("filedifflink", node=hex(changeset), file=f)
68 if len(files) > self.maxfiles:
68 if len(files) > self.maxfiles:
69 yield self.t("fileellipses")
69 yield self.t("fileellipses")
70
70
71 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
71 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
72 if not rev:
72 if not rev:
73 rev = lambda x: ""
73 rev = lambda x: ""
74 siblings = [s for s in siblings if s != nullid]
74 siblings = [s for s in siblings if s != nullid]
75 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
75 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
76 return
76 return
77 for s in siblings:
77 for s in siblings:
78 yield dict(node=hex(s), rev=rev(s), **args)
78 yield dict(node=hex(s), rev=rev(s), **args)
79
79
80 def renamelink(self, fl, node):
80 def renamelink(self, fl, node):
81 r = fl.renamed(node)
81 r = fl.renamed(node)
82 if r:
82 if r:
83 return [dict(file=r[0], node=hex(r[1]))]
83 return [dict(file=r[0], node=hex(r[1]))]
84 return []
84 return []
85
85
86 def showtag(self, t1, node=nullid, **args):
86 def showtag(self, t1, node=nullid, **args):
87 for t in self.repo.nodetags(node):
87 for t in self.repo.nodetags(node):
88 yield self.t(t1, tag=t, **args)
88 yield self.t(t1, tag=t, **args)
89
89
90 def diff(self, node1, node2, files):
90 def diff(self, node1, node2, files):
91 def filterfiles(filters, files):
91 def filterfiles(filters, files):
92 l = [x for x in files if x in filters]
92 l = [x for x in files if x in filters]
93
93
94 for t in filters:
94 for t in filters:
95 if t and t[-1] != os.sep:
95 if t and t[-1] != os.sep:
96 t += os.sep
96 t += os.sep
97 l += [x for x in files if x.startswith(t)]
97 l += [x for x in files if x.startswith(t)]
98 return l
98 return l
99
99
100 parity = [0]
100 parity = [0]
101 def diffblock(diff, f, fn):
101 def diffblock(diff, f, fn):
102 yield self.t("diffblock",
102 yield self.t("diffblock",
103 lines=prettyprintlines(diff),
103 lines=prettyprintlines(diff),
104 parity=parity[0],
104 parity=parity[0],
105 file=f,
105 file=f,
106 filenode=hex(fn or nullid))
106 filenode=hex(fn or nullid))
107 parity[0] = 1 - parity[0]
107 parity[0] = 1 - parity[0]
108
108
109 def prettyprintlines(diff):
109 def prettyprintlines(diff):
110 for l in diff.splitlines(1):
110 for l in diff.splitlines(1):
111 if l.startswith('+'):
111 if l.startswith('+'):
112 yield self.t("difflineplus", line=l)
112 yield self.t("difflineplus", line=l)
113 elif l.startswith('-'):
113 elif l.startswith('-'):
114 yield self.t("difflineminus", line=l)
114 yield self.t("difflineminus", line=l)
115 elif l.startswith('@'):
115 elif l.startswith('@'):
116 yield self.t("difflineat", line=l)
116 yield self.t("difflineat", line=l)
117 else:
117 else:
118 yield self.t("diffline", line=l)
118 yield self.t("diffline", line=l)
119
119
120 r = self.repo
120 r = self.repo
121 cl = r.changelog
121 cl = r.changelog
122 mf = r.manifest
122 mf = r.manifest
123 change1 = cl.read(node1)
123 change1 = cl.read(node1)
124 change2 = cl.read(node2)
124 change2 = cl.read(node2)
125 mmap1 = mf.read(change1[0])
125 mmap1 = mf.read(change1[0])
126 mmap2 = mf.read(change2[0])
126 mmap2 = mf.read(change2[0])
127 date1 = util.datestr(change1[2])
127 date1 = util.datestr(change1[2])
128 date2 = util.datestr(change2[2])
128 date2 = util.datestr(change2[2])
129
129
130 modified, added, removed, deleted, unknown = r.changes(node1, node2)
130 modified, added, removed, deleted, unknown = r.changes(node1, node2)
131 if files:
131 if files:
132 modified, added, removed = map(lambda x: filterfiles(files, x),
132 modified, added, removed = map(lambda x: filterfiles(files, x),
133 (modified, added, removed))
133 (modified, added, removed))
134
134
135 diffopts = self.repo.ui.diffopts()
135 diffopts = self.repo.ui.diffopts()
136 showfunc = diffopts['showfunc']
136 showfunc = diffopts['showfunc']
137 ignorews = diffopts['ignorews']
137 ignorews = diffopts['ignorews']
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 showfunc=showfunc, ignorews=ignorews), f, tn)
142 showfunc=showfunc, ignorews=ignorews), 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 showfunc=showfunc, ignorews=ignorews), f, tn)
147 showfunc=showfunc, ignorews=ignorews), 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 showfunc=showfunc, ignorews=ignorews), f, tn)
152 showfunc=showfunc, ignorews=ignorews), f, tn)
153
153
154 def changelog(self, pos):
154 def changelog(self, pos):
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 for f in seq(1, self.maxchanges):
169 for f in seq(1, self.maxchanges):
170 if f < self.maxchanges or f <= last:
170 if f < self.maxchanges or f <= last:
171 continue
171 continue
172 if f > count:
172 if f > count:
173 break
173 break
174 last = f
174 last = f
175 r = "%d" % f
175 r = "%d" % f
176 if pos + f < count:
176 if pos + f < count:
177 l.append(("+" + r, pos + f))
177 l.append(("+" + r, pos + f))
178 if pos - f >= 0:
178 if pos - f >= 0:
179 l.insert(0, ("-" + r, pos - f))
179 l.insert(0, ("-" + r, pos - f))
180
180
181 yield {"rev": 0, "label": "(0)"}
181 yield {"rev": 0, "label": "(0)"}
182
182
183 for label, rev in l:
183 for label, rev in l:
184 yield {"label": label, "rev": rev}
184 yield {"label": label, "rev": rev}
185
185
186 yield {"label": "tip", "rev": "tip"}
186 yield {"label": "tip", "rev": "tip"}
187
187
188 def changelist(**map):
188 def changelist(**map):
189 parity = (start - end) & 1
189 parity = (start - end) & 1
190 cl = self.repo.changelog
190 cl = self.repo.changelog
191 l = [] # build a list in forward order for efficiency
191 l = [] # build a list in forward order for efficiency
192 for i in range(start, end):
192 for i in range(start, end):
193 n = cl.node(i)
193 n = cl.node(i)
194 changes = cl.read(n)
194 changes = cl.read(n)
195 hn = hex(n)
195 hn = hex(n)
196
196
197 l.insert(0, {"parity": parity,
197 l.insert(0, {"parity": parity,
198 "author": changes[1],
198 "author": changes[1],
199 "parent": self.siblings(cl.parents(n), cl.rev,
199 "parent": self.siblings(cl.parents(n), cl.rev,
200 cl.rev(n) - 1),
200 cl.rev(n) - 1),
201 "child": self.siblings(cl.children(n), cl.rev,
201 "child": self.siblings(cl.children(n), cl.rev,
202 cl.rev(n) + 1),
202 cl.rev(n) + 1),
203 "changelogtag": self.showtag("changelogtag",n),
203 "changelogtag": self.showtag("changelogtag",n),
204 "manifest": hex(changes[0]),
204 "manifest": hex(changes[0]),
205 "desc": changes[4],
205 "desc": changes[4],
206 "date": changes[2],
206 "date": changes[2],
207 "files": self.listfilediffs(changes[3], n),
207 "files": self.listfilediffs(changes[3], n),
208 "rev": i,
208 "rev": i,
209 "node": hn})
209 "node": hn})
210 parity = 1 - parity
210 parity = 1 - parity
211
211
212 for e in l:
212 for e in l:
213 yield e
213 yield e
214
214
215 cl = self.repo.changelog
215 cl = self.repo.changelog
216 mf = cl.read(cl.tip())[0]
216 mf = cl.read(cl.tip())[0]
217 count = cl.count()
217 count = cl.count()
218 start = max(0, pos - self.maxchanges + 1)
218 start = max(0, pos - self.maxchanges + 1)
219 end = min(count, start + self.maxchanges)
219 end = min(count, start + self.maxchanges)
220 pos = end - 1
220 pos = end - 1
221
221
222 yield self.t('changelog',
222 yield self.t('changelog',
223 changenav=changenav,
223 changenav=changenav,
224 manifest=hex(mf),
224 manifest=hex(mf),
225 rev=pos, changesets=count, entries=changelist,
225 rev=pos, changesets=count, entries=changelist,
226 archives=self.archivelist("tip"))
226 archives=self.archivelist("tip"))
227
227
228 def search(self, query):
228 def search(self, query):
229
229
230 def changelist(**map):
230 def changelist(**map):
231 cl = self.repo.changelog
231 cl = self.repo.changelog
232 count = 0
232 count = 0
233 qw = query.lower().split()
233 qw = query.lower().split()
234
234
235 def revgen():
235 def revgen():
236 for i in range(cl.count() - 1, 0, -100):
236 for i in range(cl.count() - 1, 0, -100):
237 l = []
237 l = []
238 for j in range(max(0, i - 100), i):
238 for j in range(max(0, i - 100), i):
239 n = cl.node(j)
239 n = cl.node(j)
240 changes = cl.read(n)
240 changes = cl.read(n)
241 l.append((n, j, changes))
241 l.append((n, j, changes))
242 l.reverse()
242 l.reverse()
243 for e in l:
243 for e in l:
244 yield e
244 yield e
245
245
246 for n, i, changes in revgen():
246 for n, i, changes in revgen():
247 miss = 0
247 miss = 0
248 for q in qw:
248 for q in qw:
249 if not (q in changes[1].lower() or
249 if not (q in changes[1].lower() or
250 q in changes[4].lower() or
250 q in changes[4].lower() or
251 q in " ".join(changes[3][:20]).lower()):
251 q in " ".join(changes[3][:20]).lower()):
252 miss = 1
252 miss = 1
253 break
253 break
254 if miss:
254 if miss:
255 continue
255 continue
256
256
257 count += 1
257 count += 1
258 hn = hex(n)
258 hn = hex(n)
259
259
260 yield self.t('searchentry',
260 yield self.t('searchentry',
261 parity=count & 1,
261 parity=count & 1,
262 author=changes[1],
262 author=changes[1],
263 parent=self.siblings(cl.parents(n), cl.rev),
263 parent=self.siblings(cl.parents(n), cl.rev),
264 child=self.siblings(cl.children(n), cl.rev),
264 child=self.siblings(cl.children(n), cl.rev),
265 changelogtag=self.showtag("changelogtag",n),
265 changelogtag=self.showtag("changelogtag",n),
266 manifest=hex(changes[0]),
266 manifest=hex(changes[0]),
267 desc=changes[4],
267 desc=changes[4],
268 date=changes[2],
268 date=changes[2],
269 files=self.listfilediffs(changes[3], n),
269 files=self.listfilediffs(changes[3], n),
270 rev=i,
270 rev=i,
271 node=hn)
271 node=hn)
272
272
273 if count >= self.maxchanges:
273 if count >= self.maxchanges:
274 break
274 break
275
275
276 cl = self.repo.changelog
276 cl = self.repo.changelog
277 mf = cl.read(cl.tip())[0]
277 mf = cl.read(cl.tip())[0]
278
278
279 yield self.t('search',
279 yield self.t('search',
280 query=query,
280 query=query,
281 manifest=hex(mf),
281 manifest=hex(mf),
282 entries=changelist)
282 entries=changelist)
283
283
284 def changeset(self, nodeid):
284 def changeset(self, nodeid):
285 cl = self.repo.changelog
285 cl = self.repo.changelog
286 n = self.repo.lookup(nodeid)
286 n = self.repo.lookup(nodeid)
287 nodeid = hex(n)
287 nodeid = hex(n)
288 changes = cl.read(n)
288 changes = cl.read(n)
289 p1 = cl.parents(n)[0]
289 p1 = cl.parents(n)[0]
290
290
291 files = []
291 files = []
292 mf = self.repo.manifest.read(changes[0])
292 mf = self.repo.manifest.read(changes[0])
293 for f in changes[3]:
293 for f in changes[3]:
294 files.append(self.t("filenodelink",
294 files.append(self.t("filenodelink",
295 filenode=hex(mf.get(f, nullid)), file=f))
295 filenode=hex(mf.get(f, nullid)), file=f))
296
296
297 def diff(**map):
297 def diff(**map):
298 yield self.diff(p1, n, None)
298 yield self.diff(p1, n, None)
299
299
300 yield self.t('changeset',
300 yield self.t('changeset',
301 diff=diff,
301 diff=diff,
302 rev=cl.rev(n),
302 rev=cl.rev(n),
303 node=nodeid,
303 node=nodeid,
304 parent=self.siblings(cl.parents(n), cl.rev),
304 parent=self.siblings(cl.parents(n), cl.rev),
305 child=self.siblings(cl.children(n), cl.rev),
305 child=self.siblings(cl.children(n), cl.rev),
306 changesettag=self.showtag("changesettag",n),
306 changesettag=self.showtag("changesettag",n),
307 manifest=hex(changes[0]),
307 manifest=hex(changes[0]),
308 author=changes[1],
308 author=changes[1],
309 desc=changes[4],
309 desc=changes[4],
310 date=changes[2],
310 date=changes[2],
311 files=files,
311 files=files,
312 archives=self.archivelist(nodeid))
312 archives=self.archivelist(nodeid))
313
313
314 def filelog(self, f, filenode):
314 def filelog(self, f, filenode):
315 cl = self.repo.changelog
315 cl = self.repo.changelog
316 fl = self.repo.file(f)
316 fl = self.repo.file(f)
317 filenode = hex(fl.lookup(filenode))
317 filenode = hex(fl.lookup(filenode))
318 count = fl.count()
318 count = fl.count()
319
319
320 def entries(**map):
320 def entries(**map):
321 l = []
321 l = []
322 parity = (count - 1) & 1
322 parity = (count - 1) & 1
323
323
324 for i in range(count):
324 for i in range(count):
325 n = fl.node(i)
325 n = fl.node(i)
326 lr = fl.linkrev(n)
326 lr = fl.linkrev(n)
327 cn = cl.node(lr)
327 cn = cl.node(lr)
328 cs = cl.read(cl.node(lr))
328 cs = cl.read(cl.node(lr))
329
329
330 l.insert(0, {"parity": parity,
330 l.insert(0, {"parity": parity,
331 "filenode": hex(n),
331 "filenode": hex(n),
332 "filerev": i,
332 "filerev": i,
333 "file": f,
333 "file": f,
334 "node": hex(cn),
334 "node": hex(cn),
335 "author": cs[1],
335 "author": cs[1],
336 "date": cs[2],
336 "date": cs[2],
337 "rename": self.renamelink(fl, n),
337 "rename": self.renamelink(fl, n),
338 "parent": self.siblings(fl.parents(n),
338 "parent": self.siblings(fl.parents(n),
339 fl.rev, file=f),
339 fl.rev, file=f),
340 "child": self.siblings(fl.children(n),
340 "child": self.siblings(fl.children(n),
341 fl.rev, file=f),
341 fl.rev, file=f),
342 "desc": cs[4]})
342 "desc": cs[4]})
343 parity = 1 - parity
343 parity = 1 - parity
344
344
345 for e in l:
345 for e in l:
346 yield e
346 yield e
347
347
348 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
348 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
349
349
350 def filerevision(self, f, node):
350 def filerevision(self, f, node):
351 fl = self.repo.file(f)
351 fl = self.repo.file(f)
352 n = fl.lookup(node)
352 n = fl.lookup(node)
353 node = hex(n)
353 node = hex(n)
354 text = fl.read(n)
354 text = fl.read(n)
355 changerev = fl.linkrev(n)
355 changerev = fl.linkrev(n)
356 cl = self.repo.changelog
356 cl = self.repo.changelog
357 cn = cl.node(changerev)
357 cn = cl.node(changerev)
358 cs = cl.read(cn)
358 cs = cl.read(cn)
359 mfn = cs[0]
359 mfn = cs[0]
360
360
361 mt = mimetypes.guess_type(f)[0]
361 mt = mimetypes.guess_type(f)[0]
362 rawtext = text
362 rawtext = text
363 if util.binary(text):
363 if util.binary(text):
364 mt = mt or 'application/octet-stream'
364 mt = mt or 'application/octet-stream'
365 text = "(binary:%s)" % mt
365 text = "(binary:%s)" % mt
366 mt = mt or 'text/plain'
366 mt = mt or 'text/plain'
367
367
368 def lines():
368 def lines():
369 for l, t in enumerate(text.splitlines(1)):
369 for l, t in enumerate(text.splitlines(1)):
370 yield {"line": t,
370 yield {"line": t,
371 "linenumber": "% 6d" % (l + 1),
371 "linenumber": "% 6d" % (l + 1),
372 "parity": l & 1}
372 "parity": l & 1}
373
373
374 yield self.t("filerevision",
374 yield self.t("filerevision",
375 file=f,
375 file=f,
376 filenode=node,
376 filenode=node,
377 path=_up(f),
377 path=_up(f),
378 text=lines(),
378 text=lines(),
379 raw=rawtext,
379 raw=rawtext,
380 mimetype=mt,
380 mimetype=mt,
381 rev=changerev,
381 rev=changerev,
382 node=hex(cn),
382 node=hex(cn),
383 manifest=hex(mfn),
383 manifest=hex(mfn),
384 author=cs[1],
384 author=cs[1],
385 date=cs[2],
385 date=cs[2],
386 parent=self.siblings(fl.parents(n), fl.rev, file=f),
386 parent=self.siblings(fl.parents(n), fl.rev, file=f),
387 child=self.siblings(fl.children(n), fl.rev, file=f),
387 child=self.siblings(fl.children(n), fl.rev, file=f),
388 rename=self.renamelink(fl, n),
388 rename=self.renamelink(fl, n),
389 permissions=self.repo.manifest.readflags(mfn)[f])
389 permissions=self.repo.manifest.readflags(mfn)[f])
390
390
391 def fileannotate(self, f, node):
391 def fileannotate(self, f, node):
392 bcache = {}
392 bcache = {}
393 ncache = {}
393 ncache = {}
394 fl = self.repo.file(f)
394 fl = self.repo.file(f)
395 n = fl.lookup(node)
395 n = fl.lookup(node)
396 node = hex(n)
396 node = hex(n)
397 changerev = fl.linkrev(n)
397 changerev = fl.linkrev(n)
398
398
399 cl = self.repo.changelog
399 cl = self.repo.changelog
400 cn = cl.node(changerev)
400 cn = cl.node(changerev)
401 cs = cl.read(cn)
401 cs = cl.read(cn)
402 mfn = cs[0]
402 mfn = cs[0]
403
403
404 def annotate(**map):
404 def annotate(**map):
405 parity = 1
405 parity = 1
406 last = None
406 last = None
407 for r, l in fl.annotate(n):
407 for r, l in fl.annotate(n):
408 try:
408 try:
409 cnode = ncache[r]
409 cnode = ncache[r]
410 except KeyError:
410 except KeyError:
411 cnode = ncache[r] = self.repo.changelog.node(r)
411 cnode = ncache[r] = self.repo.changelog.node(r)
412
412
413 try:
413 try:
414 name = bcache[r]
414 name = bcache[r]
415 except KeyError:
415 except KeyError:
416 cl = self.repo.changelog.read(cnode)
416 cl = self.repo.changelog.read(cnode)
417 bcache[r] = name = self.repo.ui.shortuser(cl[1])
417 bcache[r] = name = self.repo.ui.shortuser(cl[1])
418
418
419 if last != cnode:
419 if last != cnode:
420 parity = 1 - parity
420 parity = 1 - parity
421 last = cnode
421 last = cnode
422
422
423 yield {"parity": parity,
423 yield {"parity": parity,
424 "node": hex(cnode),
424 "node": hex(cnode),
425 "rev": r,
425 "rev": r,
426 "author": name,
426 "author": name,
427 "file": f,
427 "file": f,
428 "line": l}
428 "line": l}
429
429
430 yield self.t("fileannotate",
430 yield self.t("fileannotate",
431 file=f,
431 file=f,
432 filenode=node,
432 filenode=node,
433 annotate=annotate,
433 annotate=annotate,
434 path=_up(f),
434 path=_up(f),
435 rev=changerev,
435 rev=changerev,
436 node=hex(cn),
436 node=hex(cn),
437 manifest=hex(mfn),
437 manifest=hex(mfn),
438 author=cs[1],
438 author=cs[1],
439 date=cs[2],
439 date=cs[2],
440 rename=self.renamelink(fl, n),
440 rename=self.renamelink(fl, n),
441 parent=self.siblings(fl.parents(n), fl.rev, file=f),
441 parent=self.siblings(fl.parents(n), fl.rev, file=f),
442 child=self.siblings(fl.children(n), fl.rev, file=f),
442 child=self.siblings(fl.children(n), fl.rev, file=f),
443 permissions=self.repo.manifest.readflags(mfn)[f])
443 permissions=self.repo.manifest.readflags(mfn)[f])
444
444
445 def manifest(self, mnode, path):
445 def manifest(self, mnode, path):
446 man = self.repo.manifest
446 man = self.repo.manifest
447 mn = man.lookup(mnode)
447 mn = man.lookup(mnode)
448 mnode = hex(mn)
448 mnode = hex(mn)
449 mf = man.read(mn)
449 mf = man.read(mn)
450 rev = man.rev(mn)
450 rev = man.rev(mn)
451 changerev = man.linkrev(mn)
451 changerev = man.linkrev(mn)
452 node = self.repo.changelog.node(changerev)
452 node = self.repo.changelog.node(changerev)
453 mff = man.readflags(mn)
453 mff = man.readflags(mn)
454
454
455 files = {}
455 files = {}
456
456
457 p = path[1:]
457 p = path[1:]
458 if p and p[-1] != "/":
458 if p and p[-1] != "/":
459 p += "/"
459 p += "/"
460 l = len(p)
460 l = len(p)
461
461
462 for f,n in mf.items():
462 for f,n in mf.items():
463 if f[:l] != p:
463 if f[:l] != p:
464 continue
464 continue
465 remain = f[l:]
465 remain = f[l:]
466 if "/" in remain:
466 if "/" in remain:
467 short = remain[:remain.find("/") + 1] # bleah
467 short = remain[:remain.find("/") + 1] # bleah
468 files[short] = (f, None)
468 files[short] = (f, None)
469 else:
469 else:
470 short = os.path.basename(remain)
470 short = os.path.basename(remain)
471 files[short] = (f, n)
471 files[short] = (f, n)
472
472
473 def filelist(**map):
473 def filelist(**map):
474 parity = 0
474 parity = 0
475 fl = files.keys()
475 fl = files.keys()
476 fl.sort()
476 fl.sort()
477 for f in fl:
477 for f in fl:
478 full, fnode = files[f]
478 full, fnode = files[f]
479 if not fnode:
479 if not fnode:
480 continue
480 continue
481
481
482 yield {"file": full,
482 yield {"file": full,
483 "manifest": mnode,
483 "manifest": mnode,
484 "filenode": hex(fnode),
484 "filenode": hex(fnode),
485 "parity": parity,
485 "parity": parity,
486 "basename": f,
486 "basename": f,
487 "permissions": mff[full]}
487 "permissions": mff[full]}
488 parity = 1 - parity
488 parity = 1 - parity
489
489
490 def dirlist(**map):
490 def dirlist(**map):
491 parity = 0
491 parity = 0
492 fl = files.keys()
492 fl = files.keys()
493 fl.sort()
493 fl.sort()
494 for f in fl:
494 for f in fl:
495 full, fnode = files[f]
495 full, fnode = files[f]
496 if fnode:
496 if fnode:
497 continue
497 continue
498
498
499 yield {"parity": parity,
499 yield {"parity": parity,
500 "path": os.path.join(path, f),
500 "path": os.path.join(path, f),
501 "manifest": mnode,
501 "manifest": mnode,
502 "basename": f[:-1]}
502 "basename": f[:-1]}
503 parity = 1 - parity
503 parity = 1 - parity
504
504
505 yield self.t("manifest",
505 yield self.t("manifest",
506 manifest=mnode,
506 manifest=mnode,
507 rev=rev,
507 rev=rev,
508 node=hex(node),
508 node=hex(node),
509 path=path,
509 path=path,
510 up=_up(path),
510 up=_up(path),
511 fentries=filelist,
511 fentries=filelist,
512 dentries=dirlist,
512 dentries=dirlist,
513 archives=self.archivelist(hex(node)))
513 archives=self.archivelist(hex(node)))
514
514
515 def tags(self):
515 def tags(self):
516 cl = self.repo.changelog
516 cl = self.repo.changelog
517 mf = cl.read(cl.tip())[0]
517 mf = cl.read(cl.tip())[0]
518
518
519 i = self.repo.tagslist()
519 i = self.repo.tagslist()
520 i.reverse()
520 i.reverse()
521
521
522 def entries(notip=False, **map):
522 def entries(notip=False, **map):
523 parity = 0
523 parity = 0
524 for k,n in i:
524 for k,n in i:
525 if notip and k == "tip": continue
525 if notip and k == "tip": continue
526 yield {"parity": parity,
526 yield {"parity": parity,
527 "tag": k,
527 "tag": k,
528 "tagmanifest": hex(cl.read(n)[0]),
528 "tagmanifest": hex(cl.read(n)[0]),
529 "date": cl.read(n)[2],
529 "date": cl.read(n)[2],
530 "node": hex(n)}
530 "node": hex(n)}
531 parity = 1 - parity
531 parity = 1 - parity
532
532
533 yield self.t("tags",
533 yield self.t("tags",
534 manifest=hex(mf),
534 manifest=hex(mf),
535 entries=lambda **x: entries(False, **x),
535 entries=lambda **x: entries(False, **x),
536 entriesnotip=lambda **x: entries(True, **x))
536 entriesnotip=lambda **x: entries(True, **x))
537
537
538 def summary(self):
538 def summary(self):
539 cl = self.repo.changelog
539 cl = self.repo.changelog
540 mf = cl.read(cl.tip())[0]
540 mf = cl.read(cl.tip())[0]
541
541
542 i = self.repo.tagslist()
542 i = self.repo.tagslist()
543 i.reverse()
543 i.reverse()
544
544
545 def tagentries(**map):
545 def tagentries(**map):
546 parity = 0
546 parity = 0
547 count = 0
547 count = 0
548 for k,n in i:
548 for k,n in i:
549 if k == "tip": # skip tip
549 if k == "tip": # skip tip
550 continue;
550 continue;
551
551
552 count += 1
552 count += 1
553 if count > 10: # limit to 10 tags
553 if count > 10: # limit to 10 tags
554 break;
554 break;
555
555
556 c = cl.read(n)
556 c = cl.read(n)
557 m = c[0]
557 m = c[0]
558 t = c[2]
558 t = c[2]
559
559
560 yield self.t("tagentry",
560 yield self.t("tagentry",
561 parity = parity,
561 parity = parity,
562 tag = k,
562 tag = k,
563 node = hex(n),
563 node = hex(n),
564 date = t,
564 date = t,
565 tagmanifest = hex(m))
565 tagmanifest = hex(m))
566 parity = 1 - parity
566 parity = 1 - parity
567
567
568 def changelist(**map):
568 def changelist(**map):
569 parity = 0
569 parity = 0
570 cl = self.repo.changelog
570 cl = self.repo.changelog
571 l = [] # build a list in forward order for efficiency
571 l = [] # build a list in forward order for efficiency
572 for i in range(start, end):
572 for i in range(start, end):
573 n = cl.node(i)
573 n = cl.node(i)
574 changes = cl.read(n)
574 changes = cl.read(n)
575 hn = hex(n)
575 hn = hex(n)
576 t = changes[2]
576 t = changes[2]
577
577
578 l.insert(0, self.t(
578 l.insert(0, self.t(
579 'shortlogentry',
579 'shortlogentry',
580 parity = parity,
580 parity = parity,
581 author = changes[1],
581 author = changes[1],
582 manifest = hex(changes[0]),
582 manifest = hex(changes[0]),
583 desc = changes[4],
583 desc = changes[4],
584 date = t,
584 date = t,
585 rev = i,
585 rev = i,
586 node = hn))
586 node = hn))
587 parity = 1 - parity
587 parity = 1 - parity
588
588
589 yield l
589 yield l
590
590
591 cl = self.repo.changelog
591 cl = self.repo.changelog
592 mf = cl.read(cl.tip())[0]
592 mf = cl.read(cl.tip())[0]
593 count = cl.count()
593 count = cl.count()
594 start = max(0, count - self.maxchanges)
594 start = max(0, count - self.maxchanges)
595 end = min(count, start + self.maxchanges)
595 end = min(count, start + self.maxchanges)
596
596
597 yield self.t("summary",
597 yield self.t("summary",
598 desc = self.repo.ui.config("web", "description", "unknown"),
598 desc = self.repo.ui.config("web", "description", "unknown"),
599 owner = (self.repo.ui.config("ui", "username") or # preferred
599 owner = (self.repo.ui.config("ui", "username") or # preferred
600 self.repo.ui.config("web", "contact") or # deprecated
600 self.repo.ui.config("web", "contact") or # deprecated
601 self.repo.ui.config("web", "author", "unknown")), # also
601 self.repo.ui.config("web", "author", "unknown")), # also
602 lastchange = (0, 0), # FIXME
602 lastchange = (0, 0), # FIXME
603 manifest = hex(mf),
603 manifest = hex(mf),
604 tags = tagentries,
604 tags = tagentries,
605 shortlog = changelist)
605 shortlog = changelist)
606
606
607 def filediff(self, file, changeset):
607 def filediff(self, file, changeset):
608 cl = self.repo.changelog
608 cl = self.repo.changelog
609 n = self.repo.lookup(changeset)
609 n = self.repo.lookup(changeset)
610 changeset = hex(n)
610 changeset = hex(n)
611 p1 = cl.parents(n)[0]
611 p1 = cl.parents(n)[0]
612 cs = cl.read(n)
612 cs = cl.read(n)
613 mf = self.repo.manifest.read(cs[0])
613 mf = self.repo.manifest.read(cs[0])
614
614
615 def diff(**map):
615 def diff(**map):
616 yield self.diff(p1, n, [file])
616 yield self.diff(p1, n, [file])
617
617
618 yield self.t("filediff",
618 yield self.t("filediff",
619 file=file,
619 file=file,
620 filenode=hex(mf.get(file, nullid)),
620 filenode=hex(mf.get(file, nullid)),
621 node=changeset,
621 node=changeset,
622 rev=self.repo.changelog.rev(n),
622 rev=self.repo.changelog.rev(n),
623 parent=self.siblings(cl.parents(n), cl.rev),
623 parent=self.siblings(cl.parents(n), cl.rev),
624 child=self.siblings(cl.children(n), cl.rev),
624 child=self.siblings(cl.children(n), cl.rev),
625 diff=diff)
625 diff=diff)
626
626
627 archive_specs = {
627 archive_specs = {
628 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
628 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
629 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
629 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
630 'zip': ('application/zip', 'zip', '.zip', None),
630 'zip': ('application/zip', 'zip', '.zip', None),
631 }
631 }
632
632
633 def archive(self, req, cnode, type_):
633 def archive(self, req, cnode, type_):
634 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
634 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
635 name = "%s-%s" % (reponame, short(cnode))
635 name = "%s-%s" % (reponame, short(cnode))
636 mimetype, artype, extension, encoding = self.archive_specs[type_]
636 mimetype, artype, extension, encoding = self.archive_specs[type_]
637 headers = [('Content-type', mimetype),
637 headers = [('Content-type', mimetype),
638 ('Content-disposition', 'attachment; filename=%s%s' %
638 ('Content-disposition', 'attachment; filename=%s%s' %
639 (name, extension))]
639 (name, extension))]
640 if encoding:
640 if encoding:
641 headers.append(('Content-encoding', encoding))
641 headers.append(('Content-encoding', encoding))
642 req.header(headers)
642 req.header(headers)
643 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
643 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
644
644
645 # add tags to things
645 # add tags to things
646 # tags -> list of changesets corresponding to tags
646 # tags -> list of changesets corresponding to tags
647 # find tag, changeset, file
647 # find tag, changeset, file
648
648
649 def cleanpath(self, path):
649 def cleanpath(self, path):
650 p = util.normpath(path)
650 p = util.normpath(path)
651 if p[:2] == "..":
651 if p[:2] == "..":
652 raise Exception("suspicious path")
652 raise Exception("suspicious path")
653 return p
653 return p
654
654
655 def run(self, req=hgrequest()):
655 def run(self, req=hgrequest()):
656 def header(**map):
656 def header(**map):
657 yield self.t("header", **map)
657 yield self.t("header", **map)
658
658
659 def footer(**map):
659 def footer(**map):
660 yield self.t("footer",
660 yield self.t("footer",
661 motd=self.repo.ui.config("web", "motd", ""),
661 motd=self.repo.ui.config("web", "motd", ""),
662 **map)
662 **map)
663
663
664 def expand_form(form):
664 def expand_form(form):
665 shortcuts = {
665 shortcuts = {
666 'cl': [('cmd', ['changelog']), ('rev', None)],
666 'cl': [('cmd', ['changelog']), ('rev', None)],
667 'cs': [('cmd', ['changeset']), ('node', None)],
667 'cs': [('cmd', ['changeset']), ('node', None)],
668 'f': [('cmd', ['file']), ('filenode', None)],
668 'f': [('cmd', ['file']), ('filenode', None)],
669 'fl': [('cmd', ['filelog']), ('filenode', None)],
669 'fl': [('cmd', ['filelog']), ('filenode', None)],
670 'fd': [('cmd', ['filediff']), ('node', None)],
670 'fd': [('cmd', ['filediff']), ('node', None)],
671 'fa': [('cmd', ['annotate']), ('filenode', None)],
671 'fa': [('cmd', ['annotate']), ('filenode', None)],
672 'mf': [('cmd', ['manifest']), ('manifest', None)],
672 'mf': [('cmd', ['manifest']), ('manifest', None)],
673 'ca': [('cmd', ['archive']), ('node', None)],
673 'ca': [('cmd', ['archive']), ('node', None)],
674 'tags': [('cmd', ['tags'])],
674 'tags': [('cmd', ['tags'])],
675 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
675 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
676 'static': [('cmd', ['static']), ('file', None)]
676 'static': [('cmd', ['static']), ('file', None)]
677 }
677 }
678
678
679 for k in shortcuts.iterkeys():
679 for k in shortcuts.iterkeys():
680 if form.has_key(k):
680 if form.has_key(k):
681 for name, value in shortcuts[k]:
681 for name, value in shortcuts[k]:
682 if value is None:
682 if value is None:
683 value = form[k]
683 value = form[k]
684 form[name] = value
684 form[name] = value
685 del form[k]
685 del form[k]
686
686
687 self.refresh()
687 self.refresh()
688
688
689 expand_form(req.form)
689 expand_form(req.form)
690
690
691 m = os.path.join(self.templatepath, "map")
691 m = os.path.join(self.templatepath, "map")
692 style = self.repo.ui.config("web", "style", "")
692 style = self.repo.ui.config("web", "style", "")
693 if req.form.has_key('style'):
693 if req.form.has_key('style'):
694 style = req.form['style'][0]
694 style = req.form['style'][0]
695 if style:
695 if style:
696 b = os.path.basename("map-" + style)
696 b = os.path.basename("map-" + style)
697 p = os.path.join(self.templatepath, b)
697 p = os.path.join(self.templatepath, b)
698 if os.path.isfile(p):
698 if os.path.isfile(p):
699 m = p
699 m = p
700
700
701 port = req.env["SERVER_PORT"]
701 port = req.env["SERVER_PORT"]
702 port = port != "80" and (":" + port) or ""
702 port = port != "80" and (":" + port) or ""
703 uri = req.env["REQUEST_URI"]
703 uri = req.env["REQUEST_URI"]
704 if "?" in uri:
704 if "?" in uri:
705 uri = uri.split("?")[0]
705 uri = uri.split("?")[0]
706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
706 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
707 if not self.reponame:
707 if not self.reponame:
708 self.reponame = (self.repo.ui.config("web", "name")
708 self.reponame = (self.repo.ui.config("web", "name")
709 or uri.strip('/') or self.repo.root)
709 or uri.strip('/') or self.repo.root)
710
710
711 self.t = templater.templater(m, templater.common_filters,
711 self.t = templater.templater(m, templater.common_filters,
712 defaults={"url": url,
712 defaults={"url": url,
713 "repo": self.reponame,
713 "repo": self.reponame,
714 "header": header,
714 "header": header,
715 "footer": footer,
715 "footer": footer,
716 })
716 })
717
717
718 if not req.form.has_key('cmd'):
718 if not req.form.has_key('cmd'):
719 req.form['cmd'] = [self.t.cache['default'],]
719 req.form['cmd'] = [self.t.cache['default'],]
720
720
721 cmd = req.form['cmd'][0]
721 cmd = req.form['cmd'][0]
722
722
723 method = getattr(self, 'do_' + cmd, None)
723 method = getattr(self, 'do_' + cmd, None)
724 if method:
724 if method:
725 method(req)
725 method(req)
726 else:
726 else:
727 req.write(self.t("error"))
727 req.write(self.t("error"))
728 req.done()
728 req.done()
729
729
730 def do_changelog(self, req):
730 def do_changelog(self, req):
731 hi = self.repo.changelog.count() - 1
731 hi = self.repo.changelog.count() - 1
732 if req.form.has_key('rev'):
732 if req.form.has_key('rev'):
733 hi = req.form['rev'][0]
733 hi = req.form['rev'][0]
734 try:
734 try:
735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
736 except hg.RepoError:
736 except hg.RepoError:
737 req.write(self.search(hi)) # XXX redirect to 404 page?
737 req.write(self.search(hi)) # XXX redirect to 404 page?
738 return
738 return
739
739
740 req.write(self.changelog(hi))
740 req.write(self.changelog(hi))
741
741
742 def do_changeset(self, req):
742 def do_changeset(self, req):
743 req.write(self.changeset(req.form['node'][0]))
743 req.write(self.changeset(req.form['node'][0]))
744
744
745 def do_manifest(self, req):
745 def do_manifest(self, req):
746 req.write(self.manifest(req.form['manifest'][0],
746 req.write(self.manifest(req.form['manifest'][0],
747 self.cleanpath(req.form['path'][0])))
747 self.cleanpath(req.form['path'][0])))
748
748
749 def do_tags(self, req):
749 def do_tags(self, req):
750 req.write(self.tags())
750 req.write(self.tags())
751
751
752 def do_summary(self, req):
752 def do_summary(self, req):
753 req.write(self.summary())
753 req.write(self.summary())
754
754
755 def do_filediff(self, req):
755 def do_filediff(self, req):
756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
757 req.form['node'][0]))
757 req.form['node'][0]))
758
758
759 def do_file(self, req):
759 def do_file(self, req):
760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
761 req.form['filenode'][0]))
761 req.form['filenode'][0]))
762
762
763 def do_annotate(self, req):
763 def do_annotate(self, req):
764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
765 req.form['filenode'][0]))
765 req.form['filenode'][0]))
766
766
767 def do_filelog(self, req):
767 def do_filelog(self, req):
768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
769 req.form['filenode'][0]))
769 req.form['filenode'][0]))
770
770
771 def do_heads(self, req):
771 def do_heads(self, req):
772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
773 req.httphdr("application/mercurial-0.1", length=len(resp))
773 req.httphdr("application/mercurial-0.1", length=len(resp))
774 req.write(resp)
774 req.write(resp)
775
775
776 def do_branches(self, req):
776 def do_branches(self, req):
777 nodes = []
777 nodes = []
778 if req.form.has_key('nodes'):
778 if req.form.has_key('nodes'):
779 nodes = map(bin, req.form['nodes'][0].split(" "))
779 nodes = map(bin, req.form['nodes'][0].split(" "))
780 resp = cStringIO.StringIO()
780 resp = cStringIO.StringIO()
781 for b in self.repo.branches(nodes):
781 for b in self.repo.branches(nodes):
782 resp.write(" ".join(map(hex, b)) + "\n")
782 resp.write(" ".join(map(hex, b)) + "\n")
783 resp = resp.getvalue()
783 resp = resp.getvalue()
784 req.httphdr("application/mercurial-0.1", length=len(resp))
784 req.httphdr("application/mercurial-0.1", length=len(resp))
785 req.write(resp)
785 req.write(resp)
786
786
787 def do_between(self, req):
787 def do_between(self, req):
788 nodes = []
788 nodes = []
789 if req.form.has_key('pairs'):
789 if req.form.has_key('pairs'):
790 pairs = [map(bin, p.split("-"))
790 pairs = [map(bin, p.split("-"))
791 for p in req.form['pairs'][0].split(" ")]
791 for p in req.form['pairs'][0].split(" ")]
792 resp = cStringIO.StringIO()
792 resp = cStringIO.StringIO()
793 for b in self.repo.between(pairs):
793 for b in self.repo.between(pairs):
794 resp.write(" ".join(map(hex, b)) + "\n")
794 resp.write(" ".join(map(hex, b)) + "\n")
795 resp = resp.getvalue()
795 resp = resp.getvalue()
796 req.httphdr("application/mercurial-0.1", length=len(resp))
796 req.httphdr("application/mercurial-0.1", length=len(resp))
797 req.write(resp)
797 req.write(resp)
798
798
799 def do_changegroup(self, req):
799 def do_changegroup(self, req):
800 req.httphdr("application/mercurial-0.1")
800 req.httphdr("application/mercurial-0.1")
801 nodes = []
801 nodes = []
802 if not self.allowpull:
802 if not self.allowpull:
803 return
803 return
804
804
805 if req.form.has_key('roots'):
805 if req.form.has_key('roots'):
806 nodes = map(bin, req.form['roots'][0].split(" "))
806 nodes = map(bin, req.form['roots'][0].split(" "))
807
807
808 z = zlib.compressobj()
808 z = zlib.compressobj()
809 f = self.repo.changegroup(nodes, 'serve')
809 f = self.repo.changegroup(nodes, 'serve')
810 while 1:
810 while 1:
811 chunk = f.read(4096)
811 chunk = f.read(4096)
812 if not chunk:
812 if not chunk:
813 break
813 break
814 req.write(z.compress(chunk))
814 req.write(z.compress(chunk))
815
815
816 req.write(z.flush())
816 req.write(z.flush())
817
817
818 def do_archive(self, req):
818 def do_archive(self, req):
819 changeset = self.repo.lookup(req.form['node'][0])
819 changeset = self.repo.lookup(req.form['node'][0])
820 type_ = req.form['type'][0]
820 type_ = req.form['type'][0]
821 allowed = self.repo.ui.config("web", "allow_archive", "").split()
821 allowed = self.repo.ui.config("web", "allow_archive", "").split()
822 if (type_ in self.archives and (type_ in allowed or
822 if (type_ in self.archives and (type_ in allowed or
823 self.repo.ui.configbool("web", "allow" + type_, False))):
823 self.repo.ui.configbool("web", "allow" + type_, False))):
824 self.archive(req, changeset, type_)
824 self.archive(req, changeset, type_)
825 return
825 return
826
826
827 req.write(self.t("error"))
827 req.write(self.t("error"))
828
828
829 def do_static(self, req):
829 def do_static(self, req):
830 fname = req.form['file'][0]
830 fname = req.form['file'][0]
831 static = self.repo.ui.config("web", "static",
831 static = self.repo.ui.config("web", "static",
832 os.path.join(self.templatepath,
832 os.path.join(self.templatepath,
833 "static"))
833 "static"))
834 req.write(staticfile(static, fname)
834 req.write(staticfile(static, fname)
835 or self.t("error", error="%r not found" % fname))
835 or self.t("error", error="%r not found" % fname))
836
837 def do_capabilities(self, req):
838 resp = ''
839 req.httphdr("application/mercurial-0.1", length=len(resp))
840 req.write(resp)
841
@@ -1,242 +1,254 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from remoterepo import *
9 from remoterepo import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13 demandload(globals(), "keepalive")
13 demandload(globals(), "keepalive")
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgr):
15 class passwordmgr(urllib2.HTTPPasswordMgr):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgr.__init__(self)
17 urllib2.HTTPPasswordMgr.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgr.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgr.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 if authinfo != (None, None):
23 if authinfo != (None, None):
24 return authinfo
24 return authinfo
25
25
26 if not ui.interactive:
26 if not ui.interactive:
27 raise util.Abort(_('http authorization required'))
27 raise util.Abort(_('http authorization required'))
28
28
29 self.ui.write(_("http authorization required\n"))
29 self.ui.write(_("http authorization required\n"))
30 self.ui.status(_("realm: %s\n") % realm)
30 self.ui.status(_("realm: %s\n") % realm)
31 user = self.ui.prompt(_("user:"), default=None)
31 user = self.ui.prompt(_("user:"), default=None)
32 passwd = self.ui.getpass()
32 passwd = self.ui.getpass()
33
33
34 self.add_password(realm, authuri, user, passwd)
34 self.add_password(realm, authuri, user, passwd)
35 return (user, passwd)
35 return (user, passwd)
36
36
37 def netlocsplit(netloc):
37 def netlocsplit(netloc):
38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39
39
40 a = netloc.find('@')
40 a = netloc.find('@')
41 if a == -1:
41 if a == -1:
42 user, passwd = None, None
42 user, passwd = None, None
43 else:
43 else:
44 userpass, netloc = netloc[:a], netloc[a+1:]
44 userpass, netloc = netloc[:a], netloc[a+1:]
45 c = userpass.find(':')
45 c = userpass.find(':')
46 if c == -1:
46 if c == -1:
47 user, passwd = urllib.unquote(userpass), None
47 user, passwd = urllib.unquote(userpass), None
48 else:
48 else:
49 user = urllib.unquote(userpass[:c])
49 user = urllib.unquote(userpass[:c])
50 passwd = urllib.unquote(userpass[c+1:])
50 passwd = urllib.unquote(userpass[c+1:])
51 c = netloc.find(':')
51 c = netloc.find(':')
52 if c == -1:
52 if c == -1:
53 host, port = netloc, None
53 host, port = netloc, None
54 else:
54 else:
55 host, port = netloc[:c], netloc[c+1:]
55 host, port = netloc[:c], netloc[c+1:]
56 return host, port, user, passwd
56 return host, port, user, passwd
57
57
58 def netlocunsplit(host, port, user=None, passwd=None):
58 def netlocunsplit(host, port, user=None, passwd=None):
59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 if port:
60 if port:
61 hostport = host + ':' + port
61 hostport = host + ':' + port
62 else:
62 else:
63 hostport = host
63 hostport = host
64 if user:
64 if user:
65 if passwd:
65 if passwd:
66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
67 else:
67 else:
68 userpass = urllib.quote(user)
68 userpass = urllib.quote(user)
69 return userpass + '@' + hostport
69 return userpass + '@' + hostport
70 return hostport
70 return hostport
71
71
72 class httprepository(remoterepository):
72 class httprepository(remoterepository):
73 def __init__(self, ui, path):
73 def __init__(self, ui, path):
74 self.capabilities = ()
74 self.caps = None
75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
76 if query or frag:
76 if query or frag:
77 raise util.Abort(_('unsupported URL component: "%s"') %
77 raise util.Abort(_('unsupported URL component: "%s"') %
78 (query or frag))
78 (query or frag))
79 if not urlpath: urlpath = '/'
79 if not urlpath: urlpath = '/'
80 host, port, user, passwd = netlocsplit(netloc)
80 host, port, user, passwd = netlocsplit(netloc)
81
81
82 # urllib cannot handle URLs with embedded user or passwd
82 # urllib cannot handle URLs with embedded user or passwd
83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
84 urlpath, '', ''))
84 urlpath, '', ''))
85 self.ui = ui
85 self.ui = ui
86
86
87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
88 proxyauthinfo = None
88 proxyauthinfo = None
89 handler = keepalive.HTTPHandler()
89 handler = keepalive.HTTPHandler()
90
90
91 if proxyurl:
91 if proxyurl:
92 # proxy can be proper url or host[:port]
92 # proxy can be proper url or host[:port]
93 if not (proxyurl.startswith('http:') or
93 if not (proxyurl.startswith('http:') or
94 proxyurl.startswith('https:')):
94 proxyurl.startswith('https:')):
95 proxyurl = 'http://' + proxyurl + '/'
95 proxyurl = 'http://' + proxyurl + '/'
96 snpqf = urlparse.urlsplit(proxyurl)
96 snpqf = urlparse.urlsplit(proxyurl)
97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
98 hpup = netlocsplit(proxynetloc)
98 hpup = netlocsplit(proxynetloc)
99
99
100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
101 if not proxyuser:
101 if not proxyuser:
102 proxyuser = ui.config("http_proxy", "user")
102 proxyuser = ui.config("http_proxy", "user")
103 proxypasswd = ui.config("http_proxy", "passwd")
103 proxypasswd = ui.config("http_proxy", "passwd")
104
104
105 # see if we should use a proxy for this url
105 # see if we should use a proxy for this url
106 no_list = [ "localhost", "127.0.0.1" ]
106 no_list = [ "localhost", "127.0.0.1" ]
107 no_list.extend([p.strip().lower() for
107 no_list.extend([p.strip().lower() for
108 p in ui.config("http_proxy", "no", '').split(',')
108 p in ui.config("http_proxy", "no", '').split(',')
109 if p.strip()])
109 if p.strip()])
110 no_list.extend([p.strip().lower() for
110 no_list.extend([p.strip().lower() for
111 p in os.getenv("no_proxy", '').split(',')
111 p in os.getenv("no_proxy", '').split(',')
112 if p.strip()])
112 if p.strip()])
113 # "http_proxy.always" config is for running tests on localhost
113 # "http_proxy.always" config is for running tests on localhost
114 if (not ui.configbool("http_proxy", "always") and
114 if (not ui.configbool("http_proxy", "always") and
115 host.lower() in no_list):
115 host.lower() in no_list):
116 ui.debug(_('disabling proxy for %s\n') % host)
116 ui.debug(_('disabling proxy for %s\n') % host)
117 else:
117 else:
118 proxyurl = urlparse.urlunsplit((
118 proxyurl = urlparse.urlunsplit((
119 proxyscheme, netlocunsplit(proxyhost, proxyport,
119 proxyscheme, netlocunsplit(proxyhost, proxyport,
120 proxyuser, proxypasswd or ''),
120 proxyuser, proxypasswd or ''),
121 proxypath, proxyquery, proxyfrag))
121 proxypath, proxyquery, proxyfrag))
122 handler = urllib2.ProxyHandler({scheme: proxyurl})
122 handler = urllib2.ProxyHandler({scheme: proxyurl})
123 ui.debug(_('proxying through %s\n') % proxyurl)
123 ui.debug(_('proxying through %s\n') % proxyurl)
124
124
125 # urllib2 takes proxy values from the environment and those
125 # urllib2 takes proxy values from the environment and those
126 # will take precedence if found, so drop them
126 # will take precedence if found, so drop them
127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
128 try:
128 try:
129 if os.environ.has_key(env):
129 if os.environ.has_key(env):
130 del os.environ[env]
130 del os.environ[env]
131 except OSError:
131 except OSError:
132 pass
132 pass
133
133
134 passmgr = passwordmgr(ui)
134 passmgr = passwordmgr(ui)
135 if user:
135 if user:
136 ui.debug(_('will use user %s for http auth\n') % user)
136 ui.debug(_('will use user %s for http auth\n') % user)
137 passmgr.add_password(None, host, user, passwd or '')
137 passmgr.add_password(None, host, user, passwd or '')
138
138
139 opener = urllib2.build_opener(
139 opener = urllib2.build_opener(
140 handler,
140 handler,
141 urllib2.HTTPBasicAuthHandler(passmgr),
141 urllib2.HTTPBasicAuthHandler(passmgr),
142 urllib2.HTTPDigestAuthHandler(passmgr))
142 urllib2.HTTPDigestAuthHandler(passmgr))
143
143
144 # 1.0 here is the _protocol_ version
144 # 1.0 here is the _protocol_ version
145 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
145 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
146 urllib2.install_opener(opener)
146 urllib2.install_opener(opener)
147
147
148 # look up capabilities only when needed
149
150 def get_caps(self):
151 if self.caps is None:
152 try:
153 self.caps = self.do_read('capabilities').split()
154 except hg.RepoError:
155 self.caps = ()
156 return self.caps
157
158 capabilities = property(get_caps)
159
148 def dev(self):
160 def dev(self):
149 return -1
161 return -1
150
162
151 def lock(self):
163 def lock(self):
152 raise util.Abort(_('operation not supported over http'))
164 raise util.Abort(_('operation not supported over http'))
153
165
154 def do_cmd(self, cmd, **args):
166 def do_cmd(self, cmd, **args):
155 self.ui.debug(_("sending %s command\n") % cmd)
167 self.ui.debug(_("sending %s command\n") % cmd)
156 q = {"cmd": cmd}
168 q = {"cmd": cmd}
157 q.update(args)
169 q.update(args)
158 qs = urllib.urlencode(q)
170 qs = urllib.urlencode(q)
159 cu = "%s?%s" % (self.url, qs)
171 cu = "%s?%s" % (self.url, qs)
160 try:
172 try:
161 resp = urllib2.urlopen(cu)
173 resp = urllib2.urlopen(cu)
162 except httplib.HTTPException, inst:
174 except httplib.HTTPException, inst:
163 self.ui.debug(_('http error while sending %s command\n') % cmd)
175 self.ui.debug(_('http error while sending %s command\n') % cmd)
164 self.ui.print_exc()
176 self.ui.print_exc()
165 raise IOError(None, inst)
177 raise IOError(None, inst)
166 try:
178 try:
167 proto = resp.getheader('content-type')
179 proto = resp.getheader('content-type')
168 except AttributeError:
180 except AttributeError:
169 proto = resp.headers['content-type']
181 proto = resp.headers['content-type']
170
182
171 # accept old "text/plain" and "application/hg-changegroup" for now
183 # accept old "text/plain" and "application/hg-changegroup" for now
172 if not proto.startswith('application/mercurial') and \
184 if not proto.startswith('application/mercurial') and \
173 not proto.startswith('text/plain') and \
185 not proto.startswith('text/plain') and \
174 not proto.startswith('application/hg-changegroup'):
186 not proto.startswith('application/hg-changegroup'):
175 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
187 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
176 self.url)
188 self.url)
177
189
178 if proto.startswith('application/mercurial'):
190 if proto.startswith('application/mercurial'):
179 version = proto[22:]
191 version = proto[22:]
180 if float(version) > 0.1:
192 if float(version) > 0.1:
181 raise hg.RepoError(_("'%s' uses newer protocol %s") %
193 raise hg.RepoError(_("'%s' uses newer protocol %s") %
182 (self.url, version))
194 (self.url, version))
183
195
184 return resp
196 return resp
185
197
186 def do_read(self, cmd, **args):
198 def do_read(self, cmd, **args):
187 fp = self.do_cmd(cmd, **args)
199 fp = self.do_cmd(cmd, **args)
188 try:
200 try:
189 return fp.read()
201 return fp.read()
190 finally:
202 finally:
191 # if using keepalive, allow connection to be reused
203 # if using keepalive, allow connection to be reused
192 fp.close()
204 fp.close()
193
205
194 def heads(self):
206 def heads(self):
195 d = self.do_read("heads")
207 d = self.do_read("heads")
196 try:
208 try:
197 return map(bin, d[:-1].split(" "))
209 return map(bin, d[:-1].split(" "))
198 except:
210 except:
199 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
211 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
200 raise
212 raise
201
213
202 def branches(self, nodes):
214 def branches(self, nodes):
203 n = " ".join(map(hex, nodes))
215 n = " ".join(map(hex, nodes))
204 d = self.do_read("branches", nodes=n)
216 d = self.do_read("branches", nodes=n)
205 try:
217 try:
206 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
218 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
207 return br
219 return br
208 except:
220 except:
209 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
221 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
210 raise
222 raise
211
223
212 def between(self, pairs):
224 def between(self, pairs):
213 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
225 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
214 d = self.do_read("between", pairs=n)
226 d = self.do_read("between", pairs=n)
215 try:
227 try:
216 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
228 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
217 return p
229 return p
218 except:
230 except:
219 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
231 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
220 raise
232 raise
221
233
222 def changegroup(self, nodes, kind):
234 def changegroup(self, nodes, kind):
223 n = " ".join(map(hex, nodes))
235 n = " ".join(map(hex, nodes))
224 f = self.do_cmd("changegroup", roots=n)
236 f = self.do_cmd("changegroup", roots=n)
225 bytes = 0
237 bytes = 0
226
238
227 def zgenerator(f):
239 def zgenerator(f):
228 zd = zlib.decompressobj()
240 zd = zlib.decompressobj()
229 try:
241 try:
230 for chnk in f:
242 for chnk in f:
231 yield zd.decompress(chnk)
243 yield zd.decompress(chnk)
232 except httplib.HTTPException, inst:
244 except httplib.HTTPException, inst:
233 raise IOError(None, _('connection ended unexpectedly'))
245 raise IOError(None, _('connection ended unexpectedly'))
234 yield zd.flush()
246 yield zd.flush()
235
247
236 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
248 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
237
249
238 def unbundle(self, cg, heads, source):
250 def unbundle(self, cg, heads, source):
239 raise util.Abort(_('operation not supported over http'))
251 raise util.Abort(_('operation not supported over http'))
240
252
241 class httpsrepository(httprepository):
253 class httpsrepository(httprepository):
242 pass
254 pass
General Comments 0
You need to be logged in to leave comments. Login now