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