##// END OF EJS Templates
Add line anchors to annotate, changeset, diff, file views for hgweb
Edward Lee -
r6122:800e2756 default
parent child Browse files
Show More
@@ -1,78 +1,84 b''
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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, mimetypes
9 import os, mimetypes
10
10
11 def get_mtime(repo_path):
11 def get_mtime(repo_path):
12 store_path = os.path.join(repo_path, ".hg")
12 store_path = os.path.join(repo_path, ".hg")
13 if not os.path.isdir(os.path.join(store_path, "data")):
13 if not os.path.isdir(os.path.join(store_path, "data")):
14 store_path = os.path.join(store_path, "store")
14 store_path = os.path.join(store_path, "store")
15 cl_path = os.path.join(store_path, "00changelog.i")
15 cl_path = os.path.join(store_path, "00changelog.i")
16 if os.path.exists(cl_path):
16 if os.path.exists(cl_path):
17 return os.stat(cl_path).st_mtime
17 return os.stat(cl_path).st_mtime
18 else:
18 else:
19 return os.stat(store_path).st_mtime
19 return os.stat(store_path).st_mtime
20
20
21 def staticfile(directory, fname, req):
21 def staticfile(directory, fname, req):
22 """return a file inside directory with guessed content-type header
22 """return a file inside directory with guessed content-type header
23
23
24 fname always uses '/' as directory separator and isn't allowed to
24 fname always uses '/' as directory separator and isn't allowed to
25 contain unusual path components.
25 contain unusual path components.
26 Content-type is guessed using the mimetypes module.
26 Content-type is guessed using the mimetypes module.
27 Return an empty string if fname is illegal or file not found.
27 Return an empty string if fname is illegal or file not found.
28
28
29 """
29 """
30 parts = fname.split('/')
30 parts = fname.split('/')
31 path = directory
31 path = directory
32 for part in parts:
32 for part in parts:
33 if (part in ('', os.curdir, os.pardir) or
33 if (part in ('', os.curdir, os.pardir) or
34 os.sep in part or os.altsep is not None and os.altsep in part):
34 os.sep in part or os.altsep is not None and os.altsep in part):
35 return ""
35 return ""
36 path = os.path.join(path, part)
36 path = os.path.join(path, part)
37 try:
37 try:
38 os.stat(path)
38 os.stat(path)
39 ct = mimetypes.guess_type(path)[0] or "text/plain"
39 ct = mimetypes.guess_type(path)[0] or "text/plain"
40 req.header([('Content-type', ct),
40 req.header([('Content-type', ct),
41 ('Content-length', str(os.path.getsize(path)))])
41 ('Content-length', str(os.path.getsize(path)))])
42 return file(path, 'rb').read()
42 return file(path, 'rb').read()
43 except (TypeError, OSError):
43 except (TypeError, OSError):
44 # illegal fname or unreadable file
44 # illegal fname or unreadable file
45 return ""
45 return ""
46
46
47 def style_map(templatepath, style):
47 def style_map(templatepath, style):
48 """Return path to mapfile for a given style.
48 """Return path to mapfile for a given style.
49
49
50 Searches mapfile in the following locations:
50 Searches mapfile in the following locations:
51 1. templatepath/style/map
51 1. templatepath/style/map
52 2. templatepath/map-style
52 2. templatepath/map-style
53 3. templatepath/map
53 3. templatepath/map
54 """
54 """
55 locations = style and [os.path.join(style, "map"), "map-"+style] or []
55 locations = style and [os.path.join(style, "map"), "map-"+style] or []
56 locations.append("map")
56 locations.append("map")
57 for location in locations:
57 for location in locations:
58 mapfile = os.path.join(templatepath, location)
58 mapfile = os.path.join(templatepath, location)
59 if os.path.isfile(mapfile):
59 if os.path.isfile(mapfile):
60 return mapfile
60 return mapfile
61 raise RuntimeError("No hgweb templates found in %r" % templatepath)
61 raise RuntimeError("No hgweb templates found in %r" % templatepath)
62
62
63 def paritygen(stripecount, offset=0):
63 def paritygen(stripecount, offset=0):
64 """count parity of horizontal stripes for easier reading"""
64 """count parity of horizontal stripes for easier reading"""
65 if stripecount and offset:
65 if stripecount and offset:
66 # account for offset, e.g. due to building the list in reverse
66 # account for offset, e.g. due to building the list in reverse
67 count = (stripecount + offset) % stripecount
67 count = (stripecount + offset) % stripecount
68 parity = (stripecount + offset) / stripecount & 1
68 parity = (stripecount + offset) / stripecount & 1
69 else:
69 else:
70 count = 0
70 count = 0
71 parity = 0
71 parity = 0
72 while True:
72 while True:
73 yield parity
73 yield parity
74 count += 1
74 count += 1
75 if stripecount and count >= stripecount:
75 if stripecount and count >= stripecount:
76 parity = 1 - parity
76 parity = 1 - parity
77 count = 0
77 count = 0
78
78
79 def countgen(start=0, step=1):
80 """count forever -- useful for line numbers"""
81 while True:
82 yield start
83 start += step
84
@@ -1,1208 +1,1220 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a 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-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 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, mimetypes, re, zlib, mimetools, cStringIO, sys
9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
10 import tempfile, urllib, bz2
11 from mercurial.node import *
11 from mercurial.node import *
12 from mercurial.i18n import gettext as _
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 from mercurial import revlog, templater
14 from mercurial import revlog, templater
15 from common import get_mtime, staticfile, style_map, paritygen
15 from common import get_mtime, staticfile, style_map, paritygen, countgen
16
16
17 def _up(p):
17 def _up(p):
18 if p[0] != "/":
18 if p[0] != "/":
19 p = "/" + p
19 p = "/" + p
20 if p[-1] == "/":
20 if p[-1] == "/":
21 p = p[:-1]
21 p = p[:-1]
22 up = os.path.dirname(p)
22 up = os.path.dirname(p)
23 if up == "/":
23 if up == "/":
24 return "/"
24 return "/"
25 return up + "/"
25 return up + "/"
26
26
27 def revnavgen(pos, pagelen, limit, nodefunc):
27 def revnavgen(pos, pagelen, limit, nodefunc):
28 def seq(factor, limit=None):
28 def seq(factor, limit=None):
29 if limit:
29 if limit:
30 yield limit
30 yield limit
31 if limit >= 20 and limit <= 40:
31 if limit >= 20 and limit <= 40:
32 yield 50
32 yield 50
33 else:
33 else:
34 yield 1 * factor
34 yield 1 * factor
35 yield 3 * factor
35 yield 3 * factor
36 for f in seq(factor * 10):
36 for f in seq(factor * 10):
37 yield f
37 yield f
38
38
39 def nav(**map):
39 def nav(**map):
40 l = []
40 l = []
41 last = 0
41 last = 0
42 for f in seq(1, pagelen):
42 for f in seq(1, pagelen):
43 if f < pagelen or f <= last:
43 if f < pagelen or f <= last:
44 continue
44 continue
45 if f > limit:
45 if f > limit:
46 break
46 break
47 last = f
47 last = f
48 if pos + f < limit:
48 if pos + f < limit:
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 if pos - f >= 0:
50 if pos - f >= 0:
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52
52
53 try:
53 try:
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55
55
56 for label, node in l:
56 for label, node in l:
57 yield {"label": label, "node": node}
57 yield {"label": label, "node": node}
58
58
59 yield {"label": "tip", "node": "tip"}
59 yield {"label": "tip", "node": "tip"}
60 except hg.RepoError:
60 except hg.RepoError:
61 pass
61 pass
62
62
63 return nav
63 return nav
64
64
65 class hgweb(object):
65 class hgweb(object):
66 def __init__(self, repo, name=None):
66 def __init__(self, repo, name=None):
67 if isinstance(repo, str):
67 if isinstance(repo, str):
68 parentui = ui.ui(report_untrusted=False, interactive=False)
68 parentui = ui.ui(report_untrusted=False, interactive=False)
69 self.repo = hg.repository(parentui, repo)
69 self.repo = hg.repository(parentui, repo)
70 else:
70 else:
71 self.repo = repo
71 self.repo = repo
72
72
73 self.mtime = -1
73 self.mtime = -1
74 self.reponame = name
74 self.reponame = name
75 self.archives = 'zip', 'gz', 'bz2'
75 self.archives = 'zip', 'gz', 'bz2'
76 self.stripecount = 1
76 self.stripecount = 1
77 # a repo owner may set web.templates in .hg/hgrc to get any file
77 # a repo owner may set web.templates in .hg/hgrc to get any file
78 # readable by the user running the CGI script
78 # readable by the user running the CGI script
79 self.templatepath = self.config("web", "templates",
79 self.templatepath = self.config("web", "templates",
80 templater.templatepath(),
80 templater.templatepath(),
81 untrusted=False)
81 untrusted=False)
82
82
83 # The CGI scripts are often run by a user different from the repo owner.
83 # The CGI scripts are often run by a user different from the repo owner.
84 # Trust the settings from the .hg/hgrc files by default.
84 # Trust the settings from the .hg/hgrc files by default.
85 def config(self, section, name, default=None, untrusted=True):
85 def config(self, section, name, default=None, untrusted=True):
86 return self.repo.ui.config(section, name, default,
86 return self.repo.ui.config(section, name, default,
87 untrusted=untrusted)
87 untrusted=untrusted)
88
88
89 def configbool(self, section, name, default=False, untrusted=True):
89 def configbool(self, section, name, default=False, untrusted=True):
90 return self.repo.ui.configbool(section, name, default,
90 return self.repo.ui.configbool(section, name, default,
91 untrusted=untrusted)
91 untrusted=untrusted)
92
92
93 def configlist(self, section, name, default=None, untrusted=True):
93 def configlist(self, section, name, default=None, untrusted=True):
94 return self.repo.ui.configlist(section, name, default,
94 return self.repo.ui.configlist(section, name, default,
95 untrusted=untrusted)
95 untrusted=untrusted)
96
96
97 def refresh(self):
97 def refresh(self):
98 mtime = get_mtime(self.repo.root)
98 mtime = get_mtime(self.repo.root)
99 if mtime != self.mtime:
99 if mtime != self.mtime:
100 self.mtime = mtime
100 self.mtime = mtime
101 self.repo = hg.repository(self.repo.ui, self.repo.root)
101 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 self.maxchanges = int(self.config("web", "maxchanges", 10))
102 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 self.stripecount = int(self.config("web", "stripes", 1))
103 self.stripecount = int(self.config("web", "stripes", 1))
104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 self.maxfiles = int(self.config("web", "maxfiles", 10))
105 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 self.allowpull = self.configbool("web", "allowpull", True)
106 self.allowpull = self.configbool("web", "allowpull", True)
107 self.encoding = self.config("web", "encoding", util._encoding)
107 self.encoding = self.config("web", "encoding", util._encoding)
108
108
109 def archivelist(self, nodeid):
109 def archivelist(self, nodeid):
110 allowed = self.configlist("web", "allow_archive")
110 allowed = self.configlist("web", "allow_archive")
111 for i, spec in self.archive_specs.iteritems():
111 for i, spec in self.archive_specs.iteritems():
112 if i in allowed or self.configbool("web", "allow" + i):
112 if i in allowed or self.configbool("web", "allow" + i):
113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114
114
115 def listfilediffs(self, files, changeset):
115 def listfilediffs(self, files, changeset):
116 for f in files[:self.maxfiles]:
116 for f in files[:self.maxfiles]:
117 yield self.t("filedifflink", node=hex(changeset), file=f)
117 yield self.t("filedifflink", node=hex(changeset), file=f)
118 if len(files) > self.maxfiles:
118 if len(files) > self.maxfiles:
119 yield self.t("fileellipses")
119 yield self.t("fileellipses")
120
120
121 def siblings(self, siblings=[], hiderev=None, **args):
121 def siblings(self, siblings=[], hiderev=None, **args):
122 siblings = [s for s in siblings if s.node() != nullid]
122 siblings = [s for s in siblings if s.node() != nullid]
123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 return
124 return
125 for s in siblings:
125 for s in siblings:
126 d = {'node': hex(s.node()), 'rev': s.rev()}
126 d = {'node': hex(s.node()), 'rev': s.rev()}
127 if hasattr(s, 'path'):
127 if hasattr(s, 'path'):
128 d['file'] = s.path()
128 d['file'] = s.path()
129 d.update(args)
129 d.update(args)
130 yield d
130 yield d
131
131
132 def renamelink(self, fl, node):
132 def renamelink(self, fl, node):
133 r = fl.renamed(node)
133 r = fl.renamed(node)
134 if r:
134 if r:
135 return [dict(file=r[0], node=hex(r[1]))]
135 return [dict(file=r[0], node=hex(r[1]))]
136 return []
136 return []
137
137
138 def nodetagsdict(self, node):
138 def nodetagsdict(self, node):
139 return [{"name": i} for i in self.repo.nodetags(node)]
139 return [{"name": i} for i in self.repo.nodetags(node)]
140
140
141 def nodebranchdict(self, ctx):
141 def nodebranchdict(self, ctx):
142 branches = []
142 branches = []
143 branch = ctx.branch()
143 branch = ctx.branch()
144 # If this is an empty repo, ctx.node() == nullid,
144 # If this is an empty repo, ctx.node() == nullid,
145 # ctx.branch() == 'default', but branchtags() is
145 # ctx.branch() == 'default', but branchtags() is
146 # an empty dict. Using dict.get avoids a traceback.
146 # an empty dict. Using dict.get avoids a traceback.
147 if self.repo.branchtags().get(branch) == ctx.node():
147 if self.repo.branchtags().get(branch) == ctx.node():
148 branches.append({"name": branch})
148 branches.append({"name": branch})
149 return branches
149 return branches
150
150
151 def showtag(self, t1, node=nullid, **args):
151 def showtag(self, t1, node=nullid, **args):
152 for t in self.repo.nodetags(node):
152 for t in self.repo.nodetags(node):
153 yield self.t(t1, tag=t, **args)
153 yield self.t(t1, tag=t, **args)
154
154
155 def diff(self, node1, node2, files):
155 def diff(self, node1, node2, files):
156 def filterfiles(filters, files):
156 def filterfiles(filters, files):
157 l = [x for x in files if x in filters]
157 l = [x for x in files if x in filters]
158
158
159 for t in filters:
159 for t in filters:
160 if t and t[-1] != os.sep:
160 if t and t[-1] != os.sep:
161 t += os.sep
161 t += os.sep
162 l += [x for x in files if x.startswith(t)]
162 l += [x for x in files if x.startswith(t)]
163 return l
163 return l
164
164
165 parity = paritygen(self.stripecount)
165 parity = paritygen(self.stripecount)
166 def diffblock(diff, f, fn):
166 def diffblock(diff, f, fn):
167 yield self.t("diffblock",
167 yield self.t("diffblock",
168 lines=prettyprintlines(diff),
168 lines=prettyprintlines(diff),
169 parity=parity.next(),
169 parity=parity.next(),
170 file=f,
170 file=f,
171 filenode=hex(fn or nullid))
171 filenode=hex(fn or nullid))
172
172
173 blockcount = countgen()
173 def prettyprintlines(diff):
174 def prettyprintlines(diff):
174 for l in diff.splitlines(1):
175 blockno = blockcount.next()
176 for lineno, l in enumerate(diff.splitlines(1)):
177 if blockno == 0:
178 lineno = lineno + 1
179 else:
180 lineno = "%d.%d" % (blockno, lineno + 1)
181 type = "diffline"
175 if l.startswith('+'):
182 if l.startswith('+'):
176 yield self.t("difflineplus", line=l)
183 type = "difflineplus"
177 elif l.startswith('-'):
184 elif l.startswith('-'):
178 yield self.t("difflineminus", line=l)
185 type = "difflineminus"
179 elif l.startswith('@'):
186 elif l.startswith('@'):
180 yield self.t("difflineat", line=l)
187 type = "difflineat"
181 else:
188 yield self.t(type,
182 yield self.t("diffline", line=l)
189 line=l,
190 lineid="l%s" % lineno,
191 linenumber="% 8s" % lineno)
183
192
184 r = self.repo
193 r = self.repo
185 c1 = r.changectx(node1)
194 c1 = r.changectx(node1)
186 c2 = r.changectx(node2)
195 c2 = r.changectx(node2)
187 date1 = util.datestr(c1.date())
196 date1 = util.datestr(c1.date())
188 date2 = util.datestr(c2.date())
197 date2 = util.datestr(c2.date())
189
198
190 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
199 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 if files:
200 if files:
192 modified, added, removed = map(lambda x: filterfiles(files, x),
201 modified, added, removed = map(lambda x: filterfiles(files, x),
193 (modified, added, removed))
202 (modified, added, removed))
194
203
195 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
204 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 for f in modified:
205 for f in modified:
197 to = c1.filectx(f).data()
206 to = c1.filectx(f).data()
198 tn = c2.filectx(f).data()
207 tn = c2.filectx(f).data()
199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
208 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
200 opts=diffopts), f, tn)
209 opts=diffopts), f, tn)
201 for f in added:
210 for f in added:
202 to = None
211 to = None
203 tn = c2.filectx(f).data()
212 tn = c2.filectx(f).data()
204 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
213 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
205 opts=diffopts), f, tn)
214 opts=diffopts), f, tn)
206 for f in removed:
215 for f in removed:
207 to = c1.filectx(f).data()
216 to = c1.filectx(f).data()
208 tn = None
217 tn = None
209 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
218 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
210 opts=diffopts), f, tn)
219 opts=diffopts), f, tn)
211
220
212 def changelog(self, ctx, shortlog=False):
221 def changelog(self, ctx, shortlog=False):
213 def changelist(limit=0,**map):
222 def changelist(limit=0,**map):
214 cl = self.repo.changelog
223 cl = self.repo.changelog
215 l = [] # build a list in forward order for efficiency
224 l = [] # build a list in forward order for efficiency
216 for i in xrange(start, end):
225 for i in xrange(start, end):
217 ctx = self.repo.changectx(i)
226 ctx = self.repo.changectx(i)
218 n = ctx.node()
227 n = ctx.node()
219
228
220 l.insert(0, {"parity": parity.next(),
229 l.insert(0, {"parity": parity.next(),
221 "author": ctx.user(),
230 "author": ctx.user(),
222 "parent": self.siblings(ctx.parents(), i - 1),
231 "parent": self.siblings(ctx.parents(), i - 1),
223 "child": self.siblings(ctx.children(), i + 1),
232 "child": self.siblings(ctx.children(), i + 1),
224 "changelogtag": self.showtag("changelogtag",n),
233 "changelogtag": self.showtag("changelogtag",n),
225 "desc": ctx.description(),
234 "desc": ctx.description(),
226 "date": ctx.date(),
235 "date": ctx.date(),
227 "files": self.listfilediffs(ctx.files(), n),
236 "files": self.listfilediffs(ctx.files(), n),
228 "rev": i,
237 "rev": i,
229 "node": hex(n),
238 "node": hex(n),
230 "tags": self.nodetagsdict(n),
239 "tags": self.nodetagsdict(n),
231 "branches": self.nodebranchdict(ctx)})
240 "branches": self.nodebranchdict(ctx)})
232
241
233 if limit > 0:
242 if limit > 0:
234 l = l[:limit]
243 l = l[:limit]
235
244
236 for e in l:
245 for e in l:
237 yield e
246 yield e
238
247
239 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
248 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 cl = self.repo.changelog
249 cl = self.repo.changelog
241 count = cl.count()
250 count = cl.count()
242 pos = ctx.rev()
251 pos = ctx.rev()
243 start = max(0, pos - maxchanges + 1)
252 start = max(0, pos - maxchanges + 1)
244 end = min(count, start + maxchanges)
253 end = min(count, start + maxchanges)
245 pos = end - 1
254 pos = end - 1
246 parity = paritygen(self.stripecount, offset=start-end)
255 parity = paritygen(self.stripecount, offset=start-end)
247
256
248 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
257 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249
258
250 yield self.t(shortlog and 'shortlog' or 'changelog',
259 yield self.t(shortlog and 'shortlog' or 'changelog',
251 changenav=changenav,
260 changenav=changenav,
252 node=hex(cl.tip()),
261 node=hex(cl.tip()),
253 rev=pos, changesets=count,
262 rev=pos, changesets=count,
254 entries=lambda **x: changelist(limit=0,**x),
263 entries=lambda **x: changelist(limit=0,**x),
255 latestentry=lambda **x: changelist(limit=1,**x),
264 latestentry=lambda **x: changelist(limit=1,**x),
256 archives=self.archivelist("tip"))
265 archives=self.archivelist("tip"))
257
266
258 def search(self, query):
267 def search(self, query):
259
268
260 def changelist(**map):
269 def changelist(**map):
261 cl = self.repo.changelog
270 cl = self.repo.changelog
262 count = 0
271 count = 0
263 qw = query.lower().split()
272 qw = query.lower().split()
264
273
265 def revgen():
274 def revgen():
266 for i in xrange(cl.count() - 1, 0, -100):
275 for i in xrange(cl.count() - 1, 0, -100):
267 l = []
276 l = []
268 for j in xrange(max(0, i - 100), i):
277 for j in xrange(max(0, i - 100), i):
269 ctx = self.repo.changectx(j)
278 ctx = self.repo.changectx(j)
270 l.append(ctx)
279 l.append(ctx)
271 l.reverse()
280 l.reverse()
272 for e in l:
281 for e in l:
273 yield e
282 yield e
274
283
275 for ctx in revgen():
284 for ctx in revgen():
276 miss = 0
285 miss = 0
277 for q in qw:
286 for q in qw:
278 if not (q in ctx.user().lower() or
287 if not (q in ctx.user().lower() or
279 q in ctx.description().lower() or
288 q in ctx.description().lower() or
280 q in " ".join(ctx.files()).lower()):
289 q in " ".join(ctx.files()).lower()):
281 miss = 1
290 miss = 1
282 break
291 break
283 if miss:
292 if miss:
284 continue
293 continue
285
294
286 count += 1
295 count += 1
287 n = ctx.node()
296 n = ctx.node()
288
297
289 yield self.t('searchentry',
298 yield self.t('searchentry',
290 parity=parity.next(),
299 parity=parity.next(),
291 author=ctx.user(),
300 author=ctx.user(),
292 parent=self.siblings(ctx.parents()),
301 parent=self.siblings(ctx.parents()),
293 child=self.siblings(ctx.children()),
302 child=self.siblings(ctx.children()),
294 changelogtag=self.showtag("changelogtag",n),
303 changelogtag=self.showtag("changelogtag",n),
295 desc=ctx.description(),
304 desc=ctx.description(),
296 date=ctx.date(),
305 date=ctx.date(),
297 files=self.listfilediffs(ctx.files(), n),
306 files=self.listfilediffs(ctx.files(), n),
298 rev=ctx.rev(),
307 rev=ctx.rev(),
299 node=hex(n),
308 node=hex(n),
300 tags=self.nodetagsdict(n),
309 tags=self.nodetagsdict(n),
301 branches=self.nodebranchdict(ctx))
310 branches=self.nodebranchdict(ctx))
302
311
303 if count >= self.maxchanges:
312 if count >= self.maxchanges:
304 break
313 break
305
314
306 cl = self.repo.changelog
315 cl = self.repo.changelog
307 parity = paritygen(self.stripecount)
316 parity = paritygen(self.stripecount)
308
317
309 yield self.t('search',
318 yield self.t('search',
310 query=query,
319 query=query,
311 node=hex(cl.tip()),
320 node=hex(cl.tip()),
312 entries=changelist,
321 entries=changelist,
313 archives=self.archivelist("tip"))
322 archives=self.archivelist("tip"))
314
323
315 def changeset(self, ctx):
324 def changeset(self, ctx):
316 n = ctx.node()
325 n = ctx.node()
317 parents = ctx.parents()
326 parents = ctx.parents()
318 p1 = parents[0].node()
327 p1 = parents[0].node()
319
328
320 files = []
329 files = []
321 parity = paritygen(self.stripecount)
330 parity = paritygen(self.stripecount)
322 for f in ctx.files():
331 for f in ctx.files():
323 files.append(self.t("filenodelink",
332 files.append(self.t("filenodelink",
324 node=hex(n), file=f,
333 node=hex(n), file=f,
325 parity=parity.next()))
334 parity=parity.next()))
326
335
327 def diff(**map):
336 def diff(**map):
328 yield self.diff(p1, n, None)
337 yield self.diff(p1, n, None)
329
338
330 yield self.t('changeset',
339 yield self.t('changeset',
331 diff=diff,
340 diff=diff,
332 rev=ctx.rev(),
341 rev=ctx.rev(),
333 node=hex(n),
342 node=hex(n),
334 parent=self.siblings(parents),
343 parent=self.siblings(parents),
335 child=self.siblings(ctx.children()),
344 child=self.siblings(ctx.children()),
336 changesettag=self.showtag("changesettag",n),
345 changesettag=self.showtag("changesettag",n),
337 author=ctx.user(),
346 author=ctx.user(),
338 desc=ctx.description(),
347 desc=ctx.description(),
339 date=ctx.date(),
348 date=ctx.date(),
340 files=files,
349 files=files,
341 archives=self.archivelist(hex(n)),
350 archives=self.archivelist(hex(n)),
342 tags=self.nodetagsdict(n),
351 tags=self.nodetagsdict(n),
343 branches=self.nodebranchdict(ctx))
352 branches=self.nodebranchdict(ctx))
344
353
345 def filelog(self, fctx):
354 def filelog(self, fctx):
346 f = fctx.path()
355 f = fctx.path()
347 fl = fctx.filelog()
356 fl = fctx.filelog()
348 count = fl.count()
357 count = fl.count()
349 pagelen = self.maxshortchanges
358 pagelen = self.maxshortchanges
350 pos = fctx.filerev()
359 pos = fctx.filerev()
351 start = max(0, pos - pagelen + 1)
360 start = max(0, pos - pagelen + 1)
352 end = min(count, start + pagelen)
361 end = min(count, start + pagelen)
353 pos = end - 1
362 pos = end - 1
354 parity = paritygen(self.stripecount, offset=start-end)
363 parity = paritygen(self.stripecount, offset=start-end)
355
364
356 def entries(limit=0, **map):
365 def entries(limit=0, **map):
357 l = []
366 l = []
358
367
359 for i in xrange(start, end):
368 for i in xrange(start, end):
360 ctx = fctx.filectx(i)
369 ctx = fctx.filectx(i)
361 n = fl.node(i)
370 n = fl.node(i)
362
371
363 l.insert(0, {"parity": parity.next(),
372 l.insert(0, {"parity": parity.next(),
364 "filerev": i,
373 "filerev": i,
365 "file": f,
374 "file": f,
366 "node": hex(ctx.node()),
375 "node": hex(ctx.node()),
367 "author": ctx.user(),
376 "author": ctx.user(),
368 "date": ctx.date(),
377 "date": ctx.date(),
369 "rename": self.renamelink(fl, n),
378 "rename": self.renamelink(fl, n),
370 "parent": self.siblings(fctx.parents()),
379 "parent": self.siblings(fctx.parents()),
371 "child": self.siblings(fctx.children()),
380 "child": self.siblings(fctx.children()),
372 "desc": ctx.description()})
381 "desc": ctx.description()})
373
382
374 if limit > 0:
383 if limit > 0:
375 l = l[:limit]
384 l = l[:limit]
376
385
377 for e in l:
386 for e in l:
378 yield e
387 yield e
379
388
380 nodefunc = lambda x: fctx.filectx(fileid=x)
389 nodefunc = lambda x: fctx.filectx(fileid=x)
381 nav = revnavgen(pos, pagelen, count, nodefunc)
390 nav = revnavgen(pos, pagelen, count, nodefunc)
382 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
391 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 entries=lambda **x: entries(limit=0, **x),
392 entries=lambda **x: entries(limit=0, **x),
384 latestentry=lambda **x: entries(limit=1, **x))
393 latestentry=lambda **x: entries(limit=1, **x))
385
394
386 def filerevision(self, fctx):
395 def filerevision(self, fctx):
387 f = fctx.path()
396 f = fctx.path()
388 text = fctx.data()
397 text = fctx.data()
389 fl = fctx.filelog()
398 fl = fctx.filelog()
390 n = fctx.filenode()
399 n = fctx.filenode()
391 parity = paritygen(self.stripecount)
400 parity = paritygen(self.stripecount)
392
401
393 mt = mimetypes.guess_type(f)[0]
402 mt = mimetypes.guess_type(f)[0]
394 rawtext = text
403 rawtext = text
395 if util.binary(text):
404 if util.binary(text):
396 mt = mt or 'application/octet-stream'
405 mt = mt or 'application/octet-stream'
397 text = "(binary:%s)" % mt
406 text = "(binary:%s)" % mt
398 mt = mt or 'text/plain'
407 mt = mt or 'text/plain'
399
408
400 def lines():
409 def lines():
401 for l, t in enumerate(text.splitlines(1)):
410 for lineno, t in enumerate(text.splitlines(1)):
402 yield {"line": t,
411 yield {"line": t,
403 "linenumber": "% 6d" % (l + 1),
412 "lineid": "l%d" % (lineno + 1),
413 "linenumber": "% 6d" % (lineno + 1),
404 "parity": parity.next()}
414 "parity": parity.next()}
405
415
406 yield self.t("filerevision",
416 yield self.t("filerevision",
407 file=f,
417 file=f,
408 path=_up(f),
418 path=_up(f),
409 text=lines(),
419 text=lines(),
410 raw=rawtext,
420 raw=rawtext,
411 mimetype=mt,
421 mimetype=mt,
412 rev=fctx.rev(),
422 rev=fctx.rev(),
413 node=hex(fctx.node()),
423 node=hex(fctx.node()),
414 author=fctx.user(),
424 author=fctx.user(),
415 date=fctx.date(),
425 date=fctx.date(),
416 desc=fctx.description(),
426 desc=fctx.description(),
417 parent=self.siblings(fctx.parents()),
427 parent=self.siblings(fctx.parents()),
418 child=self.siblings(fctx.children()),
428 child=self.siblings(fctx.children()),
419 rename=self.renamelink(fl, n),
429 rename=self.renamelink(fl, n),
420 permissions=fctx.manifest().flags(f))
430 permissions=fctx.manifest().flags(f))
421
431
422 def fileannotate(self, fctx):
432 def fileannotate(self, fctx):
423 f = fctx.path()
433 f = fctx.path()
424 n = fctx.filenode()
434 n = fctx.filenode()
425 fl = fctx.filelog()
435 fl = fctx.filelog()
426 parity = paritygen(self.stripecount)
436 parity = paritygen(self.stripecount)
427
437
428 def annotate(**map):
438 def annotate(**map):
429 last = None
439 last = None
430 for f, l in fctx.annotate(follow=True):
440 for lineno, (f, l) in enumerate(fctx.annotate(follow=True)):
431 fnode = f.filenode()
441 fnode = f.filenode()
432 name = self.repo.ui.shortuser(f.user())
442 name = self.repo.ui.shortuser(f.user())
433
443
434 if last != fnode:
444 if last != fnode:
435 last = fnode
445 last = fnode
436
446
437 yield {"parity": parity.next(),
447 yield {"parity": parity.next(),
438 "node": hex(f.node()),
448 "node": hex(f.node()),
439 "rev": f.rev(),
449 "rev": f.rev(),
440 "author": name,
450 "author": name,
441 "file": f.path(),
451 "file": f.path(),
442 "line": l}
452 "line": l,
453 "lineid": "l%d" % (lineno + 1),
454 "linenumber": "% 6d" % (lineno + 1)}
443
455
444 yield self.t("fileannotate",
456 yield self.t("fileannotate",
445 file=f,
457 file=f,
446 annotate=annotate,
458 annotate=annotate,
447 path=_up(f),
459 path=_up(f),
448 rev=fctx.rev(),
460 rev=fctx.rev(),
449 node=hex(fctx.node()),
461 node=hex(fctx.node()),
450 author=fctx.user(),
462 author=fctx.user(),
451 date=fctx.date(),
463 date=fctx.date(),
452 desc=fctx.description(),
464 desc=fctx.description(),
453 rename=self.renamelink(fl, n),
465 rename=self.renamelink(fl, n),
454 parent=self.siblings(fctx.parents()),
466 parent=self.siblings(fctx.parents()),
455 child=self.siblings(fctx.children()),
467 child=self.siblings(fctx.children()),
456 permissions=fctx.manifest().flags(f))
468 permissions=fctx.manifest().flags(f))
457
469
458 def manifest(self, ctx, path):
470 def manifest(self, ctx, path):
459 mf = ctx.manifest()
471 mf = ctx.manifest()
460 node = ctx.node()
472 node = ctx.node()
461
473
462 files = {}
474 files = {}
463 parity = paritygen(self.stripecount)
475 parity = paritygen(self.stripecount)
464
476
465 if path and path[-1] != "/":
477 if path and path[-1] != "/":
466 path += "/"
478 path += "/"
467 l = len(path)
479 l = len(path)
468 abspath = "/" + path
480 abspath = "/" + path
469
481
470 for f, n in mf.items():
482 for f, n in mf.items():
471 if f[:l] != path:
483 if f[:l] != path:
472 continue
484 continue
473 remain = f[l:]
485 remain = f[l:]
474 if "/" in remain:
486 if "/" in remain:
475 short = remain[:remain.index("/") + 1] # bleah
487 short = remain[:remain.index("/") + 1] # bleah
476 files[short] = (f, None)
488 files[short] = (f, None)
477 else:
489 else:
478 short = os.path.basename(remain)
490 short = os.path.basename(remain)
479 files[short] = (f, n)
491 files[short] = (f, n)
480
492
481 def filelist(**map):
493 def filelist(**map):
482 fl = files.keys()
494 fl = files.keys()
483 fl.sort()
495 fl.sort()
484 for f in fl:
496 for f in fl:
485 full, fnode = files[f]
497 full, fnode = files[f]
486 if not fnode:
498 if not fnode:
487 continue
499 continue
488
500
489 fctx = ctx.filectx(full)
501 fctx = ctx.filectx(full)
490 yield {"file": full,
502 yield {"file": full,
491 "parity": parity.next(),
503 "parity": parity.next(),
492 "basename": f,
504 "basename": f,
493 "date": fctx.changectx().date(),
505 "date": fctx.changectx().date(),
494 "size": fctx.size(),
506 "size": fctx.size(),
495 "permissions": mf.flags(full)}
507 "permissions": mf.flags(full)}
496
508
497 def dirlist(**map):
509 def dirlist(**map):
498 fl = files.keys()
510 fl = files.keys()
499 fl.sort()
511 fl.sort()
500 for f in fl:
512 for f in fl:
501 full, fnode = files[f]
513 full, fnode = files[f]
502 if fnode:
514 if fnode:
503 continue
515 continue
504
516
505 yield {"parity": parity.next(),
517 yield {"parity": parity.next(),
506 "path": "%s%s" % (abspath, f),
518 "path": "%s%s" % (abspath, f),
507 "basename": f[:-1]}
519 "basename": f[:-1]}
508
520
509 yield self.t("manifest",
521 yield self.t("manifest",
510 rev=ctx.rev(),
522 rev=ctx.rev(),
511 node=hex(node),
523 node=hex(node),
512 path=abspath,
524 path=abspath,
513 up=_up(abspath),
525 up=_up(abspath),
514 upparity=parity.next(),
526 upparity=parity.next(),
515 fentries=filelist,
527 fentries=filelist,
516 dentries=dirlist,
528 dentries=dirlist,
517 archives=self.archivelist(hex(node)),
529 archives=self.archivelist(hex(node)),
518 tags=self.nodetagsdict(node),
530 tags=self.nodetagsdict(node),
519 branches=self.nodebranchdict(ctx))
531 branches=self.nodebranchdict(ctx))
520
532
521 def tags(self):
533 def tags(self):
522 i = self.repo.tagslist()
534 i = self.repo.tagslist()
523 i.reverse()
535 i.reverse()
524 parity = paritygen(self.stripecount)
536 parity = paritygen(self.stripecount)
525
537
526 def entries(notip=False,limit=0, **map):
538 def entries(notip=False,limit=0, **map):
527 count = 0
539 count = 0
528 for k, n in i:
540 for k, n in i:
529 if notip and k == "tip":
541 if notip and k == "tip":
530 continue
542 continue
531 if limit > 0 and count >= limit:
543 if limit > 0 and count >= limit:
532 continue
544 continue
533 count = count + 1
545 count = count + 1
534 yield {"parity": parity.next(),
546 yield {"parity": parity.next(),
535 "tag": k,
547 "tag": k,
536 "date": self.repo.changectx(n).date(),
548 "date": self.repo.changectx(n).date(),
537 "node": hex(n)}
549 "node": hex(n)}
538
550
539 yield self.t("tags",
551 yield self.t("tags",
540 node=hex(self.repo.changelog.tip()),
552 node=hex(self.repo.changelog.tip()),
541 entries=lambda **x: entries(False,0, **x),
553 entries=lambda **x: entries(False,0, **x),
542 entriesnotip=lambda **x: entries(True,0, **x),
554 entriesnotip=lambda **x: entries(True,0, **x),
543 latestentry=lambda **x: entries(True,1, **x))
555 latestentry=lambda **x: entries(True,1, **x))
544
556
545 def summary(self):
557 def summary(self):
546 i = self.repo.tagslist()
558 i = self.repo.tagslist()
547 i.reverse()
559 i.reverse()
548
560
549 def tagentries(**map):
561 def tagentries(**map):
550 parity = paritygen(self.stripecount)
562 parity = paritygen(self.stripecount)
551 count = 0
563 count = 0
552 for k, n in i:
564 for k, n in i:
553 if k == "tip": # skip tip
565 if k == "tip": # skip tip
554 continue;
566 continue;
555
567
556 count += 1
568 count += 1
557 if count > 10: # limit to 10 tags
569 if count > 10: # limit to 10 tags
558 break;
570 break;
559
571
560 yield self.t("tagentry",
572 yield self.t("tagentry",
561 parity=parity.next(),
573 parity=parity.next(),
562 tag=k,
574 tag=k,
563 node=hex(n),
575 node=hex(n),
564 date=self.repo.changectx(n).date())
576 date=self.repo.changectx(n).date())
565
577
566
578
567 def branches(**map):
579 def branches(**map):
568 parity = paritygen(self.stripecount)
580 parity = paritygen(self.stripecount)
569
581
570 b = self.repo.branchtags()
582 b = self.repo.branchtags()
571 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
583 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
572 l.sort()
584 l.sort()
573
585
574 for r,n,t in l:
586 for r,n,t in l:
575 ctx = self.repo.changectx(n)
587 ctx = self.repo.changectx(n)
576
588
577 yield {'parity': parity.next(),
589 yield {'parity': parity.next(),
578 'branch': t,
590 'branch': t,
579 'node': hex(n),
591 'node': hex(n),
580 'date': ctx.date()}
592 'date': ctx.date()}
581
593
582 def changelist(**map):
594 def changelist(**map):
583 parity = paritygen(self.stripecount, offset=start-end)
595 parity = paritygen(self.stripecount, offset=start-end)
584 l = [] # build a list in forward order for efficiency
596 l = [] # build a list in forward order for efficiency
585 for i in xrange(start, end):
597 for i in xrange(start, end):
586 ctx = self.repo.changectx(i)
598 ctx = self.repo.changectx(i)
587 n = ctx.node()
599 n = ctx.node()
588 hn = hex(n)
600 hn = hex(n)
589
601
590 l.insert(0, self.t(
602 l.insert(0, self.t(
591 'shortlogentry',
603 'shortlogentry',
592 parity=parity.next(),
604 parity=parity.next(),
593 author=ctx.user(),
605 author=ctx.user(),
594 desc=ctx.description(),
606 desc=ctx.description(),
595 date=ctx.date(),
607 date=ctx.date(),
596 rev=i,
608 rev=i,
597 node=hn,
609 node=hn,
598 tags=self.nodetagsdict(n),
610 tags=self.nodetagsdict(n),
599 branches=self.nodebranchdict(ctx)))
611 branches=self.nodebranchdict(ctx)))
600
612
601 yield l
613 yield l
602
614
603 cl = self.repo.changelog
615 cl = self.repo.changelog
604 count = cl.count()
616 count = cl.count()
605 start = max(0, count - self.maxchanges)
617 start = max(0, count - self.maxchanges)
606 end = min(count, start + self.maxchanges)
618 end = min(count, start + self.maxchanges)
607
619
608 yield self.t("summary",
620 yield self.t("summary",
609 desc=self.config("web", "description", "unknown"),
621 desc=self.config("web", "description", "unknown"),
610 owner=(self.config("ui", "username") or # preferred
622 owner=(self.config("ui", "username") or # preferred
611 self.config("web", "contact") or # deprecated
623 self.config("web", "contact") or # deprecated
612 self.config("web", "author", "unknown")), # also
624 self.config("web", "author", "unknown")), # also
613 lastchange=cl.read(cl.tip())[2],
625 lastchange=cl.read(cl.tip())[2],
614 tags=tagentries,
626 tags=tagentries,
615 branches=branches,
627 branches=branches,
616 shortlog=changelist,
628 shortlog=changelist,
617 node=hex(cl.tip()),
629 node=hex(cl.tip()),
618 archives=self.archivelist("tip"))
630 archives=self.archivelist("tip"))
619
631
620 def filediff(self, fctx):
632 def filediff(self, fctx):
621 n = fctx.node()
633 n = fctx.node()
622 path = fctx.path()
634 path = fctx.path()
623 parents = fctx.parents()
635 parents = fctx.parents()
624 p1 = parents and parents[0].node() or nullid
636 p1 = parents and parents[0].node() or nullid
625
637
626 def diff(**map):
638 def diff(**map):
627 yield self.diff(p1, n, [path])
639 yield self.diff(p1, n, [path])
628
640
629 yield self.t("filediff",
641 yield self.t("filediff",
630 file=path,
642 file=path,
631 node=hex(n),
643 node=hex(n),
632 rev=fctx.rev(),
644 rev=fctx.rev(),
633 parent=self.siblings(parents),
645 parent=self.siblings(parents),
634 child=self.siblings(fctx.children()),
646 child=self.siblings(fctx.children()),
635 diff=diff)
647 diff=diff)
636
648
637 archive_specs = {
649 archive_specs = {
638 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
650 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
639 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
651 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
640 'zip': ('application/zip', 'zip', '.zip', None),
652 'zip': ('application/zip', 'zip', '.zip', None),
641 }
653 }
642
654
643 def archive(self, req, key, type_):
655 def archive(self, req, key, type_):
644 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
656 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
645 cnode = self.repo.lookup(key)
657 cnode = self.repo.lookup(key)
646 arch_version = key
658 arch_version = key
647 if cnode == key or key == 'tip':
659 if cnode == key or key == 'tip':
648 arch_version = short(cnode)
660 arch_version = short(cnode)
649 name = "%s-%s" % (reponame, arch_version)
661 name = "%s-%s" % (reponame, arch_version)
650 mimetype, artype, extension, encoding = self.archive_specs[type_]
662 mimetype, artype, extension, encoding = self.archive_specs[type_]
651 headers = [('Content-type', mimetype),
663 headers = [('Content-type', mimetype),
652 ('Content-disposition', 'attachment; filename=%s%s' %
664 ('Content-disposition', 'attachment; filename=%s%s' %
653 (name, extension))]
665 (name, extension))]
654 if encoding:
666 if encoding:
655 headers.append(('Content-encoding', encoding))
667 headers.append(('Content-encoding', encoding))
656 req.header(headers)
668 req.header(headers)
657 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
669 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
658
670
659 # add tags to things
671 # add tags to things
660 # tags -> list of changesets corresponding to tags
672 # tags -> list of changesets corresponding to tags
661 # find tag, changeset, file
673 # find tag, changeset, file
662
674
663 def cleanpath(self, path):
675 def cleanpath(self, path):
664 path = path.lstrip('/')
676 path = path.lstrip('/')
665 return util.canonpath(self.repo.root, '', path)
677 return util.canonpath(self.repo.root, '', path)
666
678
667 def run(self):
679 def run(self):
668 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
680 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
669 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
681 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
670 import mercurial.hgweb.wsgicgi as wsgicgi
682 import mercurial.hgweb.wsgicgi as wsgicgi
671 from request import wsgiapplication
683 from request import wsgiapplication
672 def make_web_app():
684 def make_web_app():
673 return self
685 return self
674 wsgicgi.launch(wsgiapplication(make_web_app))
686 wsgicgi.launch(wsgiapplication(make_web_app))
675
687
676 def run_wsgi(self, req):
688 def run_wsgi(self, req):
677 def header(**map):
689 def header(**map):
678 header_file = cStringIO.StringIO(
690 header_file = cStringIO.StringIO(
679 ''.join(self.t("header", encoding=self.encoding, **map)))
691 ''.join(self.t("header", encoding=self.encoding, **map)))
680 msg = mimetools.Message(header_file, 0)
692 msg = mimetools.Message(header_file, 0)
681 req.header(msg.items())
693 req.header(msg.items())
682 yield header_file.read()
694 yield header_file.read()
683
695
684 def rawfileheader(**map):
696 def rawfileheader(**map):
685 req.header([('Content-type', map['mimetype']),
697 req.header([('Content-type', map['mimetype']),
686 ('Content-disposition', 'filename=%s' % map['file']),
698 ('Content-disposition', 'filename=%s' % map['file']),
687 ('Content-length', str(len(map['raw'])))])
699 ('Content-length', str(len(map['raw'])))])
688 yield ''
700 yield ''
689
701
690 def footer(**map):
702 def footer(**map):
691 yield self.t("footer", **map)
703 yield self.t("footer", **map)
692
704
693 def motd(**map):
705 def motd(**map):
694 yield self.config("web", "motd", "")
706 yield self.config("web", "motd", "")
695
707
696 def expand_form(form):
708 def expand_form(form):
697 shortcuts = {
709 shortcuts = {
698 'cl': [('cmd', ['changelog']), ('rev', None)],
710 'cl': [('cmd', ['changelog']), ('rev', None)],
699 'sl': [('cmd', ['shortlog']), ('rev', None)],
711 'sl': [('cmd', ['shortlog']), ('rev', None)],
700 'cs': [('cmd', ['changeset']), ('node', None)],
712 'cs': [('cmd', ['changeset']), ('node', None)],
701 'f': [('cmd', ['file']), ('filenode', None)],
713 'f': [('cmd', ['file']), ('filenode', None)],
702 'fl': [('cmd', ['filelog']), ('filenode', None)],
714 'fl': [('cmd', ['filelog']), ('filenode', None)],
703 'fd': [('cmd', ['filediff']), ('node', None)],
715 'fd': [('cmd', ['filediff']), ('node', None)],
704 'fa': [('cmd', ['annotate']), ('filenode', None)],
716 'fa': [('cmd', ['annotate']), ('filenode', None)],
705 'mf': [('cmd', ['manifest']), ('manifest', None)],
717 'mf': [('cmd', ['manifest']), ('manifest', None)],
706 'ca': [('cmd', ['archive']), ('node', None)],
718 'ca': [('cmd', ['archive']), ('node', None)],
707 'tags': [('cmd', ['tags'])],
719 'tags': [('cmd', ['tags'])],
708 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
720 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
709 'static': [('cmd', ['static']), ('file', None)]
721 'static': [('cmd', ['static']), ('file', None)]
710 }
722 }
711
723
712 for k in shortcuts.iterkeys():
724 for k in shortcuts.iterkeys():
713 if form.has_key(k):
725 if form.has_key(k):
714 for name, value in shortcuts[k]:
726 for name, value in shortcuts[k]:
715 if value is None:
727 if value is None:
716 value = form[k]
728 value = form[k]
717 form[name] = value
729 form[name] = value
718 del form[k]
730 del form[k]
719
731
720 def rewrite_request(req):
732 def rewrite_request(req):
721 '''translate new web interface to traditional format'''
733 '''translate new web interface to traditional format'''
722
734
723 def spliturl(req):
735 def spliturl(req):
724 def firstitem(query):
736 def firstitem(query):
725 return query.split('&', 1)[0].split(';', 1)[0]
737 return query.split('&', 1)[0].split(';', 1)[0]
726
738
727 def normurl(url):
739 def normurl(url):
728 inner = '/'.join([x for x in url.split('/') if x])
740 inner = '/'.join([x for x in url.split('/') if x])
729 tl = len(url) > 1 and url.endswith('/') and '/' or ''
741 tl = len(url) > 1 and url.endswith('/') and '/' or ''
730
742
731 return '%s%s%s' % (url.startswith('/') and '/' or '',
743 return '%s%s%s' % (url.startswith('/') and '/' or '',
732 inner, tl)
744 inner, tl)
733
745
734 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
746 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
735 pi = normurl(req.env.get('PATH_INFO', ''))
747 pi = normurl(req.env.get('PATH_INFO', ''))
736 if pi:
748 if pi:
737 # strip leading /
749 # strip leading /
738 pi = pi[1:]
750 pi = pi[1:]
739 if pi:
751 if pi:
740 root = root[:root.rfind(pi)]
752 root = root[:root.rfind(pi)]
741 if req.env.has_key('REPO_NAME'):
753 if req.env.has_key('REPO_NAME'):
742 rn = req.env['REPO_NAME'] + '/'
754 rn = req.env['REPO_NAME'] + '/'
743 root += rn
755 root += rn
744 query = pi[len(rn):]
756 query = pi[len(rn):]
745 else:
757 else:
746 query = pi
758 query = pi
747 else:
759 else:
748 root += '?'
760 root += '?'
749 query = firstitem(req.env['QUERY_STRING'])
761 query = firstitem(req.env['QUERY_STRING'])
750
762
751 return (root, query)
763 return (root, query)
752
764
753 req.url, query = spliturl(req)
765 req.url, query = spliturl(req)
754
766
755 if req.form.has_key('cmd'):
767 if req.form.has_key('cmd'):
756 # old style
768 # old style
757 return
769 return
758
770
759 args = query.split('/', 2)
771 args = query.split('/', 2)
760 if not args or not args[0]:
772 if not args or not args[0]:
761 return
773 return
762
774
763 cmd = args.pop(0)
775 cmd = args.pop(0)
764 style = cmd.rfind('-')
776 style = cmd.rfind('-')
765 if style != -1:
777 if style != -1:
766 req.form['style'] = [cmd[:style]]
778 req.form['style'] = [cmd[:style]]
767 cmd = cmd[style+1:]
779 cmd = cmd[style+1:]
768 # avoid accepting e.g. style parameter as command
780 # avoid accepting e.g. style parameter as command
769 if hasattr(self, 'do_' + cmd):
781 if hasattr(self, 'do_' + cmd):
770 req.form['cmd'] = [cmd]
782 req.form['cmd'] = [cmd]
771
783
772 if args and args[0]:
784 if args and args[0]:
773 node = args.pop(0)
785 node = args.pop(0)
774 req.form['node'] = [node]
786 req.form['node'] = [node]
775 if args:
787 if args:
776 req.form['file'] = args
788 req.form['file'] = args
777
789
778 if cmd == 'static':
790 if cmd == 'static':
779 req.form['file'] = req.form['node']
791 req.form['file'] = req.form['node']
780 elif cmd == 'archive':
792 elif cmd == 'archive':
781 fn = req.form['node'][0]
793 fn = req.form['node'][0]
782 for type_, spec in self.archive_specs.iteritems():
794 for type_, spec in self.archive_specs.iteritems():
783 ext = spec[2]
795 ext = spec[2]
784 if fn.endswith(ext):
796 if fn.endswith(ext):
785 req.form['node'] = [fn[:-len(ext)]]
797 req.form['node'] = [fn[:-len(ext)]]
786 req.form['type'] = [type_]
798 req.form['type'] = [type_]
787
799
788 def sessionvars(**map):
800 def sessionvars(**map):
789 fields = []
801 fields = []
790 if req.form.has_key('style'):
802 if req.form.has_key('style'):
791 style = req.form['style'][0]
803 style = req.form['style'][0]
792 if style != self.config('web', 'style', ''):
804 if style != self.config('web', 'style', ''):
793 fields.append(('style', style))
805 fields.append(('style', style))
794
806
795 separator = req.url[-1] == '?' and ';' or '?'
807 separator = req.url[-1] == '?' and ';' or '?'
796 for name, value in fields:
808 for name, value in fields:
797 yield dict(name=name, value=value, separator=separator)
809 yield dict(name=name, value=value, separator=separator)
798 separator = ';'
810 separator = ';'
799
811
800 self.refresh()
812 self.refresh()
801
813
802 expand_form(req.form)
814 expand_form(req.form)
803 rewrite_request(req)
815 rewrite_request(req)
804
816
805 style = self.config("web", "style", "")
817 style = self.config("web", "style", "")
806 if req.form.has_key('style'):
818 if req.form.has_key('style'):
807 style = req.form['style'][0]
819 style = req.form['style'][0]
808 mapfile = style_map(self.templatepath, style)
820 mapfile = style_map(self.templatepath, style)
809
821
810 proto = req.env.get('wsgi.url_scheme')
822 proto = req.env.get('wsgi.url_scheme')
811 if proto == 'https':
823 if proto == 'https':
812 proto = 'https'
824 proto = 'https'
813 default_port = "443"
825 default_port = "443"
814 else:
826 else:
815 proto = 'http'
827 proto = 'http'
816 default_port = "80"
828 default_port = "80"
817
829
818 port = req.env["SERVER_PORT"]
830 port = req.env["SERVER_PORT"]
819 port = port != default_port and (":" + port) or ""
831 port = port != default_port and (":" + port) or ""
820 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
832 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
821 staticurl = self.config("web", "staticurl") or req.url + 'static/'
833 staticurl = self.config("web", "staticurl") or req.url + 'static/'
822 if not staticurl.endswith('/'):
834 if not staticurl.endswith('/'):
823 staticurl += '/'
835 staticurl += '/'
824
836
825 if not self.reponame:
837 if not self.reponame:
826 self.reponame = (self.config("web", "name")
838 self.reponame = (self.config("web", "name")
827 or req.env.get('REPO_NAME')
839 or req.env.get('REPO_NAME')
828 or req.url.strip('/') or self.repo.root)
840 or req.url.strip('/') or self.repo.root)
829
841
830 self.t = templater.templater(mapfile, templater.common_filters,
842 self.t = templater.templater(mapfile, templater.common_filters,
831 defaults={"url": req.url,
843 defaults={"url": req.url,
832 "staticurl": staticurl,
844 "staticurl": staticurl,
833 "urlbase": urlbase,
845 "urlbase": urlbase,
834 "repo": self.reponame,
846 "repo": self.reponame,
835 "header": header,
847 "header": header,
836 "footer": footer,
848 "footer": footer,
837 "motd": motd,
849 "motd": motd,
838 "rawfileheader": rawfileheader,
850 "rawfileheader": rawfileheader,
839 "sessionvars": sessionvars
851 "sessionvars": sessionvars
840 })
852 })
841
853
842 try:
854 try:
843 if not req.form.has_key('cmd'):
855 if not req.form.has_key('cmd'):
844 req.form['cmd'] = [self.t.cache['default']]
856 req.form['cmd'] = [self.t.cache['default']]
845
857
846 cmd = req.form['cmd'][0]
858 cmd = req.form['cmd'][0]
847
859
848 method = getattr(self, 'do_' + cmd, None)
860 method = getattr(self, 'do_' + cmd, None)
849 if method:
861 if method:
850 try:
862 try:
851 method(req)
863 method(req)
852 except (hg.RepoError, revlog.RevlogError), inst:
864 except (hg.RepoError, revlog.RevlogError), inst:
853 req.write(self.t("error", error=str(inst)))
865 req.write(self.t("error", error=str(inst)))
854 else:
866 else:
855 req.write(self.t("error", error='No such method: ' + cmd))
867 req.write(self.t("error", error='No such method: ' + cmd))
856 finally:
868 finally:
857 self.t = None
869 self.t = None
858
870
859 def changectx(self, req):
871 def changectx(self, req):
860 if req.form.has_key('node'):
872 if req.form.has_key('node'):
861 changeid = req.form['node'][0]
873 changeid = req.form['node'][0]
862 elif req.form.has_key('manifest'):
874 elif req.form.has_key('manifest'):
863 changeid = req.form['manifest'][0]
875 changeid = req.form['manifest'][0]
864 else:
876 else:
865 changeid = self.repo.changelog.count() - 1
877 changeid = self.repo.changelog.count() - 1
866
878
867 try:
879 try:
868 ctx = self.repo.changectx(changeid)
880 ctx = self.repo.changectx(changeid)
869 except hg.RepoError:
881 except hg.RepoError:
870 man = self.repo.manifest
882 man = self.repo.manifest
871 mn = man.lookup(changeid)
883 mn = man.lookup(changeid)
872 ctx = self.repo.changectx(man.linkrev(mn))
884 ctx = self.repo.changectx(man.linkrev(mn))
873
885
874 return ctx
886 return ctx
875
887
876 def filectx(self, req):
888 def filectx(self, req):
877 path = self.cleanpath(req.form['file'][0])
889 path = self.cleanpath(req.form['file'][0])
878 if req.form.has_key('node'):
890 if req.form.has_key('node'):
879 changeid = req.form['node'][0]
891 changeid = req.form['node'][0]
880 else:
892 else:
881 changeid = req.form['filenode'][0]
893 changeid = req.form['filenode'][0]
882 try:
894 try:
883 ctx = self.repo.changectx(changeid)
895 ctx = self.repo.changectx(changeid)
884 fctx = ctx.filectx(path)
896 fctx = ctx.filectx(path)
885 except hg.RepoError:
897 except hg.RepoError:
886 fctx = self.repo.filectx(path, fileid=changeid)
898 fctx = self.repo.filectx(path, fileid=changeid)
887
899
888 return fctx
900 return fctx
889
901
890 def do_log(self, req):
902 def do_log(self, req):
891 if req.form.has_key('file') and req.form['file'][0]:
903 if req.form.has_key('file') and req.form['file'][0]:
892 self.do_filelog(req)
904 self.do_filelog(req)
893 else:
905 else:
894 self.do_changelog(req)
906 self.do_changelog(req)
895
907
896 def do_rev(self, req):
908 def do_rev(self, req):
897 self.do_changeset(req)
909 self.do_changeset(req)
898
910
899 def do_file(self, req):
911 def do_file(self, req):
900 path = self.cleanpath(req.form.get('file', [''])[0])
912 path = self.cleanpath(req.form.get('file', [''])[0])
901 if path:
913 if path:
902 try:
914 try:
903 req.write(self.filerevision(self.filectx(req)))
915 req.write(self.filerevision(self.filectx(req)))
904 return
916 return
905 except revlog.LookupError:
917 except revlog.LookupError:
906 pass
918 pass
907
919
908 req.write(self.manifest(self.changectx(req), path))
920 req.write(self.manifest(self.changectx(req), path))
909
921
910 def do_diff(self, req):
922 def do_diff(self, req):
911 self.do_filediff(req)
923 self.do_filediff(req)
912
924
913 def do_changelog(self, req, shortlog = False):
925 def do_changelog(self, req, shortlog = False):
914 if req.form.has_key('node'):
926 if req.form.has_key('node'):
915 ctx = self.changectx(req)
927 ctx = self.changectx(req)
916 else:
928 else:
917 if req.form.has_key('rev'):
929 if req.form.has_key('rev'):
918 hi = req.form['rev'][0]
930 hi = req.form['rev'][0]
919 else:
931 else:
920 hi = self.repo.changelog.count() - 1
932 hi = self.repo.changelog.count() - 1
921 try:
933 try:
922 ctx = self.repo.changectx(hi)
934 ctx = self.repo.changectx(hi)
923 except hg.RepoError:
935 except hg.RepoError:
924 req.write(self.search(hi)) # XXX redirect to 404 page?
936 req.write(self.search(hi)) # XXX redirect to 404 page?
925 return
937 return
926
938
927 req.write(self.changelog(ctx, shortlog = shortlog))
939 req.write(self.changelog(ctx, shortlog = shortlog))
928
940
929 def do_shortlog(self, req):
941 def do_shortlog(self, req):
930 self.do_changelog(req, shortlog = True)
942 self.do_changelog(req, shortlog = True)
931
943
932 def do_changeset(self, req):
944 def do_changeset(self, req):
933 req.write(self.changeset(self.changectx(req)))
945 req.write(self.changeset(self.changectx(req)))
934
946
935 def do_manifest(self, req):
947 def do_manifest(self, req):
936 req.write(self.manifest(self.changectx(req),
948 req.write(self.manifest(self.changectx(req),
937 self.cleanpath(req.form['path'][0])))
949 self.cleanpath(req.form['path'][0])))
938
950
939 def do_tags(self, req):
951 def do_tags(self, req):
940 req.write(self.tags())
952 req.write(self.tags())
941
953
942 def do_summary(self, req):
954 def do_summary(self, req):
943 req.write(self.summary())
955 req.write(self.summary())
944
956
945 def do_filediff(self, req):
957 def do_filediff(self, req):
946 req.write(self.filediff(self.filectx(req)))
958 req.write(self.filediff(self.filectx(req)))
947
959
948 def do_annotate(self, req):
960 def do_annotate(self, req):
949 req.write(self.fileannotate(self.filectx(req)))
961 req.write(self.fileannotate(self.filectx(req)))
950
962
951 def do_filelog(self, req):
963 def do_filelog(self, req):
952 req.write(self.filelog(self.filectx(req)))
964 req.write(self.filelog(self.filectx(req)))
953
965
954 def do_lookup(self, req):
966 def do_lookup(self, req):
955 try:
967 try:
956 r = hex(self.repo.lookup(req.form['key'][0]))
968 r = hex(self.repo.lookup(req.form['key'][0]))
957 success = 1
969 success = 1
958 except Exception,inst:
970 except Exception,inst:
959 r = str(inst)
971 r = str(inst)
960 success = 0
972 success = 0
961 resp = "%s %s\n" % (success, r)
973 resp = "%s %s\n" % (success, r)
962 req.httphdr("application/mercurial-0.1", length=len(resp))
974 req.httphdr("application/mercurial-0.1", length=len(resp))
963 req.write(resp)
975 req.write(resp)
964
976
965 def do_heads(self, req):
977 def do_heads(self, req):
966 resp = " ".join(map(hex, self.repo.heads())) + "\n"
978 resp = " ".join(map(hex, self.repo.heads())) + "\n"
967 req.httphdr("application/mercurial-0.1", length=len(resp))
979 req.httphdr("application/mercurial-0.1", length=len(resp))
968 req.write(resp)
980 req.write(resp)
969
981
970 def do_branches(self, req):
982 def do_branches(self, req):
971 nodes = []
983 nodes = []
972 if req.form.has_key('nodes'):
984 if req.form.has_key('nodes'):
973 nodes = map(bin, req.form['nodes'][0].split(" "))
985 nodes = map(bin, req.form['nodes'][0].split(" "))
974 resp = cStringIO.StringIO()
986 resp = cStringIO.StringIO()
975 for b in self.repo.branches(nodes):
987 for b in self.repo.branches(nodes):
976 resp.write(" ".join(map(hex, b)) + "\n")
988 resp.write(" ".join(map(hex, b)) + "\n")
977 resp = resp.getvalue()
989 resp = resp.getvalue()
978 req.httphdr("application/mercurial-0.1", length=len(resp))
990 req.httphdr("application/mercurial-0.1", length=len(resp))
979 req.write(resp)
991 req.write(resp)
980
992
981 def do_between(self, req):
993 def do_between(self, req):
982 if req.form.has_key('pairs'):
994 if req.form.has_key('pairs'):
983 pairs = [map(bin, p.split("-"))
995 pairs = [map(bin, p.split("-"))
984 for p in req.form['pairs'][0].split(" ")]
996 for p in req.form['pairs'][0].split(" ")]
985 resp = cStringIO.StringIO()
997 resp = cStringIO.StringIO()
986 for b in self.repo.between(pairs):
998 for b in self.repo.between(pairs):
987 resp.write(" ".join(map(hex, b)) + "\n")
999 resp.write(" ".join(map(hex, b)) + "\n")
988 resp = resp.getvalue()
1000 resp = resp.getvalue()
989 req.httphdr("application/mercurial-0.1", length=len(resp))
1001 req.httphdr("application/mercurial-0.1", length=len(resp))
990 req.write(resp)
1002 req.write(resp)
991
1003
992 def do_changegroup(self, req):
1004 def do_changegroup(self, req):
993 req.httphdr("application/mercurial-0.1")
1005 req.httphdr("application/mercurial-0.1")
994 nodes = []
1006 nodes = []
995 if not self.allowpull:
1007 if not self.allowpull:
996 return
1008 return
997
1009
998 if req.form.has_key('roots'):
1010 if req.form.has_key('roots'):
999 nodes = map(bin, req.form['roots'][0].split(" "))
1011 nodes = map(bin, req.form['roots'][0].split(" "))
1000
1012
1001 z = zlib.compressobj()
1013 z = zlib.compressobj()
1002 f = self.repo.changegroup(nodes, 'serve')
1014 f = self.repo.changegroup(nodes, 'serve')
1003 while 1:
1015 while 1:
1004 chunk = f.read(4096)
1016 chunk = f.read(4096)
1005 if not chunk:
1017 if not chunk:
1006 break
1018 break
1007 req.write(z.compress(chunk))
1019 req.write(z.compress(chunk))
1008
1020
1009 req.write(z.flush())
1021 req.write(z.flush())
1010
1022
1011 def do_changegroupsubset(self, req):
1023 def do_changegroupsubset(self, req):
1012 req.httphdr("application/mercurial-0.1")
1024 req.httphdr("application/mercurial-0.1")
1013 bases = []
1025 bases = []
1014 heads = []
1026 heads = []
1015 if not self.allowpull:
1027 if not self.allowpull:
1016 return
1028 return
1017
1029
1018 if req.form.has_key('bases'):
1030 if req.form.has_key('bases'):
1019 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1031 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1020 if req.form.has_key('heads'):
1032 if req.form.has_key('heads'):
1021 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1033 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1022
1034
1023 z = zlib.compressobj()
1035 z = zlib.compressobj()
1024 f = self.repo.changegroupsubset(bases, heads, 'serve')
1036 f = self.repo.changegroupsubset(bases, heads, 'serve')
1025 while 1:
1037 while 1:
1026 chunk = f.read(4096)
1038 chunk = f.read(4096)
1027 if not chunk:
1039 if not chunk:
1028 break
1040 break
1029 req.write(z.compress(chunk))
1041 req.write(z.compress(chunk))
1030
1042
1031 req.write(z.flush())
1043 req.write(z.flush())
1032
1044
1033 def do_archive(self, req):
1045 def do_archive(self, req):
1034 type_ = req.form['type'][0]
1046 type_ = req.form['type'][0]
1035 allowed = self.configlist("web", "allow_archive")
1047 allowed = self.configlist("web", "allow_archive")
1036 if (type_ in self.archives and (type_ in allowed or
1048 if (type_ in self.archives and (type_ in allowed or
1037 self.configbool("web", "allow" + type_, False))):
1049 self.configbool("web", "allow" + type_, False))):
1038 self.archive(req, req.form['node'][0], type_)
1050 self.archive(req, req.form['node'][0], type_)
1039 return
1051 return
1040
1052
1041 req.write(self.t("error"))
1053 req.write(self.t("error"))
1042
1054
1043 def do_static(self, req):
1055 def do_static(self, req):
1044 fname = req.form['file'][0]
1056 fname = req.form['file'][0]
1045 # a repo owner may set web.static in .hg/hgrc to get any file
1057 # a repo owner may set web.static in .hg/hgrc to get any file
1046 # readable by the user running the CGI script
1058 # readable by the user running the CGI script
1047 static = self.config("web", "static",
1059 static = self.config("web", "static",
1048 os.path.join(self.templatepath, "static"),
1060 os.path.join(self.templatepath, "static"),
1049 untrusted=False)
1061 untrusted=False)
1050 req.write(staticfile(static, fname, req)
1062 req.write(staticfile(static, fname, req)
1051 or self.t("error", error="%r not found" % fname))
1063 or self.t("error", error="%r not found" % fname))
1052
1064
1053 def do_capabilities(self, req):
1065 def do_capabilities(self, req):
1054 caps = ['lookup', 'changegroupsubset']
1066 caps = ['lookup', 'changegroupsubset']
1055 if self.configbool('server', 'uncompressed'):
1067 if self.configbool('server', 'uncompressed'):
1056 caps.append('stream=%d' % self.repo.changelog.version)
1068 caps.append('stream=%d' % self.repo.changelog.version)
1057 # XXX: make configurable and/or share code with do_unbundle:
1069 # XXX: make configurable and/or share code with do_unbundle:
1058 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1070 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1059 if unbundleversions:
1071 if unbundleversions:
1060 caps.append('unbundle=%s' % ','.join(unbundleversions))
1072 caps.append('unbundle=%s' % ','.join(unbundleversions))
1061 resp = ' '.join(caps)
1073 resp = ' '.join(caps)
1062 req.httphdr("application/mercurial-0.1", length=len(resp))
1074 req.httphdr("application/mercurial-0.1", length=len(resp))
1063 req.write(resp)
1075 req.write(resp)
1064
1076
1065 def check_perm(self, req, op, default):
1077 def check_perm(self, req, op, default):
1066 '''check permission for operation based on user auth.
1078 '''check permission for operation based on user auth.
1067 return true if op allowed, else false.
1079 return true if op allowed, else false.
1068 default is policy to use if no config given.'''
1080 default is policy to use if no config given.'''
1069
1081
1070 user = req.env.get('REMOTE_USER')
1082 user = req.env.get('REMOTE_USER')
1071
1083
1072 deny = self.configlist('web', 'deny_' + op)
1084 deny = self.configlist('web', 'deny_' + op)
1073 if deny and (not user or deny == ['*'] or user in deny):
1085 if deny and (not user or deny == ['*'] or user in deny):
1074 return False
1086 return False
1075
1087
1076 allow = self.configlist('web', 'allow_' + op)
1088 allow = self.configlist('web', 'allow_' + op)
1077 return (allow and (allow == ['*'] or user in allow)) or default
1089 return (allow and (allow == ['*'] or user in allow)) or default
1078
1090
1079 def do_unbundle(self, req):
1091 def do_unbundle(self, req):
1080 def bail(response, headers={}):
1092 def bail(response, headers={}):
1081 length = int(req.env['CONTENT_LENGTH'])
1093 length = int(req.env['CONTENT_LENGTH'])
1082 for s in util.filechunkiter(req, limit=length):
1094 for s in util.filechunkiter(req, limit=length):
1083 # drain incoming bundle, else client will not see
1095 # drain incoming bundle, else client will not see
1084 # response when run outside cgi script
1096 # response when run outside cgi script
1085 pass
1097 pass
1086 req.httphdr("application/mercurial-0.1", headers=headers)
1098 req.httphdr("application/mercurial-0.1", headers=headers)
1087 req.write('0\n')
1099 req.write('0\n')
1088 req.write(response)
1100 req.write(response)
1089
1101
1090 # require ssl by default, auth info cannot be sniffed and
1102 # require ssl by default, auth info cannot be sniffed and
1091 # replayed
1103 # replayed
1092 ssl_req = self.configbool('web', 'push_ssl', True)
1104 ssl_req = self.configbool('web', 'push_ssl', True)
1093 if ssl_req:
1105 if ssl_req:
1094 if req.env.get('wsgi.url_scheme') != 'https':
1106 if req.env.get('wsgi.url_scheme') != 'https':
1095 bail(_('ssl required\n'))
1107 bail(_('ssl required\n'))
1096 return
1108 return
1097 proto = 'https'
1109 proto = 'https'
1098 else:
1110 else:
1099 proto = 'http'
1111 proto = 'http'
1100
1112
1101 # do not allow push unless explicitly allowed
1113 # do not allow push unless explicitly allowed
1102 if not self.check_perm(req, 'push', False):
1114 if not self.check_perm(req, 'push', False):
1103 bail(_('push not authorized\n'),
1115 bail(_('push not authorized\n'),
1104 headers={'status': '401 Unauthorized'})
1116 headers={'status': '401 Unauthorized'})
1105 return
1117 return
1106
1118
1107 their_heads = req.form['heads'][0].split(' ')
1119 their_heads = req.form['heads'][0].split(' ')
1108
1120
1109 def check_heads():
1121 def check_heads():
1110 heads = map(hex, self.repo.heads())
1122 heads = map(hex, self.repo.heads())
1111 return their_heads == [hex('force')] or their_heads == heads
1123 return their_heads == [hex('force')] or their_heads == heads
1112
1124
1113 # fail early if possible
1125 # fail early if possible
1114 if not check_heads():
1126 if not check_heads():
1115 bail(_('unsynced changes\n'))
1127 bail(_('unsynced changes\n'))
1116 return
1128 return
1117
1129
1118 req.httphdr("application/mercurial-0.1")
1130 req.httphdr("application/mercurial-0.1")
1119
1131
1120 # do not lock repo until all changegroup data is
1132 # do not lock repo until all changegroup data is
1121 # streamed. save to temporary file.
1133 # streamed. save to temporary file.
1122
1134
1123 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1135 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1124 fp = os.fdopen(fd, 'wb+')
1136 fp = os.fdopen(fd, 'wb+')
1125 try:
1137 try:
1126 length = int(req.env['CONTENT_LENGTH'])
1138 length = int(req.env['CONTENT_LENGTH'])
1127 for s in util.filechunkiter(req, limit=length):
1139 for s in util.filechunkiter(req, limit=length):
1128 fp.write(s)
1140 fp.write(s)
1129
1141
1130 try:
1142 try:
1131 lock = self.repo.lock()
1143 lock = self.repo.lock()
1132 try:
1144 try:
1133 if not check_heads():
1145 if not check_heads():
1134 req.write('0\n')
1146 req.write('0\n')
1135 req.write(_('unsynced changes\n'))
1147 req.write(_('unsynced changes\n'))
1136 return
1148 return
1137
1149
1138 fp.seek(0)
1150 fp.seek(0)
1139 header = fp.read(6)
1151 header = fp.read(6)
1140 if not header.startswith("HG"):
1152 if not header.startswith("HG"):
1141 # old client with uncompressed bundle
1153 # old client with uncompressed bundle
1142 def generator(f):
1154 def generator(f):
1143 yield header
1155 yield header
1144 for chunk in f:
1156 for chunk in f:
1145 yield chunk
1157 yield chunk
1146 elif not header.startswith("HG10"):
1158 elif not header.startswith("HG10"):
1147 req.write("0\n")
1159 req.write("0\n")
1148 req.write(_("unknown bundle version\n"))
1160 req.write(_("unknown bundle version\n"))
1149 return
1161 return
1150 elif header == "HG10GZ":
1162 elif header == "HG10GZ":
1151 def generator(f):
1163 def generator(f):
1152 zd = zlib.decompressobj()
1164 zd = zlib.decompressobj()
1153 for chunk in f:
1165 for chunk in f:
1154 yield zd.decompress(chunk)
1166 yield zd.decompress(chunk)
1155 elif header == "HG10BZ":
1167 elif header == "HG10BZ":
1156 def generator(f):
1168 def generator(f):
1157 zd = bz2.BZ2Decompressor()
1169 zd = bz2.BZ2Decompressor()
1158 zd.decompress("BZ")
1170 zd.decompress("BZ")
1159 for chunk in f:
1171 for chunk in f:
1160 yield zd.decompress(chunk)
1172 yield zd.decompress(chunk)
1161 elif header == "HG10UN":
1173 elif header == "HG10UN":
1162 def generator(f):
1174 def generator(f):
1163 for chunk in f:
1175 for chunk in f:
1164 yield chunk
1176 yield chunk
1165 else:
1177 else:
1166 req.write("0\n")
1178 req.write("0\n")
1167 req.write(_("unknown bundle compression type\n"))
1179 req.write(_("unknown bundle compression type\n"))
1168 return
1180 return
1169 gen = generator(util.filechunkiter(fp, 4096))
1181 gen = generator(util.filechunkiter(fp, 4096))
1170
1182
1171 # send addchangegroup output to client
1183 # send addchangegroup output to client
1172
1184
1173 old_stdout = sys.stdout
1185 old_stdout = sys.stdout
1174 sys.stdout = cStringIO.StringIO()
1186 sys.stdout = cStringIO.StringIO()
1175
1187
1176 try:
1188 try:
1177 url = 'remote:%s:%s' % (proto,
1189 url = 'remote:%s:%s' % (proto,
1178 req.env.get('REMOTE_HOST', ''))
1190 req.env.get('REMOTE_HOST', ''))
1179 try:
1191 try:
1180 ret = self.repo.addchangegroup(
1192 ret = self.repo.addchangegroup(
1181 util.chunkbuffer(gen), 'serve', url)
1193 util.chunkbuffer(gen), 'serve', url)
1182 except util.Abort, inst:
1194 except util.Abort, inst:
1183 sys.stdout.write("abort: %s\n" % inst)
1195 sys.stdout.write("abort: %s\n" % inst)
1184 ret = 0
1196 ret = 0
1185 finally:
1197 finally:
1186 val = sys.stdout.getvalue()
1198 val = sys.stdout.getvalue()
1187 sys.stdout = old_stdout
1199 sys.stdout = old_stdout
1188 req.write('%d\n' % ret)
1200 req.write('%d\n' % ret)
1189 req.write(val)
1201 req.write(val)
1190 finally:
1202 finally:
1191 del lock
1203 del lock
1192 except (OSError, IOError), inst:
1204 except (OSError, IOError), inst:
1193 req.write('0\n')
1205 req.write('0\n')
1194 filename = getattr(inst, 'filename', '')
1206 filename = getattr(inst, 'filename', '')
1195 # Don't send our filesystem layout to the client
1207 # Don't send our filesystem layout to the client
1196 if filename.startswith(self.repo.root):
1208 if filename.startswith(self.repo.root):
1197 filename = filename[len(self.repo.root)+1:]
1209 filename = filename[len(self.repo.root)+1:]
1198 else:
1210 else:
1199 filename = ''
1211 filename = ''
1200 error = getattr(inst, 'strerror', 'Unknown error')
1212 error = getattr(inst, 'strerror', 'Unknown error')
1201 req.write('%s: %s\n' % (error, filename))
1213 req.write('%s: %s\n' % (error, filename))
1202 finally:
1214 finally:
1203 fp.close()
1215 fp.close()
1204 os.unlink(tempname)
1216 os.unlink(tempname)
1205
1217
1206 def do_stream_out(self, req):
1218 def do_stream_out(self, req):
1207 req.httphdr("application/mercurial-0.1")
1219 req.httphdr("application/mercurial-0.1")
1208 streamclone.stream_out(self.repo, req, untrusted=True)
1220 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,58 +1,58 b''
1 default = 'summary'
1 default = 'summary'
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 summary = summary.tmpl
6 summary = summary.tmpl
7 error = error.tmpl
7 error = error.tmpl
8 notfound = notfound.tmpl
8 notfound = notfound.tmpl
9 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
11 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
12 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
13 filenodelink = '<tr class="parity#parity#"><td><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">#file|escape#</a></td><td></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> | <a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a></td></tr>'
14 fileellipses = '...'
14 fileellipses = '...'
15 changelogentry = changelogentry.tmpl
15 changelogentry = changelogentry.tmpl
16 searchentry = changelogentry.tmpl
16 searchentry = changelogentry.tmpl
17 changeset = changeset.tmpl
17 changeset = changeset.tmpl
18 manifest = manifest.tmpl
18 manifest = manifest.tmpl
19 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
19 manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">manifest</a></td></tr>'
20 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#date|isodate#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
20 manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#date|isodate#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
21 filerevision = filerevision.tmpl
21 filerevision = filerevision.tmpl
22 fileannotate = fileannotate.tmpl
22 fileannotate = fileannotate.tmpl
23 filediff = filediff.tmpl
23 filediff = filediff.tmpl
24 filelog = filelog.tmpl
24 filelog = filelog.tmpl
25 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><span class="linenr"> #linenumber#</span> #line|escape#</pre></div>'
25 fileline = '<div style="font-family:monospace" class="parity#parity#"><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</pre></div>'
26 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 annotateline = '<tr style="font-family:monospace" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a></pre></td><td><pre>#line|escape#</pre></td></tr>'
27 difflineplus = '<div style="color:#008800;">#line|escape#</div>'
27 difflineplus = '<div style="color:#008800;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
28 difflineminus = '<div style="color:#cc0000;">#line|escape#</div>'
28 difflineminus = '<div style="color:#cc0000;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
29 difflineat = '<div style="color:#990099;">#line|escape#</div>'
29 difflineat = '<div style="color:#990099;"><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
30 diffline = '<div>#line|escape#</div>'
30 diffline = '<div><a class="linenr" href="##lineid#" id="#lineid#">#linenumber#</a> #line|escape#</div>'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
32 changesetparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
33 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
33 filerevparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 filerename = '{file|escape}@'
34 filerename = '{file|escape}@'
35 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
35 filelogrename = '| <a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">base</a>'
36 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
36 fileannotateparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
38 changesetchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
39 filerevchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
40 fileannotatechild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
41 tags = tags.tmpl
41 tags = tags.tmpl
42 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
42 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
43 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
43 branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
44 diffblock = '<pre>#lines#</pre>'
44 diffblock = '<pre>#lines#</pre>'
45 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
45 filediffparent = '<tr><td>parent {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
46 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
46 filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="{url}file/{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
47 filediffchild = '<tr><td>child {rev}</td><td style="font-family:monospace"><a class="list" href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
48 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 shortlog = shortlog.tmpl
49 shortlog = shortlog.tmpl
50 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
50 tagtag = '<span class="tagtag" title="{name}">{name}</span> '
51 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
51 branchtag = '<span class="branchtag" title="{name}">{name}</span> '
52 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
52 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author|person#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b> <span class="logtags">{branches%branchtag}{tags%tagtag}</span></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
53 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
53 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
54 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
54 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
55 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks">#archives%archiveentry#</td><td><a class="rss_logo" href="#url#rss-log">RSS</a> <a class="rss_logo" href="#url#atom-log">Atom</a></td></tr>'
55 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks">#archives%archiveentry#</td><td><a class="rss_logo" href="#url#rss-log">RSS</a> <a class="rss_logo" href="#url#atom-log">Atom</a></td></tr>'
56 index = index.tmpl
56 index = index.tmpl
57 urlparameter = '#separator##name#=#value|urlescape#'
57 urlparameter = '#separator##name#=#value|urlescape#'
58 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
58 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
@@ -1,56 +1,56 b''
1 default = 'shortlog'
1 default = 'shortlog'
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 shortlog = shortlog.tmpl
6 shortlog = shortlog.tmpl
7 shortlogentry = shortlogentry.tmpl
7 shortlogentry = shortlogentry.tmpl
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
8 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
9 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
10 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
11 filedifflink = '<a href="#url#diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
12 filenodelink = '<a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#</a> '
13 fileellipses = '...'
13 fileellipses = '...'
14 changelogentry = changelogentry.tmpl
14 changelogentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
15 searchentry = changelogentry.tmpl
16 changeset = changeset.tmpl
16 changeset = changeset.tmpl
17 manifest = manifest.tmpl
17 manifest = manifest.tmpl
18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
18 manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td>&nbsp;<td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt class="date">#date|isodate#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
19 manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt>&nbsp;<td align=right><tt class="date">#date|isodate#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
20 filerevision = filerevision.tmpl
20 filerevision = filerevision.tmpl
21 fileannotate = fileannotate.tmpl
21 fileannotate = fileannotate.tmpl
22 filediff = filediff.tmpl
22 filediff = filediff.tmpl
23 filelog = filelog.tmpl
23 filelog = filelog.tmpl
24 fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>'
24 fileline = '<div class="parity#parity#"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</div>'
25 filelogentry = filelogentry.tmpl
25 filelogentry = filelogentry.tmpl
26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>'
26 annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#author|obfuscate#@#rev#</a></td><td><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a></td><td><pre>#line|escape#</pre></td></tr>'
27 difflineplus = '<span class="plusline">#line|escape#</span>'
27 difflineplus = '<span class="plusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
28 difflineminus = '<span class="minusline">#line|escape#</span>'
28 difflineminus = '<span class="minusline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
29 difflineat = '<span class="atline">#line|escape#</span>'
29 difflineat = '<span class="atline"><a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#</span>'
30 diffline = '#line|escape#'
30 diffline = '<a class="lineno" href="##lineid#" id="#lineid#">#linenumber#</a>#line|escape#'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
31 changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
32 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
33 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
34 filerename = '{file|escape}@'
34 filerename = '{file|escape}@'
35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
35 filelogrename = '<tr><th>base:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#file|escape#@#node|short#</a></td></tr>'
36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
36 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
37 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
38 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
39 filerevchild = '<tr><td class="metatag">child:</td><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
40 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
41 tags = tags.tmpl
41 tags = tags.tmpl
42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
42 tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="#url#rev/#node|short#{sessionvars%urlparameter}">#tag|escape#</a></li>'
43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
43 diffblock = '<pre class="parity#parity#">#lines#</pre>'
44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
44 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
45 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
46 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
47 filelogparent = '<tr><th>parent #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
48 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="#url#rev/#node|short#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
49 filelogchild = '<tr><th>child #rev#:</th><td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
50 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> <a href="#url#atom-log">Atom</a> #archives%archiveentry#</td></tr>'
50 indexentry = '<tr class="parity#parity#"><td><a href="#url#{sessionvars%urlparameter}">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#rss-log">RSS</a> <a href="#url#atom-log">Atom</a> #archives%archiveentry#</td></tr>'
51 index = index.tmpl
51 index = index.tmpl
52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
52 archiveentry = '<a href="#url#archive/#node|short##extension|urlescape#">#type|escape#</a> '
53 notfound = notfound.tmpl
53 notfound = notfound.tmpl
54 error = error.tmpl
54 error = error.tmpl
55 urlparameter = '#separator##name#=#value|urlescape#'
55 urlparameter = '#separator##name#=#value|urlescape#'
56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
56 hiddenformentry = '<input type="hidden" name="#name#" value="#value|escape#" />'
General Comments 0
You need to be logged in to leave comments. Login now