##// END OF EJS Templates
tarball support v0.3...
Wojciech Milkowski -
r1076:01db658c default
parent child Browse files
Show More
@@ -1,907 +1,991 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, time, re, difflib, socket, sys, zlib
9 import os, cgi, time, re, difflib, socket, sys, zlib
10 from mercurial.hg import *
10 from mercurial.hg import *
11 from mercurial.ui import *
11 from mercurial.ui import *
12
12
13 def templatepath():
13 def templatepath():
14 for f in "templates", "../templates":
14 for f in "templates", "../templates":
15 p = os.path.join(os.path.dirname(__file__), f)
15 p = os.path.join(os.path.dirname(__file__), f)
16 if os.path.isdir(p):
16 if os.path.isdir(p):
17 return p
17 return p
18
18
19 def age(t):
19 def age(t):
20 def plural(t, c):
20 def plural(t, c):
21 if c == 1:
21 if c == 1:
22 return t
22 return t
23 return t + "s"
23 return t + "s"
24 def fmt(t, c):
24 def fmt(t, c):
25 return "%d %s" % (c, plural(t, c))
25 return "%d %s" % (c, plural(t, c))
26
26
27 now = time.time()
27 now = time.time()
28 delta = max(1, int(now - t))
28 delta = max(1, int(now - t))
29
29
30 scales = [["second", 1],
30 scales = [["second", 1],
31 ["minute", 60],
31 ["minute", 60],
32 ["hour", 3600],
32 ["hour", 3600],
33 ["day", 3600 * 24],
33 ["day", 3600 * 24],
34 ["week", 3600 * 24 * 7],
34 ["week", 3600 * 24 * 7],
35 ["month", 3600 * 24 * 30],
35 ["month", 3600 * 24 * 30],
36 ["year", 3600 * 24 * 365]]
36 ["year", 3600 * 24 * 365]]
37
37
38 scales.reverse()
38 scales.reverse()
39
39
40 for t, s in scales:
40 for t, s in scales:
41 n = delta / s
41 n = delta / s
42 if n >= 2 or s == 1:
42 if n >= 2 or s == 1:
43 return fmt(t, n)
43 return fmt(t, n)
44
44
45 def nl2br(text):
45 def nl2br(text):
46 return text.replace('\n', '<br/>\n')
46 return text.replace('\n', '<br/>\n')
47
47
48 def obfuscate(text):
48 def obfuscate(text):
49 return ''.join(['&#%d;' % ord(c) for c in text])
49 return ''.join(['&#%d;' % ord(c) for c in text])
50
50
51 def up(p):
51 def up(p):
52 if p[0] != "/":
52 if p[0] != "/":
53 p = "/" + p
53 p = "/" + p
54 if p[-1] == "/":
54 if p[-1] == "/":
55 p = p[:-1]
55 p = p[:-1]
56 up = os.path.dirname(p)
56 up = os.path.dirname(p)
57 if up == "/":
57 if up == "/":
58 return "/"
58 return "/"
59 return up + "/"
59 return up + "/"
60
60
61 def httphdr(type):
61 def httphdr(type, file="", size=0):
62 sys.stdout.write('Content-type: %s\n\n' % type)
62 sys.stdout.write('Content-type: %s\n' % type)
63 if file:
64 sys.stdout.write('Content-disposition: attachment; filename=%s\n'
65 % file)
66 if size > 0:
67 sys.stdout.write('Content-length: %d\n' % size)
68 sys.stdout.write('\n')
63
69
64 def write(*things):
70 def write(*things):
65 for thing in things:
71 for thing in things:
66 if hasattr(thing, "__iter__"):
72 if hasattr(thing, "__iter__"):
67 for part in thing:
73 for part in thing:
68 write(part)
74 write(part)
69 else:
75 else:
70 sys.stdout.write(str(thing))
76 sys.stdout.write(str(thing))
71
77
72 class templater:
78 class templater:
73 def __init__(self, mapfile, filters={}, defaults={}):
79 def __init__(self, mapfile, filters={}, defaults={}):
74 self.cache = {}
80 self.cache = {}
75 self.map = {}
81 self.map = {}
76 self.base = os.path.dirname(mapfile)
82 self.base = os.path.dirname(mapfile)
77 self.filters = filters
83 self.filters = filters
78 self.defaults = defaults
84 self.defaults = defaults
79
85
80 for l in file(mapfile):
86 for l in file(mapfile):
81 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
87 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
82 if m:
88 if m:
83 self.cache[m.group(1)] = m.group(2)
89 self.cache[m.group(1)] = m.group(2)
84 else:
90 else:
85 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
91 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
86 if m:
92 if m:
87 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
93 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
88 else:
94 else:
89 raise LookupError("unknown map entry '%s'" % l)
95 raise LookupError("unknown map entry '%s'" % l)
90
96
91 def __call__(self, t, **map):
97 def __call__(self, t, **map):
92 m = self.defaults.copy()
98 m = self.defaults.copy()
93 m.update(map)
99 m.update(map)
94 try:
100 try:
95 tmpl = self.cache[t]
101 tmpl = self.cache[t]
96 except KeyError:
102 except KeyError:
97 tmpl = self.cache[t] = file(self.map[t]).read()
103 tmpl = self.cache[t] = file(self.map[t]).read()
98 return self.template(tmpl, self.filters, **m)
104 return self.template(tmpl, self.filters, **m)
99
105
100 def template(self, tmpl, filters={}, **map):
106 def template(self, tmpl, filters={}, **map):
101 while tmpl:
107 while tmpl:
102 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
108 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
103 if m:
109 if m:
104 yield tmpl[:m.start(0)]
110 yield tmpl[:m.start(0)]
105 v = map.get(m.group(1), "")
111 v = map.get(m.group(1), "")
106 v = callable(v) and v(**map) or v
112 v = callable(v) and v(**map) or v
107
113
108 format = m.group(2)
114 format = m.group(2)
109 fl = m.group(4)
115 fl = m.group(4)
110
116
111 if format:
117 if format:
112 q = v.__iter__
118 q = v.__iter__
113 for i in q():
119 for i in q():
114 lm = map.copy()
120 lm = map.copy()
115 lm.update(i)
121 lm.update(i)
116 yield self(format[1:], **lm)
122 yield self(format[1:], **lm)
117
123
118 v = ""
124 v = ""
119
125
120 elif fl:
126 elif fl:
121 for f in fl.split("|")[1:]:
127 for f in fl.split("|")[1:]:
122 v = filters[f](v)
128 v = filters[f](v)
123
129
124 yield v
130 yield v
125 tmpl = tmpl[m.end(0):]
131 tmpl = tmpl[m.end(0):]
126 else:
132 else:
127 yield tmpl
133 yield tmpl
128 return
134 return
129
135
130 def rfc822date(x):
136 def rfc822date(x):
131 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
137 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
132
138
133 common_filters = {
139 common_filters = {
134 "escape": cgi.escape,
140 "escape": cgi.escape,
135 "age": age,
141 "age": age,
136 "date": (lambda x: time.asctime(time.gmtime(x))),
142 "date": (lambda x: time.asctime(time.gmtime(x))),
137 "addbreaks": nl2br,
143 "addbreaks": nl2br,
138 "obfuscate": obfuscate,
144 "obfuscate": obfuscate,
139 "short": (lambda x: x[:12]),
145 "short": (lambda x: x[:12]),
140 "firstline": (lambda x: x.splitlines(1)[0]),
146 "firstline": (lambda x: x.splitlines(1)[0]),
141 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
147 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
142 "rfc822date": rfc822date,
148 "rfc822date": rfc822date,
143 }
149 }
144
150
145 class hgweb:
151 class hgweb:
146 def __init__(self, repo, name=None):
152 def __init__(self, repo, name=None):
147 if type(repo) == type(""):
153 if type(repo) == type(""):
148 self.repo = repository(ui(), repo)
154 self.repo = repository(ui(), repo)
149 else:
155 else:
150 self.repo = repo
156 self.repo = repo
151
157
152 self.mtime = -1
158 self.mtime = -1
153 self.reponame = name or self.repo.ui.config("web", "name",
159 self.reponame = name or self.repo.ui.config("web", "name",
154 self.repo.root)
160 self.repo.root)
155
161
156 def refresh(self):
162 def refresh(self):
157 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
163 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
158 if s.st_mtime != self.mtime:
164 if s.st_mtime != self.mtime:
159 self.mtime = s.st_mtime
165 self.mtime = s.st_mtime
160 self.repo = repository(self.repo.ui, self.repo.root)
166 self.repo = repository(self.repo.ui, self.repo.root)
161 self.maxchanges = self.repo.ui.config("web", "maxchanges", 10)
167 self.maxchanges = self.repo.ui.config("web", "maxchanges", 10)
162 self.maxfiles = self.repo.ui.config("web", "maxchanges", 10)
168 self.maxfiles = self.repo.ui.config("web", "maxchanges", 10)
163 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
169 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
170 self.allowzip = self.repo.ui.configbool("web", "zip", True)
171 self.allowgz = self.repo.ui.configbool("web", "gz", True)
172 self.allowbz2 = self.repo.ui.configbool("web", "bz2", True)
164
173
165 def date(self, cs):
174 def date(self, cs):
166 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
175 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
167
176
168 def listfiles(self, files, mf):
177 def listfiles(self, files, mf):
169 for f in files[:self.maxfiles]:
178 for f in files[:self.maxfiles]:
170 yield self.t("filenodelink", node=hex(mf[f]), file=f)
179 yield self.t("filenodelink", node=hex(mf[f]), file=f)
171 if len(files) > self.maxfiles:
180 if len(files) > self.maxfiles:
172 yield self.t("fileellipses")
181 yield self.t("fileellipses")
173
182
174 def listfilediffs(self, files, changeset):
183 def listfilediffs(self, files, changeset):
175 for f in files[:self.maxfiles]:
184 for f in files[:self.maxfiles]:
176 yield self.t("filedifflink", node=hex(changeset), file=f)
185 yield self.t("filedifflink", node=hex(changeset), file=f)
177 if len(files) > self.maxfiles:
186 if len(files) > self.maxfiles:
178 yield self.t("fileellipses")
187 yield self.t("fileellipses")
179
188
180 def parents(self, t1, nodes=[], rev=None,**args):
189 def parents(self, t1, nodes=[], rev=None,**args):
181 if not rev:
190 if not rev:
182 rev = lambda x: ""
191 rev = lambda x: ""
183 for node in nodes:
192 for node in nodes:
184 if node != nullid:
193 if node != nullid:
185 yield self.t(t1, node=hex(node), rev=rev(node), **args)
194 yield self.t(t1, node=hex(node), rev=rev(node), **args)
186
195
187 def showtag(self, t1, node=nullid, **args):
196 def showtag(self, t1, node=nullid, **args):
188 for t in self.repo.nodetags(node):
197 for t in self.repo.nodetags(node):
189 yield self.t(t1, tag=t, **args)
198 yield self.t(t1, tag=t, **args)
190
199
200 def tarballbuttons(self, m):
201 s = ''
202 if self.allowzip:
203 s += '<a href="?cmd=tarball;manifest=%s;type=zip">zip</a>\n' % m
204 if self.allowgz:
205 s += '<a href="?cmd=tarball;manifest=%s;type=gz">gz</a>\n' % m
206 if self.allowbz2:
207 s += '<a href="?cmd=tarball;manifest=%s;type=bz2">bz2</a>\n' % m
208 return s
209
191 def diff(self, node1, node2, files):
210 def diff(self, node1, node2, files):
192 def filterfiles(list, files):
211 def filterfiles(list, files):
193 l = [x for x in list if x in files]
212 l = [x for x in list if x in files]
194
213
195 for f in files:
214 for f in files:
196 if f[-1] != os.sep:
215 if f[-1] != os.sep:
197 f += os.sep
216 f += os.sep
198 l += [x for x in list if x.startswith(f)]
217 l += [x for x in list if x.startswith(f)]
199 return l
218 return l
200
219
201 parity = [0]
220 parity = [0]
202 def diffblock(diff, f, fn):
221 def diffblock(diff, f, fn):
203 yield self.t("diffblock",
222 yield self.t("diffblock",
204 lines=prettyprintlines(diff),
223 lines=prettyprintlines(diff),
205 parity=parity[0],
224 parity=parity[0],
206 file=f,
225 file=f,
207 filenode=hex(fn or nullid))
226 filenode=hex(fn or nullid))
208 parity[0] = 1 - parity[0]
227 parity[0] = 1 - parity[0]
209
228
210 def prettyprintlines(diff):
229 def prettyprintlines(diff):
211 for l in diff.splitlines(1):
230 for l in diff.splitlines(1):
212 if l.startswith('+'):
231 if l.startswith('+'):
213 yield self.t("difflineplus", line=l)
232 yield self.t("difflineplus", line=l)
214 elif l.startswith('-'):
233 elif l.startswith('-'):
215 yield self.t("difflineminus", line=l)
234 yield self.t("difflineminus", line=l)
216 elif l.startswith('@'):
235 elif l.startswith('@'):
217 yield self.t("difflineat", line=l)
236 yield self.t("difflineat", line=l)
218 else:
237 else:
219 yield self.t("diffline", line=l)
238 yield self.t("diffline", line=l)
220
239
221 r = self.repo
240 r = self.repo
222 cl = r.changelog
241 cl = r.changelog
223 mf = r.manifest
242 mf = r.manifest
224 change1 = cl.read(node1)
243 change1 = cl.read(node1)
225 change2 = cl.read(node2)
244 change2 = cl.read(node2)
226 mmap1 = mf.read(change1[0])
245 mmap1 = mf.read(change1[0])
227 mmap2 = mf.read(change2[0])
246 mmap2 = mf.read(change2[0])
228 date1 = self.date(change1)
247 date1 = self.date(change1)
229 date2 = self.date(change2)
248 date2 = self.date(change2)
230
249
231 c, a, d, u = r.changes(node1, node2)
250 c, a, d, u = r.changes(node1, node2)
232 if files:
251 if files:
233 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
252 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
234
253
235 for f in c:
254 for f in c:
236 to = r.file(f).read(mmap1[f])
255 to = r.file(f).read(mmap1[f])
237 tn = r.file(f).read(mmap2[f])
256 tn = r.file(f).read(mmap2[f])
238 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
257 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
239 for f in a:
258 for f in a:
240 to = None
259 to = None
241 tn = r.file(f).read(mmap2[f])
260 tn = r.file(f).read(mmap2[f])
242 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
261 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
243 for f in d:
262 for f in d:
244 to = r.file(f).read(mmap1[f])
263 to = r.file(f).read(mmap1[f])
245 tn = None
264 tn = None
246 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
265 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
247
266
248 def changelog(self, pos):
267 def changelog(self, pos):
249 def changenav(**map):
268 def changenav(**map):
250 def seq(factor=1):
269 def seq(factor=1):
251 yield 1 * factor
270 yield 1 * factor
252 yield 3 * factor
271 yield 3 * factor
253 #yield 5 * factor
272 #yield 5 * factor
254 for f in seq(factor * 10):
273 for f in seq(factor * 10):
255 yield f
274 yield f
256
275
257 l = []
276 l = []
258 for f in seq():
277 for f in seq():
259 if f < self.maxchanges / 2:
278 if f < self.maxchanges / 2:
260 continue
279 continue
261 if f > count:
280 if f > count:
262 break
281 break
263 r = "%d" % f
282 r = "%d" % f
264 if pos + f < count:
283 if pos + f < count:
265 l.append(("+" + r, pos + f))
284 l.append(("+" + r, pos + f))
266 if pos - f >= 0:
285 if pos - f >= 0:
267 l.insert(0, ("-" + r, pos - f))
286 l.insert(0, ("-" + r, pos - f))
268
287
269 yield {"rev": 0, "label": "(0)"}
288 yield {"rev": 0, "label": "(0)"}
270
289
271 for label, rev in l:
290 for label, rev in l:
272 yield {"label": label, "rev": rev}
291 yield {"label": label, "rev": rev}
273
292
274 yield {"label": "tip", "rev": ""}
293 yield {"label": "tip", "rev": ""}
275
294
276 def changelist(**map):
295 def changelist(**map):
277 parity = (start - end) & 1
296 parity = (start - end) & 1
278 cl = self.repo.changelog
297 cl = self.repo.changelog
279 l = [] # build a list in forward order for efficiency
298 l = [] # build a list in forward order for efficiency
280 for i in range(start, end):
299 for i in range(start, end):
281 n = cl.node(i)
300 n = cl.node(i)
282 changes = cl.read(n)
301 changes = cl.read(n)
283 hn = hex(n)
302 hn = hex(n)
284 t = float(changes[2].split(' ')[0])
303 t = float(changes[2].split(' ')[0])
285
304
286 l.insert(0, {"parity": parity,
305 l.insert(0, {"parity": parity,
287 "author": changes[1],
306 "author": changes[1],
288 "parent": self.parents("changelogparent",
307 "parent": self.parents("changelogparent",
289 cl.parents(n), cl.rev),
308 cl.parents(n), cl.rev),
290 "changelogtag": self.showtag("changelogtag",n),
309 "changelogtag": self.showtag("changelogtag",n),
291 "manifest": hex(changes[0]),
310 "manifest": hex(changes[0]),
292 "desc": changes[4],
311 "desc": changes[4],
293 "date": t,
312 "date": t,
294 "files": self.listfilediffs(changes[3], n),
313 "files": self.listfilediffs(changes[3], n),
295 "rev": i,
314 "rev": i,
296 "node": hn})
315 "node": hn})
297 parity = 1 - parity
316 parity = 1 - parity
298
317
299 for e in l:
318 for e in l:
300 yield e
319 yield e
301
320
302 cl = self.repo.changelog
321 cl = self.repo.changelog
303 mf = cl.read(cl.tip())[0]
322 mf = cl.read(cl.tip())[0]
304 count = cl.count()
323 count = cl.count()
305 start = max(0, pos - self.maxchanges + 1)
324 start = max(0, pos - self.maxchanges + 1)
306 end = min(count, start + self.maxchanges)
325 end = min(count, start + self.maxchanges)
307 pos = end - 1
326 pos = end - 1
308
327
309 yield self.t('changelog',
328 yield self.t('changelog',
310 changenav=changenav,
329 changenav=changenav,
311 manifest=hex(mf),
330 manifest=hex(mf),
312 rev=pos, changesets=count, entries=changelist)
331 rev=pos, changesets=count, entries=changelist)
313
332
314 def search(self, query):
333 def search(self, query):
315
334
316 def changelist(**map):
335 def changelist(**map):
317 cl = self.repo.changelog
336 cl = self.repo.changelog
318 count = 0
337 count = 0
319 qw = query.lower().split()
338 qw = query.lower().split()
320
339
321 def revgen():
340 def revgen():
322 for i in range(cl.count() - 1, 0, -100):
341 for i in range(cl.count() - 1, 0, -100):
323 l = []
342 l = []
324 for j in range(max(0, i - 100), i):
343 for j in range(max(0, i - 100), i):
325 n = cl.node(j)
344 n = cl.node(j)
326 changes = cl.read(n)
345 changes = cl.read(n)
327 l.append((n, j, changes))
346 l.append((n, j, changes))
328 l.reverse()
347 l.reverse()
329 for e in l:
348 for e in l:
330 yield e
349 yield e
331
350
332 for n, i, changes in revgen():
351 for n, i, changes in revgen():
333 miss = 0
352 miss = 0
334 for q in qw:
353 for q in qw:
335 if not (q in changes[1].lower() or
354 if not (q in changes[1].lower() or
336 q in changes[4].lower() or
355 q in changes[4].lower() or
337 q in " ".join(changes[3][:20]).lower()):
356 q in " ".join(changes[3][:20]).lower()):
338 miss = 1
357 miss = 1
339 break
358 break
340 if miss:
359 if miss:
341 continue
360 continue
342
361
343 count += 1
362 count += 1
344 hn = hex(n)
363 hn = hex(n)
345 t = float(changes[2].split(' ')[0])
364 t = float(changes[2].split(' ')[0])
346
365
347 yield self.t('searchentry',
366 yield self.t('searchentry',
348 parity=count & 1,
367 parity=count & 1,
349 author=changes[1],
368 author=changes[1],
350 parent=self.parents("changelogparent",
369 parent=self.parents("changelogparent",
351 cl.parents(n), cl.rev),
370 cl.parents(n), cl.rev),
352 changelogtag=self.showtag("changelogtag",n),
371 changelogtag=self.showtag("changelogtag",n),
353 manifest=hex(changes[0]),
372 manifest=hex(changes[0]),
354 desc=changes[4],
373 desc=changes[4],
355 date=t,
374 date=t,
356 files=self.listfilediffs(changes[3], n),
375 files=self.listfilediffs(changes[3], n),
357 rev=i,
376 rev=i,
358 node=hn)
377 node=hn)
359
378
360 if count >= self.maxchanges:
379 if count >= self.maxchanges:
361 break
380 break
362
381
363 cl = self.repo.changelog
382 cl = self.repo.changelog
364 mf = cl.read(cl.tip())[0]
383 mf = cl.read(cl.tip())[0]
365
384
366 yield self.t('search',
385 yield self.t('search',
367 query=query,
386 query=query,
368 manifest=hex(mf),
387 manifest=hex(mf),
369 entries=changelist)
388 entries=changelist)
370
389
371 def changeset(self, nodeid):
390 def changeset(self, nodeid):
372 n = bin(nodeid)
391 n = bin(nodeid)
373 cl = self.repo.changelog
392 cl = self.repo.changelog
374 changes = cl.read(n)
393 changes = cl.read(n)
375 p1 = cl.parents(n)[0]
394 p1 = cl.parents(n)[0]
376 t = float(changes[2].split(' ')[0])
395 t = float(changes[2].split(' ')[0])
377
396
378 files = []
397 files = []
379 mf = self.repo.manifest.read(changes[0])
398 mf = self.repo.manifest.read(changes[0])
380 for f in changes[3]:
399 for f in changes[3]:
381 files.append(self.t("filenodelink",
400 files.append(self.t("filenodelink",
382 filenode=hex(mf.get(f, nullid)), file=f))
401 filenode=hex(mf.get(f, nullid)), file=f))
383
402
384 def diff(**map):
403 def diff(**map):
385 yield self.diff(p1, n, None)
404 yield self.diff(p1, n, None)
386
405
387 yield self.t('changeset',
406 yield self.t('changeset',
388 diff=diff,
407 diff=diff,
389 rev=cl.rev(n),
408 rev=cl.rev(n),
390 node=nodeid,
409 node=nodeid,
391 parent=self.parents("changesetparent",
410 parent=self.parents("changesetparent",
392 cl.parents(n), cl.rev),
411 cl.parents(n), cl.rev),
393 changesettag=self.showtag("changesettag",n),
412 changesettag=self.showtag("changesettag",n),
394 manifest=hex(changes[0]),
413 manifest=hex(changes[0]),
395 author=changes[1],
414 author=changes[1],
396 desc=changes[4],
415 desc=changes[4],
397 date=t,
416 date=t,
398 files=files)
417 files=files,
418 tarballbuttons=self.tarballbuttons(hex(changes[0])))
399
419
400 def filelog(self, f, filenode):
420 def filelog(self, f, filenode):
401 cl = self.repo.changelog
421 cl = self.repo.changelog
402 fl = self.repo.file(f)
422 fl = self.repo.file(f)
403 count = fl.count()
423 count = fl.count()
404
424
405 def entries(**map):
425 def entries(**map):
406 l = []
426 l = []
407 parity = (count - 1) & 1
427 parity = (count - 1) & 1
408
428
409 for i in range(count):
429 for i in range(count):
410 n = fl.node(i)
430 n = fl.node(i)
411 lr = fl.linkrev(n)
431 lr = fl.linkrev(n)
412 cn = cl.node(lr)
432 cn = cl.node(lr)
413 cs = cl.read(cl.node(lr))
433 cs = cl.read(cl.node(lr))
414 t = float(cs[2].split(' ')[0])
434 t = float(cs[2].split(' ')[0])
415
435
416 l.insert(0, {"parity": parity,
436 l.insert(0, {"parity": parity,
417 "filenode": hex(n),
437 "filenode": hex(n),
418 "filerev": i,
438 "filerev": i,
419 "file": f,
439 "file": f,
420 "node": hex(cn),
440 "node": hex(cn),
421 "author": cs[1],
441 "author": cs[1],
422 "date": t,
442 "date": t,
423 "parent": self.parents("filelogparent",
443 "parent": self.parents("filelogparent",
424 fl.parents(n),
444 fl.parents(n),
425 fl.rev, file=f),
445 fl.rev, file=f),
426 "desc": cs[4]})
446 "desc": cs[4]})
427 parity = 1 - parity
447 parity = 1 - parity
428
448
429 for e in l:
449 for e in l:
430 yield e
450 yield e
431
451
432 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
452 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
433
453
434 def filerevision(self, f, node):
454 def filerevision(self, f, node):
435 fl = self.repo.file(f)
455 fl = self.repo.file(f)
436 n = bin(node)
456 n = bin(node)
437 text = fl.read(n)
457 text = fl.read(n)
438 changerev = fl.linkrev(n)
458 changerev = fl.linkrev(n)
439 cl = self.repo.changelog
459 cl = self.repo.changelog
440 cn = cl.node(changerev)
460 cn = cl.node(changerev)
441 cs = cl.read(cn)
461 cs = cl.read(cn)
442 t = float(cs[2].split(' ')[0])
462 t = float(cs[2].split(' ')[0])
443 mfn = cs[0]
463 mfn = cs[0]
444
464
445 def lines():
465 def lines():
446 for l, t in enumerate(text.splitlines(1)):
466 for l, t in enumerate(text.splitlines(1)):
447 yield {"line": t,
467 yield {"line": t,
448 "linenumber": "% 6d" % (l + 1),
468 "linenumber": "% 6d" % (l + 1),
449 "parity": l & 1}
469 "parity": l & 1}
450
470
451 yield self.t("filerevision",
471 yield self.t("filerevision",
452 file=f,
472 file=f,
453 filenode=node,
473 filenode=node,
454 path=up(f),
474 path=up(f),
455 text=lines(),
475 text=lines(),
456 rev=changerev,
476 rev=changerev,
457 node=hex(cn),
477 node=hex(cn),
458 manifest=hex(mfn),
478 manifest=hex(mfn),
459 author=cs[1],
479 author=cs[1],
460 date=t,
480 date=t,
461 parent=self.parents("filerevparent",
481 parent=self.parents("filerevparent",
462 fl.parents(n), fl.rev, file=f),
482 fl.parents(n), fl.rev, file=f),
463 permissions=self.repo.manifest.readflags(mfn)[f])
483 permissions=self.repo.manifest.readflags(mfn)[f])
464
484
465 def fileannotate(self, f, node):
485 def fileannotate(self, f, node):
466 bcache = {}
486 bcache = {}
467 ncache = {}
487 ncache = {}
468 fl = self.repo.file(f)
488 fl = self.repo.file(f)
469 n = bin(node)
489 n = bin(node)
470 changerev = fl.linkrev(n)
490 changerev = fl.linkrev(n)
471
491
472 cl = self.repo.changelog
492 cl = self.repo.changelog
473 cn = cl.node(changerev)
493 cn = cl.node(changerev)
474 cs = cl.read(cn)
494 cs = cl.read(cn)
475 t = float(cs[2].split(' ')[0])
495 t = float(cs[2].split(' ')[0])
476 mfn = cs[0]
496 mfn = cs[0]
477
497
478 def annotate(**map):
498 def annotate(**map):
479 parity = 1
499 parity = 1
480 last = None
500 last = None
481 for r, l in fl.annotate(n):
501 for r, l in fl.annotate(n):
482 try:
502 try:
483 cnode = ncache[r]
503 cnode = ncache[r]
484 except KeyError:
504 except KeyError:
485 cnode = ncache[r] = self.repo.changelog.node(r)
505 cnode = ncache[r] = self.repo.changelog.node(r)
486
506
487 try:
507 try:
488 name = bcache[r]
508 name = bcache[r]
489 except KeyError:
509 except KeyError:
490 cl = self.repo.changelog.read(cnode)
510 cl = self.repo.changelog.read(cnode)
491 name = cl[1]
511 name = cl[1]
492 f = name.find('@')
512 f = name.find('@')
493 if f >= 0:
513 if f >= 0:
494 name = name[:f]
514 name = name[:f]
495 f = name.find('<')
515 f = name.find('<')
496 if f >= 0:
516 if f >= 0:
497 name = name[f+1:]
517 name = name[f+1:]
498 bcache[r] = name
518 bcache[r] = name
499
519
500 if last != cnode:
520 if last != cnode:
501 parity = 1 - parity
521 parity = 1 - parity
502 last = cnode
522 last = cnode
503
523
504 yield {"parity": parity,
524 yield {"parity": parity,
505 "node": hex(cnode),
525 "node": hex(cnode),
506 "rev": r,
526 "rev": r,
507 "author": name,
527 "author": name,
508 "file": f,
528 "file": f,
509 "line": l}
529 "line": l}
510
530
511 yield self.t("fileannotate",
531 yield self.t("fileannotate",
512 file=f,
532 file=f,
513 filenode=node,
533 filenode=node,
514 annotate=annotate,
534 annotate=annotate,
515 path=up(f),
535 path=up(f),
516 rev=changerev,
536 rev=changerev,
517 node=hex(cn),
537 node=hex(cn),
518 manifest=hex(mfn),
538 manifest=hex(mfn),
519 author=cs[1],
539 author=cs[1],
520 date=t,
540 date=t,
521 parent=self.parents("fileannotateparent",
541 parent=self.parents("fileannotateparent",
522 fl.parents(n), fl.rev, file=f),
542 fl.parents(n), fl.rev, file=f),
523 permissions=self.repo.manifest.readflags(mfn)[f])
543 permissions=self.repo.manifest.readflags(mfn)[f])
524
544
525 def manifest(self, mnode, path):
545 def manifest(self, mnode, path):
526 mf = self.repo.manifest.read(bin(mnode))
546 mf = self.repo.manifest.read(bin(mnode))
527 rev = self.repo.manifest.rev(bin(mnode))
547 rev = self.repo.manifest.rev(bin(mnode))
528 node = self.repo.changelog.node(rev)
548 node = self.repo.changelog.node(rev)
529 mff=self.repo.manifest.readflags(bin(mnode))
549 mff=self.repo.manifest.readflags(bin(mnode))
530
550
531 files = {}
551 files = {}
532
552
533 p = path[1:]
553 p = path[1:]
534 l = len(p)
554 l = len(p)
535
555
536 for f,n in mf.items():
556 for f,n in mf.items():
537 if f[:l] != p:
557 if f[:l] != p:
538 continue
558 continue
539 remain = f[l:]
559 remain = f[l:]
540 if "/" in remain:
560 if "/" in remain:
541 short = remain[:remain.find("/") + 1] # bleah
561 short = remain[:remain.find("/") + 1] # bleah
542 files[short] = (f, None)
562 files[short] = (f, None)
543 else:
563 else:
544 short = os.path.basename(remain)
564 short = os.path.basename(remain)
545 files[short] = (f, n)
565 files[short] = (f, n)
546
566
547 def filelist(**map):
567 def filelist(**map):
548 parity = 0
568 parity = 0
549 fl = files.keys()
569 fl = files.keys()
550 fl.sort()
570 fl.sort()
551 for f in fl:
571 for f in fl:
552 full, fnode = files[f]
572 full, fnode = files[f]
553 if not fnode:
573 if not fnode:
554 continue
574 continue
555
575
556 yield {"file": full,
576 yield {"file": full,
557 "manifest": mnode,
577 "manifest": mnode,
558 "filenode": hex(fnode),
578 "filenode": hex(fnode),
559 "parity": parity,
579 "parity": parity,
560 "basename": f,
580 "basename": f,
561 "permissions": mff[full]}
581 "permissions": mff[full]}
562 parity = 1 - parity
582 parity = 1 - parity
563
583
564 def dirlist(**map):
584 def dirlist(**map):
565 parity = 0
585 parity = 0
566 fl = files.keys()
586 fl = files.keys()
567 fl.sort()
587 fl.sort()
568 for f in fl:
588 for f in fl:
569 full, fnode = files[f]
589 full, fnode = files[f]
570 if fnode:
590 if fnode:
571 continue
591 continue
572
592
573 yield {"parity": parity,
593 yield {"parity": parity,
574 "path": os.path.join(path, f),
594 "path": os.path.join(path, f),
575 "manifest": mnode,
595 "manifest": mnode,
576 "basename": f[:-1]}
596 "basename": f[:-1]}
577 parity = 1 - parity
597 parity = 1 - parity
578
598
579 yield self.t("manifest",
599 yield self.t("manifest",
580 manifest=mnode,
600 manifest=mnode,
581 rev=rev,
601 rev=rev,
582 node=hex(node),
602 node=hex(node),
583 path=path,
603 path=path,
584 up=up(path),
604 up=up(path),
585 fentries=filelist,
605 fentries=filelist,
586 dentries=dirlist)
606 dentries=dirlist)
587
607
588 def tags(self):
608 def tags(self):
589 cl = self.repo.changelog
609 cl = self.repo.changelog
590 mf = cl.read(cl.tip())[0]
610 mf = cl.read(cl.tip())[0]
591
611
592 i = self.repo.tagslist()
612 i = self.repo.tagslist()
593 i.reverse()
613 i.reverse()
594
614
595 def entries(**map):
615 def entries(**map):
596 parity = 0
616 parity = 0
597 for k,n in i:
617 for k,n in i:
598 yield {"parity": parity,
618 yield {"parity": parity,
599 "tag": k,
619 "tag": k,
600 "node": hex(n)}
620 "node": hex(n)}
601 parity = 1 - parity
621 parity = 1 - parity
602
622
603 yield self.t("tags",
623 yield self.t("tags",
604 manifest=hex(mf),
624 manifest=hex(mf),
605 entries=entries)
625 entries=entries)
606
626
607 def filediff(self, file, changeset):
627 def filediff(self, file, changeset):
608 n = bin(changeset)
628 n = bin(changeset)
609 cl = self.repo.changelog
629 cl = self.repo.changelog
610 p1 = cl.parents(n)[0]
630 p1 = cl.parents(n)[0]
611 cs = cl.read(n)
631 cs = cl.read(n)
612 mf = self.repo.manifest.read(cs[0])
632 mf = self.repo.manifest.read(cs[0])
613
633
614 def diff(**map):
634 def diff(**map):
615 yield self.diff(p1, n, file)
635 yield self.diff(p1, n, file)
616
636
617 yield self.t("filediff",
637 yield self.t("filediff",
618 file=file,
638 file=file,
619 filenode=hex(mf.get(file, nullid)),
639 filenode=hex(mf.get(file, nullid)),
620 node=changeset,
640 node=changeset,
621 rev=self.repo.changelog.rev(n),
641 rev=self.repo.changelog.rev(n),
622 parent=self.parents("filediffparent",
642 parent=self.parents("filediffparent",
623 cl.parents(n), cl.rev),
643 cl.parents(n), cl.rev),
624 diff=diff)
644 diff=diff)
625
645
646 def ziparchive(self, mnode):
647 import zipfile
648
649 tmp = tempfile.mkstemp()[1]
650 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
651 mf = self.repo.manifest.read(bin(mnode))
652 rev = self.repo.manifest.rev(bin(mnode))
653 cnode = short(self.repo.changelog.node(rev))
654 name = os.path.basename(self.repo.path[:-4]) # without '/.hg' suffix
655 name += '-' + str(rev) + '-' + cnode + '/'
656
657 for fname in mf.keys():
658 r = self.repo.file(fname)
659 zf.writestr(name + fname, r.read(mf[fname]))
660 zf.close()
661
662 f = open(tmp, 'r')
663 httphdr('application/zip', name[:-1] + '.zip', os.path.getsize(tmp))
664 sys.stdout.write(f.read())
665 f.close()
666 os.unlink(tmp)
667
668 def tararchive(self, mnode, type):
669 import StringIO
670 import time
671 import tarfile
672
673 #if type == "gz":
674 # tf = tarfile.TarFile.gzopen('', 'w', sys.stdout, compressionlevel)
675 #else:
676 # tf = tarfile.TarFile.bz2open('', 'w', sys.stdout, compressionlevel)
677 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=sys.stdout)
678
679 mf = self.repo.manifest.read(bin(mnode))
680 rev = self.repo.manifest.rev(bin(mnode))
681 cnode = short(self.repo.changelog.node(rev))
682 mff = self.repo.manifest.readflags(bin(mnode))
683 mtime = int(time.time())
684 name = os.path.basename(self.repo.path[:-4]) # without '/.hg' suffix
685 name += '-' + str(rev) + '-' + cnode + '/'
686
687 httphdr('application/octet-stream', name[:-1] + '.tar.' + type)
688 for fname in mf.keys():
689 r = self.repo.file(fname)
690 rcont = r.read(mf[fname])
691 finfo = tarfile.TarInfo(name + fname)
692 finfo.mtime = mtime
693 finfo.size = len(rcont)
694 finfo.mode = mff[fname] and 0755 or 0644
695 tf.addfile(finfo, StringIO.StringIO(rcont))
696 tf.close()
697
626 # add tags to things
698 # add tags to things
627 # tags -> list of changesets corresponding to tags
699 # tags -> list of changesets corresponding to tags
628 # find tag, changeset, file
700 # find tag, changeset, file
629
701
630 def run(self):
702 def run(self):
631 def header(**map):
703 def header(**map):
632 yield self.t("header", **map)
704 yield self.t("header", **map)
633
705
634 def footer(**map):
706 def footer(**map):
635 yield self.t("footer", **map)
707 yield self.t("footer", **map)
636
708
637 self.refresh()
709 self.refresh()
638 args = cgi.parse()
710 args = cgi.parse()
639
711
640 t = self.repo.ui.config("web", "templates", templatepath())
712 t = self.repo.ui.config("web", "templates", templatepath())
641 m = os.path.join(t, "map")
713 m = os.path.join(t, "map")
642 style = self.repo.ui.config("web", "style", "")
714 style = self.repo.ui.config("web", "style", "")
643 if args.has_key('style'):
715 if args.has_key('style'):
644 style = args['style'][0]
716 style = args['style'][0]
645 if style:
717 if style:
646 b = os.path.basename("map-" + style)
718 b = os.path.basename("map-" + style)
647 p = os.path.join(t, b)
719 p = os.path.join(t, b)
648 if os.path.isfile(p):
720 if os.path.isfile(p):
649 m = p
721 m = p
650
722
651 port = os.environ["SERVER_PORT"]
723 port = os.environ["SERVER_PORT"]
652 port = port != "80" and (":" + port) or ""
724 port = port != "80" and (":" + port) or ""
653 uri = os.environ["REQUEST_URI"]
725 uri = os.environ["REQUEST_URI"]
654 if "?" in uri:
726 if "?" in uri:
655 uri = uri.split("?")[0]
727 uri = uri.split("?")[0]
656 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
728 url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)
657
729
658 self.t = templater(m, common_filters,
730 self.t = templater(m, common_filters,
659 {"url": url,
731 {"url": url,
660 "repo": self.reponame,
732 "repo": self.reponame,
661 "header": header,
733 "header": header,
662 "footer": footer,
734 "footer": footer,
663 })
735 })
664
736
665 if not args.has_key('cmd'):
737 if not args.has_key('cmd'):
666 args['cmd'] = [self.t.cache['default'],]
738 args['cmd'] = [self.t.cache['default'],]
667
739
668 if args['cmd'][0] == 'changelog':
740 if args['cmd'][0] == 'changelog':
669 c = self.repo.changelog.count() - 1
741 c = self.repo.changelog.count() - 1
670 hi = c
742 hi = c
671 if args.has_key('rev'):
743 if args.has_key('rev'):
672 hi = args['rev'][0]
744 hi = args['rev'][0]
673 try:
745 try:
674 hi = self.repo.changelog.rev(self.repo.lookup(hi))
746 hi = self.repo.changelog.rev(self.repo.lookup(hi))
675 except RepoError:
747 except RepoError:
676 write(self.search(hi))
748 write(self.search(hi))
677 return
749 return
678
750
679 write(self.changelog(hi))
751 write(self.changelog(hi))
680
752
681 elif args['cmd'][0] == 'changeset':
753 elif args['cmd'][0] == 'changeset':
682 write(self.changeset(args['node'][0]))
754 write(self.changeset(args['node'][0]))
683
755
684 elif args['cmd'][0] == 'manifest':
756 elif args['cmd'][0] == 'manifest':
685 write(self.manifest(args['manifest'][0], args['path'][0]))
757 write(self.manifest(args['manifest'][0], args['path'][0]))
686
758
687 elif args['cmd'][0] == 'tags':
759 elif args['cmd'][0] == 'tags':
688 write(self.tags())
760 write(self.tags())
689
761
690 elif args['cmd'][0] == 'filediff':
762 elif args['cmd'][0] == 'filediff':
691 write(self.filediff(args['file'][0], args['node'][0]))
763 write(self.filediff(args['file'][0], args['node'][0]))
692
764
693 elif args['cmd'][0] == 'file':
765 elif args['cmd'][0] == 'file':
694 write(self.filerevision(args['file'][0], args['filenode'][0]))
766 write(self.filerevision(args['file'][0], args['filenode'][0]))
695
767
696 elif args['cmd'][0] == 'annotate':
768 elif args['cmd'][0] == 'annotate':
697 write(self.fileannotate(args['file'][0], args['filenode'][0]))
769 write(self.fileannotate(args['file'][0], args['filenode'][0]))
698
770
699 elif args['cmd'][0] == 'filelog':
771 elif args['cmd'][0] == 'filelog':
700 write(self.filelog(args['file'][0], args['filenode'][0]))
772 write(self.filelog(args['file'][0], args['filenode'][0]))
701
773
702 elif args['cmd'][0] == 'heads':
774 elif args['cmd'][0] == 'heads':
703 httphdr("application/mercurial-0.1")
775 httphdr("application/mercurial-0.1")
704 h = self.repo.heads()
776 h = self.repo.heads()
705 sys.stdout.write(" ".join(map(hex, h)) + "\n")
777 sys.stdout.write(" ".join(map(hex, h)) + "\n")
706
778
707 elif args['cmd'][0] == 'branches':
779 elif args['cmd'][0] == 'branches':
708 httphdr("application/mercurial-0.1")
780 httphdr("application/mercurial-0.1")
709 nodes = []
781 nodes = []
710 if args.has_key('nodes'):
782 if args.has_key('nodes'):
711 nodes = map(bin, args['nodes'][0].split(" "))
783 nodes = map(bin, args['nodes'][0].split(" "))
712 for b in self.repo.branches(nodes):
784 for b in self.repo.branches(nodes):
713 sys.stdout.write(" ".join(map(hex, b)) + "\n")
785 sys.stdout.write(" ".join(map(hex, b)) + "\n")
714
786
715 elif args['cmd'][0] == 'between':
787 elif args['cmd'][0] == 'between':
716 httphdr("application/mercurial-0.1")
788 httphdr("application/mercurial-0.1")
717 nodes = []
789 nodes = []
718 if args.has_key('pairs'):
790 if args.has_key('pairs'):
719 pairs = [map(bin, p.split("-"))
791 pairs = [map(bin, p.split("-"))
720 for p in args['pairs'][0].split(" ")]
792 for p in args['pairs'][0].split(" ")]
721 for b in self.repo.between(pairs):
793 for b in self.repo.between(pairs):
722 sys.stdout.write(" ".join(map(hex, b)) + "\n")
794 sys.stdout.write(" ".join(map(hex, b)) + "\n")
723
795
724 elif args['cmd'][0] == 'changegroup':
796 elif args['cmd'][0] == 'changegroup':
725 httphdr("application/mercurial-0.1")
797 httphdr("application/mercurial-0.1")
726 nodes = []
798 nodes = []
727 if not self.allowpull:
799 if not self.allowpull:
728 return
800 return
729
801
730 if args.has_key('roots'):
802 if args.has_key('roots'):
731 nodes = map(bin, args['roots'][0].split(" "))
803 nodes = map(bin, args['roots'][0].split(" "))
732
804
733 z = zlib.compressobj()
805 z = zlib.compressobj()
734 f = self.repo.changegroup(nodes)
806 f = self.repo.changegroup(nodes)
735 while 1:
807 while 1:
736 chunk = f.read(4096)
808 chunk = f.read(4096)
737 if not chunk:
809 if not chunk:
738 break
810 break
739 sys.stdout.write(z.compress(chunk))
811 sys.stdout.write(z.compress(chunk))
740
812
741 sys.stdout.write(z.flush())
813 sys.stdout.write(z.flush())
742
814
815 elif args['cmd'][0] == 'tarball':
816 manifest = args['manifest'][0]
817 type = args['type'][0]
818 if type == 'zip' and self.allowzip:
819 self.ziparchive(manifest)
820 elif type == 'gz' and self.allowgz:
821 self.tararchive(manifest, 'gz')
822 elif type == 'bz2' and self.allowbz2:
823 self.tararchive(manifest, 'bz2')
824 else:
825 write(self.t("error"))
826
743 else:
827 else:
744 write(self.t("error"))
828 write(self.t("error"))
745
829
746 def create_server(repo):
830 def create_server(repo):
747
831
748 def openlog(opt, default):
832 def openlog(opt, default):
749 if opt and opt != '-':
833 if opt and opt != '-':
750 return open(opt, 'w')
834 return open(opt, 'w')
751 return default
835 return default
752
836
753 address = repo.ui.config("web", "address", "")
837 address = repo.ui.config("web", "address", "")
754 port = int(repo.ui.config("web", "port", 8000))
838 port = int(repo.ui.config("web", "port", 8000))
755 use_ipv6 = repo.ui.configbool("web", "ipv6")
839 use_ipv6 = repo.ui.configbool("web", "ipv6")
756 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
840 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
757 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
841 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
758
842
759 import BaseHTTPServer
843 import BaseHTTPServer
760
844
761 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
845 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
762 address_family = getattr(socket, 'AF_INET6', None)
846 address_family = getattr(socket, 'AF_INET6', None)
763
847
764 def __init__(self, *args, **kwargs):
848 def __init__(self, *args, **kwargs):
765 if self.address_family is None:
849 if self.address_family is None:
766 raise RepoError('IPv6 not available on this system')
850 raise RepoError('IPv6 not available on this system')
767 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
851 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
768
852
769 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
853 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
770 def log_error(self, format, *args):
854 def log_error(self, format, *args):
771 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
855 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
772 self.log_date_time_string(),
856 self.log_date_time_string(),
773 format % args))
857 format % args))
774
858
775 def log_message(self, format, *args):
859 def log_message(self, format, *args):
776 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
860 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
777 self.log_date_time_string(),
861 self.log_date_time_string(),
778 format % args))
862 format % args))
779
863
780 def do_POST(self):
864 def do_POST(self):
781 try:
865 try:
782 self.do_hgweb()
866 self.do_hgweb()
783 except socket.error, inst:
867 except socket.error, inst:
784 if inst.args[0] != 32:
868 if inst.args[0] != 32:
785 raise
869 raise
786
870
787 def do_GET(self):
871 def do_GET(self):
788 self.do_POST()
872 self.do_POST()
789
873
790 def do_hgweb(self):
874 def do_hgweb(self):
791 query = ""
875 query = ""
792 p = self.path.find("?")
876 p = self.path.find("?")
793 if p:
877 if p:
794 query = self.path[p + 1:]
878 query = self.path[p + 1:]
795 query = query.replace('+', ' ')
879 query = query.replace('+', ' ')
796
880
797 env = {}
881 env = {}
798 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
882 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
799 env['REQUEST_METHOD'] = self.command
883 env['REQUEST_METHOD'] = self.command
800 env['SERVER_NAME'] = self.server.server_name
884 env['SERVER_NAME'] = self.server.server_name
801 env['SERVER_PORT'] = str(self.server.server_port)
885 env['SERVER_PORT'] = str(self.server.server_port)
802 env['REQUEST_URI'] = "/"
886 env['REQUEST_URI'] = "/"
803 if query:
887 if query:
804 env['QUERY_STRING'] = query
888 env['QUERY_STRING'] = query
805 host = self.address_string()
889 host = self.address_string()
806 if host != self.client_address[0]:
890 if host != self.client_address[0]:
807 env['REMOTE_HOST'] = host
891 env['REMOTE_HOST'] = host
808 env['REMOTE_ADDR'] = self.client_address[0]
892 env['REMOTE_ADDR'] = self.client_address[0]
809
893
810 if self.headers.typeheader is None:
894 if self.headers.typeheader is None:
811 env['CONTENT_TYPE'] = self.headers.type
895 env['CONTENT_TYPE'] = self.headers.type
812 else:
896 else:
813 env['CONTENT_TYPE'] = self.headers.typeheader
897 env['CONTENT_TYPE'] = self.headers.typeheader
814 length = self.headers.getheader('content-length')
898 length = self.headers.getheader('content-length')
815 if length:
899 if length:
816 env['CONTENT_LENGTH'] = length
900 env['CONTENT_LENGTH'] = length
817 accept = []
901 accept = []
818 for line in self.headers.getallmatchingheaders('accept'):
902 for line in self.headers.getallmatchingheaders('accept'):
819 if line[:1] in "\t\n\r ":
903 if line[:1] in "\t\n\r ":
820 accept.append(line.strip())
904 accept.append(line.strip())
821 else:
905 else:
822 accept = accept + line[7:].split(',')
906 accept = accept + line[7:].split(',')
823 env['HTTP_ACCEPT'] = ','.join(accept)
907 env['HTTP_ACCEPT'] = ','.join(accept)
824
908
825 os.environ.update(env)
909 os.environ.update(env)
826
910
827 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
911 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
828 try:
912 try:
829 sys.stdin = self.rfile
913 sys.stdin = self.rfile
830 sys.stdout = self.wfile
914 sys.stdout = self.wfile
831 sys.argv = ["hgweb.py"]
915 sys.argv = ["hgweb.py"]
832 if '=' not in query:
916 if '=' not in query:
833 sys.argv.append(query)
917 sys.argv.append(query)
834 self.send_response(200, "Script output follows")
918 self.send_response(200, "Script output follows")
835 hg.run()
919 hg.run()
836 finally:
920 finally:
837 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
921 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
838
922
839 hg = hgweb(repo)
923 hg = hgweb(repo)
840 if use_ipv6:
924 if use_ipv6:
841 return IPv6HTTPServer((address, port), hgwebhandler)
925 return IPv6HTTPServer((address, port), hgwebhandler)
842 else:
926 else:
843 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
927 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
844
928
845 def server(path, name, templates, address, port, use_ipv6=False,
929 def server(path, name, templates, address, port, use_ipv6=False,
846 accesslog=sys.stdout, errorlog=sys.stderr):
930 accesslog=sys.stdout, errorlog=sys.stderr):
847 httpd = create_server(path, name, templates, address, port, use_ipv6,
931 httpd = create_server(path, name, templates, address, port, use_ipv6,
848 accesslog, errorlog)
932 accesslog, errorlog)
849 httpd.serve_forever()
933 httpd.serve_forever()
850
934
851 # This is a stopgap
935 # This is a stopgap
852 class hgwebdir:
936 class hgwebdir:
853 def __init__(self, config):
937 def __init__(self, config):
854 self.cp = ConfigParser.SafeConfigParser()
938 self.cp = ConfigParser.SafeConfigParser()
855 self.cp.read(config)
939 self.cp.read(config)
856
940
857 def run(self):
941 def run(self):
858 try:
942 try:
859 virtual = os.environ["PATH_INFO"]
943 virtual = os.environ["PATH_INFO"]
860 except:
944 except:
861 virtual = ""
945 virtual = ""
862
946
863 if virtual[1:]:
947 if virtual[1:]:
864 real = self.cp.get("paths", virtual[1:])
948 real = self.cp.get("paths", virtual[1:])
865 h = hgweb(real)
949 h = hgweb(real)
866 h.run()
950 h.run()
867 return
951 return
868
952
869 def header(**map):
953 def header(**map):
870 yield tmpl("header", **map)
954 yield tmpl("header", **map)
871
955
872 def footer(**map):
956 def footer(**map):
873 yield tmpl("footer", **map)
957 yield tmpl("footer", **map)
874
958
875 templates = templatepath()
959 templates = templatepath()
876 m = os.path.join(templates, "map")
960 m = os.path.join(templates, "map")
877 tmpl = templater(m, common_filters,
961 tmpl = templater(m, common_filters,
878 {"header": header, "footer": footer})
962 {"header": header, "footer": footer})
879
963
880 def entries(**map):
964 def entries(**map):
881 parity = 0
965 parity = 0
882 l = self.cp.items("paths")
966 l = self.cp.items("paths")
883 l.sort()
967 l.sort()
884 for v,r in l:
968 for v,r in l:
885 cp2 = ConfigParser.SafeConfigParser()
969 cp2 = ConfigParser.SafeConfigParser()
886 cp2.read(os.path.join(r, ".hg", "hgrc"))
970 cp2.read(os.path.join(r, ".hg", "hgrc"))
887
971
888 def get(sec, val, default):
972 def get(sec, val, default):
889 try:
973 try:
890 return cp2.get(sec, val)
974 return cp2.get(sec, val)
891 except:
975 except:
892 return default
976 return default
893
977
894 url = os.environ["REQUEST_URI"] + "/" + v
978 url = os.environ["REQUEST_URI"] + "/" + v
895 url = url.replace("//", "/")
979 url = url.replace("//", "/")
896
980
897 yield dict(author=get("web", "author", "unknown"),
981 yield dict(author=get("web", "author", "unknown"),
898 name=get("web", "name", v),
982 name=get("web", "name", v),
899 url=url,
983 url=url,
900 parity=parity,
984 parity=parity,
901 shortdesc=get("web", "description", "unknown"),
985 shortdesc=get("web", "description", "unknown"),
902 lastupdate=os.stat(os.path.join(r, ".hg",
986 lastupdate=os.stat(os.path.join(r, ".hg",
903 "00changelog.d")).st_mtime)
987 "00changelog.d")).st_mtime)
904
988
905 parity = 1 - parity
989 parity = 1 - parity
906
990
907 write(tmpl("index", entries=entries))
991 write(tmpl("index", entries=entries))
@@ -1,49 +1,50 b''
1 #header#
1 #header#
2 <title>#repo|escape#: changeset #node|short#</title>
2 <title>#repo|escape#: changeset #node|short#</title>
3 </head>
3 </head>
4 <body>
4 <body>
5
5
6 <div class="buttons">
6 <div class="buttons">
7 <a href="?cmd=changelog;rev=#rev#">changelog</a>
7 <a href="?cmd=changelog;rev=#rev#">changelog</a>
8 <a href="?cmd=tags">tags</a>
8 <a href="?cmd=tags">tags</a>
9 <a href="?cmd=manifest;manifest=#manifest#;path=/">manifest</a>
9 <a href="?cmd=manifest;manifest=#manifest#;path=/">manifest</a>
10 <a href="?cmd=changeset;node=#node#;style=raw">raw</a>
10 <a href="?cmd=changeset;node=#node#;style=raw">raw</a>
11 #tarballbuttons#
11 </div>
12 </div>
12
13
13 <h2>changeset: #desc|escape|firstline#</h2>
14 <h2>changeset: #desc|escape|firstline#</h2>
14
15
15 <table id="changesetEntry">
16 <table id="changesetEntry">
16 <tr>
17 <tr>
17 <th class="changeset">changeset #rev#:</th>
18 <th class="changeset">changeset #rev#:</th>
18 <td class="changeset"><a href="?cmd=changeset;node=#node#">#node|short#</a></td>
19 <td class="changeset"><a href="?cmd=changeset;node=#node#">#node|short#</a></td>
19 </tr>
20 </tr>
20 #parent#
21 #parent#
21 #changesettag#
22 #changesettag#
22 <tr>
23 <tr>
23 <th class="manifest">manifest:</th>
24 <th class="manifest">manifest:</th>
24 <td class="manifest"><a href="?cmd=manifest;manifest=#manifest#;path=/">#manifest|short#</a></td>
25 <td class="manifest"><a href="?cmd=manifest;manifest=#manifest#;path=/">#manifest|short#</a></td>
25 </tr>
26 </tr>
26 <tr>
27 <tr>
27 <th class="author">author:</th>
28 <th class="author">author:</th>
28 <td class="author">#author|obfuscate#</td>
29 <td class="author">#author|obfuscate#</td>
29 </tr>
30 </tr>
30 <tr>
31 <tr>
31 <th class="date">date:</th>
32 <th class="date">date:</th>
32 <td class="date">#date|date# (#date|age# ago)</td></tr>
33 <td class="date">#date|date# (#date|age# ago)</td></tr>
33 <tr>
34 <tr>
34 <th class="files">files:</th>
35 <th class="files">files:</th>
35 <td class="files">#files#</td></tr>
36 <td class="files">#files#</td></tr>
36 <tr>
37 <tr>
37 <th class="description">description:</th>
38 <th class="description">description:</th>
38 <td class="description">#desc|escape|addbreaks#</td>
39 <td class="description">#desc|escape|addbreaks#</td>
39 </tr>
40 </tr>
40 </table>
41 </table>
41
42
42 <div id="changesetDiff">
43 <div id="changesetDiff">
43 #diff#
44 #diff#
44 </div>
45 </div>
45
46
46 </body>
47 </body>
47 </html>
48 </html>
48
49
49
50
General Comments 0
You need to be logged in to leave comments. Login now