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