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