Mercurial > hg-stable
annotate mercurial/hgweb.py @ 249:619e775aa7f9
import and startup cleanups
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
import and startup cleanups
add commands:run()
add copyright notice to commands
eliminate/reorganize imports to speed up start time:
0.5b:
$ time bash -c 'for i in `seq 100`; do ~/bin/hg > /dev/null; done'
real 0m7.718s
user 0m6.719s
sys 0m0.794s
new:
$ time bash -c 'for i in `seq 100`; do hg > /dev/null; done'
real 0m2.171s
user 0m1.684s
sys 0m0.444s
just python:
$ time bash -c 'for i in `seq 100`; do python -c pass; done'
real 0m0.988s
user 0m0.771s
sys 0m0.207s
Ignoring the fixed cost of loading the Python interpreter, we're 5.6
times faster. With the Python load time, we're still 3.5 times faster.
manifest hash: acce5882a55c76eb165316f5741724c8ce4ef587
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (GNU/Linux)
iD8DBQFCoihAywK+sNU5EO8RAqMdAJwMe6Ur0R9G6jjayNa5hH2C3c4k/gCeIYvc
N178vaWWGciX9zq+g5qCAls=
=buhv
-----END PGP SIGNATURE-----
author | mpm@selenic.com |
---|---|
date | Sat, 04 Jun 2005 14:16:32 -0800 |
parents | afe895fcc0d0 |
children | 268bcb5a072a |
rev | line source |
---|---|
238
3b92f8fe47ae
hgweb.py: kill #! line, clean up copyright notice
mpm@selenic.com
parents:
222
diff
changeset
|
1 # hgweb.py - web interface to a mercurial repository |
131 | 2 # |
238
3b92f8fe47ae
hgweb.py: kill #! line, clean up copyright notice
mpm@selenic.com
parents:
222
diff
changeset
|
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
3b92f8fe47ae
hgweb.py: kill #! line, clean up copyright notice
mpm@selenic.com
parents:
222
diff
changeset
|
4 # Copyright 2005 Matt Mackall <mpm@selenic.com> |
131 | 5 # |
6 # This software may be used and distributed according to the terms | |
7 # of the GNU General Public License, incorporated herein by reference. | |
8 | |
9 # useful for debugging | |
10 import cgitb | |
11 cgitb.enable() | |
12 | |
13 import os, cgi, time, re, difflib, sys, zlib | |
138 | 14 from mercurial.hg import * |
215 | 15 from mercurial.ui import * |
138 | 16 |
157
2653740d8118
Install the templates where they can be found by hgweb.py
mpm@selenic.com
parents:
156
diff
changeset
|
17 def templatepath(): |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
18 for f in "templates", "../templates": |
157
2653740d8118
Install the templates where they can be found by hgweb.py
mpm@selenic.com
parents:
156
diff
changeset
|
19 p = os.path.join(os.path.dirname(__file__), f) |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
20 if os.path.isdir(p): return p |
157
2653740d8118
Install the templates where they can be found by hgweb.py
mpm@selenic.com
parents:
156
diff
changeset
|
21 |
138 | 22 def age(t): |
23 def plural(t, c): | |
24 if c == 1: return t | |
25 return t + "s" | |
26 def fmt(t, c): | |
27 return "%d %s" % (c, plural(t, c)) | |
28 | |
29 now = time.time() | |
30 delta = max(1, int(now - t)) | |
31 | |
32 scales = [["second", 1], | |
33 ["minute", 60], | |
34 ["hour", 3600], | |
35 ["day", 3600 * 24], | |
36 ["week", 3600 * 24 * 7], | |
37 ["month", 3600 * 24 * 30], | |
38 ["year", 3600 * 24 * 365]] | |
39 | |
40 scales.reverse() | |
41 | |
42 for t, s in scales: | |
43 n = delta / s | |
195 | 44 if n >= 2 or s == 1: return fmt(t, n) |
131 | 45 |
46 def nl2br(text): | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
47 return text.replace('\n', '<br/>\n') |
131 | 48 |
49 def obfuscate(text): | |
138 | 50 return ''.join([ '&#%d' % ord(c) for c in text ]) |
51 | |
52 def up(p): | |
53 if p[0] != "/": p = "/" + p | |
54 if p[-1] == "/": p = p[:-1] | |
55 up = os.path.dirname(p) | |
56 if up == "/": | |
57 return "/" | |
58 return up + "/" | |
131 | 59 |
60 def httphdr(type): | |
61 print 'Content-type: %s\n' % type | |
62 | |
135 | 63 def write(*things): |
64 for thing in things: | |
65 if hasattr(thing, "__iter__"): | |
66 for part in thing: | |
67 write(part) | |
68 else: | |
69 sys.stdout.write(str(thing)) | |
70 | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
71 def template(tmpl, filters = {}, **map): |
138 | 72 while tmpl: |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
73 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl) |
138 | 74 if m: |
75 yield tmpl[:m.start(0)] | |
76 v = map.get(m.group(1), "") | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
77 v = callable(v) and v() or v |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
78 |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
79 fl = m.group(2) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
80 if fl: |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
81 for f in fl.split("|")[1:]: |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
82 v = filters[f](v) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
83 |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
84 yield v |
138 | 85 tmpl = tmpl[m.end(0):] |
86 else: | |
87 yield tmpl | |
88 return | |
89 | |
90 class templater: | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
91 def __init__(self, mapfile, filters = {}): |
138 | 92 self.cache = {} |
93 self.map = {} | |
94 self.base = os.path.dirname(mapfile) | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
95 self.filters = filters |
138 | 96 |
97 for l in file(mapfile): | |
98 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l) | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
99 if m: |
138 | 100 self.cache[m.group(1)] = m.group(2) |
101 else: | |
102 m = re.match(r'(\S+)\s*=\s*(\S+)', l) | |
103 if m: | |
104 self.map[m.group(1)] = os.path.join(self.base, m.group(2)) | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
105 else: |
138 | 106 raise "unknown map entry '%s'" % l |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
107 |
138 | 108 def __call__(self, t, **map): |
109 try: | |
110 tmpl = self.cache[t] | |
111 except KeyError: | |
112 tmpl = self.cache[t] = file(self.map[t]).read() | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
113 return template(tmpl, self.filters, **map) |
138 | 114 |
115 class hgweb: | |
116 maxchanges = 20 | |
117 maxfiles = 10 | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
118 |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
119 def __init__(self, path, name, templates = ""): |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
120 self.templates = templates or templatepath() |
138 | 121 self.reponame = name |
122 self.repo = repository(ui(), path) | |
197 | 123 self.viewonly = 0 |
131 | 124 |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
125 self.filters = { |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
126 "escape": cgi.escape, |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
127 "age": age, |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
128 "date": (lambda x: time.asctime(time.gmtime(x))), |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
129 "addbreaks": nl2br, |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
130 "obfuscate": obfuscate, |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
131 "firstline": (lambda x: x.splitlines(1)[0]), |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
132 } |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
133 |
138 | 134 def date(self, cs): |
135 return time.asctime(time.gmtime(float(cs[2].split(' ')[0]))) | |
136 | |
137 def listfiles(self, files, mf): | |
138 for f in files[:self.maxfiles]: | |
139 yield self.t("filenodelink", node = hex(mf[f]), file = f) | |
140 if len(files) > self.maxfiles: | |
141 yield self.t("fileellipses") | |
142 | |
143 def listfilediffs(self, files, changeset): | |
144 for f in files[:self.maxfiles]: | |
145 yield self.t("filedifflink", node = hex(changeset), file = f) | |
146 if len(files) > self.maxfiles: | |
147 yield self.t("fileellipses") | |
148 | |
156 | 149 def parent(self, t1, node=nullid, rev=-1, **args): |
142 | 150 if node != hex(nullid): |
156 | 151 yield self.t(t1, node = node, rev = rev, **args) |
142 | 152 |
138 | 153 def diff(self, node1, node2, files): |
154 def filterfiles(list, files): | |
155 l = [ x for x in list if x in files ] | |
156 | |
157 for f in files: | |
158 if f[-1] != os.sep: f += os.sep | |
159 l += [ x for x in list if x.startswith(f) ] | |
160 return l | |
131 | 161 |
172
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
162 parity = [0] |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
163 def diffblock(diff, f, fn): |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
164 yield self.t("diffblock", |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
165 lines = prettyprintlines(diff), |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
166 parity = parity[0], |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
167 file = f, |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
168 filenode = hex(fn)) |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
169 parity[0] = 1 - parity[0] |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
170 |
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
171 def prettyprintlines(diff): |
138 | 172 for l in diff.splitlines(1): |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
173 if l.startswith('+'): |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
174 yield self.t("difflineplus", line = l) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
175 elif l.startswith('-'): |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
176 yield self.t("difflineminus", line = l) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
177 elif l.startswith('@'): |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
178 yield self.t("difflineat", line = l) |
138 | 179 else: |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
180 yield self.t("diffline", line = l) |
131 | 181 |
138 | 182 r = self.repo |
183 cl = r.changelog | |
184 mf = r.manifest | |
185 change1 = cl.read(node1) | |
186 change2 = cl.read(node2) | |
187 mmap1 = mf.read(change1[0]) | |
188 mmap2 = mf.read(change2[0]) | |
189 date1 = self.date(change1) | |
190 date2 = self.date(change2) | |
131 | 191 |
138 | 192 c, a, d = r.diffrevs(node1, node2) |
193 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d)) | |
131 | 194 |
138 | 195 for f in c: |
196 to = r.file(f).read(mmap1[f]) | |
197 tn = r.file(f).read(mmap2[f]) | |
172
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
198 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) |
138 | 199 for f in a: |
200 to = "" | |
201 tn = r.file(f).read(mmap2[f]) | |
172
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
202 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) |
138 | 203 for f in d: |
204 to = r.file(f).read(mmap1[f]) | |
205 tn = "" | |
172
e9b1147db448
hgweb: alternating colors for multifile diffs
mpm@selenic.com
parents:
168
diff
changeset
|
206 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) |
131 | 207 |
142 | 208 def header(self): |
209 yield self.t("header", repo = self.reponame) | |
210 | |
211 def footer(self): | |
212 yield self.t("footer", repo = self.reponame) | |
213 | |
180 | 214 def changelog(self, pos): |
138 | 215 def changenav(): |
216 def seq(factor = 1): | |
217 yield 1 * factor | |
173
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
218 yield 3 * factor |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
219 #yield 5 * factor |
138 | 220 for f in seq(factor * 10): |
221 yield f | |
131 | 222 |
173
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
223 l = [] |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
224 for f in seq(): |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
225 if f < self.maxchanges / 2: continue |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
226 if f > count: break |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
227 r = "%d" % f |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
228 if pos + f < count - (f/2): l.append(("+" + r, pos + f)) |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
229 if pos - f >= 0 + (f/2): l.insert(0, ("-" + r, pos - f)) |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
230 |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
231 yield self.t("naventry", rev = 0, label="(0)") |
138 | 232 |
173
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
233 for label, rev in l: |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
234 yield self.t("naventry", label = label, rev = rev) |
8da1df932c16
hgweb: make navigation of changesets a bit nicer
mpm@selenic.com
parents:
172
diff
changeset
|
235 |
198
c88ef31fb5c0
hgweb: fix tip so that it doesn't need refreshing
mpm@selenic.com
parents:
197
diff
changeset
|
236 yield self.t("naventry", label="tip") |
131 | 237 |
138 | 238 def changelist(): |
142 | 239 parity = (start - end) & 1 |
138 | 240 cl = self.repo.changelog |
241 l = [] # build a list in forward order for efficiency | |
242 for i in range(start, end + 1): | |
243 n = cl.node(i) | |
244 changes = cl.read(n) | |
245 hn = hex(n) | |
246 p1, p2 = cl.parents(n) | |
247 t = float(changes[2].split(' ')[0]) | |
131 | 248 |
138 | 249 l.insert(0, self.t( |
250 'changelogentry', | |
142 | 251 parity = parity, |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
252 author = changes[1], |
142 | 253 parent1 = self.parent("changelogparent", |
254 hex(p1), cl.rev(p1)), | |
255 parent2 = self.parent("changelogparent", | |
256 hex(p2), cl.rev(p2)), | |
138 | 257 p1 = hex(p1), p2 = hex(p2), |
258 p1rev = cl.rev(p1), p2rev = cl.rev(p2), | |
259 manifest = hex(changes[0]), | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
260 desc = changes[4], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
261 date = t, |
138 | 262 files = self.listfilediffs(changes[3], n), |
263 rev = i, | |
264 node = hn)) | |
142 | 265 parity = 1 - parity |
138 | 266 |
267 yield l | |
131 | 268 |
168 | 269 cl = self.repo.changelog |
270 mf = cl.read(cl.tip())[0] | |
271 count = cl.count() | |
138 | 272 end = min(pos, count - 1) |
273 start = max(0, pos - self.maxchanges) | |
274 end = min(count - 1, start + self.maxchanges) | |
275 | |
142 | 276 yield self.t('changelog', |
277 header = self.header(), | |
278 footer = self.footer(), | |
279 repo = self.reponame, | |
280 changenav = changenav, | |
168 | 281 manifest = hex(mf), |
142 | 282 rev = pos, changesets = count, entries = changelist) |
131 | 283 |
138 | 284 def changeset(self, nodeid): |
285 n = bin(nodeid) | |
286 cl = self.repo.changelog | |
287 changes = cl.read(n) | |
288 p1, p2 = cl.parents(n) | |
289 p1rev, p2rev = cl.rev(p1), cl.rev(p2) | |
290 t = float(changes[2].split(' ')[0]) | |
291 | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
292 files = [] |
138 | 293 mf = self.repo.manifest.read(changes[0]) |
131 | 294 for f in changes[3]: |
138 | 295 files.append(self.t("filenodelink", |
296 filenode = hex(mf[f]), file = f)) | |
297 | |
298 def diff(): | |
299 yield self.diff(p1, n, changes[3]) | |
131 | 300 |
138 | 301 yield self.t('changeset', |
142 | 302 header = self.header(), |
303 footer = self.footer(), | |
304 repo = self.reponame, | |
138 | 305 diff = diff, |
306 rev = cl.rev(n), | |
307 node = nodeid, | |
142 | 308 parent1 = self.parent("changesetparent", |
309 hex(p1), cl.rev(p1)), | |
310 parent2 = self.parent("changesetparent", | |
311 hex(p2), cl.rev(p2)), | |
138 | 312 p1 = hex(p1), p2 = hex(p2), |
313 p1rev = cl.rev(p1), p2rev = cl.rev(p2), | |
314 manifest = hex(changes[0]), | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
315 author = changes[1], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
316 desc = changes[4], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
317 date = t, |
138 | 318 files = files) |
131 | 319 |
138 | 320 def filelog(self, f, filenode): |
321 cl = self.repo.changelog | |
322 fl = self.repo.file(f) | |
323 count = fl.count() | |
324 | |
325 def entries(): | |
326 l = [] | |
142 | 327 parity = (count - 1) & 1 |
328 | |
138 | 329 for i in range(count): |
330 | |
331 n = fl.node(i) | |
332 lr = fl.linkrev(n) | |
333 cn = cl.node(lr) | |
334 cs = cl.read(cl.node(lr)) | |
335 p1, p2 = fl.parents(n) | |
336 t = float(cs[2].split(' ')[0]) | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
337 |
138 | 338 l.insert(0, self.t("filelogentry", |
142 | 339 parity = parity, |
138 | 340 filenode = hex(n), |
341 filerev = i, | |
342 file = f, | |
343 node = hex(cn), | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
344 author = cs[1], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
345 date = t, |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
346 desc = cs[4], |
138 | 347 p1 = hex(p1), p2 = hex(p2), |
348 p1rev = fl.rev(p1), p2rev = fl.rev(p2))) | |
142 | 349 parity = 1 - parity |
138 | 350 |
351 yield l | |
352 | |
353 yield self.t("filelog", | |
142 | 354 header = self.header(), |
355 footer = self.footer(), | |
356 repo = self.reponame, | |
138 | 357 file = f, |
358 filenode = filenode, | |
359 entries = entries) | |
131 | 360 |
138 | 361 def filerevision(self, f, node): |
362 fl = self.repo.file(f) | |
363 n = bin(node) | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
364 text = fl.read(n) |
138 | 365 changerev = fl.linkrev(n) |
366 cl = self.repo.changelog | |
367 cn = cl.node(changerev) | |
368 cs = cl.read(cn) | |
369 p1, p2 = fl.parents(n) | |
370 t = float(cs[2].split(' ')[0]) | |
371 mfn = cs[0] | |
142 | 372 |
373 def lines(): | |
374 for l, t in enumerate(text.splitlines(1)): | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
375 yield self.t("fileline", line = t, |
142 | 376 linenumber = "% 6d" % (l + 1), |
377 parity = l & 1) | |
138 | 378 |
379 yield self.t("filerevision", file = f, | |
142 | 380 header = self.header(), |
381 footer = self.footer(), | |
382 repo = self.reponame, | |
138 | 383 filenode = node, |
384 path = up(f), | |
142 | 385 text = lines(), |
138 | 386 rev = changerev, |
387 node = hex(cn), | |
388 manifest = hex(mfn), | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
389 author = cs[1], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
390 date = t, |
142 | 391 parent1 = self.parent("filerevparent", |
156 | 392 hex(p1), fl.rev(p1), file=f), |
142 | 393 parent2 = self.parent("filerevparent", |
156 | 394 hex(p2), fl.rev(p2), file=f), |
138 | 395 p1 = hex(p1), p2 = hex(p2), |
396 p1rev = fl.rev(p1), p2rev = fl.rev(p2)) | |
397 | |
398 def fileannotate(self, f, node): | |
399 bcache = {} | |
400 ncache = {} | |
401 fl = self.repo.file(f) | |
402 n = bin(node) | |
403 changerev = fl.linkrev(n) | |
404 | |
405 cl = self.repo.changelog | |
406 cn = cl.node(changerev) | |
407 cs = cl.read(cn) | |
408 p1, p2 = fl.parents(n) | |
409 t = float(cs[2].split(' ')[0]) | |
410 mfn = cs[0] | |
131 | 411 |
138 | 412 def annotate(): |
142 | 413 parity = 1 |
414 last = None | |
138 | 415 for r, l in fl.annotate(n): |
416 try: | |
417 cnode = ncache[r] | |
418 except KeyError: | |
419 cnode = ncache[r] = self.repo.changelog.node(r) | |
420 | |
421 try: | |
422 name = bcache[r] | |
423 except KeyError: | |
424 cl = self.repo.changelog.read(cnode) | |
425 name = cl[1] | |
426 f = name.find('@') | |
427 if f >= 0: | |
428 name = name[:f] | |
429 bcache[r] = name | |
131 | 430 |
142 | 431 if last != cnode: |
432 parity = 1 - parity | |
433 last = cnode | |
434 | |
138 | 435 yield self.t("annotateline", |
142 | 436 parity = parity, |
138 | 437 node = hex(cnode), |
438 rev = r, | |
439 author = name, | |
440 file = f, | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
441 line = l) |
138 | 442 |
443 yield self.t("fileannotate", | |
142 | 444 header = self.header(), |
445 footer = self.footer(), | |
446 repo = self.reponame, | |
138 | 447 file = f, |
448 filenode = node, | |
449 annotate = annotate, | |
450 path = up(f), | |
451 rev = changerev, | |
452 node = hex(cn), | |
453 manifest = hex(mfn), | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
454 author = cs[1], |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
455 date = t, |
156 | 456 parent1 = self.parent("fileannotateparent", |
457 hex(p1), fl.rev(p1), file=f), | |
458 parent2 = self.parent("fileannotateparent", | |
459 hex(p2), fl.rev(p2), file=f), | |
138 | 460 p1 = hex(p1), p2 = hex(p2), |
461 p1rev = fl.rev(p1), p2rev = fl.rev(p2)) | |
136 | 462 |
138 | 463 def manifest(self, mnode, path): |
464 mf = self.repo.manifest.read(bin(mnode)) | |
465 rev = self.repo.manifest.rev(bin(mnode)) | |
466 node = self.repo.changelog.node(rev) | |
467 | |
468 files = {} | |
142 | 469 |
138 | 470 p = path[1:] |
471 l = len(p) | |
131 | 472 |
138 | 473 for f,n in mf.items(): |
474 if f[:l] != p: | |
475 continue | |
476 remain = f[l:] | |
477 if "/" in remain: | |
478 short = remain[:remain.find("/") + 1] # bleah | |
142 | 479 files[short] = (f, None) |
138 | 480 else: |
481 short = os.path.basename(remain) | |
482 files[short] = (f, n) | |
131 | 483 |
138 | 484 def filelist(): |
142 | 485 parity = 0 |
138 | 486 fl = files.keys() |
487 fl.sort() | |
488 for f in fl: | |
489 full, fnode = files[f] | |
142 | 490 if fnode: |
491 yield self.t("manifestfileentry", | |
492 file = full, | |
493 manifest = mnode, | |
494 filenode = hex(fnode), | |
495 parity = parity, | |
496 basename = f) | |
497 else: | |
498 yield self.t("manifestdirentry", | |
499 parity = parity, | |
500 path = os.path.join(path, f), | |
501 manifest = mnode, basename = f[:-1]) | |
502 parity = 1 - parity | |
138 | 503 |
504 yield self.t("manifest", | |
142 | 505 header = self.header(), |
506 footer = self.footer(), | |
507 repo = self.reponame, | |
138 | 508 manifest = mnode, |
509 rev = rev, | |
510 node = hex(node), | |
511 path = path, | |
512 up = up(path), | |
142 | 513 entries = filelist) |
131 | 514 |
168 | 515 def tags(self): |
516 cl = self.repo.changelog | |
517 mf = cl.read(cl.tip())[0] | |
518 | |
519 self.repo.lookup(0) # prime the cache | |
520 i = self.repo.tags.items() | |
183 | 521 n = [ (cl.rev(e[1]), e) for e in i ] # sort by revision |
522 n.sort() | |
523 n.reverse() | |
524 i = [ e[1] for e in n ] | |
168 | 525 |
526 def entries(): | |
527 parity = 0 | |
528 for k,n in i: | |
529 yield self.t("tagentry", | |
530 parity = parity, | |
531 tag = k, | |
532 node = hex(n)) | |
533 parity = 1 - parity | |
534 | |
535 yield self.t("tags", | |
536 header = self.header(), | |
537 footer = self.footer(), | |
538 repo = self.reponame, | |
539 manifest = hex(mf), | |
540 entries = entries) | |
541 | |
138 | 542 def filediff(self, file, changeset): |
543 n = bin(changeset) | |
544 cl = self.repo.changelog | |
545 p1 = cl.parents(n)[0] | |
546 cs = cl.read(n) | |
547 mf = self.repo.manifest.read(cs[0]) | |
548 | |
549 def diff(): | |
550 yield self.diff(p1, n, file) | |
131 | 551 |
138 | 552 yield self.t("filediff", |
142 | 553 header = self.header(), |
554 footer = self.footer(), | |
555 repo = self.reponame, | |
138 | 556 file = file, |
557 filenode = hex(mf[file]), | |
558 node = changeset, | |
559 rev = self.repo.changelog.rev(n), | |
560 p1 = hex(p1), | |
561 p1rev = self.repo.changelog.rev(p1), | |
562 diff = diff) | |
563 | |
564 # add tags to things | |
565 # tags -> list of changesets corresponding to tags | |
566 # find tag, changeset, file | |
131 | 567 |
132 | 568 def run(self): |
569 args = cgi.parse() | |
570 | |
201
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
571 m = os.path.join(self.templates, "map") |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
572 if args.has_key('style'): |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
573 b = os.path.basename("map-" + args['style'][0]) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
574 p = os.path.join(self.templates, b) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
575 if os.path.isfile(p): m = p |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
576 |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
577 self.t = templater(m, self.filters) |
f918a6fa2572
hgweb: add template filters, template style maps, and raw pages
mpm@selenic.com
parents:
198
diff
changeset
|
578 |
138 | 579 if not args.has_key('cmd') or args['cmd'][0] == 'changelog': |
580 hi = self.repo.changelog.count() | |
153
e8a360cd5a9f
changed pos to rev for changelog cmd, changed & to ;
jake@edge2.net
parents:
142
diff
changeset
|
581 if args.has_key('rev'): |
165 | 582 hi = args['rev'][0] |
166
39624c47060f
hgweb: don't blow up on search for unknown keys
mpm@selenic.com
parents:
165
diff
changeset
|
583 try: |
39624c47060f
hgweb: don't blow up on search for unknown keys
mpm@selenic.com
parents:
165
diff
changeset
|
584 hi = self.repo.changelog.rev(self.repo.lookup(hi)) |
39624c47060f
hgweb: don't blow up on search for unknown keys
mpm@selenic.com
parents:
165
diff
changeset
|
585 except KeyError: |
39624c47060f
hgweb: don't blow up on search for unknown keys
mpm@selenic.com
parents:
165
diff
changeset
|
586 hi = self.repo.changelog.count() |
131 | 587 |
138 | 588 write(self.changelog(hi)) |
132 | 589 |
138 | 590 elif args['cmd'][0] == 'changeset': |
591 write(self.changeset(args['node'][0])) | |
592 | |
593 elif args['cmd'][0] == 'manifest': | |
594 write(self.manifest(args['manifest'][0], args['path'][0])) | |
595 | |
168 | 596 elif args['cmd'][0] == 'tags': |
597 write(self.tags()) | |
598 | |
138 | 599 elif args['cmd'][0] == 'filediff': |
600 write(self.filediff(args['file'][0], args['node'][0])) | |
131 | 601 |
132 | 602 elif args['cmd'][0] == 'file': |
138 | 603 write(self.filerevision(args['file'][0], args['filenode'][0])) |
131 | 604 |
138 | 605 elif args['cmd'][0] == 'annotate': |
606 write(self.fileannotate(args['file'][0], args['filenode'][0])) | |
131 | 607 |
138 | 608 elif args['cmd'][0] == 'filelog': |
609 write(self.filelog(args['file'][0], args['filenode'][0])) | |
136 | 610 |
222 | 611 elif args['cmd'][0] == 'heads': |
612 httphdr("text/plain") | |
613 h = self.repo.heads() | |
614 sys.stdout.write(" ".join(map(hex, h)) + "\n") | |
615 | |
132 | 616 elif args['cmd'][0] == 'branches': |
617 httphdr("text/plain") | |
618 nodes = [] | |
619 if args.has_key('nodes'): | |
138 | 620 nodes = map(bin, args['nodes'][0].split(" ")) |
621 for b in self.repo.branches(nodes): | |
622 sys.stdout.write(" ".join(map(hex, b)) + "\n") | |
131 | 623 |
132 | 624 elif args['cmd'][0] == 'between': |
625 httphdr("text/plain") | |
626 nodes = [] | |
627 if args.has_key('pairs'): | |
138 | 628 pairs = [ map(bin, p.split("-")) |
132 | 629 for p in args['pairs'][0].split(" ") ] |
138 | 630 for b in self.repo.between(pairs): |
631 sys.stdout.write(" ".join(map(hex, b)) + "\n") | |
132 | 632 |
633 elif args['cmd'][0] == 'changegroup': | |
634 httphdr("application/hg-changegroup") | |
635 nodes = [] | |
197 | 636 if self.viewonly: |
637 return | |
638 | |
132 | 639 if args.has_key('roots'): |
138 | 640 nodes = map(bin, args['roots'][0].split(" ")) |
131 | 641 |
132 | 642 z = zlib.compressobj() |
138 | 643 for chunk in self.repo.changegroup(nodes): |
132 | 644 sys.stdout.write(z.compress(chunk)) |
645 | |
646 sys.stdout.write(z.flush()) | |
131 | 647 |
132 | 648 else: |
138 | 649 write(self.t("error")) |
131 | 650 |
158
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
651 def server(path, name, templates, address, port): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
652 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
653 import BaseHTTPServer |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
654 import sys, os |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
655 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
656 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
657 def do_POST(self): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
658 self.do_hgweb() |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
659 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
660 def do_GET(self): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
661 self.do_hgweb() |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
662 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
663 def do_hgweb(self): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
664 query = "" |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
665 p = self.path.find("?") |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
666 if p: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
667 query = self.path[p + 1:] |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
668 query = query.replace('+', ' ') |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
669 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
670 env = {} |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
671 env['GATEWAY_INTERFACE'] = 'CGI/1.1' |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
672 env['REQUEST_METHOD'] = self.command |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
673 if query: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
674 env['QUERY_STRING'] = query |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
675 host = self.address_string() |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
676 if host != self.client_address[0]: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
677 env['REMOTE_HOST'] = host |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
678 env['REMOTE_ADDR'] = self.client_address[0] |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
679 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
680 if self.headers.typeheader is None: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
681 env['CONTENT_TYPE'] = self.headers.type |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
682 else: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
683 env['CONTENT_TYPE'] = self.headers.typeheader |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
684 length = self.headers.getheader('content-length') |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
685 if length: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
686 env['CONTENT_LENGTH'] = length |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
687 accept = [] |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
688 for line in self.headers.getallmatchingheaders('accept'): |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
689 if line[:1] in "\t\n\r ": |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
690 accept.append(line.strip()) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
691 else: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
692 accept = accept + line[7:].split(',') |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
693 env['HTTP_ACCEPT'] = ','.join(accept) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
694 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
695 os.environ.update(env) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
696 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
697 save = sys.argv, sys.stdin, sys.stdout, sys.stderr |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
698 try: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
699 sys.stdin = self.rfile |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
700 sys.stdout = self.wfile |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
701 sys.argv = ["hgweb.py"] |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
702 if '=' not in query: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
703 sys.argv.append(query) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
704 self.send_response(200, "Script output follows") |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
705 hg.run() |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
706 finally: |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
707 sys.argv, sys.stdin, sys.stdout, sys.stderr = save |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
708 |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
709 hg = hgweb(path, name, templates) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
710 httpd = BaseHTTPServer.HTTPServer((address, port), hgwebhandler) |
be7103467d2e
Add 'hg serve' command for stand-alone server
mpm@selenic.com
parents:
157
diff
changeset
|
711 httpd.serve_forever() |