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