##// END OF EJS Templates
Adding changegroupsubset and lookup to web protocol so pull -r and...
Eric Hopper -
r3444:3505fcd5 default
parent child Browse files
Show More
@@ -1,1055 +1,1082 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 import os.path
10 import os.path
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), 'urllib')
14 demandload(globals(), 'urllib')
15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
16 demandload(globals(), "mercurial:revlog,templater")
16 demandload(globals(), "mercurial:revlog,templater")
17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
18 from mercurial.node import *
18 from mercurial.node import *
19 from mercurial.i18n import gettext as _
19 from mercurial.i18n import gettext as _
20
20
21 def _up(p):
21 def _up(p):
22 if p[0] != "/":
22 if p[0] != "/":
23 p = "/" + p
23 p = "/" + p
24 if p[-1] == "/":
24 if p[-1] == "/":
25 p = p[:-1]
25 p = p[:-1]
26 up = os.path.dirname(p)
26 up = os.path.dirname(p)
27 if up == "/":
27 if up == "/":
28 return "/"
28 return "/"
29 return up + "/"
29 return up + "/"
30
30
31 def revnavgen(pos, pagelen, limit, nodefunc):
31 def revnavgen(pos, pagelen, limit, nodefunc):
32 def seq(factor, limit=None):
32 def seq(factor, limit=None):
33 if limit:
33 if limit:
34 yield limit
34 yield limit
35 if limit >= 20 and limit <= 40:
35 if limit >= 20 and limit <= 40:
36 yield 50
36 yield 50
37 else:
37 else:
38 yield 1 * factor
38 yield 1 * factor
39 yield 3 * factor
39 yield 3 * factor
40 for f in seq(factor * 10):
40 for f in seq(factor * 10):
41 yield f
41 yield f
42
42
43 def nav(**map):
43 def nav(**map):
44 l = []
44 l = []
45 last = 0
45 last = 0
46 for f in seq(1, pagelen):
46 for f in seq(1, pagelen):
47 if f < pagelen or f <= last:
47 if f < pagelen or f <= last:
48 continue
48 continue
49 if f > limit:
49 if f > limit:
50 break
50 break
51 last = f
51 last = f
52 if pos + f < limit:
52 if pos + f < limit:
53 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
53 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
54 if pos - f >= 0:
54 if pos - f >= 0:
55 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
55 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
56
56
57 try:
57 try:
58 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
58 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
59
59
60 for label, node in l:
60 for label, node in l:
61 yield {"label": label, "node": node}
61 yield {"label": label, "node": node}
62
62
63 yield {"label": "tip", "node": "tip"}
63 yield {"label": "tip", "node": "tip"}
64 except hg.RepoError:
64 except hg.RepoError:
65 pass
65 pass
66
66
67 return nav
67 return nav
68
68
69 class hgweb(object):
69 class hgweb(object):
70 def __init__(self, repo, name=None):
70 def __init__(self, repo, name=None):
71 if type(repo) == type(""):
71 if type(repo) == type(""):
72 self.repo = hg.repository(ui.ui(), repo)
72 self.repo = hg.repository(ui.ui(), repo)
73 else:
73 else:
74 self.repo = repo
74 self.repo = repo
75
75
76 self.mtime = -1
76 self.mtime = -1
77 self.reponame = name
77 self.reponame = name
78 self.archives = 'zip', 'gz', 'bz2'
78 self.archives = 'zip', 'gz', 'bz2'
79 self.stripecount = 1
79 self.stripecount = 1
80 self.templatepath = self.repo.ui.config("web", "templates",
80 self.templatepath = self.repo.ui.config("web", "templates",
81 templater.templatepath())
81 templater.templatepath())
82
82
83 def refresh(self):
83 def refresh(self):
84 mtime = get_mtime(self.repo.root)
84 mtime = get_mtime(self.repo.root)
85 if mtime != self.mtime:
85 if mtime != self.mtime:
86 self.mtime = mtime
86 self.mtime = mtime
87 self.repo = hg.repository(self.repo.ui, self.repo.root)
87 self.repo = hg.repository(self.repo.ui, self.repo.root)
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
93
93
94 def archivelist(self, nodeid):
94 def archivelist(self, nodeid):
95 allowed = self.repo.ui.configlist("web", "allow_archive")
95 allowed = self.repo.ui.configlist("web", "allow_archive")
96 for i, spec in self.archive_specs.iteritems():
96 for i, spec in self.archive_specs.iteritems():
97 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
97 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
98 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
98 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
99
99
100 def listfilediffs(self, files, changeset):
100 def listfilediffs(self, files, changeset):
101 for f in files[:self.maxfiles]:
101 for f in files[:self.maxfiles]:
102 yield self.t("filedifflink", node=hex(changeset), file=f)
102 yield self.t("filedifflink", node=hex(changeset), file=f)
103 if len(files) > self.maxfiles:
103 if len(files) > self.maxfiles:
104 yield self.t("fileellipses")
104 yield self.t("fileellipses")
105
105
106 def siblings(self, siblings=[], hiderev=None, **args):
106 def siblings(self, siblings=[], hiderev=None, **args):
107 siblings = [s for s in siblings if s.node() != nullid]
107 siblings = [s for s in siblings if s.node() != nullid]
108 if len(siblings) == 1 and siblings[0].rev() == hiderev:
108 if len(siblings) == 1 and siblings[0].rev() == hiderev:
109 return
109 return
110 for s in siblings:
110 for s in siblings:
111 d = {'node': hex(s.node()), 'rev': s.rev()}
111 d = {'node': hex(s.node()), 'rev': s.rev()}
112 if hasattr(s, 'path'):
112 if hasattr(s, 'path'):
113 d['file'] = s.path()
113 d['file'] = s.path()
114 d.update(args)
114 d.update(args)
115 yield d
115 yield d
116
116
117 def renamelink(self, fl, node):
117 def renamelink(self, fl, node):
118 r = fl.renamed(node)
118 r = fl.renamed(node)
119 if r:
119 if r:
120 return [dict(file=r[0], node=hex(r[1]))]
120 return [dict(file=r[0], node=hex(r[1]))]
121 return []
121 return []
122
122
123 def showtag(self, t1, node=nullid, **args):
123 def showtag(self, t1, node=nullid, **args):
124 for t in self.repo.nodetags(node):
124 for t in self.repo.nodetags(node):
125 yield self.t(t1, tag=t, **args)
125 yield self.t(t1, tag=t, **args)
126
126
127 def diff(self, node1, node2, files):
127 def diff(self, node1, node2, files):
128 def filterfiles(filters, files):
128 def filterfiles(filters, files):
129 l = [x for x in files if x in filters]
129 l = [x for x in files if x in filters]
130
130
131 for t in filters:
131 for t in filters:
132 if t and t[-1] != os.sep:
132 if t and t[-1] != os.sep:
133 t += os.sep
133 t += os.sep
134 l += [x for x in files if x.startswith(t)]
134 l += [x for x in files if x.startswith(t)]
135 return l
135 return l
136
136
137 parity = [0]
137 parity = [0]
138 def diffblock(diff, f, fn):
138 def diffblock(diff, f, fn):
139 yield self.t("diffblock",
139 yield self.t("diffblock",
140 lines=prettyprintlines(diff),
140 lines=prettyprintlines(diff),
141 parity=parity[0],
141 parity=parity[0],
142 file=f,
142 file=f,
143 filenode=hex(fn or nullid))
143 filenode=hex(fn or nullid))
144 parity[0] = 1 - parity[0]
144 parity[0] = 1 - parity[0]
145
145
146 def prettyprintlines(diff):
146 def prettyprintlines(diff):
147 for l in diff.splitlines(1):
147 for l in diff.splitlines(1):
148 if l.startswith('+'):
148 if l.startswith('+'):
149 yield self.t("difflineplus", line=l)
149 yield self.t("difflineplus", line=l)
150 elif l.startswith('-'):
150 elif l.startswith('-'):
151 yield self.t("difflineminus", line=l)
151 yield self.t("difflineminus", line=l)
152 elif l.startswith('@'):
152 elif l.startswith('@'):
153 yield self.t("difflineat", line=l)
153 yield self.t("difflineat", line=l)
154 else:
154 else:
155 yield self.t("diffline", line=l)
155 yield self.t("diffline", line=l)
156
156
157 r = self.repo
157 r = self.repo
158 cl = r.changelog
158 cl = r.changelog
159 mf = r.manifest
159 mf = r.manifest
160 change1 = cl.read(node1)
160 change1 = cl.read(node1)
161 change2 = cl.read(node2)
161 change2 = cl.read(node2)
162 mmap1 = mf.read(change1[0])
162 mmap1 = mf.read(change1[0])
163 mmap2 = mf.read(change2[0])
163 mmap2 = mf.read(change2[0])
164 date1 = util.datestr(change1[2])
164 date1 = util.datestr(change1[2])
165 date2 = util.datestr(change2[2])
165 date2 = util.datestr(change2[2])
166
166
167 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
167 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
168 if files:
168 if files:
169 modified, added, removed = map(lambda x: filterfiles(files, x),
169 modified, added, removed = map(lambda x: filterfiles(files, x),
170 (modified, added, removed))
170 (modified, added, removed))
171
171
172 diffopts = patch.diffopts(self.repo.ui)
172 diffopts = patch.diffopts(self.repo.ui)
173 for f in modified:
173 for f in modified:
174 to = r.file(f).read(mmap1[f])
174 to = r.file(f).read(mmap1[f])
175 tn = r.file(f).read(mmap2[f])
175 tn = r.file(f).read(mmap2[f])
176 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
176 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
177 opts=diffopts), f, tn)
177 opts=diffopts), f, tn)
178 for f in added:
178 for f in added:
179 to = None
179 to = None
180 tn = r.file(f).read(mmap2[f])
180 tn = r.file(f).read(mmap2[f])
181 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
181 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
182 opts=diffopts), f, tn)
182 opts=diffopts), f, tn)
183 for f in removed:
183 for f in removed:
184 to = r.file(f).read(mmap1[f])
184 to = r.file(f).read(mmap1[f])
185 tn = None
185 tn = None
186 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
186 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
187 opts=diffopts), f, tn)
187 opts=diffopts), f, tn)
188
188
189 def changelog(self, ctx, shortlog=False):
189 def changelog(self, ctx, shortlog=False):
190 def changelist(**map):
190 def changelist(**map):
191 parity = (start - end) & 1
191 parity = (start - end) & 1
192 cl = self.repo.changelog
192 cl = self.repo.changelog
193 l = [] # build a list in forward order for efficiency
193 l = [] # build a list in forward order for efficiency
194 for i in range(start, end):
194 for i in range(start, end):
195 ctx = self.repo.changectx(i)
195 ctx = self.repo.changectx(i)
196 n = ctx.node()
196 n = ctx.node()
197
197
198 l.insert(0, {"parity": parity,
198 l.insert(0, {"parity": parity,
199 "author": ctx.user(),
199 "author": ctx.user(),
200 "parent": self.siblings(ctx.parents(), i - 1),
200 "parent": self.siblings(ctx.parents(), i - 1),
201 "child": self.siblings(ctx.children(), i + 1),
201 "child": self.siblings(ctx.children(), i + 1),
202 "changelogtag": self.showtag("changelogtag",n),
202 "changelogtag": self.showtag("changelogtag",n),
203 "desc": ctx.description(),
203 "desc": ctx.description(),
204 "date": ctx.date(),
204 "date": ctx.date(),
205 "files": self.listfilediffs(ctx.files(), n),
205 "files": self.listfilediffs(ctx.files(), n),
206 "rev": i,
206 "rev": i,
207 "node": hex(n)})
207 "node": hex(n)})
208 parity = 1 - parity
208 parity = 1 - parity
209
209
210 for e in l:
210 for e in l:
211 yield e
211 yield e
212
212
213 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
213 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
214 cl = self.repo.changelog
214 cl = self.repo.changelog
215 count = cl.count()
215 count = cl.count()
216 pos = ctx.rev()
216 pos = ctx.rev()
217 start = max(0, pos - maxchanges + 1)
217 start = max(0, pos - maxchanges + 1)
218 end = min(count, start + maxchanges)
218 end = min(count, start + maxchanges)
219 pos = end - 1
219 pos = end - 1
220
220
221 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
221 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
222
222
223 yield self.t(shortlog and 'shortlog' or 'changelog',
223 yield self.t(shortlog and 'shortlog' or 'changelog',
224 changenav=changenav,
224 changenav=changenav,
225 node=hex(cl.tip()),
225 node=hex(cl.tip()),
226 rev=pos, changesets=count, entries=changelist,
226 rev=pos, changesets=count, entries=changelist,
227 archives=self.archivelist("tip"))
227 archives=self.archivelist("tip"))
228
228
229 def search(self, query):
229 def search(self, query):
230
230
231 def changelist(**map):
231 def changelist(**map):
232 cl = self.repo.changelog
232 cl = self.repo.changelog
233 count = 0
233 count = 0
234 qw = query.lower().split()
234 qw = query.lower().split()
235
235
236 def revgen():
236 def revgen():
237 for i in range(cl.count() - 1, 0, -100):
237 for i in range(cl.count() - 1, 0, -100):
238 l = []
238 l = []
239 for j in range(max(0, i - 100), i):
239 for j in range(max(0, i - 100), i):
240 ctx = self.repo.changectx(j)
240 ctx = self.repo.changectx(j)
241 l.append(ctx)
241 l.append(ctx)
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 ctx in revgen():
246 for ctx in revgen():
247 miss = 0
247 miss = 0
248 for q in qw:
248 for q in qw:
249 if not (q in ctx.user().lower() or
249 if not (q in ctx.user().lower() or
250 q in ctx.description().lower() or
250 q in ctx.description().lower() or
251 q in " ".join(ctx.files()[:20]).lower()):
251 q in " ".join(ctx.files()[: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 n = ctx.node()
258 n = ctx.node()
259
259
260 yield self.t('searchentry',
260 yield self.t('searchentry',
261 parity=self.stripes(count),
261 parity=self.stripes(count),
262 author=ctx.user(),
262 author=ctx.user(),
263 parent=self.siblings(ctx.parents()),
263 parent=self.siblings(ctx.parents()),
264 child=self.siblings(ctx.children()),
264 child=self.siblings(ctx.children()),
265 changelogtag=self.showtag("changelogtag",n),
265 changelogtag=self.showtag("changelogtag",n),
266 desc=ctx.description(),
266 desc=ctx.description(),
267 date=ctx.date(),
267 date=ctx.date(),
268 files=self.listfilediffs(ctx.files(), n),
268 files=self.listfilediffs(ctx.files(), n),
269 rev=ctx.rev(),
269 rev=ctx.rev(),
270 node=hex(n))
270 node=hex(n))
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
276
277 yield self.t('search',
277 yield self.t('search',
278 query=query,
278 query=query,
279 node=hex(cl.tip()),
279 node=hex(cl.tip()),
280 entries=changelist)
280 entries=changelist)
281
281
282 def changeset(self, ctx):
282 def changeset(self, ctx):
283 n = ctx.node()
283 n = ctx.node()
284 parents = ctx.parents()
284 parents = ctx.parents()
285 p1 = parents[0].node()
285 p1 = parents[0].node()
286
286
287 files = []
287 files = []
288 parity = 0
288 parity = 0
289 for f in ctx.files():
289 for f in ctx.files():
290 files.append(self.t("filenodelink",
290 files.append(self.t("filenodelink",
291 node=hex(n), file=f,
291 node=hex(n), file=f,
292 parity=parity))
292 parity=parity))
293 parity = 1 - parity
293 parity = 1 - parity
294
294
295 def diff(**map):
295 def diff(**map):
296 yield self.diff(p1, n, None)
296 yield self.diff(p1, n, None)
297
297
298 yield self.t('changeset',
298 yield self.t('changeset',
299 diff=diff,
299 diff=diff,
300 rev=ctx.rev(),
300 rev=ctx.rev(),
301 node=hex(n),
301 node=hex(n),
302 parent=self.siblings(parents),
302 parent=self.siblings(parents),
303 child=self.siblings(ctx.children()),
303 child=self.siblings(ctx.children()),
304 changesettag=self.showtag("changesettag",n),
304 changesettag=self.showtag("changesettag",n),
305 author=ctx.user(),
305 author=ctx.user(),
306 desc=ctx.description(),
306 desc=ctx.description(),
307 date=ctx.date(),
307 date=ctx.date(),
308 files=files,
308 files=files,
309 archives=self.archivelist(hex(n)))
309 archives=self.archivelist(hex(n)))
310
310
311 def filelog(self, fctx):
311 def filelog(self, fctx):
312 f = fctx.path()
312 f = fctx.path()
313 fl = fctx.filelog()
313 fl = fctx.filelog()
314 count = fl.count()
314 count = fl.count()
315 pagelen = self.maxshortchanges
315 pagelen = self.maxshortchanges
316 pos = fctx.filerev()
316 pos = fctx.filerev()
317 start = max(0, pos - pagelen + 1)
317 start = max(0, pos - pagelen + 1)
318 end = min(count, start + pagelen)
318 end = min(count, start + pagelen)
319 pos = end - 1
319 pos = end - 1
320
320
321 def entries(**map):
321 def entries(**map):
322 l = []
322 l = []
323 parity = (count - 1) & 1
323 parity = (count - 1) & 1
324
324
325 for i in range(start, end):
325 for i in range(start, end):
326 ctx = fctx.filectx(i)
326 ctx = fctx.filectx(i)
327 n = fl.node(i)
327 n = fl.node(i)
328
328
329 l.insert(0, {"parity": parity,
329 l.insert(0, {"parity": parity,
330 "filerev": i,
330 "filerev": i,
331 "file": f,
331 "file": f,
332 "node": hex(ctx.node()),
332 "node": hex(ctx.node()),
333 "author": ctx.user(),
333 "author": ctx.user(),
334 "date": ctx.date(),
334 "date": ctx.date(),
335 "rename": self.renamelink(fl, n),
335 "rename": self.renamelink(fl, n),
336 "parent": self.siblings(fctx.parents()),
336 "parent": self.siblings(fctx.parents()),
337 "child": self.siblings(fctx.children()),
337 "child": self.siblings(fctx.children()),
338 "desc": ctx.description()})
338 "desc": ctx.description()})
339 parity = 1 - parity
339 parity = 1 - parity
340
340
341 for e in l:
341 for e in l:
342 yield e
342 yield e
343
343
344 nodefunc = lambda x: fctx.filectx(fileid=x)
344 nodefunc = lambda x: fctx.filectx(fileid=x)
345 nav = revnavgen(pos, pagelen, count, nodefunc)
345 nav = revnavgen(pos, pagelen, count, nodefunc)
346 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
346 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
347 entries=entries)
347 entries=entries)
348
348
349 def filerevision(self, fctx):
349 def filerevision(self, fctx):
350 f = fctx.path()
350 f = fctx.path()
351 text = fctx.data()
351 text = fctx.data()
352 fl = fctx.filelog()
352 fl = fctx.filelog()
353 n = fctx.filenode()
353 n = fctx.filenode()
354
354
355 mt = mimetypes.guess_type(f)[0]
355 mt = mimetypes.guess_type(f)[0]
356 rawtext = text
356 rawtext = text
357 if util.binary(text):
357 if util.binary(text):
358 mt = mt or 'application/octet-stream'
358 mt = mt or 'application/octet-stream'
359 text = "(binary:%s)" % mt
359 text = "(binary:%s)" % mt
360 mt = mt or 'text/plain'
360 mt = mt or 'text/plain'
361
361
362 def lines():
362 def lines():
363 for l, t in enumerate(text.splitlines(1)):
363 for l, t in enumerate(text.splitlines(1)):
364 yield {"line": t,
364 yield {"line": t,
365 "linenumber": "% 6d" % (l + 1),
365 "linenumber": "% 6d" % (l + 1),
366 "parity": self.stripes(l)}
366 "parity": self.stripes(l)}
367
367
368 yield self.t("filerevision",
368 yield self.t("filerevision",
369 file=f,
369 file=f,
370 path=_up(f),
370 path=_up(f),
371 text=lines(),
371 text=lines(),
372 raw=rawtext,
372 raw=rawtext,
373 mimetype=mt,
373 mimetype=mt,
374 rev=fctx.rev(),
374 rev=fctx.rev(),
375 node=hex(fctx.node()),
375 node=hex(fctx.node()),
376 author=fctx.user(),
376 author=fctx.user(),
377 date=fctx.date(),
377 date=fctx.date(),
378 desc=fctx.description(),
378 desc=fctx.description(),
379 parent=self.siblings(fctx.parents()),
379 parent=self.siblings(fctx.parents()),
380 child=self.siblings(fctx.children()),
380 child=self.siblings(fctx.children()),
381 rename=self.renamelink(fl, n),
381 rename=self.renamelink(fl, n),
382 permissions=fctx.manifest().execf(f))
382 permissions=fctx.manifest().execf(f))
383
383
384 def fileannotate(self, fctx):
384 def fileannotate(self, fctx):
385 f = fctx.path()
385 f = fctx.path()
386 n = fctx.filenode()
386 n = fctx.filenode()
387 fl = fctx.filelog()
387 fl = fctx.filelog()
388
388
389 def annotate(**map):
389 def annotate(**map):
390 parity = 0
390 parity = 0
391 last = None
391 last = None
392 for f, l in fctx.annotate(follow=True):
392 for f, l in fctx.annotate(follow=True):
393 fnode = f.filenode()
393 fnode = f.filenode()
394 name = self.repo.ui.shortuser(f.user())
394 name = self.repo.ui.shortuser(f.user())
395
395
396 if last != fnode:
396 if last != fnode:
397 parity = 1 - parity
397 parity = 1 - parity
398 last = fnode
398 last = fnode
399
399
400 yield {"parity": parity,
400 yield {"parity": parity,
401 "node": hex(f.node()),
401 "node": hex(f.node()),
402 "rev": f.rev(),
402 "rev": f.rev(),
403 "author": name,
403 "author": name,
404 "file": f.path(),
404 "file": f.path(),
405 "line": l}
405 "line": l}
406
406
407 yield self.t("fileannotate",
407 yield self.t("fileannotate",
408 file=f,
408 file=f,
409 annotate=annotate,
409 annotate=annotate,
410 path=_up(f),
410 path=_up(f),
411 rev=fctx.rev(),
411 rev=fctx.rev(),
412 node=hex(fctx.node()),
412 node=hex(fctx.node()),
413 author=fctx.user(),
413 author=fctx.user(),
414 date=fctx.date(),
414 date=fctx.date(),
415 desc=fctx.description(),
415 desc=fctx.description(),
416 rename=self.renamelink(fl, n),
416 rename=self.renamelink(fl, n),
417 parent=self.siblings(fctx.parents()),
417 parent=self.siblings(fctx.parents()),
418 child=self.siblings(fctx.children()),
418 child=self.siblings(fctx.children()),
419 permissions=fctx.manifest().execf(f))
419 permissions=fctx.manifest().execf(f))
420
420
421 def manifest(self, ctx, path):
421 def manifest(self, ctx, path):
422 mf = ctx.manifest()
422 mf = ctx.manifest()
423 node = ctx.node()
423 node = ctx.node()
424
424
425 files = {}
425 files = {}
426
426
427 p = path[1:]
427 p = path[1:]
428 if p and p[-1] != "/":
428 if p and p[-1] != "/":
429 p += "/"
429 p += "/"
430 l = len(p)
430 l = len(p)
431
431
432 for f,n in mf.items():
432 for f,n in mf.items():
433 if f[:l] != p:
433 if f[:l] != p:
434 continue
434 continue
435 remain = f[l:]
435 remain = f[l:]
436 if "/" in remain:
436 if "/" in remain:
437 short = remain[:remain.index("/") + 1] # bleah
437 short = remain[:remain.index("/") + 1] # bleah
438 files[short] = (f, None)
438 files[short] = (f, None)
439 else:
439 else:
440 short = os.path.basename(remain)
440 short = os.path.basename(remain)
441 files[short] = (f, n)
441 files[short] = (f, n)
442
442
443 def filelist(**map):
443 def filelist(**map):
444 parity = 0
444 parity = 0
445 fl = files.keys()
445 fl = files.keys()
446 fl.sort()
446 fl.sort()
447 for f in fl:
447 for f in fl:
448 full, fnode = files[f]
448 full, fnode = files[f]
449 if not fnode:
449 if not fnode:
450 continue
450 continue
451
451
452 yield {"file": full,
452 yield {"file": full,
453 "parity": self.stripes(parity),
453 "parity": self.stripes(parity),
454 "basename": f,
454 "basename": f,
455 "size": ctx.filectx(full).size(),
455 "size": ctx.filectx(full).size(),
456 "permissions": mf.execf(full)}
456 "permissions": mf.execf(full)}
457 parity += 1
457 parity += 1
458
458
459 def dirlist(**map):
459 def dirlist(**map):
460 parity = 0
460 parity = 0
461 fl = files.keys()
461 fl = files.keys()
462 fl.sort()
462 fl.sort()
463 for f in fl:
463 for f in fl:
464 full, fnode = files[f]
464 full, fnode = files[f]
465 if fnode:
465 if fnode:
466 continue
466 continue
467
467
468 yield {"parity": self.stripes(parity),
468 yield {"parity": self.stripes(parity),
469 "path": os.path.join(path, f),
469 "path": os.path.join(path, f),
470 "basename": f[:-1]}
470 "basename": f[:-1]}
471 parity += 1
471 parity += 1
472
472
473 yield self.t("manifest",
473 yield self.t("manifest",
474 rev=ctx.rev(),
474 rev=ctx.rev(),
475 node=hex(node),
475 node=hex(node),
476 path=path,
476 path=path,
477 up=_up(path),
477 up=_up(path),
478 fentries=filelist,
478 fentries=filelist,
479 dentries=dirlist,
479 dentries=dirlist,
480 archives=self.archivelist(hex(node)))
480 archives=self.archivelist(hex(node)))
481
481
482 def tags(self):
482 def tags(self):
483 cl = self.repo.changelog
483 cl = self.repo.changelog
484
484
485 i = self.repo.tagslist()
485 i = self.repo.tagslist()
486 i.reverse()
486 i.reverse()
487
487
488 def entries(notip=False, **map):
488 def entries(notip=False, **map):
489 parity = 0
489 parity = 0
490 for k,n in i:
490 for k,n in i:
491 if notip and k == "tip": continue
491 if notip and k == "tip": continue
492 yield {"parity": self.stripes(parity),
492 yield {"parity": self.stripes(parity),
493 "tag": k,
493 "tag": k,
494 "date": cl.read(n)[2],
494 "date": cl.read(n)[2],
495 "node": hex(n)}
495 "node": hex(n)}
496 parity += 1
496 parity += 1
497
497
498 yield self.t("tags",
498 yield self.t("tags",
499 node=hex(self.repo.changelog.tip()),
499 node=hex(self.repo.changelog.tip()),
500 entries=lambda **x: entries(False, **x),
500 entries=lambda **x: entries(False, **x),
501 entriesnotip=lambda **x: entries(True, **x))
501 entriesnotip=lambda **x: entries(True, **x))
502
502
503 def summary(self):
503 def summary(self):
504 cl = self.repo.changelog
504 cl = self.repo.changelog
505
505
506 i = self.repo.tagslist()
506 i = self.repo.tagslist()
507 i.reverse()
507 i.reverse()
508
508
509 def tagentries(**map):
509 def tagentries(**map):
510 parity = 0
510 parity = 0
511 count = 0
511 count = 0
512 for k,n in i:
512 for k,n in i:
513 if k == "tip": # skip tip
513 if k == "tip": # skip tip
514 continue;
514 continue;
515
515
516 count += 1
516 count += 1
517 if count > 10: # limit to 10 tags
517 if count > 10: # limit to 10 tags
518 break;
518 break;
519
519
520 c = cl.read(n)
520 c = cl.read(n)
521 t = c[2]
521 t = c[2]
522
522
523 yield self.t("tagentry",
523 yield self.t("tagentry",
524 parity = self.stripes(parity),
524 parity = self.stripes(parity),
525 tag = k,
525 tag = k,
526 node = hex(n),
526 node = hex(n),
527 date = t)
527 date = t)
528 parity += 1
528 parity += 1
529
529
530 def changelist(**map):
530 def changelist(**map):
531 parity = 0
531 parity = 0
532 cl = self.repo.changelog
532 cl = self.repo.changelog
533 l = [] # build a list in forward order for efficiency
533 l = [] # build a list in forward order for efficiency
534 for i in range(start, end):
534 for i in range(start, end):
535 n = cl.node(i)
535 n = cl.node(i)
536 changes = cl.read(n)
536 changes = cl.read(n)
537 hn = hex(n)
537 hn = hex(n)
538 t = changes[2]
538 t = changes[2]
539
539
540 l.insert(0, self.t(
540 l.insert(0, self.t(
541 'shortlogentry',
541 'shortlogentry',
542 parity = parity,
542 parity = parity,
543 author = changes[1],
543 author = changes[1],
544 desc = changes[4],
544 desc = changes[4],
545 date = t,
545 date = t,
546 rev = i,
546 rev = i,
547 node = hn))
547 node = hn))
548 parity = 1 - parity
548 parity = 1 - parity
549
549
550 yield l
550 yield l
551
551
552 count = cl.count()
552 count = cl.count()
553 start = max(0, count - self.maxchanges)
553 start = max(0, count - self.maxchanges)
554 end = min(count, start + self.maxchanges)
554 end = min(count, start + self.maxchanges)
555
555
556 yield self.t("summary",
556 yield self.t("summary",
557 desc = self.repo.ui.config("web", "description", "unknown"),
557 desc = self.repo.ui.config("web", "description", "unknown"),
558 owner = (self.repo.ui.config("ui", "username") or # preferred
558 owner = (self.repo.ui.config("ui", "username") or # preferred
559 self.repo.ui.config("web", "contact") or # deprecated
559 self.repo.ui.config("web", "contact") or # deprecated
560 self.repo.ui.config("web", "author", "unknown")), # also
560 self.repo.ui.config("web", "author", "unknown")), # also
561 lastchange = cl.read(cl.tip())[2],
561 lastchange = cl.read(cl.tip())[2],
562 tags = tagentries,
562 tags = tagentries,
563 shortlog = changelist,
563 shortlog = changelist,
564 node = hex(cl.tip()),
564 node = hex(cl.tip()),
565 archives=self.archivelist("tip"))
565 archives=self.archivelist("tip"))
566
566
567 def filediff(self, fctx):
567 def filediff(self, fctx):
568 n = fctx.node()
568 n = fctx.node()
569 path = fctx.path()
569 path = fctx.path()
570 parents = fctx.parents()
570 parents = fctx.parents()
571 p1 = parents and parents[0].node() or nullid
571 p1 = parents and parents[0].node() or nullid
572
572
573 def diff(**map):
573 def diff(**map):
574 yield self.diff(p1, n, [path])
574 yield self.diff(p1, n, [path])
575
575
576 yield self.t("filediff",
576 yield self.t("filediff",
577 file=path,
577 file=path,
578 node=hex(n),
578 node=hex(n),
579 rev=fctx.rev(),
579 rev=fctx.rev(),
580 parent=self.siblings(parents),
580 parent=self.siblings(parents),
581 child=self.siblings(fctx.children()),
581 child=self.siblings(fctx.children()),
582 diff=diff)
582 diff=diff)
583
583
584 archive_specs = {
584 archive_specs = {
585 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
585 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
586 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
586 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
587 'zip': ('application/zip', 'zip', '.zip', None),
587 'zip': ('application/zip', 'zip', '.zip', None),
588 }
588 }
589
589
590 def archive(self, req, cnode, type_):
590 def archive(self, req, cnode, type_):
591 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
591 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
592 name = "%s-%s" % (reponame, short(cnode))
592 name = "%s-%s" % (reponame, short(cnode))
593 mimetype, artype, extension, encoding = self.archive_specs[type_]
593 mimetype, artype, extension, encoding = self.archive_specs[type_]
594 headers = [('Content-type', mimetype),
594 headers = [('Content-type', mimetype),
595 ('Content-disposition', 'attachment; filename=%s%s' %
595 ('Content-disposition', 'attachment; filename=%s%s' %
596 (name, extension))]
596 (name, extension))]
597 if encoding:
597 if encoding:
598 headers.append(('Content-encoding', encoding))
598 headers.append(('Content-encoding', encoding))
599 req.header(headers)
599 req.header(headers)
600 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
600 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
601
601
602 # add tags to things
602 # add tags to things
603 # tags -> list of changesets corresponding to tags
603 # tags -> list of changesets corresponding to tags
604 # find tag, changeset, file
604 # find tag, changeset, file
605
605
606 def cleanpath(self, path):
606 def cleanpath(self, path):
607 return util.canonpath(self.repo.root, '', path)
607 return util.canonpath(self.repo.root, '', path)
608
608
609 def run(self):
609 def run(self):
610 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
610 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
611 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
611 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
612 import mercurial.hgweb.wsgicgi as wsgicgi
612 import mercurial.hgweb.wsgicgi as wsgicgi
613 from request import wsgiapplication
613 from request import wsgiapplication
614 def make_web_app():
614 def make_web_app():
615 return self
615 return self
616 wsgicgi.launch(wsgiapplication(make_web_app))
616 wsgicgi.launch(wsgiapplication(make_web_app))
617
617
618 def run_wsgi(self, req):
618 def run_wsgi(self, req):
619 def header(**map):
619 def header(**map):
620 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
620 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
621 msg = mimetools.Message(header_file, 0)
621 msg = mimetools.Message(header_file, 0)
622 req.header(msg.items())
622 req.header(msg.items())
623 yield header_file.read()
623 yield header_file.read()
624
624
625 def rawfileheader(**map):
625 def rawfileheader(**map):
626 req.header([('Content-type', map['mimetype']),
626 req.header([('Content-type', map['mimetype']),
627 ('Content-disposition', 'filename=%s' % map['file']),
627 ('Content-disposition', 'filename=%s' % map['file']),
628 ('Content-length', str(len(map['raw'])))])
628 ('Content-length', str(len(map['raw'])))])
629 yield ''
629 yield ''
630
630
631 def footer(**map):
631 def footer(**map):
632 yield self.t("footer",
632 yield self.t("footer",
633 motd=self.repo.ui.config("web", "motd", ""),
633 motd=self.repo.ui.config("web", "motd", ""),
634 **map)
634 **map)
635
635
636 def expand_form(form):
636 def expand_form(form):
637 shortcuts = {
637 shortcuts = {
638 'cl': [('cmd', ['changelog']), ('rev', None)],
638 'cl': [('cmd', ['changelog']), ('rev', None)],
639 'sl': [('cmd', ['shortlog']), ('rev', None)],
639 'sl': [('cmd', ['shortlog']), ('rev', None)],
640 'cs': [('cmd', ['changeset']), ('node', None)],
640 'cs': [('cmd', ['changeset']), ('node', None)],
641 'f': [('cmd', ['file']), ('filenode', None)],
641 'f': [('cmd', ['file']), ('filenode', None)],
642 'fl': [('cmd', ['filelog']), ('filenode', None)],
642 'fl': [('cmd', ['filelog']), ('filenode', None)],
643 'fd': [('cmd', ['filediff']), ('node', None)],
643 'fd': [('cmd', ['filediff']), ('node', None)],
644 'fa': [('cmd', ['annotate']), ('filenode', None)],
644 'fa': [('cmd', ['annotate']), ('filenode', None)],
645 'mf': [('cmd', ['manifest']), ('manifest', None)],
645 'mf': [('cmd', ['manifest']), ('manifest', None)],
646 'ca': [('cmd', ['archive']), ('node', None)],
646 'ca': [('cmd', ['archive']), ('node', None)],
647 'tags': [('cmd', ['tags'])],
647 'tags': [('cmd', ['tags'])],
648 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
648 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
649 'static': [('cmd', ['static']), ('file', None)]
649 'static': [('cmd', ['static']), ('file', None)]
650 }
650 }
651
651
652 for k in shortcuts.iterkeys():
652 for k in shortcuts.iterkeys():
653 if form.has_key(k):
653 if form.has_key(k):
654 for name, value in shortcuts[k]:
654 for name, value in shortcuts[k]:
655 if value is None:
655 if value is None:
656 value = form[k]
656 value = form[k]
657 form[name] = value
657 form[name] = value
658 del form[k]
658 del form[k]
659
659
660 def rewrite_request(req):
660 def rewrite_request(req):
661 '''translate new web interface to traditional format'''
661 '''translate new web interface to traditional format'''
662
662
663 def spliturl(req):
663 def spliturl(req):
664 def firstitem(query):
664 def firstitem(query):
665 return query.split('&', 1)[0].split(';', 1)[0]
665 return query.split('&', 1)[0].split(';', 1)[0]
666
666
667 def normurl(url):
667 def normurl(url):
668 inner = '/'.join([x for x in url.split('/') if x])
668 inner = '/'.join([x for x in url.split('/') if x])
669 tl = len(url) > 1 and url.endswith('/') and '/' or ''
669 tl = len(url) > 1 and url.endswith('/') and '/' or ''
670
670
671 return '%s%s%s' % (url.startswith('/') and '/' or '',
671 return '%s%s%s' % (url.startswith('/') and '/' or '',
672 inner, tl)
672 inner, tl)
673
673
674 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
674 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
675 pi = normurl(req.env.get('PATH_INFO', ''))
675 pi = normurl(req.env.get('PATH_INFO', ''))
676 if pi:
676 if pi:
677 # strip leading /
677 # strip leading /
678 pi = pi[1:]
678 pi = pi[1:]
679 if pi:
679 if pi:
680 root = root[:-len(pi)]
680 root = root[:-len(pi)]
681 if req.env.has_key('REPO_NAME'):
681 if req.env.has_key('REPO_NAME'):
682 rn = req.env['REPO_NAME'] + '/'
682 rn = req.env['REPO_NAME'] + '/'
683 root += rn
683 root += rn
684 query = pi[len(rn):]
684 query = pi[len(rn):]
685 else:
685 else:
686 query = pi
686 query = pi
687 else:
687 else:
688 root += '?'
688 root += '?'
689 query = firstitem(req.env['QUERY_STRING'])
689 query = firstitem(req.env['QUERY_STRING'])
690
690
691 return (root, query)
691 return (root, query)
692
692
693 req.url, query = spliturl(req)
693 req.url, query = spliturl(req)
694
694
695 if req.form.has_key('cmd'):
695 if req.form.has_key('cmd'):
696 # old style
696 # old style
697 return
697 return
698
698
699 args = query.split('/', 2)
699 args = query.split('/', 2)
700 if not args or not args[0]:
700 if not args or not args[0]:
701 return
701 return
702
702
703 cmd = args.pop(0)
703 cmd = args.pop(0)
704 style = cmd.rfind('-')
704 style = cmd.rfind('-')
705 if style != -1:
705 if style != -1:
706 req.form['style'] = [cmd[:style]]
706 req.form['style'] = [cmd[:style]]
707 cmd = cmd[style+1:]
707 cmd = cmd[style+1:]
708 # avoid accepting e.g. style parameter as command
708 # avoid accepting e.g. style parameter as command
709 if hasattr(self, 'do_' + cmd):
709 if hasattr(self, 'do_' + cmd):
710 req.form['cmd'] = [cmd]
710 req.form['cmd'] = [cmd]
711
711
712 if args and args[0]:
712 if args and args[0]:
713 node = args.pop(0)
713 node = args.pop(0)
714 req.form['node'] = [node]
714 req.form['node'] = [node]
715 if args:
715 if args:
716 req.form['file'] = args
716 req.form['file'] = args
717
717
718 if cmd == 'static':
718 if cmd == 'static':
719 req.form['file'] = req.form['node']
719 req.form['file'] = req.form['node']
720 elif cmd == 'archive':
720 elif cmd == 'archive':
721 fn = req.form['node'][0]
721 fn = req.form['node'][0]
722 for type_, spec in self.archive_specs.iteritems():
722 for type_, spec in self.archive_specs.iteritems():
723 ext = spec[2]
723 ext = spec[2]
724 if fn.endswith(ext):
724 if fn.endswith(ext):
725 req.form['node'] = [fn[:-len(ext)]]
725 req.form['node'] = [fn[:-len(ext)]]
726 req.form['type'] = [type_]
726 req.form['type'] = [type_]
727
727
728 def sessionvars(**map):
728 def sessionvars(**map):
729 fields = []
729 fields = []
730 if req.form.has_key('style'):
730 if req.form.has_key('style'):
731 style = req.form['style'][0]
731 style = req.form['style'][0]
732 if style != self.repo.ui.config('web', 'style', ''):
732 if style != self.repo.ui.config('web', 'style', ''):
733 fields.append(('style', style))
733 fields.append(('style', style))
734
734
735 separator = req.url[-1] == '?' and ';' or '?'
735 separator = req.url[-1] == '?' and ';' or '?'
736 for name, value in fields:
736 for name, value in fields:
737 yield dict(name=name, value=value, separator=separator)
737 yield dict(name=name, value=value, separator=separator)
738 separator = ';'
738 separator = ';'
739
739
740 self.refresh()
740 self.refresh()
741
741
742 expand_form(req.form)
742 expand_form(req.form)
743 rewrite_request(req)
743 rewrite_request(req)
744
744
745 style = self.repo.ui.config("web", "style", "")
745 style = self.repo.ui.config("web", "style", "")
746 if req.form.has_key('style'):
746 if req.form.has_key('style'):
747 style = req.form['style'][0]
747 style = req.form['style'][0]
748 mapfile = style_map(self.templatepath, style)
748 mapfile = style_map(self.templatepath, style)
749
749
750 port = req.env["SERVER_PORT"]
750 port = req.env["SERVER_PORT"]
751 port = port != "80" and (":" + port) or ""
751 port = port != "80" and (":" + port) or ""
752 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
752 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
753
753
754 if not self.reponame:
754 if not self.reponame:
755 self.reponame = (self.repo.ui.config("web", "name")
755 self.reponame = (self.repo.ui.config("web", "name")
756 or req.env.get('REPO_NAME')
756 or req.env.get('REPO_NAME')
757 or req.url.strip('/') or self.repo.root)
757 or req.url.strip('/') or self.repo.root)
758
758
759 self.t = templater.templater(mapfile, templater.common_filters,
759 self.t = templater.templater(mapfile, templater.common_filters,
760 defaults={"url": req.url,
760 defaults={"url": req.url,
761 "urlbase": urlbase,
761 "urlbase": urlbase,
762 "repo": self.reponame,
762 "repo": self.reponame,
763 "header": header,
763 "header": header,
764 "footer": footer,
764 "footer": footer,
765 "rawfileheader": rawfileheader,
765 "rawfileheader": rawfileheader,
766 "sessionvars": sessionvars
766 "sessionvars": sessionvars
767 })
767 })
768
768
769 if not req.form.has_key('cmd'):
769 if not req.form.has_key('cmd'):
770 req.form['cmd'] = [self.t.cache['default'],]
770 req.form['cmd'] = [self.t.cache['default'],]
771
771
772 cmd = req.form['cmd'][0]
772 cmd = req.form['cmd'][0]
773
773
774 method = getattr(self, 'do_' + cmd, None)
774 method = getattr(self, 'do_' + cmd, None)
775 if method:
775 if method:
776 try:
776 try:
777 method(req)
777 method(req)
778 except (hg.RepoError, revlog.RevlogError), inst:
778 except (hg.RepoError, revlog.RevlogError), inst:
779 req.write(self.t("error", error=str(inst)))
779 req.write(self.t("error", error=str(inst)))
780 else:
780 else:
781 req.write(self.t("error", error='No such method: ' + cmd))
781 req.write(self.t("error", error='No such method: ' + cmd))
782
782
783 def changectx(self, req):
783 def changectx(self, req):
784 if req.form.has_key('node'):
784 if req.form.has_key('node'):
785 changeid = req.form['node'][0]
785 changeid = req.form['node'][0]
786 elif req.form.has_key('manifest'):
786 elif req.form.has_key('manifest'):
787 changeid = req.form['manifest'][0]
787 changeid = req.form['manifest'][0]
788 else:
788 else:
789 changeid = self.repo.changelog.count() - 1
789 changeid = self.repo.changelog.count() - 1
790
790
791 try:
791 try:
792 ctx = self.repo.changectx(changeid)
792 ctx = self.repo.changectx(changeid)
793 except hg.RepoError:
793 except hg.RepoError:
794 man = self.repo.manifest
794 man = self.repo.manifest
795 mn = man.lookup(changeid)
795 mn = man.lookup(changeid)
796 ctx = self.repo.changectx(man.linkrev(mn))
796 ctx = self.repo.changectx(man.linkrev(mn))
797
797
798 return ctx
798 return ctx
799
799
800 def filectx(self, req):
800 def filectx(self, req):
801 path = self.cleanpath(req.form['file'][0])
801 path = self.cleanpath(req.form['file'][0])
802 if req.form.has_key('node'):
802 if req.form.has_key('node'):
803 changeid = req.form['node'][0]
803 changeid = req.form['node'][0]
804 else:
804 else:
805 changeid = req.form['filenode'][0]
805 changeid = req.form['filenode'][0]
806 try:
806 try:
807 ctx = self.repo.changectx(changeid)
807 ctx = self.repo.changectx(changeid)
808 fctx = ctx.filectx(path)
808 fctx = ctx.filectx(path)
809 except hg.RepoError:
809 except hg.RepoError:
810 fctx = self.repo.filectx(path, fileid=changeid)
810 fctx = self.repo.filectx(path, fileid=changeid)
811
811
812 return fctx
812 return fctx
813
813
814 def stripes(self, parity):
814 def stripes(self, parity):
815 "make horizontal stripes for easier reading"
815 "make horizontal stripes for easier reading"
816 if self.stripecount:
816 if self.stripecount:
817 return (1 + parity / self.stripecount) & 1
817 return (1 + parity / self.stripecount) & 1
818 else:
818 else:
819 return 0
819 return 0
820
820
821 def do_log(self, req):
821 def do_log(self, req):
822 if req.form.has_key('file') and req.form['file'][0]:
822 if req.form.has_key('file') and req.form['file'][0]:
823 self.do_filelog(req)
823 self.do_filelog(req)
824 else:
824 else:
825 self.do_changelog(req)
825 self.do_changelog(req)
826
826
827 def do_rev(self, req):
827 def do_rev(self, req):
828 self.do_changeset(req)
828 self.do_changeset(req)
829
829
830 def do_file(self, req):
830 def do_file(self, req):
831 path = req.form.get('file', [''])[0]
831 path = req.form.get('file', [''])[0]
832 if path:
832 if path:
833 try:
833 try:
834 req.write(self.filerevision(self.filectx(req)))
834 req.write(self.filerevision(self.filectx(req)))
835 return
835 return
836 except hg.RepoError:
836 except hg.RepoError:
837 pass
837 pass
838 path = self.cleanpath(path)
838 path = self.cleanpath(path)
839
839
840 req.write(self.manifest(self.changectx(req), '/' + path))
840 req.write(self.manifest(self.changectx(req), '/' + path))
841
841
842 def do_diff(self, req):
842 def do_diff(self, req):
843 self.do_filediff(req)
843 self.do_filediff(req)
844
844
845 def do_changelog(self, req, shortlog = False):
845 def do_changelog(self, req, shortlog = False):
846 if req.form.has_key('node'):
846 if req.form.has_key('node'):
847 ctx = self.changectx(req)
847 ctx = self.changectx(req)
848 else:
848 else:
849 if req.form.has_key('rev'):
849 if req.form.has_key('rev'):
850 hi = req.form['rev'][0]
850 hi = req.form['rev'][0]
851 else:
851 else:
852 hi = self.repo.changelog.count() - 1
852 hi = self.repo.changelog.count() - 1
853 try:
853 try:
854 ctx = self.repo.changectx(hi)
854 ctx = self.repo.changectx(hi)
855 except hg.RepoError:
855 except hg.RepoError:
856 req.write(self.search(hi)) # XXX redirect to 404 page?
856 req.write(self.search(hi)) # XXX redirect to 404 page?
857 return
857 return
858
858
859 req.write(self.changelog(ctx, shortlog = shortlog))
859 req.write(self.changelog(ctx, shortlog = shortlog))
860
860
861 def do_shortlog(self, req):
861 def do_shortlog(self, req):
862 self.do_changelog(req, shortlog = True)
862 self.do_changelog(req, shortlog = True)
863
863
864 def do_changeset(self, req):
864 def do_changeset(self, req):
865 req.write(self.changeset(self.changectx(req)))
865 req.write(self.changeset(self.changectx(req)))
866
866
867 def do_manifest(self, req):
867 def do_manifest(self, req):
868 req.write(self.manifest(self.changectx(req),
868 req.write(self.manifest(self.changectx(req),
869 self.cleanpath(req.form['path'][0])))
869 self.cleanpath(req.form['path'][0])))
870
870
871 def do_tags(self, req):
871 def do_tags(self, req):
872 req.write(self.tags())
872 req.write(self.tags())
873
873
874 def do_summary(self, req):
874 def do_summary(self, req):
875 req.write(self.summary())
875 req.write(self.summary())
876
876
877 def do_filediff(self, req):
877 def do_filediff(self, req):
878 req.write(self.filediff(self.filectx(req)))
878 req.write(self.filediff(self.filectx(req)))
879
879
880 def do_annotate(self, req):
880 def do_annotate(self, req):
881 req.write(self.fileannotate(self.filectx(req)))
881 req.write(self.fileannotate(self.filectx(req)))
882
882
883 def do_filelog(self, req):
883 def do_filelog(self, req):
884 req.write(self.filelog(self.filectx(req)))
884 req.write(self.filelog(self.filectx(req)))
885
885
886 def do_lookup(self, req):
887 resp = hex(self.repo.lookup(req.form['key'][0])) + "\n"
888 req.httphdr("application/mercurial-0.1", length=len(resp))
889 req.write(resp)
890
886 def do_heads(self, req):
891 def do_heads(self, req):
887 resp = " ".join(map(hex, self.repo.heads())) + "\n"
892 resp = " ".join(map(hex, self.repo.heads())) + "\n"
888 req.httphdr("application/mercurial-0.1", length=len(resp))
893 req.httphdr("application/mercurial-0.1", length=len(resp))
889 req.write(resp)
894 req.write(resp)
890
895
891 def do_branches(self, req):
896 def do_branches(self, req):
892 nodes = []
897 nodes = []
893 if req.form.has_key('nodes'):
898 if req.form.has_key('nodes'):
894 nodes = map(bin, req.form['nodes'][0].split(" "))
899 nodes = map(bin, req.form['nodes'][0].split(" "))
895 resp = cStringIO.StringIO()
900 resp = cStringIO.StringIO()
896 for b in self.repo.branches(nodes):
901 for b in self.repo.branches(nodes):
897 resp.write(" ".join(map(hex, b)) + "\n")
902 resp.write(" ".join(map(hex, b)) + "\n")
898 resp = resp.getvalue()
903 resp = resp.getvalue()
899 req.httphdr("application/mercurial-0.1", length=len(resp))
904 req.httphdr("application/mercurial-0.1", length=len(resp))
900 req.write(resp)
905 req.write(resp)
901
906
902 def do_between(self, req):
907 def do_between(self, req):
903 if req.form.has_key('pairs'):
908 if req.form.has_key('pairs'):
904 pairs = [map(bin, p.split("-"))
909 pairs = [map(bin, p.split("-"))
905 for p in req.form['pairs'][0].split(" ")]
910 for p in req.form['pairs'][0].split(" ")]
906 resp = cStringIO.StringIO()
911 resp = cStringIO.StringIO()
907 for b in self.repo.between(pairs):
912 for b in self.repo.between(pairs):
908 resp.write(" ".join(map(hex, b)) + "\n")
913 resp.write(" ".join(map(hex, b)) + "\n")
909 resp = resp.getvalue()
914 resp = resp.getvalue()
910 req.httphdr("application/mercurial-0.1", length=len(resp))
915 req.httphdr("application/mercurial-0.1", length=len(resp))
911 req.write(resp)
916 req.write(resp)
912
917
913 def do_changegroup(self, req):
918 def do_changegroup(self, req):
914 req.httphdr("application/mercurial-0.1")
919 req.httphdr("application/mercurial-0.1")
915 nodes = []
920 nodes = []
916 if not self.allowpull:
921 if not self.allowpull:
917 return
922 return
918
923
919 if req.form.has_key('roots'):
924 if req.form.has_key('roots'):
920 nodes = map(bin, req.form['roots'][0].split(" "))
925 nodes = map(bin, req.form['roots'][0].split(" "))
921
926
922 z = zlib.compressobj()
927 z = zlib.compressobj()
923 f = self.repo.changegroup(nodes, 'serve')
928 f = self.repo.changegroup(nodes, 'serve')
924 while 1:
929 while 1:
925 chunk = f.read(4096)
930 chunk = f.read(4096)
926 if not chunk:
931 if not chunk:
927 break
932 break
928 req.write(z.compress(chunk))
933 req.write(z.compress(chunk))
929
934
930 req.write(z.flush())
935 req.write(z.flush())
931
936
937 def do_changegroupsubset(self, req):
938 req.httphdr("application/mercurial-0.1")
939 bases = []
940 heads = []
941 if not self.allowpull:
942 return
943
944 if req.form.has_key('bases'):
945 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
946 if req.form.has_key('heads'):
947 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
948
949 z = zlib.compressobj()
950 f = self.repo.changegroupsubset(bases, heads, 'serve')
951 while 1:
952 chunk = f.read(4096)
953 if not chunk:
954 break
955 req.write(z.compress(chunk))
956
957 req.write(z.flush())
958
932 def do_archive(self, req):
959 def do_archive(self, req):
933 changeset = self.repo.lookup(req.form['node'][0])
960 changeset = self.repo.lookup(req.form['node'][0])
934 type_ = req.form['type'][0]
961 type_ = req.form['type'][0]
935 allowed = self.repo.ui.configlist("web", "allow_archive")
962 allowed = self.repo.ui.configlist("web", "allow_archive")
936 if (type_ in self.archives and (type_ in allowed or
963 if (type_ in self.archives and (type_ in allowed or
937 self.repo.ui.configbool("web", "allow" + type_, False))):
964 self.repo.ui.configbool("web", "allow" + type_, False))):
938 self.archive(req, changeset, type_)
965 self.archive(req, changeset, type_)
939 return
966 return
940
967
941 req.write(self.t("error"))
968 req.write(self.t("error"))
942
969
943 def do_static(self, req):
970 def do_static(self, req):
944 fname = req.form['file'][0]
971 fname = req.form['file'][0]
945 static = self.repo.ui.config("web", "static",
972 static = self.repo.ui.config("web", "static",
946 os.path.join(self.templatepath,
973 os.path.join(self.templatepath,
947 "static"))
974 "static"))
948 req.write(staticfile(static, fname, req)
975 req.write(staticfile(static, fname, req)
949 or self.t("error", error="%r not found" % fname))
976 or self.t("error", error="%r not found" % fname))
950
977
951 def do_capabilities(self, req):
978 def do_capabilities(self, req):
952 caps = ['unbundle']
979 caps = ['unbundle', 'lookup', 'changegroupsubset']
953 if self.repo.ui.configbool('server', 'uncompressed'):
980 if self.repo.ui.configbool('server', 'uncompressed'):
954 caps.append('stream=%d' % self.repo.revlogversion)
981 caps.append('stream=%d' % self.repo.revlogversion)
955 resp = ' '.join(caps)
982 resp = ' '.join(caps)
956 req.httphdr("application/mercurial-0.1", length=len(resp))
983 req.httphdr("application/mercurial-0.1", length=len(resp))
957 req.write(resp)
984 req.write(resp)
958
985
959 def check_perm(self, req, op, default):
986 def check_perm(self, req, op, default):
960 '''check permission for operation based on user auth.
987 '''check permission for operation based on user auth.
961 return true if op allowed, else false.
988 return true if op allowed, else false.
962 default is policy to use if no config given.'''
989 default is policy to use if no config given.'''
963
990
964 user = req.env.get('REMOTE_USER')
991 user = req.env.get('REMOTE_USER')
965
992
966 deny = self.repo.ui.configlist('web', 'deny_' + op)
993 deny = self.repo.ui.configlist('web', 'deny_' + op)
967 if deny and (not user or deny == ['*'] or user in deny):
994 if deny and (not user or deny == ['*'] or user in deny):
968 return False
995 return False
969
996
970 allow = self.repo.ui.configlist('web', 'allow_' + op)
997 allow = self.repo.ui.configlist('web', 'allow_' + op)
971 return (allow and (allow == ['*'] or user in allow)) or default
998 return (allow and (allow == ['*'] or user in allow)) or default
972
999
973 def do_unbundle(self, req):
1000 def do_unbundle(self, req):
974 def bail(response, headers={}):
1001 def bail(response, headers={}):
975 length = int(req.env['CONTENT_LENGTH'])
1002 length = int(req.env['CONTENT_LENGTH'])
976 for s in util.filechunkiter(req, limit=length):
1003 for s in util.filechunkiter(req, limit=length):
977 # drain incoming bundle, else client will not see
1004 # drain incoming bundle, else client will not see
978 # response when run outside cgi script
1005 # response when run outside cgi script
979 pass
1006 pass
980 req.httphdr("application/mercurial-0.1", headers=headers)
1007 req.httphdr("application/mercurial-0.1", headers=headers)
981 req.write('0\n')
1008 req.write('0\n')
982 req.write(response)
1009 req.write(response)
983
1010
984 # require ssl by default, auth info cannot be sniffed and
1011 # require ssl by default, auth info cannot be sniffed and
985 # replayed
1012 # replayed
986 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
1013 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
987 if ssl_req:
1014 if ssl_req:
988 if not req.env.get('HTTPS'):
1015 if not req.env.get('HTTPS'):
989 bail(_('ssl required\n'))
1016 bail(_('ssl required\n'))
990 return
1017 return
991 proto = 'https'
1018 proto = 'https'
992 else:
1019 else:
993 proto = 'http'
1020 proto = 'http'
994
1021
995 # do not allow push unless explicitly allowed
1022 # do not allow push unless explicitly allowed
996 if not self.check_perm(req, 'push', False):
1023 if not self.check_perm(req, 'push', False):
997 bail(_('push not authorized\n'),
1024 bail(_('push not authorized\n'),
998 headers={'status': '401 Unauthorized'})
1025 headers={'status': '401 Unauthorized'})
999 return
1026 return
1000
1027
1001 req.httphdr("application/mercurial-0.1")
1028 req.httphdr("application/mercurial-0.1")
1002
1029
1003 their_heads = req.form['heads'][0].split(' ')
1030 their_heads = req.form['heads'][0].split(' ')
1004
1031
1005 def check_heads():
1032 def check_heads():
1006 heads = map(hex, self.repo.heads())
1033 heads = map(hex, self.repo.heads())
1007 return their_heads == [hex('force')] or their_heads == heads
1034 return their_heads == [hex('force')] or their_heads == heads
1008
1035
1009 # fail early if possible
1036 # fail early if possible
1010 if not check_heads():
1037 if not check_heads():
1011 bail(_('unsynced changes\n'))
1038 bail(_('unsynced changes\n'))
1012 return
1039 return
1013
1040
1014 # do not lock repo until all changegroup data is
1041 # do not lock repo until all changegroup data is
1015 # streamed. save to temporary file.
1042 # streamed. save to temporary file.
1016
1043
1017 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1044 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1018 fp = os.fdopen(fd, 'wb+')
1045 fp = os.fdopen(fd, 'wb+')
1019 try:
1046 try:
1020 length = int(req.env['CONTENT_LENGTH'])
1047 length = int(req.env['CONTENT_LENGTH'])
1021 for s in util.filechunkiter(req, limit=length):
1048 for s in util.filechunkiter(req, limit=length):
1022 fp.write(s)
1049 fp.write(s)
1023
1050
1024 lock = self.repo.lock()
1051 lock = self.repo.lock()
1025 try:
1052 try:
1026 if not check_heads():
1053 if not check_heads():
1027 req.write('0\n')
1054 req.write('0\n')
1028 req.write(_('unsynced changes\n'))
1055 req.write(_('unsynced changes\n'))
1029 return
1056 return
1030
1057
1031 fp.seek(0)
1058 fp.seek(0)
1032
1059
1033 # send addchangegroup output to client
1060 # send addchangegroup output to client
1034
1061
1035 old_stdout = sys.stdout
1062 old_stdout = sys.stdout
1036 sys.stdout = cStringIO.StringIO()
1063 sys.stdout = cStringIO.StringIO()
1037
1064
1038 try:
1065 try:
1039 url = 'remote:%s:%s' % (proto,
1066 url = 'remote:%s:%s' % (proto,
1040 req.env.get('REMOTE_HOST', ''))
1067 req.env.get('REMOTE_HOST', ''))
1041 ret = self.repo.addchangegroup(fp, 'serve', url)
1068 ret = self.repo.addchangegroup(fp, 'serve', url)
1042 finally:
1069 finally:
1043 val = sys.stdout.getvalue()
1070 val = sys.stdout.getvalue()
1044 sys.stdout = old_stdout
1071 sys.stdout = old_stdout
1045 req.write('%d\n' % ret)
1072 req.write('%d\n' % ret)
1046 req.write(val)
1073 req.write(val)
1047 finally:
1074 finally:
1048 lock.release()
1075 lock.release()
1049 finally:
1076 finally:
1050 fp.close()
1077 fp.close()
1051 os.unlink(tempname)
1078 os.unlink(tempname)
1052
1079
1053 def do_stream_out(self, req):
1080 def do_stream_out(self, req):
1054 req.httphdr("application/mercurial-0.1")
1081 req.httphdr("application/mercurial-0.1")
1055 streamclone.stream_out(self.repo, req)
1082 streamclone.stream_out(self.repo, req)
@@ -1,355 +1,379 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from node import *
9 from node import *
10 from remoterepo import *
10 from remoterepo import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
14 demandload(globals(), "errno keepalive tempfile socket")
14 demandload(globals(), "errno keepalive tempfile socket")
15
15
16 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
17 def __init__(self, ui):
17 def __init__(self, ui):
18 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
19 self.ui = ui
19 self.ui = ui
20
20
21 def find_user_password(self, realm, authuri):
21 def find_user_password(self, realm, authuri):
22 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
23 self, realm, authuri)
23 self, realm, authuri)
24 user, passwd = authinfo
24 user, passwd = authinfo
25 if user and passwd:
25 if user and passwd:
26 return (user, passwd)
26 return (user, passwd)
27
27
28 if not self.ui.interactive:
28 if not self.ui.interactive:
29 raise util.Abort(_('http authorization required'))
29 raise util.Abort(_('http authorization required'))
30
30
31 self.ui.write(_("http authorization required\n"))
31 self.ui.write(_("http authorization required\n"))
32 self.ui.status(_("realm: %s\n") % realm)
32 self.ui.status(_("realm: %s\n") % realm)
33 if user:
33 if user:
34 self.ui.status(_("user: %s\n") % user)
34 self.ui.status(_("user: %s\n") % user)
35 else:
35 else:
36 user = self.ui.prompt(_("user:"), default=None)
36 user = self.ui.prompt(_("user:"), default=None)
37
37
38 if not passwd:
38 if not passwd:
39 passwd = self.ui.getpass()
39 passwd = self.ui.getpass()
40
40
41 self.add_password(realm, authuri, user, passwd)
41 self.add_password(realm, authuri, user, passwd)
42 return (user, passwd)
42 return (user, passwd)
43
43
44 def netlocsplit(netloc):
44 def netlocsplit(netloc):
45 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
45 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
46
46
47 a = netloc.find('@')
47 a = netloc.find('@')
48 if a == -1:
48 if a == -1:
49 user, passwd = None, None
49 user, passwd = None, None
50 else:
50 else:
51 userpass, netloc = netloc[:a], netloc[a+1:]
51 userpass, netloc = netloc[:a], netloc[a+1:]
52 c = userpass.find(':')
52 c = userpass.find(':')
53 if c == -1:
53 if c == -1:
54 user, passwd = urllib.unquote(userpass), None
54 user, passwd = urllib.unquote(userpass), None
55 else:
55 else:
56 user = urllib.unquote(userpass[:c])
56 user = urllib.unquote(userpass[:c])
57 passwd = urllib.unquote(userpass[c+1:])
57 passwd = urllib.unquote(userpass[c+1:])
58 c = netloc.find(':')
58 c = netloc.find(':')
59 if c == -1:
59 if c == -1:
60 host, port = netloc, None
60 host, port = netloc, None
61 else:
61 else:
62 host, port = netloc[:c], netloc[c+1:]
62 host, port = netloc[:c], netloc[c+1:]
63 return host, port, user, passwd
63 return host, port, user, passwd
64
64
65 def netlocunsplit(host, port, user=None, passwd=None):
65 def netlocunsplit(host, port, user=None, passwd=None):
66 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
66 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
67 if port:
67 if port:
68 hostport = host + ':' + port
68 hostport = host + ':' + port
69 else:
69 else:
70 hostport = host
70 hostport = host
71 if user:
71 if user:
72 if passwd:
72 if passwd:
73 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
73 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
74 else:
74 else:
75 userpass = urllib.quote(user)
75 userpass = urllib.quote(user)
76 return userpass + '@' + hostport
76 return userpass + '@' + hostport
77 return hostport
77 return hostport
78
78
79 class httpconnection(keepalive.HTTPConnection):
79 class httpconnection(keepalive.HTTPConnection):
80 # must be able to send big bundle as stream.
80 # must be able to send big bundle as stream.
81
81
82 def send(self, data):
82 def send(self, data):
83 if isinstance(data, str):
83 if isinstance(data, str):
84 keepalive.HTTPConnection.send(self, data)
84 keepalive.HTTPConnection.send(self, data)
85 else:
85 else:
86 # if auth required, some data sent twice, so rewind here
86 # if auth required, some data sent twice, so rewind here
87 data.seek(0)
87 data.seek(0)
88 for chunk in util.filechunkiter(data):
88 for chunk in util.filechunkiter(data):
89 keepalive.HTTPConnection.send(self, chunk)
89 keepalive.HTTPConnection.send(self, chunk)
90
90
91 class basehttphandler(keepalive.HTTPHandler):
91 class basehttphandler(keepalive.HTTPHandler):
92 def http_open(self, req):
92 def http_open(self, req):
93 return self.do_open(httpconnection, req)
93 return self.do_open(httpconnection, req)
94
94
95 has_https = hasattr(urllib2, 'HTTPSHandler')
95 has_https = hasattr(urllib2, 'HTTPSHandler')
96 if has_https:
96 if has_https:
97 class httpsconnection(httplib.HTTPSConnection):
97 class httpsconnection(httplib.HTTPSConnection):
98 response_class = keepalive.HTTPResponse
98 response_class = keepalive.HTTPResponse
99 # must be able to send big bundle as stream.
99 # must be able to send big bundle as stream.
100
100
101 def send(self, data):
101 def send(self, data):
102 if isinstance(data, str):
102 if isinstance(data, str):
103 httplib.HTTPSConnection.send(self, data)
103 httplib.HTTPSConnection.send(self, data)
104 else:
104 else:
105 # if auth required, some data sent twice, so rewind here
105 # if auth required, some data sent twice, so rewind here
106 data.seek(0)
106 data.seek(0)
107 for chunk in util.filechunkiter(data):
107 for chunk in util.filechunkiter(data):
108 httplib.HTTPSConnection.send(self, chunk)
108 httplib.HTTPSConnection.send(self, chunk)
109
109
110 class httphandler(basehttphandler, urllib2.HTTPSHandler):
110 class httphandler(basehttphandler, urllib2.HTTPSHandler):
111 def https_open(self, req):
111 def https_open(self, req):
112 return self.do_open(httpsconnection, req)
112 return self.do_open(httpsconnection, req)
113 else:
113 else:
114 class httphandler(basehttphandler):
114 class httphandler(basehttphandler):
115 pass
115 pass
116
116
117 class httprepository(remoterepository):
117 class httprepository(remoterepository):
118 def __init__(self, ui, path):
118 def __init__(self, ui, path):
119 self.path = path
119 self.path = path
120 self.caps = None
120 self.caps = None
121 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
121 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
122 if query or frag:
122 if query or frag:
123 raise util.Abort(_('unsupported URL component: "%s"') %
123 raise util.Abort(_('unsupported URL component: "%s"') %
124 (query or frag))
124 (query or frag))
125 if not urlpath: urlpath = '/'
125 if not urlpath: urlpath = '/'
126 host, port, user, passwd = netlocsplit(netloc)
126 host, port, user, passwd = netlocsplit(netloc)
127
127
128 # urllib cannot handle URLs with embedded user or passwd
128 # urllib cannot handle URLs with embedded user or passwd
129 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
129 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
130 urlpath, '', ''))
130 urlpath, '', ''))
131 self.ui = ui
131 self.ui = ui
132
132
133 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
133 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
134 # XXX proxyauthinfo = None
134 # XXX proxyauthinfo = None
135 handler = httphandler()
135 handler = httphandler()
136
136
137 if proxyurl:
137 if proxyurl:
138 # proxy can be proper url or host[:port]
138 # proxy can be proper url or host[:port]
139 if not (proxyurl.startswith('http:') or
139 if not (proxyurl.startswith('http:') or
140 proxyurl.startswith('https:')):
140 proxyurl.startswith('https:')):
141 proxyurl = 'http://' + proxyurl + '/'
141 proxyurl = 'http://' + proxyurl + '/'
142 snpqf = urlparse.urlsplit(proxyurl)
142 snpqf = urlparse.urlsplit(proxyurl)
143 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
143 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
144 hpup = netlocsplit(proxynetloc)
144 hpup = netlocsplit(proxynetloc)
145
145
146 proxyhost, proxyport, proxyuser, proxypasswd = hpup
146 proxyhost, proxyport, proxyuser, proxypasswd = hpup
147 if not proxyuser:
147 if not proxyuser:
148 proxyuser = ui.config("http_proxy", "user")
148 proxyuser = ui.config("http_proxy", "user")
149 proxypasswd = ui.config("http_proxy", "passwd")
149 proxypasswd = ui.config("http_proxy", "passwd")
150
150
151 # see if we should use a proxy for this url
151 # see if we should use a proxy for this url
152 no_list = [ "localhost", "127.0.0.1" ]
152 no_list = [ "localhost", "127.0.0.1" ]
153 no_list.extend([p.lower() for
153 no_list.extend([p.lower() for
154 p in ui.configlist("http_proxy", "no")])
154 p in ui.configlist("http_proxy", "no")])
155 no_list.extend([p.strip().lower() for
155 no_list.extend([p.strip().lower() for
156 p in os.getenv("no_proxy", '').split(',')
156 p in os.getenv("no_proxy", '').split(',')
157 if p.strip()])
157 if p.strip()])
158 # "http_proxy.always" config is for running tests on localhost
158 # "http_proxy.always" config is for running tests on localhost
159 if (not ui.configbool("http_proxy", "always") and
159 if (not ui.configbool("http_proxy", "always") and
160 host.lower() in no_list):
160 host.lower() in no_list):
161 ui.debug(_('disabling proxy for %s\n') % host)
161 ui.debug(_('disabling proxy for %s\n') % host)
162 else:
162 else:
163 proxyurl = urlparse.urlunsplit((
163 proxyurl = urlparse.urlunsplit((
164 proxyscheme, netlocunsplit(proxyhost, proxyport,
164 proxyscheme, netlocunsplit(proxyhost, proxyport,
165 proxyuser, proxypasswd or ''),
165 proxyuser, proxypasswd or ''),
166 proxypath, proxyquery, proxyfrag))
166 proxypath, proxyquery, proxyfrag))
167 handler = urllib2.ProxyHandler({scheme: proxyurl})
167 handler = urllib2.ProxyHandler({scheme: proxyurl})
168 ui.debug(_('proxying through http://%s:%s\n') %
168 ui.debug(_('proxying through http://%s:%s\n') %
169 (proxyhost, proxyport))
169 (proxyhost, proxyport))
170
170
171 # urllib2 takes proxy values from the environment and those
171 # urllib2 takes proxy values from the environment and those
172 # will take precedence if found, so drop them
172 # will take precedence if found, so drop them
173 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
173 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
174 try:
174 try:
175 if os.environ.has_key(env):
175 if os.environ.has_key(env):
176 del os.environ[env]
176 del os.environ[env]
177 except OSError:
177 except OSError:
178 pass
178 pass
179
179
180 passmgr = passwordmgr(ui)
180 passmgr = passwordmgr(ui)
181 if user:
181 if user:
182 ui.debug(_('http auth: user %s, password %s\n') %
182 ui.debug(_('http auth: user %s, password %s\n') %
183 (user, passwd and '*' * len(passwd) or 'not set'))
183 (user, passwd and '*' * len(passwd) or 'not set'))
184 passmgr.add_password(None, host, user, passwd or '')
184 passmgr.add_password(None, host, user, passwd or '')
185
185
186 opener = urllib2.build_opener(
186 opener = urllib2.build_opener(
187 handler,
187 handler,
188 urllib2.HTTPBasicAuthHandler(passmgr),
188 urllib2.HTTPBasicAuthHandler(passmgr),
189 urllib2.HTTPDigestAuthHandler(passmgr))
189 urllib2.HTTPDigestAuthHandler(passmgr))
190
190
191 # 1.0 here is the _protocol_ version
191 # 1.0 here is the _protocol_ version
192 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
192 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
193 urllib2.install_opener(opener)
193 urllib2.install_opener(opener)
194
194
195 def url(self):
195 def url(self):
196 return self.path
196 return self.path
197
197
198 # look up capabilities only when needed
198 # look up capabilities only when needed
199
199
200 def get_caps(self):
200 def get_caps(self):
201 if self.caps is None:
201 if self.caps is None:
202 try:
202 try:
203 self.caps = self.do_read('capabilities').split()
203 self.caps = self.do_read('capabilities').split()
204 except hg.RepoError:
204 except hg.RepoError:
205 self.caps = ()
205 self.caps = ()
206 self.ui.debug(_('capabilities: %s\n') %
206 self.ui.debug(_('capabilities: %s\n') %
207 (' '.join(self.caps or ['none'])))
207 (' '.join(self.caps or ['none'])))
208 return self.caps
208 return self.caps
209
209
210 capabilities = property(get_caps)
210 capabilities = property(get_caps)
211
211
212 def lock(self):
212 def lock(self):
213 raise util.Abort(_('operation not supported over http'))
213 raise util.Abort(_('operation not supported over http'))
214
214
215 def do_cmd(self, cmd, **args):
215 def do_cmd(self, cmd, **args):
216 data = args.pop('data', None)
216 data = args.pop('data', None)
217 headers = args.pop('headers', {})
217 headers = args.pop('headers', {})
218 self.ui.debug(_("sending %s command\n") % cmd)
218 self.ui.debug(_("sending %s command\n") % cmd)
219 q = {"cmd": cmd}
219 q = {"cmd": cmd}
220 q.update(args)
220 q.update(args)
221 qs = urllib.urlencode(q)
221 qs = urllib.urlencode(q)
222 cu = "%s?%s" % (self._url, qs)
222 cu = "%s?%s" % (self._url, qs)
223 try:
223 try:
224 resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
224 resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
225 except urllib2.HTTPError, inst:
225 except urllib2.HTTPError, inst:
226 if inst.code == 401:
226 if inst.code == 401:
227 raise util.Abort(_('authorization failed'))
227 raise util.Abort(_('authorization failed'))
228 raise
228 raise
229 except httplib.HTTPException, inst:
229 except httplib.HTTPException, inst:
230 self.ui.debug(_('http error while sending %s command\n') % cmd)
230 self.ui.debug(_('http error while sending %s command\n') % cmd)
231 self.ui.print_exc()
231 self.ui.print_exc()
232 raise IOError(None, inst)
232 raise IOError(None, inst)
233 except IndexError:
233 except IndexError:
234 # this only happens with Python 2.3, later versions raise URLError
234 # this only happens with Python 2.3, later versions raise URLError
235 raise util.Abort(_('http error, possibly caused by proxy setting'))
235 raise util.Abort(_('http error, possibly caused by proxy setting'))
236 try:
236 try:
237 proto = resp.getheader('content-type')
237 proto = resp.getheader('content-type')
238 except AttributeError:
238 except AttributeError:
239 proto = resp.headers['content-type']
239 proto = resp.headers['content-type']
240
240
241 # accept old "text/plain" and "application/hg-changegroup" for now
241 # accept old "text/plain" and "application/hg-changegroup" for now
242 if not proto.startswith('application/mercurial') and \
242 if not proto.startswith('application/mercurial') and \
243 not proto.startswith('text/plain') and \
243 not proto.startswith('text/plain') and \
244 not proto.startswith('application/hg-changegroup'):
244 not proto.startswith('application/hg-changegroup'):
245 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
245 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
246 self._url)
246 self._url)
247
247
248 if proto.startswith('application/mercurial'):
248 if proto.startswith('application/mercurial'):
249 version = proto[22:]
249 version = proto[22:]
250 if float(version) > 0.1:
250 if float(version) > 0.1:
251 raise hg.RepoError(_("'%s' uses newer protocol %s") %
251 raise hg.RepoError(_("'%s' uses newer protocol %s") %
252 (self._url, version))
252 (self._url, version))
253
253
254 return resp
254 return resp
255
255
256 def do_read(self, cmd, **args):
256 def do_read(self, cmd, **args):
257 fp = self.do_cmd(cmd, **args)
257 fp = self.do_cmd(cmd, **args)
258 try:
258 try:
259 return fp.read()
259 return fp.read()
260 finally:
260 finally:
261 # if using keepalive, allow connection to be reused
261 # if using keepalive, allow connection to be reused
262 fp.close()
262 fp.close()
263
263
264 def lookup(self, key):
265 try:
266 d = self.do_cmd("lookup", key = key).read()
267 return bin(d[:-1])
268 except:
269 self.ui.warn('Not able to look up revision named "%s"\n' % (key,))
270 raise
271
264 def heads(self):
272 def heads(self):
265 d = self.do_read("heads")
273 d = self.do_read("heads")
266 try:
274 try:
267 return map(bin, d[:-1].split(" "))
275 return map(bin, d[:-1].split(" "))
268 except:
276 except:
269 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
277 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
270 raise
278 raise
271
279
272 def branches(self, nodes):
280 def branches(self, nodes):
273 n = " ".join(map(hex, nodes))
281 n = " ".join(map(hex, nodes))
274 d = self.do_read("branches", nodes=n)
282 d = self.do_read("branches", nodes=n)
275 try:
283 try:
276 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
284 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
277 return br
285 return br
278 except:
286 except:
279 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
287 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
280 raise
288 raise
281
289
282 def between(self, pairs):
290 def between(self, pairs):
283 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
291 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
284 d = self.do_read("between", pairs=n)
292 d = self.do_read("between", pairs=n)
285 try:
293 try:
286 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
294 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
287 return p
295 return p
288 except:
296 except:
289 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
297 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
290 raise
298 raise
291
299
292 def changegroup(self, nodes, kind):
300 def changegroup(self, nodes, kind):
293 n = " ".join(map(hex, nodes))
301 n = " ".join(map(hex, nodes))
294 f = self.do_cmd("changegroup", roots=n)
302 f = self.do_cmd("changegroup", roots=n)
295
303
296 def zgenerator(f):
304 def zgenerator(f):
297 zd = zlib.decompressobj()
305 zd = zlib.decompressobj()
298 try:
306 try:
299 for chnk in f:
307 for chnk in f:
300 yield zd.decompress(chnk)
308 yield zd.decompress(chnk)
309 except httplib.HTTPException, inst:
310 raise IOError(None, _('connection ended unexpectedly'))
311 yield zd.flush()
312
313 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
314
315 def changegroupsubset(self, bases, heads, source):
316 baselst = " ".join([hex(n) for n in bases])
317 headlst = " ".join([hex(n) for n in heads])
318 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
319
320 def zgenerator(f):
321 zd = zlib.decompressobj()
322 try:
323 for chnk in f:
324 yield zd.decompress(chnk)
301 except httplib.HTTPException:
325 except httplib.HTTPException:
302 raise IOError(None, _('connection ended unexpectedly'))
326 raise IOError(None, _('connection ended unexpectedly'))
303 yield zd.flush()
327 yield zd.flush()
304
328
305 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
329 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
306
330
307 def unbundle(self, cg, heads, source):
331 def unbundle(self, cg, heads, source):
308 # have to stream bundle to a temp file because we do not have
332 # have to stream bundle to a temp file because we do not have
309 # http 1.1 chunked transfer.
333 # http 1.1 chunked transfer.
310
334
311 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
335 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
312 fp = os.fdopen(fd, 'wb+')
336 fp = os.fdopen(fd, 'wb+')
313 try:
337 try:
314 for chunk in util.filechunkiter(cg):
338 for chunk in util.filechunkiter(cg):
315 fp.write(chunk)
339 fp.write(chunk)
316 length = fp.tell()
340 length = fp.tell()
317 try:
341 try:
318 rfp = self.do_cmd(
342 rfp = self.do_cmd(
319 'unbundle', data=fp,
343 'unbundle', data=fp,
320 headers={'content-length': length,
344 headers={'content-length': length,
321 'content-type': 'application/octet-stream'},
345 'content-type': 'application/octet-stream'},
322 heads=' '.join(map(hex, heads)))
346 heads=' '.join(map(hex, heads)))
323 try:
347 try:
324 ret = int(rfp.readline())
348 ret = int(rfp.readline())
325 self.ui.write(rfp.read())
349 self.ui.write(rfp.read())
326 return ret
350 return ret
327 finally:
351 finally:
328 rfp.close()
352 rfp.close()
329 except socket.error, err:
353 except socket.error, err:
330 if err[0] in (errno.ECONNRESET, errno.EPIPE):
354 if err[0] in (errno.ECONNRESET, errno.EPIPE):
331 raise util.Abort(_('push failed: %s') % err[1])
355 raise util.Abort(_('push failed: %s') % err[1])
332 raise util.Abort(err[1])
356 raise util.Abort(err[1])
333 finally:
357 finally:
334 fp.close()
358 fp.close()
335 os.unlink(tempname)
359 os.unlink(tempname)
336
360
337 def stream_out(self):
361 def stream_out(self):
338 return self.do_cmd('stream_out')
362 return self.do_cmd('stream_out')
339
363
340 class httpsrepository(httprepository):
364 class httpsrepository(httprepository):
341 def __init__(self, ui, path):
365 def __init__(self, ui, path):
342 if not has_https:
366 if not has_https:
343 raise util.Abort(_('Python support for SSL and HTTPS '
367 raise util.Abort(_('Python support for SSL and HTTPS '
344 'is not installed'))
368 'is not installed'))
345 httprepository.__init__(self, ui, path)
369 httprepository.__init__(self, ui, path)
346
370
347 def instance(ui, path, create):
371 def instance(ui, path, create):
348 if create:
372 if create:
349 raise util.Abort(_('cannot create new http repository'))
373 raise util.Abort(_('cannot create new http repository'))
350 if path.startswith('hg:'):
374 if path.startswith('hg:'):
351 ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
375 ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
352 path = 'http:' + path[3:]
376 path = 'http:' + path[3:]
353 if path.startswith('https:'):
377 if path.startswith('https:'):
354 return httpsrepository(ui, path)
378 return httpsrepository(ui, path)
355 return httprepository(ui, path)
379 return httprepository(ui, path)
General Comments 0
You need to be logged in to leave comments. Login now