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