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