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