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