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