##// END OF EJS Templates
hgweb: add shortcuts for all the web commands / fix empty arg bug
Matt Mackall -
r1409:964baa35 default
parent child Browse files
Show More
@@ -1,1001 +1,1011 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, keep_blank_values=1)
70 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
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": "tip"}
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):
708 def expand_form(form):
709 shortcuts = {
709 shortcuts = {
710 'cl': [('cmd', ['changelog']), ('rev', None)],
710 'cs': [('cmd', ['changeset']), ('node', None)],
711 'cs': [('cmd', ['changeset']), ('node', None)],
712 'f': [('cmd', ['file']), ('filenode', None)],
713 'fl': [('cmd', ['filelog']), ('filenode', None)],
714 'fd': [('cmd', ['filediff']), ('node', None)],
715 'fa': [('cmd', ['annotate']), ('filenode', None)],
716 'mf': [('cmd', ['manifest']), ('manifest', None)],
717 'ca': [('cmd', ['archive']), ('node', None)],
718 'tags': [('cmd', ['tags'])],
719 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
711 }
720 }
721
712 for k in shortcuts.iterkeys():
722 for k in shortcuts.iterkeys():
713 if form.has_key(k):
723 if form.has_key(k):
714 for name, value in shortcuts[k]:
724 for name, value in shortcuts[k]:
715 if value is None:
725 if value is None:
716 value = form[k]
726 value = form[k]
717 form[name] = value
727 form[name] = value
718 del form[k]
728 del form[k]
719
729
720 self.refresh()
730 self.refresh()
721
731
722 expand_form(req.form)
732 expand_form(req.form)
723
733
724 t = self.repo.ui.config("web", "templates", templatepath())
734 t = self.repo.ui.config("web", "templates", templatepath())
725 m = os.path.join(t, "map")
735 m = os.path.join(t, "map")
726 style = self.repo.ui.config("web", "style", "")
736 style = self.repo.ui.config("web", "style", "")
727 if req.form.has_key('style'):
737 if req.form.has_key('style'):
728 style = req.form['style'][0]
738 style = req.form['style'][0]
729 if style:
739 if style:
730 b = os.path.basename("map-" + style)
740 b = os.path.basename("map-" + style)
731 p = os.path.join(t, b)
741 p = os.path.join(t, b)
732 if os.path.isfile(p):
742 if os.path.isfile(p):
733 m = p
743 m = p
734
744
735 port = req.env["SERVER_PORT"]
745 port = req.env["SERVER_PORT"]
736 port = port != "80" and (":" + port) or ""
746 port = port != "80" and (":" + port) or ""
737 uri = req.env["REQUEST_URI"]
747 uri = req.env["REQUEST_URI"]
738 if "?" in uri:
748 if "?" in uri:
739 uri = uri.split("?")[0]
749 uri = uri.split("?")[0]
740 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
750 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
741 if not self.reponame:
751 if not self.reponame:
742 self.reponame = (self.repo.ui.config("web", "name")
752 self.reponame = (self.repo.ui.config("web", "name")
743 or uri.strip('/') or self.repo.root)
753 or uri.strip('/') or self.repo.root)
744
754
745 self.t = templater(m, common_filters,
755 self.t = templater(m, common_filters,
746 {"url": url,
756 {"url": url,
747 "repo": self.reponame,
757 "repo": self.reponame,
748 "header": header,
758 "header": header,
749 "footer": footer,
759 "footer": footer,
750 })
760 })
751
761
752 if not req.form.has_key('cmd'):
762 if not req.form.has_key('cmd'):
753 req.form['cmd'] = [self.t.cache['default'],]
763 req.form['cmd'] = [self.t.cache['default'],]
754
764
755 if req.form['cmd'][0] == 'changelog':
765 if req.form['cmd'][0] == 'changelog':
756 c = self.repo.changelog.count() - 1
766 c = self.repo.changelog.count() - 1
757 hi = c
767 hi = c
758 if req.form.has_key('rev'):
768 if req.form.has_key('rev'):
759 hi = req.form['rev'][0]
769 hi = req.form['rev'][0]
760 try:
770 try:
761 hi = self.repo.changelog.rev(self.repo.lookup(hi))
771 hi = self.repo.changelog.rev(self.repo.lookup(hi))
762 except hg.RepoError:
772 except hg.RepoError:
763 req.write(self.search(hi))
773 req.write(self.search(hi))
764 return
774 return
765
775
766 req.write(self.changelog(hi))
776 req.write(self.changelog(hi))
767
777
768 elif req.form['cmd'][0] == 'changeset':
778 elif req.form['cmd'][0] == 'changeset':
769 req.write(self.changeset(req.form['node'][0]))
779 req.write(self.changeset(req.form['node'][0]))
770
780
771 elif req.form['cmd'][0] == 'manifest':
781 elif req.form['cmd'][0] == 'manifest':
772 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
782 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
773
783
774 elif req.form['cmd'][0] == 'tags':
784 elif req.form['cmd'][0] == 'tags':
775 req.write(self.tags())
785 req.write(self.tags())
776
786
777 elif req.form['cmd'][0] == 'filediff':
787 elif req.form['cmd'][0] == 'filediff':
778 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
788 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
779
789
780 elif req.form['cmd'][0] == 'file':
790 elif req.form['cmd'][0] == 'file':
781 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
791 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
782
792
783 elif req.form['cmd'][0] == 'annotate':
793 elif req.form['cmd'][0] == 'annotate':
784 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
794 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
785
795
786 elif req.form['cmd'][0] == 'filelog':
796 elif req.form['cmd'][0] == 'filelog':
787 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
797 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
788
798
789 elif req.form['cmd'][0] == 'heads':
799 elif req.form['cmd'][0] == 'heads':
790 req.httphdr("application/mercurial-0.1")
800 req.httphdr("application/mercurial-0.1")
791 h = self.repo.heads()
801 h = self.repo.heads()
792 req.write(" ".join(map(hex, h)) + "\n")
802 req.write(" ".join(map(hex, h)) + "\n")
793
803
794 elif req.form['cmd'][0] == 'branches':
804 elif req.form['cmd'][0] == 'branches':
795 req.httphdr("application/mercurial-0.1")
805 req.httphdr("application/mercurial-0.1")
796 nodes = []
806 nodes = []
797 if req.form.has_key('nodes'):
807 if req.form.has_key('nodes'):
798 nodes = map(bin, req.form['nodes'][0].split(" "))
808 nodes = map(bin, req.form['nodes'][0].split(" "))
799 for b in self.repo.branches(nodes):
809 for b in self.repo.branches(nodes):
800 req.write(" ".join(map(hex, b)) + "\n")
810 req.write(" ".join(map(hex, b)) + "\n")
801
811
802 elif req.form['cmd'][0] == 'between':
812 elif req.form['cmd'][0] == 'between':
803 req.httphdr("application/mercurial-0.1")
813 req.httphdr("application/mercurial-0.1")
804 nodes = []
814 nodes = []
805 if req.form.has_key('pairs'):
815 if req.form.has_key('pairs'):
806 pairs = [map(bin, p.split("-"))
816 pairs = [map(bin, p.split("-"))
807 for p in req.form['pairs'][0].split(" ")]
817 for p in req.form['pairs'][0].split(" ")]
808 for b in self.repo.between(pairs):
818 for b in self.repo.between(pairs):
809 req.write(" ".join(map(hex, b)) + "\n")
819 req.write(" ".join(map(hex, b)) + "\n")
810
820
811 elif req.form['cmd'][0] == 'changegroup':
821 elif req.form['cmd'][0] == 'changegroup':
812 req.httphdr("application/mercurial-0.1")
822 req.httphdr("application/mercurial-0.1")
813 nodes = []
823 nodes = []
814 if not self.allowpull:
824 if not self.allowpull:
815 return
825 return
816
826
817 if req.form.has_key('roots'):
827 if req.form.has_key('roots'):
818 nodes = map(bin, req.form['roots'][0].split(" "))
828 nodes = map(bin, req.form['roots'][0].split(" "))
819
829
820 z = zlib.compressobj()
830 z = zlib.compressobj()
821 f = self.repo.changegroup(nodes)
831 f = self.repo.changegroup(nodes)
822 while 1:
832 while 1:
823 chunk = f.read(4096)
833 chunk = f.read(4096)
824 if not chunk:
834 if not chunk:
825 break
835 break
826 req.write(z.compress(chunk))
836 req.write(z.compress(chunk))
827
837
828 req.write(z.flush())
838 req.write(z.flush())
829
839
830 elif req.form['cmd'][0] == 'archive':
840 elif req.form['cmd'][0] == 'archive':
831 changeset = self.repo.lookup(req.form['node'][0])
841 changeset = self.repo.lookup(req.form['node'][0])
832 type = req.form['type'][0]
842 type = req.form['type'][0]
833 if (type in self.archives and
843 if (type in self.archives and
834 self.repo.ui.configbool("web", "allow" + type, False)):
844 self.repo.ui.configbool("web", "allow" + type, False)):
835 self.archive(req, changeset, type)
845 self.archive(req, changeset, type)
836 return
846 return
837
847
838 req.write(self.t("error"))
848 req.write(self.t("error"))
839
849
840 else:
850 else:
841 req.write(self.t("error"))
851 req.write(self.t("error"))
842
852
843 def create_server(repo):
853 def create_server(repo):
844
854
845 def openlog(opt, default):
855 def openlog(opt, default):
846 if opt and opt != '-':
856 if opt and opt != '-':
847 return open(opt, 'w')
857 return open(opt, 'w')
848 return default
858 return default
849
859
850 address = repo.ui.config("web", "address", "")
860 address = repo.ui.config("web", "address", "")
851 port = int(repo.ui.config("web", "port", 8000))
861 port = int(repo.ui.config("web", "port", 8000))
852 use_ipv6 = repo.ui.configbool("web", "ipv6")
862 use_ipv6 = repo.ui.configbool("web", "ipv6")
853 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
863 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
854 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
864 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
855
865
856 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
866 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
857 address_family = getattr(socket, 'AF_INET6', None)
867 address_family = getattr(socket, 'AF_INET6', None)
858
868
859 def __init__(self, *args, **kwargs):
869 def __init__(self, *args, **kwargs):
860 if self.address_family is None:
870 if self.address_family is None:
861 raise hg.RepoError(_('IPv6 not available on this system'))
871 raise hg.RepoError(_('IPv6 not available on this system'))
862 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
872 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
863
873
864 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
874 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
865 def log_error(self, format, *args):
875 def log_error(self, format, *args):
866 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
876 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
867 self.log_date_time_string(),
877 self.log_date_time_string(),
868 format % args))
878 format % args))
869
879
870 def log_message(self, format, *args):
880 def log_message(self, format, *args):
871 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
881 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
872 self.log_date_time_string(),
882 self.log_date_time_string(),
873 format % args))
883 format % args))
874
884
875 def do_POST(self):
885 def do_POST(self):
876 try:
886 try:
877 self.do_hgweb()
887 self.do_hgweb()
878 except socket.error, inst:
888 except socket.error, inst:
879 if inst[0] != errno.EPIPE:
889 if inst[0] != errno.EPIPE:
880 raise
890 raise
881
891
882 def do_GET(self):
892 def do_GET(self):
883 self.do_POST()
893 self.do_POST()
884
894
885 def do_hgweb(self):
895 def do_hgweb(self):
886 query = ""
896 query = ""
887 p = self.path.find("?")
897 p = self.path.find("?")
888 if p:
898 if p:
889 query = self.path[p + 1:]
899 query = self.path[p + 1:]
890 query = query.replace('+', ' ')
900 query = query.replace('+', ' ')
891
901
892 env = {}
902 env = {}
893 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
903 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
894 env['REQUEST_METHOD'] = self.command
904 env['REQUEST_METHOD'] = self.command
895 env['SERVER_NAME'] = self.server.server_name
905 env['SERVER_NAME'] = self.server.server_name
896 env['SERVER_PORT'] = str(self.server.server_port)
906 env['SERVER_PORT'] = str(self.server.server_port)
897 env['REQUEST_URI'] = "/"
907 env['REQUEST_URI'] = "/"
898 if query:
908 if query:
899 env['QUERY_STRING'] = query
909 env['QUERY_STRING'] = query
900 host = self.address_string()
910 host = self.address_string()
901 if host != self.client_address[0]:
911 if host != self.client_address[0]:
902 env['REMOTE_HOST'] = host
912 env['REMOTE_HOST'] = host
903 env['REMOTE_ADDR'] = self.client_address[0]
913 env['REMOTE_ADDR'] = self.client_address[0]
904
914
905 if self.headers.typeheader is None:
915 if self.headers.typeheader is None:
906 env['CONTENT_TYPE'] = self.headers.type
916 env['CONTENT_TYPE'] = self.headers.type
907 else:
917 else:
908 env['CONTENT_TYPE'] = self.headers.typeheader
918 env['CONTENT_TYPE'] = self.headers.typeheader
909 length = self.headers.getheader('content-length')
919 length = self.headers.getheader('content-length')
910 if length:
920 if length:
911 env['CONTENT_LENGTH'] = length
921 env['CONTENT_LENGTH'] = length
912 accept = []
922 accept = []
913 for line in self.headers.getallmatchingheaders('accept'):
923 for line in self.headers.getallmatchingheaders('accept'):
914 if line[:1] in "\t\n\r ":
924 if line[:1] in "\t\n\r ":
915 accept.append(line.strip())
925 accept.append(line.strip())
916 else:
926 else:
917 accept = accept + line[7:].split(',')
927 accept = accept + line[7:].split(',')
918 env['HTTP_ACCEPT'] = ','.join(accept)
928 env['HTTP_ACCEPT'] = ','.join(accept)
919
929
920 req = hgrequest(self.rfile, self.wfile, env)
930 req = hgrequest(self.rfile, self.wfile, env)
921 self.send_response(200, "Script output follows")
931 self.send_response(200, "Script output follows")
922 hg.run(req)
932 hg.run(req)
923
933
924 hg = hgweb(repo)
934 hg = hgweb(repo)
925 if use_ipv6:
935 if use_ipv6:
926 return IPv6HTTPServer((address, port), hgwebhandler)
936 return IPv6HTTPServer((address, port), hgwebhandler)
927 else:
937 else:
928 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
938 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
929
939
930 def server(path, name, templates, address, port, use_ipv6=False,
940 def server(path, name, templates, address, port, use_ipv6=False,
931 accesslog=sys.stdout, errorlog=sys.stderr):
941 accesslog=sys.stdout, errorlog=sys.stderr):
932 httpd = create_server(path, name, templates, address, port, use_ipv6,
942 httpd = create_server(path, name, templates, address, port, use_ipv6,
933 accesslog, errorlog)
943 accesslog, errorlog)
934 httpd.serve_forever()
944 httpd.serve_forever()
935
945
936 # This is a stopgap
946 # This is a stopgap
937 class hgwebdir:
947 class hgwebdir:
938 def __init__(self, config):
948 def __init__(self, config):
939 def cleannames(items):
949 def cleannames(items):
940 return [(name.strip('/'), path) for name, path in items]
950 return [(name.strip('/'), path) for name, path in items]
941
951
942 if type(config) == type([]):
952 if type(config) == type([]):
943 self.repos = cleannames(config)
953 self.repos = cleannames(config)
944 elif type(config) == type({}):
954 elif type(config) == type({}):
945 self.repos = cleannames(config.items())
955 self.repos = cleannames(config.items())
946 self.repos.sort()
956 self.repos.sort()
947 else:
957 else:
948 cp = ConfigParser.SafeConfigParser()
958 cp = ConfigParser.SafeConfigParser()
949 cp.read(config)
959 cp.read(config)
950 self.repos = cleannames(cp.items("paths"))
960 self.repos = cleannames(cp.items("paths"))
951 self.repos.sort()
961 self.repos.sort()
952
962
953 def run(self, req=hgrequest()):
963 def run(self, req=hgrequest()):
954 def header(**map):
964 def header(**map):
955 yield tmpl("header", **map)
965 yield tmpl("header", **map)
956
966
957 def footer(**map):
967 def footer(**map):
958 yield tmpl("footer", **map)
968 yield tmpl("footer", **map)
959
969
960 m = os.path.join(templatepath(), "map")
970 m = os.path.join(templatepath(), "map")
961 tmpl = templater(m, common_filters,
971 tmpl = templater(m, common_filters,
962 {"header": header, "footer": footer})
972 {"header": header, "footer": footer})
963
973
964 def entries(**map):
974 def entries(**map):
965 parity = 0
975 parity = 0
966 for name, path in self.repos:
976 for name, path in self.repos:
967 u = ui.ui()
977 u = ui.ui()
968 try:
978 try:
969 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
979 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
970 except IOError:
980 except IOError:
971 pass
981 pass
972 get = u.config
982 get = u.config
973
983
974 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
984 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
975 .replace("//", "/"))
985 .replace("//", "/"))
976
986
977 # update time with local timezone
987 # update time with local timezone
978 d = (os.stat(os.path.join(path,
988 d = (os.stat(os.path.join(path,
979 ".hg", "00changelog.d")).st_mtime,
989 ".hg", "00changelog.d")).st_mtime,
980 util.makedate()[1])
990 util.makedate()[1])
981
991
982 yield dict(contact=(get("ui", "username") or # preferred
992 yield dict(contact=(get("ui", "username") or # preferred
983 get("web", "contact") or # deprecated
993 get("web", "contact") or # deprecated
984 get("web", "author", "unknown")), # also
994 get("web", "author", "unknown")), # also
985 name=get("web", "name", name),
995 name=get("web", "name", name),
986 url=url,
996 url=url,
987 parity=parity,
997 parity=parity,
988 shortdesc=get("web", "description", "unknown"),
998 shortdesc=get("web", "description", "unknown"),
989 lastupdate=d)
999 lastupdate=d)
990
1000
991 parity = 1 - parity
1001 parity = 1 - parity
992
1002
993 virtual = req.env.get("PATH_INFO", "").strip('/')
1003 virtual = req.env.get("PATH_INFO", "").strip('/')
994 if virtual:
1004 if virtual:
995 real = dict(self.repos).get(virtual)
1005 real = dict(self.repos).get(virtual)
996 if real:
1006 if real:
997 hgweb(real).run(req)
1007 hgweb(real).run(req)
998 else:
1008 else:
999 req.write(tmpl("notfound", repo=virtual))
1009 req.write(tmpl("notfound", repo=virtual))
1000 else:
1010 else:
1001 req.write(tmpl("index", entries=entries))
1011 req.write(tmpl("index", entries=entries))
General Comments 0
You need to be logged in to leave comments. Login now