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