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