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