##// END OF EJS Templates
Make hgweb.staticfile() more secure and portable....
Thomas Arendsen Hein -
r1825:a9343f9d default
parent child Browse files
Show More
@@ -1,1188 +1,1185 b''
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys, urllib
9 import os, cgi, sys, urllib
10 import mimetypes
10 import mimetypes
11 from demandload import demandload
11 from demandload import demandload
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
14 demandload(globals(), "mimetypes")
14 demandload(globals(), "mimetypes")
15 from node import *
15 from node import *
16 from i18n import gettext as _
16 from i18n import gettext as _
17
17
18 def templatepath():
18 def templatepath():
19 for f in "templates", "../templates":
19 for f in "templates", "../templates":
20 p = os.path.join(os.path.dirname(__file__), f)
20 p = os.path.join(os.path.dirname(__file__), f)
21 if os.path.isdir(p):
21 if os.path.isdir(p):
22 return os.path.normpath(p)
22 return os.path.normpath(p)
23
23
24 def age(x):
24 def age(x):
25 def plural(t, c):
25 def plural(t, c):
26 if c == 1:
26 if c == 1:
27 return t
27 return t
28 return t + "s"
28 return t + "s"
29 def fmt(t, c):
29 def fmt(t, c):
30 return "%d %s" % (c, plural(t, c))
30 return "%d %s" % (c, plural(t, c))
31
31
32 now = time.time()
32 now = time.time()
33 then = x[0]
33 then = x[0]
34 delta = max(1, int(now - then))
34 delta = max(1, int(now - then))
35
35
36 scales = [["second", 1],
36 scales = [["second", 1],
37 ["minute", 60],
37 ["minute", 60],
38 ["hour", 3600],
38 ["hour", 3600],
39 ["day", 3600 * 24],
39 ["day", 3600 * 24],
40 ["week", 3600 * 24 * 7],
40 ["week", 3600 * 24 * 7],
41 ["month", 3600 * 24 * 30],
41 ["month", 3600 * 24 * 30],
42 ["year", 3600 * 24 * 365]]
42 ["year", 3600 * 24 * 365]]
43
43
44 scales.reverse()
44 scales.reverse()
45
45
46 for t, s in scales:
46 for t, s in scales:
47 n = delta / s
47 n = delta / s
48 if n >= 2 or s == 1:
48 if n >= 2 or s == 1:
49 return fmt(t, n)
49 return fmt(t, n)
50
50
51 def nl2br(text):
51 def nl2br(text):
52 return text.replace('\n', '<br/>\n')
52 return text.replace('\n', '<br/>\n')
53
53
54 def obfuscate(text):
54 def obfuscate(text):
55 return ''.join(['&#%d;' % ord(c) for c in text])
55 return ''.join(['&#%d;' % ord(c) for c in text])
56
56
57 def up(p):
57 def up(p):
58 if p[0] != "/":
58 if p[0] != "/":
59 p = "/" + p
59 p = "/" + p
60 if p[-1] == "/":
60 if p[-1] == "/":
61 p = p[:-1]
61 p = p[:-1]
62 up = os.path.dirname(p)
62 up = os.path.dirname(p)
63 if up == "/":
63 if up == "/":
64 return "/"
64 return "/"
65 return up + "/"
65 return up + "/"
66
66
67 def get_mtime(repo_path):
67 def get_mtime(repo_path):
68 hg_path = os.path.join(repo_path, ".hg")
68 hg_path = os.path.join(repo_path, ".hg")
69 cl_path = os.path.join(hg_path, "00changelog.i")
69 cl_path = os.path.join(hg_path, "00changelog.i")
70 if os.path.exists(os.path.join(cl_path)):
70 if os.path.exists(os.path.join(cl_path)):
71 return os.stat(cl_path).st_mtime
71 return os.stat(cl_path).st_mtime
72 else:
72 else:
73 return os.stat(hg_path).st_mtime
73 return os.stat(hg_path).st_mtime
74
74
75 def staticfile(directory, fname):
75 def staticfile(directory, fname):
76 fname = os.path.realpath(os.path.join(directory, fname))
76 """return a file inside directory with guessed content-type header
77
78 fname always uses '/' as directory separator and isn't allowed to
79 contain unusual path components.
80 Content-type is guessed using the mimetypes module.
81 Return an empty string if fname is illegal or file not found.
77
82
83 """
84 parts = fname.split('/')
85 path = directory
86 for part in parts:
87 if (part in ('', os.curdir, os.pardir) or
88 os.sep in part or os.altsep is not None and os.altsep in part):
89 return ""
90 path = os.path.join(path, part)
78 try:
91 try:
79 # the static dir should be a substring in the real
92 os.stat(path)
80 # file path, if it is not, we have something strange
93 ct = mimetypes.guess_type(path)[0] or "text/plain"
81 # going on => security breach attempt?
94 return "Content-type: %s\n\n%s" % (ct, file(path).read())
82 #
95 except (TypeError, OSError):
83 # This will either:
96 # illegal fname or unreadable file
84 # 1) find the `static' path at index 0 = success
85 # 2) find the `static' path at other index = error
86 # 3) not find the `static' path = ValueError generated
87 if fname.index(directory) != 0:
88 # generate ValueError manually
89 raise ValueError()
90
91 os.stat(fname)
92
93 ct = mimetypes.guess_type(fname)[0] or "text/plain"
94 return "Content-type: %s\n\n%s" % (ct, file(fname).read())
95 except ValueError:
96 # security breach attempt
97 return ""
97 return ""
98 except OSError, e:
99 if e.errno == errno.ENOENT:
100 return ""
101
98
102 class hgrequest(object):
99 class hgrequest(object):
103 def __init__(self, inp=None, out=None, env=None):
100 def __init__(self, inp=None, out=None, env=None):
104 self.inp = inp or sys.stdin
101 self.inp = inp or sys.stdin
105 self.out = out or sys.stdout
102 self.out = out or sys.stdout
106 self.env = env or os.environ
103 self.env = env or os.environ
107 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
104 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
108
105
109 def write(self, *things):
106 def write(self, *things):
110 for thing in things:
107 for thing in things:
111 if hasattr(thing, "__iter__"):
108 if hasattr(thing, "__iter__"):
112 for part in thing:
109 for part in thing:
113 self.write(part)
110 self.write(part)
114 else:
111 else:
115 try:
112 try:
116 self.out.write(str(thing))
113 self.out.write(str(thing))
117 except socket.error, inst:
114 except socket.error, inst:
118 if inst[0] != errno.ECONNRESET:
115 if inst[0] != errno.ECONNRESET:
119 raise
116 raise
120
117
121 def header(self, headers=[('Content-type','text/html')]):
118 def header(self, headers=[('Content-type','text/html')]):
122 for header in headers:
119 for header in headers:
123 self.out.write("%s: %s\r\n" % header)
120 self.out.write("%s: %s\r\n" % header)
124 self.out.write("\r\n")
121 self.out.write("\r\n")
125
122
126 def httphdr(self, type, file="", size=0):
123 def httphdr(self, type, file="", size=0):
127
124
128 headers = [('Content-type', type)]
125 headers = [('Content-type', type)]
129 if file:
126 if file:
130 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
127 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
131 if size > 0:
128 if size > 0:
132 headers.append(('Content-length', str(size)))
129 headers.append(('Content-length', str(size)))
133 self.header(headers)
130 self.header(headers)
134
131
135 class templater(object):
132 class templater(object):
136 def __init__(self, mapfile, filters={}, defaults={}):
133 def __init__(self, mapfile, filters={}, defaults={}):
137 self.cache = {}
134 self.cache = {}
138 self.map = {}
135 self.map = {}
139 self.base = os.path.dirname(mapfile)
136 self.base = os.path.dirname(mapfile)
140 self.filters = filters
137 self.filters = filters
141 self.defaults = defaults
138 self.defaults = defaults
142
139
143 for l in file(mapfile):
140 for l in file(mapfile):
144 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
141 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
145 if m:
142 if m:
146 self.cache[m.group(1)] = m.group(2)
143 self.cache[m.group(1)] = m.group(2)
147 else:
144 else:
148 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
145 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
149 if m:
146 if m:
150 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
147 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
151 else:
148 else:
152 raise LookupError(_("unknown map entry '%s'") % l)
149 raise LookupError(_("unknown map entry '%s'") % l)
153
150
154 def __call__(self, t, **map):
151 def __call__(self, t, **map):
155 m = self.defaults.copy()
152 m = self.defaults.copy()
156 m.update(map)
153 m.update(map)
157 try:
154 try:
158 tmpl = self.cache[t]
155 tmpl = self.cache[t]
159 except KeyError:
156 except KeyError:
160 tmpl = self.cache[t] = file(self.map[t]).read()
157 tmpl = self.cache[t] = file(self.map[t]).read()
161 return self.template(tmpl, self.filters, **m)
158 return self.template(tmpl, self.filters, **m)
162
159
163 def template(self, tmpl, filters={}, **map):
160 def template(self, tmpl, filters={}, **map):
164 while tmpl:
161 while tmpl:
165 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
162 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
166 if m:
163 if m:
167 yield tmpl[:m.start(0)]
164 yield tmpl[:m.start(0)]
168 v = map.get(m.group(1), "")
165 v = map.get(m.group(1), "")
169 v = callable(v) and v(**map) or v
166 v = callable(v) and v(**map) or v
170
167
171 format = m.group(2)
168 format = m.group(2)
172 fl = m.group(4)
169 fl = m.group(4)
173
170
174 if format:
171 if format:
175 q = v.__iter__
172 q = v.__iter__
176 for i in q():
173 for i in q():
177 lm = map.copy()
174 lm = map.copy()
178 lm.update(i)
175 lm.update(i)
179 yield self(format[1:], **lm)
176 yield self(format[1:], **lm)
180
177
181 v = ""
178 v = ""
182
179
183 elif fl:
180 elif fl:
184 for f in fl.split("|")[1:]:
181 for f in fl.split("|")[1:]:
185 v = filters[f](v)
182 v = filters[f](v)
186
183
187 yield v
184 yield v
188 tmpl = tmpl[m.end(0):]
185 tmpl = tmpl[m.end(0):]
189 else:
186 else:
190 yield tmpl
187 yield tmpl
191 return
188 return
192
189
193 common_filters = {
190 common_filters = {
194 "escape": lambda x: cgi.escape(x, True),
191 "escape": lambda x: cgi.escape(x, True),
195 "urlescape": urllib.quote,
192 "urlescape": urllib.quote,
196 "strip": lambda x: x.strip(),
193 "strip": lambda x: x.strip(),
197 "age": age,
194 "age": age,
198 "date": lambda x: util.datestr(x),
195 "date": lambda x: util.datestr(x),
199 "addbreaks": nl2br,
196 "addbreaks": nl2br,
200 "obfuscate": obfuscate,
197 "obfuscate": obfuscate,
201 "short": (lambda x: x[:12]),
198 "short": (lambda x: x[:12]),
202 "firstline": (lambda x: x.splitlines(1)[0]),
199 "firstline": (lambda x: x.splitlines(1)[0]),
203 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
200 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
204 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
201 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
205 }
202 }
206
203
207 class hgweb(object):
204 class hgweb(object):
208 def __init__(self, repo, name=None):
205 def __init__(self, repo, name=None):
209 if type(repo) == type(""):
206 if type(repo) == type(""):
210 self.repo = hg.repository(ui.ui(), repo)
207 self.repo = hg.repository(ui.ui(), repo)
211 else:
208 else:
212 self.repo = repo
209 self.repo = repo
213
210
214 self.mtime = -1
211 self.mtime = -1
215 self.reponame = name
212 self.reponame = name
216 self.archives = 'zip', 'gz', 'bz2'
213 self.archives = 'zip', 'gz', 'bz2'
217
214
218 def refresh(self):
215 def refresh(self):
219 mtime = get_mtime(self.repo.root)
216 mtime = get_mtime(self.repo.root)
220 if mtime != self.mtime:
217 if mtime != self.mtime:
221 self.mtime = mtime
218 self.mtime = mtime
222 self.repo = hg.repository(self.repo.ui, self.repo.root)
219 self.repo = hg.repository(self.repo.ui, self.repo.root)
223 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
220 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
224 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
221 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
225 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
222 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
226
223
227 def archivelist(self, nodeid):
224 def archivelist(self, nodeid):
228 for i in self.archives:
225 for i in self.archives:
229 if self.repo.ui.configbool("web", "allow" + i, False):
226 if self.repo.ui.configbool("web", "allow" + i, False):
230 yield {"type" : i, "node" : nodeid}
227 yield {"type" : i, "node" : nodeid}
231
228
232 def listfiles(self, files, mf):
229 def listfiles(self, files, mf):
233 for f in files[:self.maxfiles]:
230 for f in files[:self.maxfiles]:
234 yield self.t("filenodelink", node=hex(mf[f]), file=f)
231 yield self.t("filenodelink", node=hex(mf[f]), file=f)
235 if len(files) > self.maxfiles:
232 if len(files) > self.maxfiles:
236 yield self.t("fileellipses")
233 yield self.t("fileellipses")
237
234
238 def listfilediffs(self, files, changeset):
235 def listfilediffs(self, files, changeset):
239 for f in files[:self.maxfiles]:
236 for f in files[:self.maxfiles]:
240 yield self.t("filedifflink", node=hex(changeset), file=f)
237 yield self.t("filedifflink", node=hex(changeset), file=f)
241 if len(files) > self.maxfiles:
238 if len(files) > self.maxfiles:
242 yield self.t("fileellipses")
239 yield self.t("fileellipses")
243
240
244 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
241 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
245 if not rev:
242 if not rev:
246 rev = lambda x: ""
243 rev = lambda x: ""
247 siblings = [s for s in siblings if s != nullid]
244 siblings = [s for s in siblings if s != nullid]
248 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
245 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
249 return
246 return
250 for s in siblings:
247 for s in siblings:
251 yield dict(node=hex(s), rev=rev(s), **args)
248 yield dict(node=hex(s), rev=rev(s), **args)
252
249
253 def renamelink(self, fl, node):
250 def renamelink(self, fl, node):
254 r = fl.renamed(node)
251 r = fl.renamed(node)
255 if r:
252 if r:
256 return [dict(file=r[0], node=hex(r[1]))]
253 return [dict(file=r[0], node=hex(r[1]))]
257 return []
254 return []
258
255
259 def showtag(self, t1, node=nullid, **args):
256 def showtag(self, t1, node=nullid, **args):
260 for t in self.repo.nodetags(node):
257 for t in self.repo.nodetags(node):
261 yield self.t(t1, tag=t, **args)
258 yield self.t(t1, tag=t, **args)
262
259
263 def diff(self, node1, node2, files):
260 def diff(self, node1, node2, files):
264 def filterfiles(filters, files):
261 def filterfiles(filters, files):
265 l = [x for x in files if x in filters]
262 l = [x for x in files if x in filters]
266
263
267 for t in filters:
264 for t in filters:
268 if t and t[-1] != os.sep:
265 if t and t[-1] != os.sep:
269 t += os.sep
266 t += os.sep
270 l += [x for x in files if x.startswith(t)]
267 l += [x for x in files if x.startswith(t)]
271 return l
268 return l
272
269
273 parity = [0]
270 parity = [0]
274 def diffblock(diff, f, fn):
271 def diffblock(diff, f, fn):
275 yield self.t("diffblock",
272 yield self.t("diffblock",
276 lines=prettyprintlines(diff),
273 lines=prettyprintlines(diff),
277 parity=parity[0],
274 parity=parity[0],
278 file=f,
275 file=f,
279 filenode=hex(fn or nullid))
276 filenode=hex(fn or nullid))
280 parity[0] = 1 - parity[0]
277 parity[0] = 1 - parity[0]
281
278
282 def prettyprintlines(diff):
279 def prettyprintlines(diff):
283 for l in diff.splitlines(1):
280 for l in diff.splitlines(1):
284 if l.startswith('+'):
281 if l.startswith('+'):
285 yield self.t("difflineplus", line=l)
282 yield self.t("difflineplus", line=l)
286 elif l.startswith('-'):
283 elif l.startswith('-'):
287 yield self.t("difflineminus", line=l)
284 yield self.t("difflineminus", line=l)
288 elif l.startswith('@'):
285 elif l.startswith('@'):
289 yield self.t("difflineat", line=l)
286 yield self.t("difflineat", line=l)
290 else:
287 else:
291 yield self.t("diffline", line=l)
288 yield self.t("diffline", line=l)
292
289
293 r = self.repo
290 r = self.repo
294 cl = r.changelog
291 cl = r.changelog
295 mf = r.manifest
292 mf = r.manifest
296 change1 = cl.read(node1)
293 change1 = cl.read(node1)
297 change2 = cl.read(node2)
294 change2 = cl.read(node2)
298 mmap1 = mf.read(change1[0])
295 mmap1 = mf.read(change1[0])
299 mmap2 = mf.read(change2[0])
296 mmap2 = mf.read(change2[0])
300 date1 = util.datestr(change1[2])
297 date1 = util.datestr(change1[2])
301 date2 = util.datestr(change2[2])
298 date2 = util.datestr(change2[2])
302
299
303 modified, added, removed, deleted, unknown = r.changes(node1, node2)
300 modified, added, removed, deleted, unknown = r.changes(node1, node2)
304 if files:
301 if files:
305 modified, added, removed = map(lambda x: filterfiles(files, x),
302 modified, added, removed = map(lambda x: filterfiles(files, x),
306 (modified, added, removed))
303 (modified, added, removed))
307
304
308 diffopts = self.repo.ui.diffopts()
305 diffopts = self.repo.ui.diffopts()
309 showfunc = diffopts['showfunc']
306 showfunc = diffopts['showfunc']
310 ignorews = diffopts['ignorews']
307 ignorews = diffopts['ignorews']
311 for f in modified:
308 for f in modified:
312 to = r.file(f).read(mmap1[f])
309 to = r.file(f).read(mmap1[f])
313 tn = r.file(f).read(mmap2[f])
310 tn = r.file(f).read(mmap2[f])
314 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
311 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
315 showfunc=showfunc, ignorews=ignorews), f, tn)
312 showfunc=showfunc, ignorews=ignorews), f, tn)
316 for f in added:
313 for f in added:
317 to = None
314 to = None
318 tn = r.file(f).read(mmap2[f])
315 tn = r.file(f).read(mmap2[f])
319 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
316 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
320 showfunc=showfunc, ignorews=ignorews), f, tn)
317 showfunc=showfunc, ignorews=ignorews), f, tn)
321 for f in removed:
318 for f in removed:
322 to = r.file(f).read(mmap1[f])
319 to = r.file(f).read(mmap1[f])
323 tn = None
320 tn = None
324 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
321 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
325 showfunc=showfunc, ignorews=ignorews), f, tn)
322 showfunc=showfunc, ignorews=ignorews), f, tn)
326
323
327 def changelog(self, pos):
324 def changelog(self, pos):
328 def changenav(**map):
325 def changenav(**map):
329 def seq(factor, maxchanges=None):
326 def seq(factor, maxchanges=None):
330 if maxchanges:
327 if maxchanges:
331 yield maxchanges
328 yield maxchanges
332 if maxchanges >= 20 and maxchanges <= 40:
329 if maxchanges >= 20 and maxchanges <= 40:
333 yield 50
330 yield 50
334 else:
331 else:
335 yield 1 * factor
332 yield 1 * factor
336 yield 3 * factor
333 yield 3 * factor
337 for f in seq(factor * 10):
334 for f in seq(factor * 10):
338 yield f
335 yield f
339
336
340 l = []
337 l = []
341 last = 0
338 last = 0
342 for f in seq(1, self.maxchanges):
339 for f in seq(1, self.maxchanges):
343 if f < self.maxchanges or f <= last:
340 if f < self.maxchanges or f <= last:
344 continue
341 continue
345 if f > count:
342 if f > count:
346 break
343 break
347 last = f
344 last = f
348 r = "%d" % f
345 r = "%d" % f
349 if pos + f < count:
346 if pos + f < count:
350 l.append(("+" + r, pos + f))
347 l.append(("+" + r, pos + f))
351 if pos - f >= 0:
348 if pos - f >= 0:
352 l.insert(0, ("-" + r, pos - f))
349 l.insert(0, ("-" + r, pos - f))
353
350
354 yield {"rev": 0, "label": "(0)"}
351 yield {"rev": 0, "label": "(0)"}
355
352
356 for label, rev in l:
353 for label, rev in l:
357 yield {"label": label, "rev": rev}
354 yield {"label": label, "rev": rev}
358
355
359 yield {"label": "tip", "rev": "tip"}
356 yield {"label": "tip", "rev": "tip"}
360
357
361 def changelist(**map):
358 def changelist(**map):
362 parity = (start - end) & 1
359 parity = (start - end) & 1
363 cl = self.repo.changelog
360 cl = self.repo.changelog
364 l = [] # build a list in forward order for efficiency
361 l = [] # build a list in forward order for efficiency
365 for i in range(start, end):
362 for i in range(start, end):
366 n = cl.node(i)
363 n = cl.node(i)
367 changes = cl.read(n)
364 changes = cl.read(n)
368 hn = hex(n)
365 hn = hex(n)
369
366
370 l.insert(0, {"parity": parity,
367 l.insert(0, {"parity": parity,
371 "author": changes[1],
368 "author": changes[1],
372 "parent": self.siblings(cl.parents(n), cl.rev,
369 "parent": self.siblings(cl.parents(n), cl.rev,
373 cl.rev(n) - 1),
370 cl.rev(n) - 1),
374 "child": self.siblings(cl.children(n), cl.rev,
371 "child": self.siblings(cl.children(n), cl.rev,
375 cl.rev(n) + 1),
372 cl.rev(n) + 1),
376 "changelogtag": self.showtag("changelogtag",n),
373 "changelogtag": self.showtag("changelogtag",n),
377 "manifest": hex(changes[0]),
374 "manifest": hex(changes[0]),
378 "desc": changes[4],
375 "desc": changes[4],
379 "date": changes[2],
376 "date": changes[2],
380 "files": self.listfilediffs(changes[3], n),
377 "files": self.listfilediffs(changes[3], n),
381 "rev": i,
378 "rev": i,
382 "node": hn})
379 "node": hn})
383 parity = 1 - parity
380 parity = 1 - parity
384
381
385 for e in l:
382 for e in l:
386 yield e
383 yield e
387
384
388 cl = self.repo.changelog
385 cl = self.repo.changelog
389 mf = cl.read(cl.tip())[0]
386 mf = cl.read(cl.tip())[0]
390 count = cl.count()
387 count = cl.count()
391 start = max(0, pos - self.maxchanges + 1)
388 start = max(0, pos - self.maxchanges + 1)
392 end = min(count, start + self.maxchanges)
389 end = min(count, start + self.maxchanges)
393 pos = end - 1
390 pos = end - 1
394
391
395 yield self.t('changelog',
392 yield self.t('changelog',
396 changenav=changenav,
393 changenav=changenav,
397 manifest=hex(mf),
394 manifest=hex(mf),
398 rev=pos, changesets=count, entries=changelist)
395 rev=pos, changesets=count, entries=changelist)
399
396
400 def search(self, query):
397 def search(self, query):
401
398
402 def changelist(**map):
399 def changelist(**map):
403 cl = self.repo.changelog
400 cl = self.repo.changelog
404 count = 0
401 count = 0
405 qw = query.lower().split()
402 qw = query.lower().split()
406
403
407 def revgen():
404 def revgen():
408 for i in range(cl.count() - 1, 0, -100):
405 for i in range(cl.count() - 1, 0, -100):
409 l = []
406 l = []
410 for j in range(max(0, i - 100), i):
407 for j in range(max(0, i - 100), i):
411 n = cl.node(j)
408 n = cl.node(j)
412 changes = cl.read(n)
409 changes = cl.read(n)
413 l.append((n, j, changes))
410 l.append((n, j, changes))
414 l.reverse()
411 l.reverse()
415 for e in l:
412 for e in l:
416 yield e
413 yield e
417
414
418 for n, i, changes in revgen():
415 for n, i, changes in revgen():
419 miss = 0
416 miss = 0
420 for q in qw:
417 for q in qw:
421 if not (q in changes[1].lower() or
418 if not (q in changes[1].lower() or
422 q in changes[4].lower() or
419 q in changes[4].lower() or
423 q in " ".join(changes[3][:20]).lower()):
420 q in " ".join(changes[3][:20]).lower()):
424 miss = 1
421 miss = 1
425 break
422 break
426 if miss:
423 if miss:
427 continue
424 continue
428
425
429 count += 1
426 count += 1
430 hn = hex(n)
427 hn = hex(n)
431
428
432 yield self.t('searchentry',
429 yield self.t('searchentry',
433 parity=count & 1,
430 parity=count & 1,
434 author=changes[1],
431 author=changes[1],
435 parent=self.siblings(cl.parents(n), cl.rev),
432 parent=self.siblings(cl.parents(n), cl.rev),
436 child=self.siblings(cl.children(n), cl.rev),
433 child=self.siblings(cl.children(n), cl.rev),
437 changelogtag=self.showtag("changelogtag",n),
434 changelogtag=self.showtag("changelogtag",n),
438 manifest=hex(changes[0]),
435 manifest=hex(changes[0]),
439 desc=changes[4],
436 desc=changes[4],
440 date=changes[2],
437 date=changes[2],
441 files=self.listfilediffs(changes[3], n),
438 files=self.listfilediffs(changes[3], n),
442 rev=i,
439 rev=i,
443 node=hn)
440 node=hn)
444
441
445 if count >= self.maxchanges:
442 if count >= self.maxchanges:
446 break
443 break
447
444
448 cl = self.repo.changelog
445 cl = self.repo.changelog
449 mf = cl.read(cl.tip())[0]
446 mf = cl.read(cl.tip())[0]
450
447
451 yield self.t('search',
448 yield self.t('search',
452 query=query,
449 query=query,
453 manifest=hex(mf),
450 manifest=hex(mf),
454 entries=changelist)
451 entries=changelist)
455
452
456 def changeset(self, nodeid):
453 def changeset(self, nodeid):
457 cl = self.repo.changelog
454 cl = self.repo.changelog
458 n = self.repo.lookup(nodeid)
455 n = self.repo.lookup(nodeid)
459 nodeid = hex(n)
456 nodeid = hex(n)
460 changes = cl.read(n)
457 changes = cl.read(n)
461 p1 = cl.parents(n)[0]
458 p1 = cl.parents(n)[0]
462
459
463 files = []
460 files = []
464 mf = self.repo.manifest.read(changes[0])
461 mf = self.repo.manifest.read(changes[0])
465 for f in changes[3]:
462 for f in changes[3]:
466 files.append(self.t("filenodelink",
463 files.append(self.t("filenodelink",
467 filenode=hex(mf.get(f, nullid)), file=f))
464 filenode=hex(mf.get(f, nullid)), file=f))
468
465
469 def diff(**map):
466 def diff(**map):
470 yield self.diff(p1, n, None)
467 yield self.diff(p1, n, None)
471
468
472 yield self.t('changeset',
469 yield self.t('changeset',
473 diff=diff,
470 diff=diff,
474 rev=cl.rev(n),
471 rev=cl.rev(n),
475 node=nodeid,
472 node=nodeid,
476 parent=self.siblings(cl.parents(n), cl.rev),
473 parent=self.siblings(cl.parents(n), cl.rev),
477 child=self.siblings(cl.children(n), cl.rev),
474 child=self.siblings(cl.children(n), cl.rev),
478 changesettag=self.showtag("changesettag",n),
475 changesettag=self.showtag("changesettag",n),
479 manifest=hex(changes[0]),
476 manifest=hex(changes[0]),
480 author=changes[1],
477 author=changes[1],
481 desc=changes[4],
478 desc=changes[4],
482 date=changes[2],
479 date=changes[2],
483 files=files,
480 files=files,
484 archives=self.archivelist(nodeid))
481 archives=self.archivelist(nodeid))
485
482
486 def filelog(self, f, filenode):
483 def filelog(self, f, filenode):
487 cl = self.repo.changelog
484 cl = self.repo.changelog
488 fl = self.repo.file(f)
485 fl = self.repo.file(f)
489 filenode = hex(fl.lookup(filenode))
486 filenode = hex(fl.lookup(filenode))
490 count = fl.count()
487 count = fl.count()
491
488
492 def entries(**map):
489 def entries(**map):
493 l = []
490 l = []
494 parity = (count - 1) & 1
491 parity = (count - 1) & 1
495
492
496 for i in range(count):
493 for i in range(count):
497 n = fl.node(i)
494 n = fl.node(i)
498 lr = fl.linkrev(n)
495 lr = fl.linkrev(n)
499 cn = cl.node(lr)
496 cn = cl.node(lr)
500 cs = cl.read(cl.node(lr))
497 cs = cl.read(cl.node(lr))
501
498
502 l.insert(0, {"parity": parity,
499 l.insert(0, {"parity": parity,
503 "filenode": hex(n),
500 "filenode": hex(n),
504 "filerev": i,
501 "filerev": i,
505 "file": f,
502 "file": f,
506 "node": hex(cn),
503 "node": hex(cn),
507 "author": cs[1],
504 "author": cs[1],
508 "date": cs[2],
505 "date": cs[2],
509 "rename": self.renamelink(fl, n),
506 "rename": self.renamelink(fl, n),
510 "parent": self.siblings(fl.parents(n),
507 "parent": self.siblings(fl.parents(n),
511 fl.rev, file=f),
508 fl.rev, file=f),
512 "child": self.siblings(fl.children(n),
509 "child": self.siblings(fl.children(n),
513 fl.rev, file=f),
510 fl.rev, file=f),
514 "desc": cs[4]})
511 "desc": cs[4]})
515 parity = 1 - parity
512 parity = 1 - parity
516
513
517 for e in l:
514 for e in l:
518 yield e
515 yield e
519
516
520 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
517 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
521
518
522 def filerevision(self, f, node):
519 def filerevision(self, f, node):
523 fl = self.repo.file(f)
520 fl = self.repo.file(f)
524 n = fl.lookup(node)
521 n = fl.lookup(node)
525 node = hex(n)
522 node = hex(n)
526 text = fl.read(n)
523 text = fl.read(n)
527 changerev = fl.linkrev(n)
524 changerev = fl.linkrev(n)
528 cl = self.repo.changelog
525 cl = self.repo.changelog
529 cn = cl.node(changerev)
526 cn = cl.node(changerev)
530 cs = cl.read(cn)
527 cs = cl.read(cn)
531 mfn = cs[0]
528 mfn = cs[0]
532
529
533 mt = mimetypes.guess_type(f)[0]
530 mt = mimetypes.guess_type(f)[0]
534 rawtext = text
531 rawtext = text
535 if util.binary(text):
532 if util.binary(text):
536 text = "(binary:%s)" % mt
533 text = "(binary:%s)" % mt
537
534
538 def lines():
535 def lines():
539 for l, t in enumerate(text.splitlines(1)):
536 for l, t in enumerate(text.splitlines(1)):
540 yield {"line": t,
537 yield {"line": t,
541 "linenumber": "% 6d" % (l + 1),
538 "linenumber": "% 6d" % (l + 1),
542 "parity": l & 1}
539 "parity": l & 1}
543
540
544 yield self.t("filerevision",
541 yield self.t("filerevision",
545 file=f,
542 file=f,
546 filenode=node,
543 filenode=node,
547 path=up(f),
544 path=up(f),
548 text=lines(),
545 text=lines(),
549 raw=rawtext,
546 raw=rawtext,
550 mimetype=mt,
547 mimetype=mt,
551 rev=changerev,
548 rev=changerev,
552 node=hex(cn),
549 node=hex(cn),
553 manifest=hex(mfn),
550 manifest=hex(mfn),
554 author=cs[1],
551 author=cs[1],
555 date=cs[2],
552 date=cs[2],
556 parent=self.siblings(fl.parents(n), fl.rev, file=f),
553 parent=self.siblings(fl.parents(n), fl.rev, file=f),
557 child=self.siblings(fl.children(n), fl.rev, file=f),
554 child=self.siblings(fl.children(n), fl.rev, file=f),
558 rename=self.renamelink(fl, n),
555 rename=self.renamelink(fl, n),
559 permissions=self.repo.manifest.readflags(mfn)[f])
556 permissions=self.repo.manifest.readflags(mfn)[f])
560
557
561 def fileannotate(self, f, node):
558 def fileannotate(self, f, node):
562 bcache = {}
559 bcache = {}
563 ncache = {}
560 ncache = {}
564 fl = self.repo.file(f)
561 fl = self.repo.file(f)
565 n = fl.lookup(node)
562 n = fl.lookup(node)
566 node = hex(n)
563 node = hex(n)
567 changerev = fl.linkrev(n)
564 changerev = fl.linkrev(n)
568
565
569 cl = self.repo.changelog
566 cl = self.repo.changelog
570 cn = cl.node(changerev)
567 cn = cl.node(changerev)
571 cs = cl.read(cn)
568 cs = cl.read(cn)
572 mfn = cs[0]
569 mfn = cs[0]
573
570
574 def annotate(**map):
571 def annotate(**map):
575 parity = 1
572 parity = 1
576 last = None
573 last = None
577 for r, l in fl.annotate(n):
574 for r, l in fl.annotate(n):
578 try:
575 try:
579 cnode = ncache[r]
576 cnode = ncache[r]
580 except KeyError:
577 except KeyError:
581 cnode = ncache[r] = self.repo.changelog.node(r)
578 cnode = ncache[r] = self.repo.changelog.node(r)
582
579
583 try:
580 try:
584 name = bcache[r]
581 name = bcache[r]
585 except KeyError:
582 except KeyError:
586 cl = self.repo.changelog.read(cnode)
583 cl = self.repo.changelog.read(cnode)
587 bcache[r] = name = self.repo.ui.shortuser(cl[1])
584 bcache[r] = name = self.repo.ui.shortuser(cl[1])
588
585
589 if last != cnode:
586 if last != cnode:
590 parity = 1 - parity
587 parity = 1 - parity
591 last = cnode
588 last = cnode
592
589
593 yield {"parity": parity,
590 yield {"parity": parity,
594 "node": hex(cnode),
591 "node": hex(cnode),
595 "rev": r,
592 "rev": r,
596 "author": name,
593 "author": name,
597 "file": f,
594 "file": f,
598 "line": l}
595 "line": l}
599
596
600 yield self.t("fileannotate",
597 yield self.t("fileannotate",
601 file=f,
598 file=f,
602 filenode=node,
599 filenode=node,
603 annotate=annotate,
600 annotate=annotate,
604 path=up(f),
601 path=up(f),
605 rev=changerev,
602 rev=changerev,
606 node=hex(cn),
603 node=hex(cn),
607 manifest=hex(mfn),
604 manifest=hex(mfn),
608 author=cs[1],
605 author=cs[1],
609 date=cs[2],
606 date=cs[2],
610 rename=self.renamelink(fl, n),
607 rename=self.renamelink(fl, n),
611 parent=self.siblings(fl.parents(n), fl.rev, file=f),
608 parent=self.siblings(fl.parents(n), fl.rev, file=f),
612 child=self.siblings(fl.children(n), fl.rev, file=f),
609 child=self.siblings(fl.children(n), fl.rev, file=f),
613 permissions=self.repo.manifest.readflags(mfn)[f])
610 permissions=self.repo.manifest.readflags(mfn)[f])
614
611
615 def manifest(self, mnode, path):
612 def manifest(self, mnode, path):
616 man = self.repo.manifest
613 man = self.repo.manifest
617 mn = man.lookup(mnode)
614 mn = man.lookup(mnode)
618 mnode = hex(mn)
615 mnode = hex(mn)
619 mf = man.read(mn)
616 mf = man.read(mn)
620 rev = man.rev(mn)
617 rev = man.rev(mn)
621 node = self.repo.changelog.node(rev)
618 node = self.repo.changelog.node(rev)
622 mff = man.readflags(mn)
619 mff = man.readflags(mn)
623
620
624 files = {}
621 files = {}
625
622
626 p = path[1:]
623 p = path[1:]
627 if p and p[-1] != "/":
624 if p and p[-1] != "/":
628 p += "/"
625 p += "/"
629 l = len(p)
626 l = len(p)
630
627
631 for f,n in mf.items():
628 for f,n in mf.items():
632 if f[:l] != p:
629 if f[:l] != p:
633 continue
630 continue
634 remain = f[l:]
631 remain = f[l:]
635 if "/" in remain:
632 if "/" in remain:
636 short = remain[:remain.find("/") + 1] # bleah
633 short = remain[:remain.find("/") + 1] # bleah
637 files[short] = (f, None)
634 files[short] = (f, None)
638 else:
635 else:
639 short = os.path.basename(remain)
636 short = os.path.basename(remain)
640 files[short] = (f, n)
637 files[short] = (f, n)
641
638
642 def filelist(**map):
639 def filelist(**map):
643 parity = 0
640 parity = 0
644 fl = files.keys()
641 fl = files.keys()
645 fl.sort()
642 fl.sort()
646 for f in fl:
643 for f in fl:
647 full, fnode = files[f]
644 full, fnode = files[f]
648 if not fnode:
645 if not fnode:
649 continue
646 continue
650
647
651 yield {"file": full,
648 yield {"file": full,
652 "manifest": mnode,
649 "manifest": mnode,
653 "filenode": hex(fnode),
650 "filenode": hex(fnode),
654 "parity": parity,
651 "parity": parity,
655 "basename": f,
652 "basename": f,
656 "permissions": mff[full]}
653 "permissions": mff[full]}
657 parity = 1 - parity
654 parity = 1 - parity
658
655
659 def dirlist(**map):
656 def dirlist(**map):
660 parity = 0
657 parity = 0
661 fl = files.keys()
658 fl = files.keys()
662 fl.sort()
659 fl.sort()
663 for f in fl:
660 for f in fl:
664 full, fnode = files[f]
661 full, fnode = files[f]
665 if fnode:
662 if fnode:
666 continue
663 continue
667
664
668 yield {"parity": parity,
665 yield {"parity": parity,
669 "path": os.path.join(path, f),
666 "path": os.path.join(path, f),
670 "manifest": mnode,
667 "manifest": mnode,
671 "basename": f[:-1]}
668 "basename": f[:-1]}
672 parity = 1 - parity
669 parity = 1 - parity
673
670
674 yield self.t("manifest",
671 yield self.t("manifest",
675 manifest=mnode,
672 manifest=mnode,
676 rev=rev,
673 rev=rev,
677 node=hex(node),
674 node=hex(node),
678 path=path,
675 path=path,
679 up=up(path),
676 up=up(path),
680 fentries=filelist,
677 fentries=filelist,
681 dentries=dirlist,
678 dentries=dirlist,
682 archives=self.archivelist(hex(node)))
679 archives=self.archivelist(hex(node)))
683
680
684 def tags(self):
681 def tags(self):
685 cl = self.repo.changelog
682 cl = self.repo.changelog
686 mf = cl.read(cl.tip())[0]
683 mf = cl.read(cl.tip())[0]
687
684
688 i = self.repo.tagslist()
685 i = self.repo.tagslist()
689 i.reverse()
686 i.reverse()
690
687
691 def entries(notip=False, **map):
688 def entries(notip=False, **map):
692 parity = 0
689 parity = 0
693 for k,n in i:
690 for k,n in i:
694 if notip and k == "tip": continue
691 if notip and k == "tip": continue
695 yield {"parity": parity,
692 yield {"parity": parity,
696 "tag": k,
693 "tag": k,
697 "tagmanifest": hex(cl.read(n)[0]),
694 "tagmanifest": hex(cl.read(n)[0]),
698 "date": cl.read(n)[2],
695 "date": cl.read(n)[2],
699 "node": hex(n)}
696 "node": hex(n)}
700 parity = 1 - parity
697 parity = 1 - parity
701
698
702 yield self.t("tags",
699 yield self.t("tags",
703 manifest=hex(mf),
700 manifest=hex(mf),
704 entries=lambda **x: entries(False, **x),
701 entries=lambda **x: entries(False, **x),
705 entriesnotip=lambda **x: entries(True, **x))
702 entriesnotip=lambda **x: entries(True, **x))
706
703
707 def summary(self):
704 def summary(self):
708 cl = self.repo.changelog
705 cl = self.repo.changelog
709 mf = cl.read(cl.tip())[0]
706 mf = cl.read(cl.tip())[0]
710
707
711 i = self.repo.tagslist()
708 i = self.repo.tagslist()
712 i.reverse()
709 i.reverse()
713
710
714 def tagentries(**map):
711 def tagentries(**map):
715 parity = 0
712 parity = 0
716 count = 0
713 count = 0
717 for k,n in i:
714 for k,n in i:
718 if k == "tip": # skip tip
715 if k == "tip": # skip tip
719 continue;
716 continue;
720
717
721 count += 1
718 count += 1
722 if count > 10: # limit to 10 tags
719 if count > 10: # limit to 10 tags
723 break;
720 break;
724
721
725 c = cl.read(n)
722 c = cl.read(n)
726 m = c[0]
723 m = c[0]
727 t = c[2]
724 t = c[2]
728
725
729 yield self.t("tagentry",
726 yield self.t("tagentry",
730 parity = parity,
727 parity = parity,
731 tag = k,
728 tag = k,
732 node = hex(n),
729 node = hex(n),
733 date = t,
730 date = t,
734 tagmanifest = hex(m))
731 tagmanifest = hex(m))
735 parity = 1 - parity
732 parity = 1 - parity
736
733
737 def changelist(**map):
734 def changelist(**map):
738 parity = 0
735 parity = 0
739 cl = self.repo.changelog
736 cl = self.repo.changelog
740 l = [] # build a list in forward order for efficiency
737 l = [] # build a list in forward order for efficiency
741 for i in range(start, end):
738 for i in range(start, end):
742 n = cl.node(i)
739 n = cl.node(i)
743 changes = cl.read(n)
740 changes = cl.read(n)
744 hn = hex(n)
741 hn = hex(n)
745 t = changes[2]
742 t = changes[2]
746
743
747 l.insert(0, self.t(
744 l.insert(0, self.t(
748 'shortlogentry',
745 'shortlogentry',
749 parity = parity,
746 parity = parity,
750 author = changes[1],
747 author = changes[1],
751 manifest = hex(changes[0]),
748 manifest = hex(changes[0]),
752 desc = changes[4],
749 desc = changes[4],
753 date = t,
750 date = t,
754 rev = i,
751 rev = i,
755 node = hn))
752 node = hn))
756 parity = 1 - parity
753 parity = 1 - parity
757
754
758 yield l
755 yield l
759
756
760 cl = self.repo.changelog
757 cl = self.repo.changelog
761 mf = cl.read(cl.tip())[0]
758 mf = cl.read(cl.tip())[0]
762 count = cl.count()
759 count = cl.count()
763 start = max(0, count - self.maxchanges)
760 start = max(0, count - self.maxchanges)
764 end = min(count, start + self.maxchanges)
761 end = min(count, start + self.maxchanges)
765 pos = end - 1
762 pos = end - 1
766
763
767 yield self.t("summary",
764 yield self.t("summary",
768 desc = self.repo.ui.config("web", "description", "unknown"),
765 desc = self.repo.ui.config("web", "description", "unknown"),
769 owner = (self.repo.ui.config("ui", "username") or # preferred
766 owner = (self.repo.ui.config("ui", "username") or # preferred
770 self.repo.ui.config("web", "contact") or # deprecated
767 self.repo.ui.config("web", "contact") or # deprecated
771 self.repo.ui.config("web", "author", "unknown")), # also
768 self.repo.ui.config("web", "author", "unknown")), # also
772 lastchange = (0, 0), # FIXME
769 lastchange = (0, 0), # FIXME
773 manifest = hex(mf),
770 manifest = hex(mf),
774 tags = tagentries,
771 tags = tagentries,
775 shortlog = changelist)
772 shortlog = changelist)
776
773
777 def filediff(self, file, changeset):
774 def filediff(self, file, changeset):
778 cl = self.repo.changelog
775 cl = self.repo.changelog
779 n = self.repo.lookup(changeset)
776 n = self.repo.lookup(changeset)
780 changeset = hex(n)
777 changeset = hex(n)
781 p1 = cl.parents(n)[0]
778 p1 = cl.parents(n)[0]
782 cs = cl.read(n)
779 cs = cl.read(n)
783 mf = self.repo.manifest.read(cs[0])
780 mf = self.repo.manifest.read(cs[0])
784
781
785 def diff(**map):
782 def diff(**map):
786 yield self.diff(p1, n, file)
783 yield self.diff(p1, n, file)
787
784
788 yield self.t("filediff",
785 yield self.t("filediff",
789 file=file,
786 file=file,
790 filenode=hex(mf.get(file, nullid)),
787 filenode=hex(mf.get(file, nullid)),
791 node=changeset,
788 node=changeset,
792 rev=self.repo.changelog.rev(n),
789 rev=self.repo.changelog.rev(n),
793 parent=self.siblings(cl.parents(n), cl.rev),
790 parent=self.siblings(cl.parents(n), cl.rev),
794 child=self.siblings(cl.children(n), cl.rev),
791 child=self.siblings(cl.children(n), cl.rev),
795 diff=diff)
792 diff=diff)
796
793
797 def archive(self, req, cnode, type):
794 def archive(self, req, cnode, type):
798 cs = self.repo.changelog.read(cnode)
795 cs = self.repo.changelog.read(cnode)
799 mnode = cs[0]
796 mnode = cs[0]
800 mf = self.repo.manifest.read(mnode)
797 mf = self.repo.manifest.read(mnode)
801 rev = self.repo.manifest.rev(mnode)
798 rev = self.repo.manifest.rev(mnode)
802 reponame = re.sub(r"\W+", "-", self.reponame)
799 reponame = re.sub(r"\W+", "-", self.reponame)
803 name = "%s-%s/" % (reponame, short(cnode))
800 name = "%s-%s/" % (reponame, short(cnode))
804
801
805 files = mf.keys()
802 files = mf.keys()
806 files.sort()
803 files.sort()
807
804
808 if type == 'zip':
805 if type == 'zip':
809 tmp = tempfile.mkstemp()[1]
806 tmp = tempfile.mkstemp()[1]
810 try:
807 try:
811 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
808 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
812
809
813 for f in files:
810 for f in files:
814 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
811 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
815 zf.close()
812 zf.close()
816
813
817 f = open(tmp, 'r')
814 f = open(tmp, 'r')
818 req.httphdr('application/zip', name[:-1] + '.zip',
815 req.httphdr('application/zip', name[:-1] + '.zip',
819 os.path.getsize(tmp))
816 os.path.getsize(tmp))
820 req.write(f.read())
817 req.write(f.read())
821 f.close()
818 f.close()
822 finally:
819 finally:
823 os.unlink(tmp)
820 os.unlink(tmp)
824
821
825 else:
822 else:
826 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
823 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
827 mff = self.repo.manifest.readflags(mnode)
824 mff = self.repo.manifest.readflags(mnode)
828 mtime = int(time.time())
825 mtime = int(time.time())
829
826
830 if type == "gz":
827 if type == "gz":
831 encoding = "gzip"
828 encoding = "gzip"
832 else:
829 else:
833 encoding = "x-bzip2"
830 encoding = "x-bzip2"
834 req.header([('Content-type', 'application/x-tar'),
831 req.header([('Content-type', 'application/x-tar'),
835 ('Content-disposition', 'attachment; filename=%s%s%s' %
832 ('Content-disposition', 'attachment; filename=%s%s%s' %
836 (name[:-1], '.tar.', type)),
833 (name[:-1], '.tar.', type)),
837 ('Content-encoding', encoding)])
834 ('Content-encoding', encoding)])
838 for fname in files:
835 for fname in files:
839 rcont = self.repo.file(fname).read(mf[fname])
836 rcont = self.repo.file(fname).read(mf[fname])
840 finfo = tarfile.TarInfo(name + fname)
837 finfo = tarfile.TarInfo(name + fname)
841 finfo.mtime = mtime
838 finfo.mtime = mtime
842 finfo.size = len(rcont)
839 finfo.size = len(rcont)
843 finfo.mode = mff[fname] and 0755 or 0644
840 finfo.mode = mff[fname] and 0755 or 0644
844 tf.addfile(finfo, StringIO.StringIO(rcont))
841 tf.addfile(finfo, StringIO.StringIO(rcont))
845 tf.close()
842 tf.close()
846
843
847 # add tags to things
844 # add tags to things
848 # tags -> list of changesets corresponding to tags
845 # tags -> list of changesets corresponding to tags
849 # find tag, changeset, file
846 # find tag, changeset, file
850
847
851 def run(self, req=hgrequest()):
848 def run(self, req=hgrequest()):
852 def clean(path):
849 def clean(path):
853 p = os.path.normpath(path)
850 p = os.path.normpath(path)
854 if p[:2] == "..":
851 if p[:2] == "..":
855 raise "suspicious path"
852 raise "suspicious path"
856 return p
853 return p
857
854
858 def header(**map):
855 def header(**map):
859 yield self.t("header", **map)
856 yield self.t("header", **map)
860
857
861 def footer(**map):
858 def footer(**map):
862 yield self.t("footer", **map)
859 yield self.t("footer", **map)
863
860
864 def expand_form(form):
861 def expand_form(form):
865 shortcuts = {
862 shortcuts = {
866 'cl': [('cmd', ['changelog']), ('rev', None)],
863 'cl': [('cmd', ['changelog']), ('rev', None)],
867 'cs': [('cmd', ['changeset']), ('node', None)],
864 'cs': [('cmd', ['changeset']), ('node', None)],
868 'f': [('cmd', ['file']), ('filenode', None)],
865 'f': [('cmd', ['file']), ('filenode', None)],
869 'fl': [('cmd', ['filelog']), ('filenode', None)],
866 'fl': [('cmd', ['filelog']), ('filenode', None)],
870 'fd': [('cmd', ['filediff']), ('node', None)],
867 'fd': [('cmd', ['filediff']), ('node', None)],
871 'fa': [('cmd', ['annotate']), ('filenode', None)],
868 'fa': [('cmd', ['annotate']), ('filenode', None)],
872 'mf': [('cmd', ['manifest']), ('manifest', None)],
869 'mf': [('cmd', ['manifest']), ('manifest', None)],
873 'ca': [('cmd', ['archive']), ('node', None)],
870 'ca': [('cmd', ['archive']), ('node', None)],
874 'tags': [('cmd', ['tags'])],
871 'tags': [('cmd', ['tags'])],
875 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
872 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
876 'static': [('cmd', ['static']), ('file', None)]
873 'static': [('cmd', ['static']), ('file', None)]
877 }
874 }
878
875
879 for k in shortcuts.iterkeys():
876 for k in shortcuts.iterkeys():
880 if form.has_key(k):
877 if form.has_key(k):
881 for name, value in shortcuts[k]:
878 for name, value in shortcuts[k]:
882 if value is None:
879 if value is None:
883 value = form[k]
880 value = form[k]
884 form[name] = value
881 form[name] = value
885 del form[k]
882 del form[k]
886
883
887 self.refresh()
884 self.refresh()
888
885
889 expand_form(req.form)
886 expand_form(req.form)
890
887
891 t = self.repo.ui.config("web", "templates", templatepath())
888 t = self.repo.ui.config("web", "templates", templatepath())
892 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
889 static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
893 m = os.path.join(t, "map")
890 m = os.path.join(t, "map")
894 style = self.repo.ui.config("web", "style", "")
891 style = self.repo.ui.config("web", "style", "")
895 if req.form.has_key('style'):
892 if req.form.has_key('style'):
896 style = req.form['style'][0]
893 style = req.form['style'][0]
897 if style:
894 if style:
898 b = os.path.basename("map-" + style)
895 b = os.path.basename("map-" + style)
899 p = os.path.join(t, b)
896 p = os.path.join(t, b)
900 if os.path.isfile(p):
897 if os.path.isfile(p):
901 m = p
898 m = p
902
899
903 port = req.env["SERVER_PORT"]
900 port = req.env["SERVER_PORT"]
904 port = port != "80" and (":" + port) or ""
901 port = port != "80" and (":" + port) or ""
905 uri = req.env["REQUEST_URI"]
902 uri = req.env["REQUEST_URI"]
906 if "?" in uri:
903 if "?" in uri:
907 uri = uri.split("?")[0]
904 uri = uri.split("?")[0]
908 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
905 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
909 if not self.reponame:
906 if not self.reponame:
910 self.reponame = (self.repo.ui.config("web", "name")
907 self.reponame = (self.repo.ui.config("web", "name")
911 or uri.strip('/') or self.repo.root)
908 or uri.strip('/') or self.repo.root)
912
909
913 self.t = templater(m, common_filters,
910 self.t = templater(m, common_filters,
914 {"url": url,
911 {"url": url,
915 "repo": self.reponame,
912 "repo": self.reponame,
916 "header": header,
913 "header": header,
917 "footer": footer,
914 "footer": footer,
918 })
915 })
919
916
920 if not req.form.has_key('cmd'):
917 if not req.form.has_key('cmd'):
921 req.form['cmd'] = [self.t.cache['default'],]
918 req.form['cmd'] = [self.t.cache['default'],]
922
919
923 if req.form['cmd'][0] == 'changelog':
920 if req.form['cmd'][0] == 'changelog':
924 c = self.repo.changelog.count() - 1
921 c = self.repo.changelog.count() - 1
925 hi = c
922 hi = c
926 if req.form.has_key('rev'):
923 if req.form.has_key('rev'):
927 hi = req.form['rev'][0]
924 hi = req.form['rev'][0]
928 try:
925 try:
929 hi = self.repo.changelog.rev(self.repo.lookup(hi))
926 hi = self.repo.changelog.rev(self.repo.lookup(hi))
930 except hg.RepoError:
927 except hg.RepoError:
931 req.write(self.search(hi))
928 req.write(self.search(hi))
932 return
929 return
933
930
934 req.write(self.changelog(hi))
931 req.write(self.changelog(hi))
935
932
936 elif req.form['cmd'][0] == 'changeset':
933 elif req.form['cmd'][0] == 'changeset':
937 req.write(self.changeset(req.form['node'][0]))
934 req.write(self.changeset(req.form['node'][0]))
938
935
939 elif req.form['cmd'][0] == 'manifest':
936 elif req.form['cmd'][0] == 'manifest':
940 req.write(self.manifest(req.form['manifest'][0],
937 req.write(self.manifest(req.form['manifest'][0],
941 clean(req.form['path'][0])))
938 clean(req.form['path'][0])))
942
939
943 elif req.form['cmd'][0] == 'tags':
940 elif req.form['cmd'][0] == 'tags':
944 req.write(self.tags())
941 req.write(self.tags())
945
942
946 elif req.form['cmd'][0] == 'summary':
943 elif req.form['cmd'][0] == 'summary':
947 req.write(self.summary())
944 req.write(self.summary())
948
945
949 elif req.form['cmd'][0] == 'filediff':
946 elif req.form['cmd'][0] == 'filediff':
950 req.write(self.filediff(clean(req.form['file'][0]),
947 req.write(self.filediff(clean(req.form['file'][0]),
951 req.form['node'][0]))
948 req.form['node'][0]))
952
949
953 elif req.form['cmd'][0] == 'file':
950 elif req.form['cmd'][0] == 'file':
954 req.write(self.filerevision(clean(req.form['file'][0]),
951 req.write(self.filerevision(clean(req.form['file'][0]),
955 req.form['filenode'][0]))
952 req.form['filenode'][0]))
956
953
957 elif req.form['cmd'][0] == 'annotate':
954 elif req.form['cmd'][0] == 'annotate':
958 req.write(self.fileannotate(clean(req.form['file'][0]),
955 req.write(self.fileannotate(clean(req.form['file'][0]),
959 req.form['filenode'][0]))
956 req.form['filenode'][0]))
960
957
961 elif req.form['cmd'][0] == 'filelog':
958 elif req.form['cmd'][0] == 'filelog':
962 req.write(self.filelog(clean(req.form['file'][0]),
959 req.write(self.filelog(clean(req.form['file'][0]),
963 req.form['filenode'][0]))
960 req.form['filenode'][0]))
964
961
965 elif req.form['cmd'][0] == 'heads':
962 elif req.form['cmd'][0] == 'heads':
966 req.httphdr("application/mercurial-0.1")
963 req.httphdr("application/mercurial-0.1")
967 h = self.repo.heads()
964 h = self.repo.heads()
968 req.write(" ".join(map(hex, h)) + "\n")
965 req.write(" ".join(map(hex, h)) + "\n")
969
966
970 elif req.form['cmd'][0] == 'branches':
967 elif req.form['cmd'][0] == 'branches':
971 req.httphdr("application/mercurial-0.1")
968 req.httphdr("application/mercurial-0.1")
972 nodes = []
969 nodes = []
973 if req.form.has_key('nodes'):
970 if req.form.has_key('nodes'):
974 nodes = map(bin, req.form['nodes'][0].split(" "))
971 nodes = map(bin, req.form['nodes'][0].split(" "))
975 for b in self.repo.branches(nodes):
972 for b in self.repo.branches(nodes):
976 req.write(" ".join(map(hex, b)) + "\n")
973 req.write(" ".join(map(hex, b)) + "\n")
977
974
978 elif req.form['cmd'][0] == 'between':
975 elif req.form['cmd'][0] == 'between':
979 req.httphdr("application/mercurial-0.1")
976 req.httphdr("application/mercurial-0.1")
980 nodes = []
977 nodes = []
981 if req.form.has_key('pairs'):
978 if req.form.has_key('pairs'):
982 pairs = [map(bin, p.split("-"))
979 pairs = [map(bin, p.split("-"))
983 for p in req.form['pairs'][0].split(" ")]
980 for p in req.form['pairs'][0].split(" ")]
984 for b in self.repo.between(pairs):
981 for b in self.repo.between(pairs):
985 req.write(" ".join(map(hex, b)) + "\n")
982 req.write(" ".join(map(hex, b)) + "\n")
986
983
987 elif req.form['cmd'][0] == 'changegroup':
984 elif req.form['cmd'][0] == 'changegroup':
988 req.httphdr("application/mercurial-0.1")
985 req.httphdr("application/mercurial-0.1")
989 nodes = []
986 nodes = []
990 if not self.allowpull:
987 if not self.allowpull:
991 return
988 return
992
989
993 if req.form.has_key('roots'):
990 if req.form.has_key('roots'):
994 nodes = map(bin, req.form['roots'][0].split(" "))
991 nodes = map(bin, req.form['roots'][0].split(" "))
995
992
996 z = zlib.compressobj()
993 z = zlib.compressobj()
997 f = self.repo.changegroup(nodes, 'serve')
994 f = self.repo.changegroup(nodes, 'serve')
998 while 1:
995 while 1:
999 chunk = f.read(4096)
996 chunk = f.read(4096)
1000 if not chunk:
997 if not chunk:
1001 break
998 break
1002 req.write(z.compress(chunk))
999 req.write(z.compress(chunk))
1003
1000
1004 req.write(z.flush())
1001 req.write(z.flush())
1005
1002
1006 elif req.form['cmd'][0] == 'archive':
1003 elif req.form['cmd'][0] == 'archive':
1007 changeset = self.repo.lookup(req.form['node'][0])
1004 changeset = self.repo.lookup(req.form['node'][0])
1008 type = req.form['type'][0]
1005 type = req.form['type'][0]
1009 if (type in self.archives and
1006 if (type in self.archives and
1010 self.repo.ui.configbool("web", "allow" + type, False)):
1007 self.repo.ui.configbool("web", "allow" + type, False)):
1011 self.archive(req, changeset, type)
1008 self.archive(req, changeset, type)
1012 return
1009 return
1013
1010
1014 req.write(self.t("error"))
1011 req.write(self.t("error"))
1015
1012
1016 elif req.form['cmd'][0] == 'static':
1013 elif req.form['cmd'][0] == 'static':
1017 fname = req.form['file'][0]
1014 fname = req.form['file'][0]
1018 req.write(staticfile(static, fname)
1015 req.write(staticfile(static, fname)
1019 or self.t("error", error="%r not found" % fname))
1016 or self.t("error", error="%r not found" % fname))
1020
1017
1021 else:
1018 else:
1022 req.write(self.t("error"))
1019 req.write(self.t("error"))
1023
1020
1024 def create_server(repo):
1021 def create_server(repo):
1025
1022
1026 def openlog(opt, default):
1023 def openlog(opt, default):
1027 if opt and opt != '-':
1024 if opt and opt != '-':
1028 return open(opt, 'w')
1025 return open(opt, 'w')
1029 return default
1026 return default
1030
1027
1031 address = repo.ui.config("web", "address", "")
1028 address = repo.ui.config("web", "address", "")
1032 port = int(repo.ui.config("web", "port", 8000))
1029 port = int(repo.ui.config("web", "port", 8000))
1033 use_ipv6 = repo.ui.configbool("web", "ipv6")
1030 use_ipv6 = repo.ui.configbool("web", "ipv6")
1034 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
1031 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
1035 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
1032 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
1036
1033
1037 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
1034 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
1038 address_family = getattr(socket, 'AF_INET6', None)
1035 address_family = getattr(socket, 'AF_INET6', None)
1039
1036
1040 def __init__(self, *args, **kwargs):
1037 def __init__(self, *args, **kwargs):
1041 if self.address_family is None:
1038 if self.address_family is None:
1042 raise hg.RepoError(_('IPv6 not available on this system'))
1039 raise hg.RepoError(_('IPv6 not available on this system'))
1043 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
1040 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
1044
1041
1045 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
1042 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
1046 def log_error(self, format, *args):
1043 def log_error(self, format, *args):
1047 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
1044 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
1048 self.log_date_time_string(),
1045 self.log_date_time_string(),
1049 format % args))
1046 format % args))
1050
1047
1051 def log_message(self, format, *args):
1048 def log_message(self, format, *args):
1052 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
1049 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
1053 self.log_date_time_string(),
1050 self.log_date_time_string(),
1054 format % args))
1051 format % args))
1055
1052
1056 def do_POST(self):
1053 def do_POST(self):
1057 try:
1054 try:
1058 self.do_hgweb()
1055 self.do_hgweb()
1059 except socket.error, inst:
1056 except socket.error, inst:
1060 if inst[0] != errno.EPIPE:
1057 if inst[0] != errno.EPIPE:
1061 raise
1058 raise
1062
1059
1063 def do_GET(self):
1060 def do_GET(self):
1064 self.do_POST()
1061 self.do_POST()
1065
1062
1066 def do_hgweb(self):
1063 def do_hgweb(self):
1067 query = ""
1064 query = ""
1068 p = self.path.find("?")
1065 p = self.path.find("?")
1069 if p:
1066 if p:
1070 query = self.path[p + 1:]
1067 query = self.path[p + 1:]
1071 query = query.replace('+', ' ')
1068 query = query.replace('+', ' ')
1072
1069
1073 env = {}
1070 env = {}
1074 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1071 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1075 env['REQUEST_METHOD'] = self.command
1072 env['REQUEST_METHOD'] = self.command
1076 env['SERVER_NAME'] = self.server.server_name
1073 env['SERVER_NAME'] = self.server.server_name
1077 env['SERVER_PORT'] = str(self.server.server_port)
1074 env['SERVER_PORT'] = str(self.server.server_port)
1078 env['REQUEST_URI'] = "/"
1075 env['REQUEST_URI'] = "/"
1079 if query:
1076 if query:
1080 env['QUERY_STRING'] = query
1077 env['QUERY_STRING'] = query
1081 host = self.address_string()
1078 host = self.address_string()
1082 if host != self.client_address[0]:
1079 if host != self.client_address[0]:
1083 env['REMOTE_HOST'] = host
1080 env['REMOTE_HOST'] = host
1084 env['REMOTE_ADDR'] = self.client_address[0]
1081 env['REMOTE_ADDR'] = self.client_address[0]
1085
1082
1086 if self.headers.typeheader is None:
1083 if self.headers.typeheader is None:
1087 env['CONTENT_TYPE'] = self.headers.type
1084 env['CONTENT_TYPE'] = self.headers.type
1088 else:
1085 else:
1089 env['CONTENT_TYPE'] = self.headers.typeheader
1086 env['CONTENT_TYPE'] = self.headers.typeheader
1090 length = self.headers.getheader('content-length')
1087 length = self.headers.getheader('content-length')
1091 if length:
1088 if length:
1092 env['CONTENT_LENGTH'] = length
1089 env['CONTENT_LENGTH'] = length
1093 accept = []
1090 accept = []
1094 for line in self.headers.getallmatchingheaders('accept'):
1091 for line in self.headers.getallmatchingheaders('accept'):
1095 if line[:1] in "\t\n\r ":
1092 if line[:1] in "\t\n\r ":
1096 accept.append(line.strip())
1093 accept.append(line.strip())
1097 else:
1094 else:
1098 accept = accept + line[7:].split(',')
1095 accept = accept + line[7:].split(',')
1099 env['HTTP_ACCEPT'] = ','.join(accept)
1096 env['HTTP_ACCEPT'] = ','.join(accept)
1100
1097
1101 req = hgrequest(self.rfile, self.wfile, env)
1098 req = hgrequest(self.rfile, self.wfile, env)
1102 self.send_response(200, "Script output follows")
1099 self.send_response(200, "Script output follows")
1103 hg.run(req)
1100 hg.run(req)
1104
1101
1105 hg = hgweb(repo)
1102 hg = hgweb(repo)
1106 if use_ipv6:
1103 if use_ipv6:
1107 return IPv6HTTPServer((address, port), hgwebhandler)
1104 return IPv6HTTPServer((address, port), hgwebhandler)
1108 else:
1105 else:
1109 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1106 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1110
1107
1111 # This is a stopgap
1108 # This is a stopgap
1112 class hgwebdir(object):
1109 class hgwebdir(object):
1113 def __init__(self, config):
1110 def __init__(self, config):
1114 def cleannames(items):
1111 def cleannames(items):
1115 return [(name.strip('/'), path) for name, path in items]
1112 return [(name.strip('/'), path) for name, path in items]
1116
1113
1117 if type(config) == type([]):
1114 if type(config) == type([]):
1118 self.repos = cleannames(config)
1115 self.repos = cleannames(config)
1119 elif type(config) == type({}):
1116 elif type(config) == type({}):
1120 self.repos = cleannames(config.items())
1117 self.repos = cleannames(config.items())
1121 self.repos.sort()
1118 self.repos.sort()
1122 else:
1119 else:
1123 cp = ConfigParser.SafeConfigParser()
1120 cp = ConfigParser.SafeConfigParser()
1124 cp.read(config)
1121 cp.read(config)
1125 self.repos = cleannames(cp.items("paths"))
1122 self.repos = cleannames(cp.items("paths"))
1126 self.repos.sort()
1123 self.repos.sort()
1127
1124
1128 def run(self, req=hgrequest()):
1125 def run(self, req=hgrequest()):
1129 def header(**map):
1126 def header(**map):
1130 yield tmpl("header", **map)
1127 yield tmpl("header", **map)
1131
1128
1132 def footer(**map):
1129 def footer(**map):
1133 yield tmpl("footer", **map)
1130 yield tmpl("footer", **map)
1134
1131
1135 m = os.path.join(templatepath(), "map")
1132 m = os.path.join(templatepath(), "map")
1136 tmpl = templater(m, common_filters,
1133 tmpl = templater(m, common_filters,
1137 {"header": header, "footer": footer})
1134 {"header": header, "footer": footer})
1138
1135
1139 def entries(**map):
1136 def entries(**map):
1140 parity = 0
1137 parity = 0
1141 for name, path in self.repos:
1138 for name, path in self.repos:
1142 u = ui.ui()
1139 u = ui.ui()
1143 try:
1140 try:
1144 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1141 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1145 except IOError:
1142 except IOError:
1146 pass
1143 pass
1147 get = u.config
1144 get = u.config
1148
1145
1149 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1146 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1150 .replace("//", "/"))
1147 .replace("//", "/"))
1151
1148
1152 # update time with local timezone
1149 # update time with local timezone
1153 try:
1150 try:
1154 d = (get_mtime(path), util.makedate()[1])
1151 d = (get_mtime(path), util.makedate()[1])
1155 except OSError:
1152 except OSError:
1156 continue
1153 continue
1157
1154
1158 yield dict(contact=(get("ui", "username") or # preferred
1155 yield dict(contact=(get("ui", "username") or # preferred
1159 get("web", "contact") or # deprecated
1156 get("web", "contact") or # deprecated
1160 get("web", "author", "unknown")), # also
1157 get("web", "author", "unknown")), # also
1161 name=get("web", "name", name),
1158 name=get("web", "name", name),
1162 url=url,
1159 url=url,
1163 parity=parity,
1160 parity=parity,
1164 shortdesc=get("web", "description", "unknown"),
1161 shortdesc=get("web", "description", "unknown"),
1165 lastupdate=d)
1162 lastupdate=d)
1166
1163
1167 parity = 1 - parity
1164 parity = 1 - parity
1168
1165
1169 virtual = req.env.get("PATH_INFO", "").strip('/')
1166 virtual = req.env.get("PATH_INFO", "").strip('/')
1170 if virtual:
1167 if virtual:
1171 real = dict(self.repos).get(virtual)
1168 real = dict(self.repos).get(virtual)
1172 if real:
1169 if real:
1173 try:
1170 try:
1174 hgweb(real).run(req)
1171 hgweb(real).run(req)
1175 except IOError, inst:
1172 except IOError, inst:
1176 req.write(tmpl("error", error=inst.strerror))
1173 req.write(tmpl("error", error=inst.strerror))
1177 except hg.RepoError, inst:
1174 except hg.RepoError, inst:
1178 req.write(tmpl("error", error=str(inst)))
1175 req.write(tmpl("error", error=str(inst)))
1179 else:
1176 else:
1180 req.write(tmpl("notfound", repo=virtual))
1177 req.write(tmpl("notfound", repo=virtual))
1181 else:
1178 else:
1182 if req.form.has_key('static'):
1179 if req.form.has_key('static'):
1183 static = os.path.join(templatepath(), "static")
1180 static = os.path.join(templatepath(), "static")
1184 fname = req.form['static'][0]
1181 fname = req.form['static'][0]
1185 req.write(staticfile(static, fname)
1182 req.write(staticfile(static, fname)
1186 or tmpl("error", error="%r not found" % fname))
1183 or tmpl("error", error="%r not found" % fname))
1187 else:
1184 else:
1188 req.write(tmpl("index", entries=entries))
1185 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now