##// END OF EJS Templates
Merged WSGI fixes from http://hg.omnifarious.org/~hopper/webmerc/
Thomas Arendsen Hein -
r2539:8a8d9ada merge default
parent child Browse files
Show More
@@ -0,0 +1,16 b''
1 #!/usr/bin/env python
2
3 __doc__ = """This does HTTP get requests given a host:port and path and returns
4 a subset of the headers plus the body of the result."""
5
6 import httplib, sys
7 headers = [h.lower() for h in sys.argv[3:]]
8 conn = httplib.HTTPConnection(sys.argv[1])
9 conn.request("GET", sys.argv[2])
10 response = conn.getresponse()
11 print response.status, response.reason
12 for h in headers:
13 if response.getheader(h, None) is not None:
14 print "%s: %s" % (h, response.getheader(h))
15 print
16 sys.stdout.write(response.read())
@@ -0,0 +1,103 b''
1 #!/bin/sh
2
3 hg init test
4
5 cat >hgweb.cgi <<HGWEB
6 #!/usr/bin/env python
7 #
8 # An example CGI script to use hgweb, edit as necessary
9
10 import cgitb, os, sys
11 cgitb.enable()
12
13 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
14 from mercurial import hgweb
15
16 h = hgweb.hgweb("test", "Empty test repository")
17 h.run()
18 HGWEB
19 chmod 755 hgweb.cgi
20
21 cat >hgweb.config <<HGWEBDIRCONF
22 [paths]
23 test = test
24 HGWEBDIRCONF
25
26 cat >hgwebdir.cgi <<HGWEBDIR
27 #!/usr/bin/env python
28 #
29 # An example CGI script to export multiple hgweb repos, edit as necessary
30
31 import cgitb, sys
32 cgitb.enable()
33
34 # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
35 from mercurial import hgweb
36
37 # The config file looks like this. You can have paths to individual
38 # repos, collections of repos in a directory tree, or both.
39 #
40 # [paths]
41 # virtual/path = /real/path
42 # virtual/path = /real/path
43 #
44 # [collections]
45 # /prefix/to/strip/off = /root/of/tree/full/of/repos
46 #
47 # collections example: say directory tree /foo contains repos /foo/bar,
48 # /foo/quux/baz. Give this config section:
49 # [collections]
50 # /foo = /foo
51 # Then repos will list as bar and quux/baz.
52
53 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
54 # or use a dictionary with entries like 'virtual/path': '/real/path'
55
56 h = hgweb.hgwebdir("hgweb.config")
57 h.run()
58 HGWEBDIR
59 chmod 755 hgwebdir.cgi
60
61 declare -x DOCUMENT_ROOT="/var/www/hg"
62 declare -x GATEWAY_INTERFACE="CGI/1.1"
63 declare -x HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
64 declare -x HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"
65 declare -x HTTP_ACCEPT_ENCODING="gzip,deflate"
66 declare -x HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"
67 declare -x HTTP_CACHE_CONTROL="max-age=0"
68 declare -x HTTP_CONNECTION="keep-alive"
69 declare -x HTTP_HOST="hg.omnifarious.org"
70 declare -x HTTP_KEEP_ALIVE="300"
71 declare -x HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"
72 declare -x OLDPWD
73 declare -x PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"
74 declare -x PATH_INFO="/"
75 declare -x PATH_TRANSLATED="/var/www/hg/index.html"
76 declare -x PWD="/home/hopper/hg_public"
77 declare -x QUERY_STRING=""
78 declare -x REMOTE_ADDR="127.0.0.2"
79 declare -x REMOTE_PORT="44703"
80 declare -x REQUEST_METHOD="GET"
81 declare -x REQUEST_URI="/test/"
82 declare -x SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"
83 declare -x SCRIPT_NAME="/test"
84 declare -x SCRIPT_URI="http://hg.omnifarious.org/test/"
85 declare -x SCRIPT_URL="/test/"
86 declare -x SERVER_ADDR="127.0.0.1"
87 declare -x SERVER_ADMIN="eric@localhost"
88 declare -x SERVER_NAME="hg.omnifarious.org"
89 declare -x SERVER_PORT="80"
90 declare -x SERVER_PROTOCOL="HTTP/1.1"
91 declare -x SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>\
92 "
93 declare -x SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"
94 ./hgweb.cgi >page1 2>&1 ; echo $?
95 ./hgwebdir.cgi >page2 2>&1 ; echo $?
96 PATH_INFO="/test/"
97 PATH_TRANSLATED="/var/something/test.cgi"
98 REQUEST_URI="/test/test/"
99 SCRIPT_URI="http://hg.omnifarious.org/test/test/"
100 SCRIPT_URL="/test/test/"
101 ./hgwebdir.cgi >page3 2>&1 ; echo $?
102 fgrep -i error page1 page2 page3 && exit 1
103 exit 0
@@ -0,0 +1,3 b''
1 0
2 0
3 0
@@ -0,0 +1,20 b''
1 #!/bin/sh
2
3 hg init test
4 cd test
5 cat >sometext.txt <<ENDSOME
6 This is just some random text
7 that will go inside the file and take a few lines.
8 It is very boring to read, but computers don't
9 care about things like that.
10 ENDSOME
11 hg add sometext.txt
12 hg commit -d "1 0" -m "Just some text"
13 hg serve -p 20059 -A access.log -E error.log -d --pid-file=hg.pid
14 ("$TESTDIR/get-with-headers.py" localhost:20059 '/?f=f165dc289438;file=sometext.txt;style=raw' content-type content-length content-disposition) >getoutput.txt &
15
16 sleep 5
17 kill `cat hg.pid`
18 sleep 1 # wait for server to scream and die
19 cat getoutput.txt
20 cat access.log error.log | sed 's/^\([^[]*\[\)[^]]*\(\].*\)$/\1date\2/g'
@@ -0,0 +1,10 b''
1 200 Script output follows
2 content-type: text/plain
3 content-length: 157
4 content-disposition: filename=sometext.txt
5
6 This is just some random text
7 that will go inside the file and take a few lines.
8 It is very boring to read, but computers don't
9 care about things like that.
10 localhost - - [date] "GET /?f=f165dc289438;file=sometext.txt;style=raw HTTP/1.1" 200 -
@@ -1,927 +1,943 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 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 import os.path
10 import os.path
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
15 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 from mercurial.node import *
16 from mercurial.node import *
17 from mercurial.i18n import gettext as _
17 from mercurial.i18n import gettext as _
18
18
19 def _up(p):
19 def _up(p):
20 if p[0] != "/":
20 if p[0] != "/":
21 p = "/" + p
21 p = "/" + p
22 if p[-1] == "/":
22 if p[-1] == "/":
23 p = p[:-1]
23 p = p[:-1]
24 up = os.path.dirname(p)
24 up = os.path.dirname(p)
25 if up == "/":
25 if up == "/":
26 return "/"
26 return "/"
27 return up + "/"
27 return up + "/"
28
28
29 class hgweb(object):
29 class hgweb(object):
30 def __init__(self, repo, name=None):
30 def __init__(self, repo, name=None):
31 if type(repo) == type(""):
31 if type(repo) == type(""):
32 self.repo = hg.repository(ui.ui(), repo)
32 self.repo = hg.repository(ui.ui(), repo)
33 else:
33 else:
34 self.repo = repo
34 self.repo = repo
35
35
36 self.mtime = -1
36 self.mtime = -1
37 self.reponame = name
37 self.reponame = name
38 self.archives = 'zip', 'gz', 'bz2'
38 self.archives = 'zip', 'gz', 'bz2'
39 self.templatepath = self.repo.ui.config("web", "templates",
39 self.templatepath = self.repo.ui.config("web", "templates",
40 templater.templatepath())
40 templater.templatepath())
41
41
42 def refresh(self):
42 def refresh(self):
43 mtime = get_mtime(self.repo.root)
43 mtime = get_mtime(self.repo.root)
44 if mtime != self.mtime:
44 if mtime != self.mtime:
45 self.mtime = mtime
45 self.mtime = mtime
46 self.repo = hg.repository(self.repo.ui, self.repo.root)
46 self.repo = hg.repository(self.repo.ui, self.repo.root)
47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
47 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
48 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
49 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
50
50
51 def archivelist(self, nodeid):
51 def archivelist(self, nodeid):
52 allowed = self.repo.ui.configlist("web", "allow_archive")
52 allowed = self.repo.ui.configlist("web", "allow_archive")
53 for i in self.archives:
53 for i in self.archives:
54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
54 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
55 yield {"type" : i, "node" : nodeid, "url": ""}
55 yield {"type" : i, "node" : nodeid, "url": ""}
56
56
57 def listfiles(self, files, mf):
57 def listfiles(self, files, mf):
58 for f in files[:self.maxfiles]:
58 for f in files[:self.maxfiles]:
59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
59 yield self.t("filenodelink", node=hex(mf[f]), file=f)
60 if len(files) > self.maxfiles:
60 if len(files) > self.maxfiles:
61 yield self.t("fileellipses")
61 yield self.t("fileellipses")
62
62
63 def listfilediffs(self, files, changeset):
63 def listfilediffs(self, files, changeset):
64 for f in files[:self.maxfiles]:
64 for f in files[:self.maxfiles]:
65 yield self.t("filedifflink", node=hex(changeset), file=f)
65 yield self.t("filedifflink", node=hex(changeset), file=f)
66 if len(files) > self.maxfiles:
66 if len(files) > self.maxfiles:
67 yield self.t("fileellipses")
67 yield self.t("fileellipses")
68
68
69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
69 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
70 if not rev:
70 if not rev:
71 rev = lambda x: ""
71 rev = lambda x: ""
72 siblings = [s for s in siblings if s != nullid]
72 siblings = [s for s in siblings if s != nullid]
73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
73 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
74 return
74 return
75 for s in siblings:
75 for s in siblings:
76 yield dict(node=hex(s), rev=rev(s), **args)
76 yield dict(node=hex(s), rev=rev(s), **args)
77
77
78 def renamelink(self, fl, node):
78 def renamelink(self, fl, node):
79 r = fl.renamed(node)
79 r = fl.renamed(node)
80 if r:
80 if r:
81 return [dict(file=r[0], node=hex(r[1]))]
81 return [dict(file=r[0], node=hex(r[1]))]
82 return []
82 return []
83
83
84 def showtag(self, t1, node=nullid, **args):
84 def showtag(self, t1, node=nullid, **args):
85 for t in self.repo.nodetags(node):
85 for t in self.repo.nodetags(node):
86 yield self.t(t1, tag=t, **args)
86 yield self.t(t1, tag=t, **args)
87
87
88 def diff(self, node1, node2, files):
88 def diff(self, node1, node2, files):
89 def filterfiles(filters, files):
89 def filterfiles(filters, files):
90 l = [x for x in files if x in filters]
90 l = [x for x in files if x in filters]
91
91
92 for t in filters:
92 for t in filters:
93 if t and t[-1] != os.sep:
93 if t and t[-1] != os.sep:
94 t += os.sep
94 t += os.sep
95 l += [x for x in files if x.startswith(t)]
95 l += [x for x in files if x.startswith(t)]
96 return l
96 return l
97
97
98 parity = [0]
98 parity = [0]
99 def diffblock(diff, f, fn):
99 def diffblock(diff, f, fn):
100 yield self.t("diffblock",
100 yield self.t("diffblock",
101 lines=prettyprintlines(diff),
101 lines=prettyprintlines(diff),
102 parity=parity[0],
102 parity=parity[0],
103 file=f,
103 file=f,
104 filenode=hex(fn or nullid))
104 filenode=hex(fn or nullid))
105 parity[0] = 1 - parity[0]
105 parity[0] = 1 - parity[0]
106
106
107 def prettyprintlines(diff):
107 def prettyprintlines(diff):
108 for l in diff.splitlines(1):
108 for l in diff.splitlines(1):
109 if l.startswith('+'):
109 if l.startswith('+'):
110 yield self.t("difflineplus", line=l)
110 yield self.t("difflineplus", line=l)
111 elif l.startswith('-'):
111 elif l.startswith('-'):
112 yield self.t("difflineminus", line=l)
112 yield self.t("difflineminus", line=l)
113 elif l.startswith('@'):
113 elif l.startswith('@'):
114 yield self.t("difflineat", line=l)
114 yield self.t("difflineat", line=l)
115 else:
115 else:
116 yield self.t("diffline", line=l)
116 yield self.t("diffline", line=l)
117
117
118 r = self.repo
118 r = self.repo
119 cl = r.changelog
119 cl = r.changelog
120 mf = r.manifest
120 mf = r.manifest
121 change1 = cl.read(node1)
121 change1 = cl.read(node1)
122 change2 = cl.read(node2)
122 change2 = cl.read(node2)
123 mmap1 = mf.read(change1[0])
123 mmap1 = mf.read(change1[0])
124 mmap2 = mf.read(change2[0])
124 mmap2 = mf.read(change2[0])
125 date1 = util.datestr(change1[2])
125 date1 = util.datestr(change1[2])
126 date2 = util.datestr(change2[2])
126 date2 = util.datestr(change2[2])
127
127
128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
128 modified, added, removed, deleted, unknown = r.changes(node1, node2)
129 if files:
129 if files:
130 modified, added, removed = map(lambda x: filterfiles(files, x),
130 modified, added, removed = map(lambda x: filterfiles(files, x),
131 (modified, added, removed))
131 (modified, added, removed))
132
132
133 diffopts = self.repo.ui.diffopts()
133 diffopts = self.repo.ui.diffopts()
134 showfunc = diffopts['showfunc']
134 showfunc = diffopts['showfunc']
135 ignorews = diffopts['ignorews']
135 ignorews = diffopts['ignorews']
136 for f in modified:
136 for f in modified:
137 to = r.file(f).read(mmap1[f])
137 to = r.file(f).read(mmap1[f])
138 tn = r.file(f).read(mmap2[f])
138 tn = r.file(f).read(mmap2[f])
139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
139 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
140 showfunc=showfunc, ignorews=ignorews), f, tn)
140 showfunc=showfunc, ignorews=ignorews), f, tn)
141 for f in added:
141 for f in added:
142 to = None
142 to = None
143 tn = r.file(f).read(mmap2[f])
143 tn = r.file(f).read(mmap2[f])
144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
144 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
145 showfunc=showfunc, ignorews=ignorews), f, tn)
145 showfunc=showfunc, ignorews=ignorews), f, tn)
146 for f in removed:
146 for f in removed:
147 to = r.file(f).read(mmap1[f])
147 to = r.file(f).read(mmap1[f])
148 tn = None
148 tn = None
149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 showfunc=showfunc, ignorews=ignorews), f, tn)
150 showfunc=showfunc, ignorews=ignorews), f, tn)
151
151
152 def changelog(self, pos):
152 def changelog(self, pos):
153 def changenav(**map):
153 def changenav(**map):
154 def seq(factor, maxchanges=None):
154 def seq(factor, maxchanges=None):
155 if maxchanges:
155 if maxchanges:
156 yield maxchanges
156 yield maxchanges
157 if maxchanges >= 20 and maxchanges <= 40:
157 if maxchanges >= 20 and maxchanges <= 40:
158 yield 50
158 yield 50
159 else:
159 else:
160 yield 1 * factor
160 yield 1 * factor
161 yield 3 * factor
161 yield 3 * factor
162 for f in seq(factor * 10):
162 for f in seq(factor * 10):
163 yield f
163 yield f
164
164
165 l = []
165 l = []
166 last = 0
166 last = 0
167 for f in seq(1, self.maxchanges):
167 for f in seq(1, self.maxchanges):
168 if f < self.maxchanges or f <= last:
168 if f < self.maxchanges or f <= last:
169 continue
169 continue
170 if f > count:
170 if f > count:
171 break
171 break
172 last = f
172 last = f
173 r = "%d" % f
173 r = "%d" % f
174 if pos + f < count:
174 if pos + f < count:
175 l.append(("+" + r, pos + f))
175 l.append(("+" + r, pos + f))
176 if pos - f >= 0:
176 if pos - f >= 0:
177 l.insert(0, ("-" + r, pos - f))
177 l.insert(0, ("-" + r, pos - f))
178
178
179 yield {"rev": 0, "label": "(0)"}
179 yield {"rev": 0, "label": "(0)"}
180
180
181 for label, rev in l:
181 for label, rev in l:
182 yield {"label": label, "rev": rev}
182 yield {"label": label, "rev": rev}
183
183
184 yield {"label": "tip", "rev": "tip"}
184 yield {"label": "tip", "rev": "tip"}
185
185
186 def changelist(**map):
186 def changelist(**map):
187 parity = (start - end) & 1
187 parity = (start - end) & 1
188 cl = self.repo.changelog
188 cl = self.repo.changelog
189 l = [] # build a list in forward order for efficiency
189 l = [] # build a list in forward order for efficiency
190 for i in range(start, end):
190 for i in range(start, end):
191 n = cl.node(i)
191 n = cl.node(i)
192 changes = cl.read(n)
192 changes = cl.read(n)
193 hn = hex(n)
193 hn = hex(n)
194
194
195 l.insert(0, {"parity": parity,
195 l.insert(0, {"parity": parity,
196 "author": changes[1],
196 "author": changes[1],
197 "parent": self.siblings(cl.parents(n), cl.rev,
197 "parent": self.siblings(cl.parents(n), cl.rev,
198 cl.rev(n) - 1),
198 cl.rev(n) - 1),
199 "child": self.siblings(cl.children(n), cl.rev,
199 "child": self.siblings(cl.children(n), cl.rev,
200 cl.rev(n) + 1),
200 cl.rev(n) + 1),
201 "changelogtag": self.showtag("changelogtag",n),
201 "changelogtag": self.showtag("changelogtag",n),
202 "manifest": hex(changes[0]),
202 "manifest": hex(changes[0]),
203 "desc": changes[4],
203 "desc": changes[4],
204 "date": changes[2],
204 "date": changes[2],
205 "files": self.listfilediffs(changes[3], n),
205 "files": self.listfilediffs(changes[3], n),
206 "rev": i,
206 "rev": i,
207 "node": hn})
207 "node": hn})
208 parity = 1 - parity
208 parity = 1 - parity
209
209
210 for e in l:
210 for e in l:
211 yield e
211 yield e
212
212
213 cl = self.repo.changelog
213 cl = self.repo.changelog
214 mf = cl.read(cl.tip())[0]
214 mf = cl.read(cl.tip())[0]
215 count = cl.count()
215 count = cl.count()
216 start = max(0, pos - self.maxchanges + 1)
216 start = max(0, pos - self.maxchanges + 1)
217 end = min(count, start + self.maxchanges)
217 end = min(count, start + self.maxchanges)
218 pos = end - 1
218 pos = end - 1
219
219
220 yield self.t('changelog',
220 yield self.t('changelog',
221 changenav=changenav,
221 changenav=changenav,
222 manifest=hex(mf),
222 manifest=hex(mf),
223 rev=pos, changesets=count, entries=changelist,
223 rev=pos, changesets=count, entries=changelist,
224 archives=self.archivelist("tip"))
224 archives=self.archivelist("tip"))
225
225
226 def search(self, query):
226 def search(self, query):
227
227
228 def changelist(**map):
228 def changelist(**map):
229 cl = self.repo.changelog
229 cl = self.repo.changelog
230 count = 0
230 count = 0
231 qw = query.lower().split()
231 qw = query.lower().split()
232
232
233 def revgen():
233 def revgen():
234 for i in range(cl.count() - 1, 0, -100):
234 for i in range(cl.count() - 1, 0, -100):
235 l = []
235 l = []
236 for j in range(max(0, i - 100), i):
236 for j in range(max(0, i - 100), i):
237 n = cl.node(j)
237 n = cl.node(j)
238 changes = cl.read(n)
238 changes = cl.read(n)
239 l.append((n, j, changes))
239 l.append((n, j, changes))
240 l.reverse()
240 l.reverse()
241 for e in l:
241 for e in l:
242 yield e
242 yield e
243
243
244 for n, i, changes in revgen():
244 for n, i, changes in revgen():
245 miss = 0
245 miss = 0
246 for q in qw:
246 for q in qw:
247 if not (q in changes[1].lower() or
247 if not (q in changes[1].lower() or
248 q in changes[4].lower() or
248 q in changes[4].lower() or
249 q in " ".join(changes[3][:20]).lower()):
249 q in " ".join(changes[3][:20]).lower()):
250 miss = 1
250 miss = 1
251 break
251 break
252 if miss:
252 if miss:
253 continue
253 continue
254
254
255 count += 1
255 count += 1
256 hn = hex(n)
256 hn = hex(n)
257
257
258 yield self.t('searchentry',
258 yield self.t('searchentry',
259 parity=count & 1,
259 parity=count & 1,
260 author=changes[1],
260 author=changes[1],
261 parent=self.siblings(cl.parents(n), cl.rev),
261 parent=self.siblings(cl.parents(n), cl.rev),
262 child=self.siblings(cl.children(n), cl.rev),
262 child=self.siblings(cl.children(n), cl.rev),
263 changelogtag=self.showtag("changelogtag",n),
263 changelogtag=self.showtag("changelogtag",n),
264 manifest=hex(changes[0]),
264 manifest=hex(changes[0]),
265 desc=changes[4],
265 desc=changes[4],
266 date=changes[2],
266 date=changes[2],
267 files=self.listfilediffs(changes[3], n),
267 files=self.listfilediffs(changes[3], n),
268 rev=i,
268 rev=i,
269 node=hn)
269 node=hn)
270
270
271 if count >= self.maxchanges:
271 if count >= self.maxchanges:
272 break
272 break
273
273
274 cl = self.repo.changelog
274 cl = self.repo.changelog
275 mf = cl.read(cl.tip())[0]
275 mf = cl.read(cl.tip())[0]
276
276
277 yield self.t('search',
277 yield self.t('search',
278 query=query,
278 query=query,
279 manifest=hex(mf),
279 manifest=hex(mf),
280 entries=changelist)
280 entries=changelist)
281
281
282 def changeset(self, nodeid):
282 def changeset(self, nodeid):
283 cl = self.repo.changelog
283 cl = self.repo.changelog
284 n = self.repo.lookup(nodeid)
284 n = self.repo.lookup(nodeid)
285 nodeid = hex(n)
285 nodeid = hex(n)
286 changes = cl.read(n)
286 changes = cl.read(n)
287 p1 = cl.parents(n)[0]
287 p1 = cl.parents(n)[0]
288
288
289 files = []
289 files = []
290 mf = self.repo.manifest.read(changes[0])
290 mf = self.repo.manifest.read(changes[0])
291 for f in changes[3]:
291 for f in changes[3]:
292 files.append(self.t("filenodelink",
292 files.append(self.t("filenodelink",
293 filenode=hex(mf.get(f, nullid)), file=f))
293 filenode=hex(mf.get(f, nullid)), file=f))
294
294
295 def diff(**map):
295 def diff(**map):
296 yield self.diff(p1, n, None)
296 yield self.diff(p1, n, None)
297
297
298 yield self.t('changeset',
298 yield self.t('changeset',
299 diff=diff,
299 diff=diff,
300 rev=cl.rev(n),
300 rev=cl.rev(n),
301 node=nodeid,
301 node=nodeid,
302 parent=self.siblings(cl.parents(n), cl.rev),
302 parent=self.siblings(cl.parents(n), cl.rev),
303 child=self.siblings(cl.children(n), cl.rev),
303 child=self.siblings(cl.children(n), cl.rev),
304 changesettag=self.showtag("changesettag",n),
304 changesettag=self.showtag("changesettag",n),
305 manifest=hex(changes[0]),
305 manifest=hex(changes[0]),
306 author=changes[1],
306 author=changes[1],
307 desc=changes[4],
307 desc=changes[4],
308 date=changes[2],
308 date=changes[2],
309 files=files,
309 files=files,
310 archives=self.archivelist(nodeid))
310 archives=self.archivelist(nodeid))
311
311
312 def filelog(self, f, filenode):
312 def filelog(self, f, filenode):
313 cl = self.repo.changelog
313 cl = self.repo.changelog
314 fl = self.repo.file(f)
314 fl = self.repo.file(f)
315 filenode = hex(fl.lookup(filenode))
315 filenode = hex(fl.lookup(filenode))
316 count = fl.count()
316 count = fl.count()
317
317
318 def entries(**map):
318 def entries(**map):
319 l = []
319 l = []
320 parity = (count - 1) & 1
320 parity = (count - 1) & 1
321
321
322 for i in range(count):
322 for i in range(count):
323 n = fl.node(i)
323 n = fl.node(i)
324 lr = fl.linkrev(n)
324 lr = fl.linkrev(n)
325 cn = cl.node(lr)
325 cn = cl.node(lr)
326 cs = cl.read(cl.node(lr))
326 cs = cl.read(cl.node(lr))
327
327
328 l.insert(0, {"parity": parity,
328 l.insert(0, {"parity": parity,
329 "filenode": hex(n),
329 "filenode": hex(n),
330 "filerev": i,
330 "filerev": i,
331 "file": f,
331 "file": f,
332 "node": hex(cn),
332 "node": hex(cn),
333 "author": cs[1],
333 "author": cs[1],
334 "date": cs[2],
334 "date": cs[2],
335 "rename": self.renamelink(fl, n),
335 "rename": self.renamelink(fl, n),
336 "parent": self.siblings(fl.parents(n),
336 "parent": self.siblings(fl.parents(n),
337 fl.rev, file=f),
337 fl.rev, file=f),
338 "child": self.siblings(fl.children(n),
338 "child": self.siblings(fl.children(n),
339 fl.rev, file=f),
339 fl.rev, file=f),
340 "desc": cs[4]})
340 "desc": cs[4]})
341 parity = 1 - parity
341 parity = 1 - parity
342
342
343 for e in l:
343 for e in l:
344 yield e
344 yield e
345
345
346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
346 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
347
347
348 def filerevision(self, f, node):
348 def filerevision(self, f, node):
349 fl = self.repo.file(f)
349 fl = self.repo.file(f)
350 n = fl.lookup(node)
350 n = fl.lookup(node)
351 node = hex(n)
351 node = hex(n)
352 text = fl.read(n)
352 text = fl.read(n)
353 changerev = fl.linkrev(n)
353 changerev = fl.linkrev(n)
354 cl = self.repo.changelog
354 cl = self.repo.changelog
355 cn = cl.node(changerev)
355 cn = cl.node(changerev)
356 cs = cl.read(cn)
356 cs = cl.read(cn)
357 mfn = cs[0]
357 mfn = cs[0]
358
358
359 mt = mimetypes.guess_type(f)[0]
359 mt = mimetypes.guess_type(f)[0]
360 rawtext = text
360 rawtext = text
361 if util.binary(text):
361 if util.binary(text):
362 mt = mt or 'application/octet-stream'
362 mt = mt or 'application/octet-stream'
363 text = "(binary:%s)" % mt
363 text = "(binary:%s)" % mt
364 mt = mt or 'text/plain'
364 mt = mt or 'text/plain'
365
365
366 def lines():
366 def lines():
367 for l, t in enumerate(text.splitlines(1)):
367 for l, t in enumerate(text.splitlines(1)):
368 yield {"line": t,
368 yield {"line": t,
369 "linenumber": "% 6d" % (l + 1),
369 "linenumber": "% 6d" % (l + 1),
370 "parity": l & 1}
370 "parity": l & 1}
371
371
372 yield self.t("filerevision",
372 yield self.t("filerevision",
373 file=f,
373 file=f,
374 filenode=node,
374 filenode=node,
375 path=_up(f),
375 path=_up(f),
376 text=lines(),
376 text=lines(),
377 raw=rawtext,
377 raw=rawtext,
378 mimetype=mt,
378 mimetype=mt,
379 rev=changerev,
379 rev=changerev,
380 node=hex(cn),
380 node=hex(cn),
381 manifest=hex(mfn),
381 manifest=hex(mfn),
382 author=cs[1],
382 author=cs[1],
383 date=cs[2],
383 date=cs[2],
384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
384 parent=self.siblings(fl.parents(n), fl.rev, file=f),
385 child=self.siblings(fl.children(n), fl.rev, file=f),
385 child=self.siblings(fl.children(n), fl.rev, file=f),
386 rename=self.renamelink(fl, n),
386 rename=self.renamelink(fl, n),
387 permissions=self.repo.manifest.readflags(mfn)[f])
387 permissions=self.repo.manifest.readflags(mfn)[f])
388
388
389 def fileannotate(self, f, node):
389 def fileannotate(self, f, node):
390 bcache = {}
390 bcache = {}
391 ncache = {}
391 ncache = {}
392 fl = self.repo.file(f)
392 fl = self.repo.file(f)
393 n = fl.lookup(node)
393 n = fl.lookup(node)
394 node = hex(n)
394 node = hex(n)
395 changerev = fl.linkrev(n)
395 changerev = fl.linkrev(n)
396
396
397 cl = self.repo.changelog
397 cl = self.repo.changelog
398 cn = cl.node(changerev)
398 cn = cl.node(changerev)
399 cs = cl.read(cn)
399 cs = cl.read(cn)
400 mfn = cs[0]
400 mfn = cs[0]
401
401
402 def annotate(**map):
402 def annotate(**map):
403 parity = 1
403 parity = 1
404 last = None
404 last = None
405 for r, l in fl.annotate(n):
405 for r, l in fl.annotate(n):
406 try:
406 try:
407 cnode = ncache[r]
407 cnode = ncache[r]
408 except KeyError:
408 except KeyError:
409 cnode = ncache[r] = self.repo.changelog.node(r)
409 cnode = ncache[r] = self.repo.changelog.node(r)
410
410
411 try:
411 try:
412 name = bcache[r]
412 name = bcache[r]
413 except KeyError:
413 except KeyError:
414 cl = self.repo.changelog.read(cnode)
414 cl = self.repo.changelog.read(cnode)
415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
415 bcache[r] = name = self.repo.ui.shortuser(cl[1])
416
416
417 if last != cnode:
417 if last != cnode:
418 parity = 1 - parity
418 parity = 1 - parity
419 last = cnode
419 last = cnode
420
420
421 yield {"parity": parity,
421 yield {"parity": parity,
422 "node": hex(cnode),
422 "node": hex(cnode),
423 "rev": r,
423 "rev": r,
424 "author": name,
424 "author": name,
425 "file": f,
425 "file": f,
426 "line": l}
426 "line": l}
427
427
428 yield self.t("fileannotate",
428 yield self.t("fileannotate",
429 file=f,
429 file=f,
430 filenode=node,
430 filenode=node,
431 annotate=annotate,
431 annotate=annotate,
432 path=_up(f),
432 path=_up(f),
433 rev=changerev,
433 rev=changerev,
434 node=hex(cn),
434 node=hex(cn),
435 manifest=hex(mfn),
435 manifest=hex(mfn),
436 author=cs[1],
436 author=cs[1],
437 date=cs[2],
437 date=cs[2],
438 rename=self.renamelink(fl, n),
438 rename=self.renamelink(fl, n),
439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
439 parent=self.siblings(fl.parents(n), fl.rev, file=f),
440 child=self.siblings(fl.children(n), fl.rev, file=f),
440 child=self.siblings(fl.children(n), fl.rev, file=f),
441 permissions=self.repo.manifest.readflags(mfn)[f])
441 permissions=self.repo.manifest.readflags(mfn)[f])
442
442
443 def manifest(self, mnode, path):
443 def manifest(self, mnode, path):
444 man = self.repo.manifest
444 man = self.repo.manifest
445 mn = man.lookup(mnode)
445 mn = man.lookup(mnode)
446 mnode = hex(mn)
446 mnode = hex(mn)
447 mf = man.read(mn)
447 mf = man.read(mn)
448 rev = man.rev(mn)
448 rev = man.rev(mn)
449 changerev = man.linkrev(mn)
449 changerev = man.linkrev(mn)
450 node = self.repo.changelog.node(changerev)
450 node = self.repo.changelog.node(changerev)
451 mff = man.readflags(mn)
451 mff = man.readflags(mn)
452
452
453 files = {}
453 files = {}
454
454
455 p = path[1:]
455 p = path[1:]
456 if p and p[-1] != "/":
456 if p and p[-1] != "/":
457 p += "/"
457 p += "/"
458 l = len(p)
458 l = len(p)
459
459
460 for f,n in mf.items():
460 for f,n in mf.items():
461 if f[:l] != p:
461 if f[:l] != p:
462 continue
462 continue
463 remain = f[l:]
463 remain = f[l:]
464 if "/" in remain:
464 if "/" in remain:
465 short = remain[:remain.find("/") + 1] # bleah
465 short = remain[:remain.find("/") + 1] # bleah
466 files[short] = (f, None)
466 files[short] = (f, None)
467 else:
467 else:
468 short = os.path.basename(remain)
468 short = os.path.basename(remain)
469 files[short] = (f, n)
469 files[short] = (f, n)
470
470
471 def filelist(**map):
471 def filelist(**map):
472 parity = 0
472 parity = 0
473 fl = files.keys()
473 fl = files.keys()
474 fl.sort()
474 fl.sort()
475 for f in fl:
475 for f in fl:
476 full, fnode = files[f]
476 full, fnode = files[f]
477 if not fnode:
477 if not fnode:
478 continue
478 continue
479
479
480 yield {"file": full,
480 yield {"file": full,
481 "manifest": mnode,
481 "manifest": mnode,
482 "filenode": hex(fnode),
482 "filenode": hex(fnode),
483 "parity": parity,
483 "parity": parity,
484 "basename": f,
484 "basename": f,
485 "permissions": mff[full]}
485 "permissions": mff[full]}
486 parity = 1 - parity
486 parity = 1 - parity
487
487
488 def dirlist(**map):
488 def dirlist(**map):
489 parity = 0
489 parity = 0
490 fl = files.keys()
490 fl = files.keys()
491 fl.sort()
491 fl.sort()
492 for f in fl:
492 for f in fl:
493 full, fnode = files[f]
493 full, fnode = files[f]
494 if fnode:
494 if fnode:
495 continue
495 continue
496
496
497 yield {"parity": parity,
497 yield {"parity": parity,
498 "path": os.path.join(path, f),
498 "path": os.path.join(path, f),
499 "manifest": mnode,
499 "manifest": mnode,
500 "basename": f[:-1]}
500 "basename": f[:-1]}
501 parity = 1 - parity
501 parity = 1 - parity
502
502
503 yield self.t("manifest",
503 yield self.t("manifest",
504 manifest=mnode,
504 manifest=mnode,
505 rev=rev,
505 rev=rev,
506 node=hex(node),
506 node=hex(node),
507 path=path,
507 path=path,
508 up=_up(path),
508 up=_up(path),
509 fentries=filelist,
509 fentries=filelist,
510 dentries=dirlist,
510 dentries=dirlist,
511 archives=self.archivelist(hex(node)))
511 archives=self.archivelist(hex(node)))
512
512
513 def tags(self):
513 def tags(self):
514 cl = self.repo.changelog
514 cl = self.repo.changelog
515 mf = cl.read(cl.tip())[0]
515 mf = cl.read(cl.tip())[0]
516
516
517 i = self.repo.tagslist()
517 i = self.repo.tagslist()
518 i.reverse()
518 i.reverse()
519
519
520 def entries(notip=False, **map):
520 def entries(notip=False, **map):
521 parity = 0
521 parity = 0
522 for k,n in i:
522 for k,n in i:
523 if notip and k == "tip": continue
523 if notip and k == "tip": continue
524 yield {"parity": parity,
524 yield {"parity": parity,
525 "tag": k,
525 "tag": k,
526 "tagmanifest": hex(cl.read(n)[0]),
526 "tagmanifest": hex(cl.read(n)[0]),
527 "date": cl.read(n)[2],
527 "date": cl.read(n)[2],
528 "node": hex(n)}
528 "node": hex(n)}
529 parity = 1 - parity
529 parity = 1 - parity
530
530
531 yield self.t("tags",
531 yield self.t("tags",
532 manifest=hex(mf),
532 manifest=hex(mf),
533 entries=lambda **x: entries(False, **x),
533 entries=lambda **x: entries(False, **x),
534 entriesnotip=lambda **x: entries(True, **x))
534 entriesnotip=lambda **x: entries(True, **x))
535
535
536 def summary(self):
536 def summary(self):
537 cl = self.repo.changelog
537 cl = self.repo.changelog
538 mf = cl.read(cl.tip())[0]
538 mf = cl.read(cl.tip())[0]
539
539
540 i = self.repo.tagslist()
540 i = self.repo.tagslist()
541 i.reverse()
541 i.reverse()
542
542
543 def tagentries(**map):
543 def tagentries(**map):
544 parity = 0
544 parity = 0
545 count = 0
545 count = 0
546 for k,n in i:
546 for k,n in i:
547 if k == "tip": # skip tip
547 if k == "tip": # skip tip
548 continue;
548 continue;
549
549
550 count += 1
550 count += 1
551 if count > 10: # limit to 10 tags
551 if count > 10: # limit to 10 tags
552 break;
552 break;
553
553
554 c = cl.read(n)
554 c = cl.read(n)
555 m = c[0]
555 m = c[0]
556 t = c[2]
556 t = c[2]
557
557
558 yield self.t("tagentry",
558 yield self.t("tagentry",
559 parity = parity,
559 parity = parity,
560 tag = k,
560 tag = k,
561 node = hex(n),
561 node = hex(n),
562 date = t,
562 date = t,
563 tagmanifest = hex(m))
563 tagmanifest = hex(m))
564 parity = 1 - parity
564 parity = 1 - parity
565
565
566 def changelist(**map):
566 def changelist(**map):
567 parity = 0
567 parity = 0
568 cl = self.repo.changelog
568 cl = self.repo.changelog
569 l = [] # build a list in forward order for efficiency
569 l = [] # build a list in forward order for efficiency
570 for i in range(start, end):
570 for i in range(start, end):
571 n = cl.node(i)
571 n = cl.node(i)
572 changes = cl.read(n)
572 changes = cl.read(n)
573 hn = hex(n)
573 hn = hex(n)
574 t = changes[2]
574 t = changes[2]
575
575
576 l.insert(0, self.t(
576 l.insert(0, self.t(
577 'shortlogentry',
577 'shortlogentry',
578 parity = parity,
578 parity = parity,
579 author = changes[1],
579 author = changes[1],
580 manifest = hex(changes[0]),
580 manifest = hex(changes[0]),
581 desc = changes[4],
581 desc = changes[4],
582 date = t,
582 date = t,
583 rev = i,
583 rev = i,
584 node = hn))
584 node = hn))
585 parity = 1 - parity
585 parity = 1 - parity
586
586
587 yield l
587 yield l
588
588
589 cl = self.repo.changelog
589 cl = self.repo.changelog
590 mf = cl.read(cl.tip())[0]
590 mf = cl.read(cl.tip())[0]
591 count = cl.count()
591 count = cl.count()
592 start = max(0, count - self.maxchanges)
592 start = max(0, count - self.maxchanges)
593 end = min(count, start + self.maxchanges)
593 end = min(count, start + self.maxchanges)
594
594
595 yield self.t("summary",
595 yield self.t("summary",
596 desc = self.repo.ui.config("web", "description", "unknown"),
596 desc = self.repo.ui.config("web", "description", "unknown"),
597 owner = (self.repo.ui.config("ui", "username") or # preferred
597 owner = (self.repo.ui.config("ui", "username") or # preferred
598 self.repo.ui.config("web", "contact") or # deprecated
598 self.repo.ui.config("web", "contact") or # deprecated
599 self.repo.ui.config("web", "author", "unknown")), # also
599 self.repo.ui.config("web", "author", "unknown")), # also
600 lastchange = (0, 0), # FIXME
600 lastchange = (0, 0), # FIXME
601 manifest = hex(mf),
601 manifest = hex(mf),
602 tags = tagentries,
602 tags = tagentries,
603 shortlog = changelist)
603 shortlog = changelist)
604
604
605 def filediff(self, file, changeset):
605 def filediff(self, file, changeset):
606 cl = self.repo.changelog
606 cl = self.repo.changelog
607 n = self.repo.lookup(changeset)
607 n = self.repo.lookup(changeset)
608 changeset = hex(n)
608 changeset = hex(n)
609 p1 = cl.parents(n)[0]
609 p1 = cl.parents(n)[0]
610 cs = cl.read(n)
610 cs = cl.read(n)
611 mf = self.repo.manifest.read(cs[0])
611 mf = self.repo.manifest.read(cs[0])
612
612
613 def diff(**map):
613 def diff(**map):
614 yield self.diff(p1, n, [file])
614 yield self.diff(p1, n, [file])
615
615
616 yield self.t("filediff",
616 yield self.t("filediff",
617 file=file,
617 file=file,
618 filenode=hex(mf.get(file, nullid)),
618 filenode=hex(mf.get(file, nullid)),
619 node=changeset,
619 node=changeset,
620 rev=self.repo.changelog.rev(n),
620 rev=self.repo.changelog.rev(n),
621 parent=self.siblings(cl.parents(n), cl.rev),
621 parent=self.siblings(cl.parents(n), cl.rev),
622 child=self.siblings(cl.children(n), cl.rev),
622 child=self.siblings(cl.children(n), cl.rev),
623 diff=diff)
623 diff=diff)
624
624
625 archive_specs = {
625 archive_specs = {
626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
626 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
627 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
628 'zip': ('application/zip', 'zip', '.zip', None),
628 'zip': ('application/zip', 'zip', '.zip', None),
629 }
629 }
630
630
631 def archive(self, req, cnode, type_):
631 def archive(self, req, cnode, type_):
632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
632 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
633 name = "%s-%s" % (reponame, short(cnode))
633 name = "%s-%s" % (reponame, short(cnode))
634 mimetype, artype, extension, encoding = self.archive_specs[type_]
634 mimetype, artype, extension, encoding = self.archive_specs[type_]
635 headers = [('Content-type', mimetype),
635 headers = [('Content-type', mimetype),
636 ('Content-disposition', 'attachment; filename=%s%s' %
636 ('Content-disposition', 'attachment; filename=%s%s' %
637 (name, extension))]
637 (name, extension))]
638 if encoding:
638 if encoding:
639 headers.append(('Content-encoding', encoding))
639 headers.append(('Content-encoding', encoding))
640 req.header(headers)
640 req.header(headers)
641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
641 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
642
642
643 # add tags to things
643 # add tags to things
644 # tags -> list of changesets corresponding to tags
644 # tags -> list of changesets corresponding to tags
645 # find tag, changeset, file
645 # find tag, changeset, file
646
646
647 def cleanpath(self, path):
647 def cleanpath(self, path):
648 p = util.normpath(path)
648 p = util.normpath(path)
649 if p[:2] == "..":
649 if p[:2] == "..":
650 raise Exception("suspicious path")
650 raise Exception("suspicious path")
651 return p
651 return p
652
652
653 def run(self, req):
653 def run(self):
654 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
655 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
656 import mercurial.hgweb.wsgicgi as wsgicgi
657 from request import wsgiapplication
658 def make_web_app():
659 return self
660 wsgicgi.launch(wsgiapplication(make_web_app))
661
662 def run_wsgi(self, req):
654 def header(**map):
663 def header(**map):
655 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
664 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
656 msg = mimetools.Message(header_file, 0)
665 msg = mimetools.Message(header_file, 0)
657 req.header(msg.items())
666 req.header(msg.items())
658 yield header_file.read()
667 yield header_file.read()
659
668
669 def rawfileheader(**map):
670 req.header([('Content-type', map['mimetype']),
671 ('Content-disposition', 'filename=%s' % map['file']),
672 ('Content-length', str(len(map['raw'])))])
673 yield ''
674
660 def footer(**map):
675 def footer(**map):
661 yield self.t("footer",
676 yield self.t("footer",
662 motd=self.repo.ui.config("web", "motd", ""),
677 motd=self.repo.ui.config("web", "motd", ""),
663 **map)
678 **map)
664
679
665 def expand_form(form):
680 def expand_form(form):
666 shortcuts = {
681 shortcuts = {
667 'cl': [('cmd', ['changelog']), ('rev', None)],
682 'cl': [('cmd', ['changelog']), ('rev', None)],
668 'cs': [('cmd', ['changeset']), ('node', None)],
683 'cs': [('cmd', ['changeset']), ('node', None)],
669 'f': [('cmd', ['file']), ('filenode', None)],
684 'f': [('cmd', ['file']), ('filenode', None)],
670 'fl': [('cmd', ['filelog']), ('filenode', None)],
685 'fl': [('cmd', ['filelog']), ('filenode', None)],
671 'fd': [('cmd', ['filediff']), ('node', None)],
686 'fd': [('cmd', ['filediff']), ('node', None)],
672 'fa': [('cmd', ['annotate']), ('filenode', None)],
687 'fa': [('cmd', ['annotate']), ('filenode', None)],
673 'mf': [('cmd', ['manifest']), ('manifest', None)],
688 'mf': [('cmd', ['manifest']), ('manifest', None)],
674 'ca': [('cmd', ['archive']), ('node', None)],
689 'ca': [('cmd', ['archive']), ('node', None)],
675 'tags': [('cmd', ['tags'])],
690 'tags': [('cmd', ['tags'])],
676 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
691 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
677 'static': [('cmd', ['static']), ('file', None)]
692 'static': [('cmd', ['static']), ('file', None)]
678 }
693 }
679
694
680 for k in shortcuts.iterkeys():
695 for k in shortcuts.iterkeys():
681 if form.has_key(k):
696 if form.has_key(k):
682 for name, value in shortcuts[k]:
697 for name, value in shortcuts[k]:
683 if value is None:
698 if value is None:
684 value = form[k]
699 value = form[k]
685 form[name] = value
700 form[name] = value
686 del form[k]
701 del form[k]
687
702
688 self.refresh()
703 self.refresh()
689
704
690 expand_form(req.form)
705 expand_form(req.form)
691
706
692 m = os.path.join(self.templatepath, "map")
707 m = os.path.join(self.templatepath, "map")
693 style = self.repo.ui.config("web", "style", "")
708 style = self.repo.ui.config("web", "style", "")
694 if req.form.has_key('style'):
709 if req.form.has_key('style'):
695 style = req.form['style'][0]
710 style = req.form['style'][0]
696 if style:
711 if style:
697 b = os.path.basename("map-" + style)
712 b = os.path.basename("map-" + style)
698 p = os.path.join(self.templatepath, b)
713 p = os.path.join(self.templatepath, b)
699 if os.path.isfile(p):
714 if os.path.isfile(p):
700 m = p
715 m = p
701
716
702 port = req.env["SERVER_PORT"]
717 port = req.env["SERVER_PORT"]
703 port = port != "80" and (":" + port) or ""
718 port = port != "80" and (":" + port) or ""
704 uri = req.env["REQUEST_URI"]
719 uri = req.env["REQUEST_URI"]
705 if "?" in uri:
720 if "?" in uri:
706 uri = uri.split("?")[0]
721 uri = uri.split("?")[0]
707 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
722 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
708 if not self.reponame:
723 if not self.reponame:
709 self.reponame = (self.repo.ui.config("web", "name")
724 self.reponame = (self.repo.ui.config("web", "name")
710 or uri.strip('/') or self.repo.root)
725 or uri.strip('/') or self.repo.root)
711
726
712 self.t = templater.templater(m, templater.common_filters,
727 self.t = templater.templater(m, templater.common_filters,
713 defaults={"url": url,
728 defaults={"url": url,
714 "repo": self.reponame,
729 "repo": self.reponame,
715 "header": header,
730 "header": header,
716 "footer": footer,
731 "footer": footer,
732 "rawfileheader": rawfileheader,
717 })
733 })
718
734
719 if not req.form.has_key('cmd'):
735 if not req.form.has_key('cmd'):
720 req.form['cmd'] = [self.t.cache['default'],]
736 req.form['cmd'] = [self.t.cache['default'],]
721
737
722 cmd = req.form['cmd'][0]
738 cmd = req.form['cmd'][0]
723
739
724 method = getattr(self, 'do_' + cmd, None)
740 method = getattr(self, 'do_' + cmd, None)
725 if method:
741 if method:
726 method(req)
742 method(req)
727 else:
743 else:
728 req.write(self.t("error"))
744 req.write(self.t("error"))
729
745
730 def do_changelog(self, req):
746 def do_changelog(self, req):
731 hi = self.repo.changelog.count() - 1
747 hi = self.repo.changelog.count() - 1
732 if req.form.has_key('rev'):
748 if req.form.has_key('rev'):
733 hi = req.form['rev'][0]
749 hi = req.form['rev'][0]
734 try:
750 try:
735 hi = self.repo.changelog.rev(self.repo.lookup(hi))
751 hi = self.repo.changelog.rev(self.repo.lookup(hi))
736 except hg.RepoError:
752 except hg.RepoError:
737 req.write(self.search(hi)) # XXX redirect to 404 page?
753 req.write(self.search(hi)) # XXX redirect to 404 page?
738 return
754 return
739
755
740 req.write(self.changelog(hi))
756 req.write(self.changelog(hi))
741
757
742 def do_changeset(self, req):
758 def do_changeset(self, req):
743 req.write(self.changeset(req.form['node'][0]))
759 req.write(self.changeset(req.form['node'][0]))
744
760
745 def do_manifest(self, req):
761 def do_manifest(self, req):
746 req.write(self.manifest(req.form['manifest'][0],
762 req.write(self.manifest(req.form['manifest'][0],
747 self.cleanpath(req.form['path'][0])))
763 self.cleanpath(req.form['path'][0])))
748
764
749 def do_tags(self, req):
765 def do_tags(self, req):
750 req.write(self.tags())
766 req.write(self.tags())
751
767
752 def do_summary(self, req):
768 def do_summary(self, req):
753 req.write(self.summary())
769 req.write(self.summary())
754
770
755 def do_filediff(self, req):
771 def do_filediff(self, req):
756 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
772 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
757 req.form['node'][0]))
773 req.form['node'][0]))
758
774
759 def do_file(self, req):
775 def do_file(self, req):
760 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
776 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
761 req.form['filenode'][0]))
777 req.form['filenode'][0]))
762
778
763 def do_annotate(self, req):
779 def do_annotate(self, req):
764 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
780 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
765 req.form['filenode'][0]))
781 req.form['filenode'][0]))
766
782
767 def do_filelog(self, req):
783 def do_filelog(self, req):
768 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
784 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
769 req.form['filenode'][0]))
785 req.form['filenode'][0]))
770
786
771 def do_heads(self, req):
787 def do_heads(self, req):
772 resp = " ".join(map(hex, self.repo.heads())) + "\n"
788 resp = " ".join(map(hex, self.repo.heads())) + "\n"
773 req.httphdr("application/mercurial-0.1", length=len(resp))
789 req.httphdr("application/mercurial-0.1", length=len(resp))
774 req.write(resp)
790 req.write(resp)
775
791
776 def do_branches(self, req):
792 def do_branches(self, req):
777 nodes = []
793 nodes = []
778 if req.form.has_key('nodes'):
794 if req.form.has_key('nodes'):
779 nodes = map(bin, req.form['nodes'][0].split(" "))
795 nodes = map(bin, req.form['nodes'][0].split(" "))
780 resp = cStringIO.StringIO()
796 resp = cStringIO.StringIO()
781 for b in self.repo.branches(nodes):
797 for b in self.repo.branches(nodes):
782 resp.write(" ".join(map(hex, b)) + "\n")
798 resp.write(" ".join(map(hex, b)) + "\n")
783 resp = resp.getvalue()
799 resp = resp.getvalue()
784 req.httphdr("application/mercurial-0.1", length=len(resp))
800 req.httphdr("application/mercurial-0.1", length=len(resp))
785 req.write(resp)
801 req.write(resp)
786
802
787 def do_between(self, req):
803 def do_between(self, req):
788 nodes = []
804 nodes = []
789 if req.form.has_key('pairs'):
805 if req.form.has_key('pairs'):
790 pairs = [map(bin, p.split("-"))
806 pairs = [map(bin, p.split("-"))
791 for p in req.form['pairs'][0].split(" ")]
807 for p in req.form['pairs'][0].split(" ")]
792 resp = cStringIO.StringIO()
808 resp = cStringIO.StringIO()
793 for b in self.repo.between(pairs):
809 for b in self.repo.between(pairs):
794 resp.write(" ".join(map(hex, b)) + "\n")
810 resp.write(" ".join(map(hex, b)) + "\n")
795 resp = resp.getvalue()
811 resp = resp.getvalue()
796 req.httphdr("application/mercurial-0.1", length=len(resp))
812 req.httphdr("application/mercurial-0.1", length=len(resp))
797 req.write(resp)
813 req.write(resp)
798
814
799 def do_changegroup(self, req):
815 def do_changegroup(self, req):
800 req.httphdr("application/mercurial-0.1")
816 req.httphdr("application/mercurial-0.1")
801 nodes = []
817 nodes = []
802 if not self.allowpull:
818 if not self.allowpull:
803 return
819 return
804
820
805 if req.form.has_key('roots'):
821 if req.form.has_key('roots'):
806 nodes = map(bin, req.form['roots'][0].split(" "))
822 nodes = map(bin, req.form['roots'][0].split(" "))
807
823
808 z = zlib.compressobj()
824 z = zlib.compressobj()
809 f = self.repo.changegroup(nodes, 'serve')
825 f = self.repo.changegroup(nodes, 'serve')
810 while 1:
826 while 1:
811 chunk = f.read(4096)
827 chunk = f.read(4096)
812 if not chunk:
828 if not chunk:
813 break
829 break
814 req.write(z.compress(chunk))
830 req.write(z.compress(chunk))
815
831
816 req.write(z.flush())
832 req.write(z.flush())
817
833
818 def do_archive(self, req):
834 def do_archive(self, req):
819 changeset = self.repo.lookup(req.form['node'][0])
835 changeset = self.repo.lookup(req.form['node'][0])
820 type_ = req.form['type'][0]
836 type_ = req.form['type'][0]
821 allowed = self.repo.ui.configlist("web", "allow_archive")
837 allowed = self.repo.ui.configlist("web", "allow_archive")
822 if (type_ in self.archives and (type_ in allowed or
838 if (type_ in self.archives and (type_ in allowed or
823 self.repo.ui.configbool("web", "allow" + type_, False))):
839 self.repo.ui.configbool("web", "allow" + type_, False))):
824 self.archive(req, changeset, type_)
840 self.archive(req, changeset, type_)
825 return
841 return
826
842
827 req.write(self.t("error"))
843 req.write(self.t("error"))
828
844
829 def do_static(self, req):
845 def do_static(self, req):
830 fname = req.form['file'][0]
846 fname = req.form['file'][0]
831 static = self.repo.ui.config("web", "static",
847 static = self.repo.ui.config("web", "static",
832 os.path.join(self.templatepath,
848 os.path.join(self.templatepath,
833 "static"))
849 "static"))
834 req.write(staticfile(static, fname, req)
850 req.write(staticfile(static, fname, req)
835 or self.t("error", error="%r not found" % fname))
851 or self.t("error", error="%r not found" % fname))
836
852
837 def do_capabilities(self, req):
853 def do_capabilities(self, req):
838 resp = 'unbundle'
854 resp = 'unbundle'
839 req.httphdr("application/mercurial-0.1", length=len(resp))
855 req.httphdr("application/mercurial-0.1", length=len(resp))
840 req.write(resp)
856 req.write(resp)
841
857
842 def check_perm(self, req, op, default):
858 def check_perm(self, req, op, default):
843 '''check permission for operation based on user auth.
859 '''check permission for operation based on user auth.
844 return true if op allowed, else false.
860 return true if op allowed, else false.
845 default is policy to use if no config given.'''
861 default is policy to use if no config given.'''
846
862
847 user = req.env.get('REMOTE_USER')
863 user = req.env.get('REMOTE_USER')
848
864
849 deny = self.repo.ui.configlist('web', 'deny_' + op)
865 deny = self.repo.ui.configlist('web', 'deny_' + op)
850 if deny and (not user or deny == ['*'] or user in deny):
866 if deny and (not user or deny == ['*'] or user in deny):
851 return False
867 return False
852
868
853 allow = self.repo.ui.configlist('web', 'allow_' + op)
869 allow = self.repo.ui.configlist('web', 'allow_' + op)
854 return (allow and (allow == ['*'] or user in allow)) or default
870 return (allow and (allow == ['*'] or user in allow)) or default
855
871
856 def do_unbundle(self, req):
872 def do_unbundle(self, req):
857 def bail(response, headers={}):
873 def bail(response, headers={}):
858 length = int(req.env['CONTENT_LENGTH'])
874 length = int(req.env['CONTENT_LENGTH'])
859 for s in util.filechunkiter(req, limit=length):
875 for s in util.filechunkiter(req, limit=length):
860 # drain incoming bundle, else client will not see
876 # drain incoming bundle, else client will not see
861 # response when run outside cgi script
877 # response when run outside cgi script
862 pass
878 pass
863 req.httphdr("application/mercurial-0.1", headers=headers)
879 req.httphdr("application/mercurial-0.1", headers=headers)
864 req.write('0\n')
880 req.write('0\n')
865 req.write(response)
881 req.write(response)
866
882
867 # require ssl by default, auth info cannot be sniffed and
883 # require ssl by default, auth info cannot be sniffed and
868 # replayed
884 # replayed
869 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
885 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
870 if ssl_req and not req.env.get('HTTPS'):
886 if ssl_req and not req.env.get('HTTPS'):
871 bail(_('ssl required\n'))
887 bail(_('ssl required\n'))
872 return
888 return
873
889
874 # do not allow push unless explicitly allowed
890 # do not allow push unless explicitly allowed
875 if not self.check_perm(req, 'push', False):
891 if not self.check_perm(req, 'push', False):
876 bail(_('push not authorized\n'),
892 bail(_('push not authorized\n'),
877 headers={'status': '401 Unauthorized'})
893 headers={'status': '401 Unauthorized'})
878 return
894 return
879
895
880 req.httphdr("application/mercurial-0.1")
896 req.httphdr("application/mercurial-0.1")
881
897
882 their_heads = req.form['heads'][0].split(' ')
898 their_heads = req.form['heads'][0].split(' ')
883
899
884 def check_heads():
900 def check_heads():
885 heads = map(hex, self.repo.heads())
901 heads = map(hex, self.repo.heads())
886 return their_heads == [hex('force')] or their_heads == heads
902 return their_heads == [hex('force')] or their_heads == heads
887
903
888 # fail early if possible
904 # fail early if possible
889 if not check_heads():
905 if not check_heads():
890 bail(_('unsynced changes\n'))
906 bail(_('unsynced changes\n'))
891 return
907 return
892
908
893 # do not lock repo until all changegroup data is
909 # do not lock repo until all changegroup data is
894 # streamed. save to temporary file.
910 # streamed. save to temporary file.
895
911
896 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
912 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
897 fp = os.fdopen(fd, 'wb+')
913 fp = os.fdopen(fd, 'wb+')
898 try:
914 try:
899 length = int(req.env['CONTENT_LENGTH'])
915 length = int(req.env['CONTENT_LENGTH'])
900 for s in util.filechunkiter(req, limit=length):
916 for s in util.filechunkiter(req, limit=length):
901 fp.write(s)
917 fp.write(s)
902
918
903 lock = self.repo.lock()
919 lock = self.repo.lock()
904 try:
920 try:
905 if not check_heads():
921 if not check_heads():
906 req.write('0\n')
922 req.write('0\n')
907 req.write(_('unsynced changes\n'))
923 req.write(_('unsynced changes\n'))
908 return
924 return
909
925
910 fp.seek(0)
926 fp.seek(0)
911
927
912 # send addchangegroup output to client
928 # send addchangegroup output to client
913
929
914 old_stdout = sys.stdout
930 old_stdout = sys.stdout
915 sys.stdout = cStringIO.StringIO()
931 sys.stdout = cStringIO.StringIO()
916
932
917 try:
933 try:
918 ret = self.repo.addchangegroup(fp, 'serve')
934 ret = self.repo.addchangegroup(fp, 'serve')
919 req.write('%d\n' % ret)
935 req.write('%d\n' % ret)
920 req.write(sys.stdout.getvalue())
936 req.write(sys.stdout.getvalue())
921 finally:
937 finally:
922 sys.stdout = old_stdout
938 sys.stdout = old_stdout
923 finally:
939 finally:
924 lock.release()
940 lock.release()
925 finally:
941 finally:
926 fp.close()
942 fp.close()
927 os.unlink(tempname)
943 os.unlink(tempname)
@@ -1,157 +1,166 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial.demandload import demandload
10 from mercurial.demandload import demandload
11 demandload(globals(), "ConfigParser mimetools cStringIO")
11 demandload(globals(), "ConfigParser mimetools cStringIO")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
15 from mercurial.i18n import gettext as _
15 from mercurial.i18n import gettext as _
16
16
17 # This is a stopgap
17 # This is a stopgap
18 class hgwebdir(object):
18 class hgwebdir(object):
19 def __init__(self, config):
19 def __init__(self, config):
20 def cleannames(items):
20 def cleannames(items):
21 return [(name.strip(os.sep), path) for name, path in items]
21 return [(name.strip(os.sep), path) for name, path in items]
22
22
23 self.motd = ""
23 self.motd = ""
24 self.repos_sorted = ('name', False)
24 self.repos_sorted = ('name', False)
25 if isinstance(config, (list, tuple)):
25 if isinstance(config, (list, tuple)):
26 self.repos = cleannames(config)
26 self.repos = cleannames(config)
27 self.repos_sorted = ('', False)
27 self.repos_sorted = ('', False)
28 elif isinstance(config, dict):
28 elif isinstance(config, dict):
29 self.repos = cleannames(config.items())
29 self.repos = cleannames(config.items())
30 self.repos.sort()
30 self.repos.sort()
31 else:
31 else:
32 cp = ConfigParser.SafeConfigParser()
32 cp = ConfigParser.SafeConfigParser()
33 cp.read(config)
33 cp.read(config)
34 self.repos = []
34 self.repos = []
35 if cp.has_section('web') and cp.has_option('web', 'motd'):
35 if cp.has_section('web') and cp.has_option('web', 'motd'):
36 self.motd = cp.get('web', 'motd')
36 self.motd = cp.get('web', 'motd')
37 if cp.has_section('paths'):
37 if cp.has_section('paths'):
38 self.repos.extend(cleannames(cp.items('paths')))
38 self.repos.extend(cleannames(cp.items('paths')))
39 if cp.has_section('collections'):
39 if cp.has_section('collections'):
40 for prefix, root in cp.items('collections'):
40 for prefix, root in cp.items('collections'):
41 for path in util.walkrepos(root):
41 for path in util.walkrepos(root):
42 repo = os.path.normpath(path)
42 repo = os.path.normpath(path)
43 name = repo
43 name = repo
44 if name.startswith(prefix):
44 if name.startswith(prefix):
45 name = name[len(prefix):]
45 name = name[len(prefix):]
46 self.repos.append((name.lstrip(os.sep), repo))
46 self.repos.append((name.lstrip(os.sep), repo))
47 self.repos.sort()
47 self.repos.sort()
48
48
49 def run(self, req):
49 def run(self):
50 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
51 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
52 import mercurial.hgweb.wsgicgi as wsgicgi
53 from request import wsgiapplication
54 def make_web_app():
55 return self
56 wsgicgi.launch(wsgiapplication(make_web_app))
57
58 def run_wsgi(self, req):
50 def header(**map):
59 def header(**map):
51 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
60 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
52 msg = mimetools.Message(header_file, 0)
61 msg = mimetools.Message(header_file, 0)
53 req.header(msg.items())
62 req.header(msg.items())
54 yield header_file.read()
63 yield header_file.read()
55
64
56 def footer(**map):
65 def footer(**map):
57 yield tmpl("footer", motd=self.motd, **map)
66 yield tmpl("footer", motd=self.motd, **map)
58
67
59 m = os.path.join(templater.templatepath(), "map")
68 m = os.path.join(templater.templatepath(), "map")
60 tmpl = templater.templater(m, templater.common_filters,
69 tmpl = templater.templater(m, templater.common_filters,
61 defaults={"header": header,
70 defaults={"header": header,
62 "footer": footer})
71 "footer": footer})
63
72
64 def archivelist(ui, nodeid, url):
73 def archivelist(ui, nodeid, url):
65 allowed = ui.configlist("web", "allow_archive")
74 allowed = ui.configlist("web", "allow_archive")
66 for i in ['zip', 'gz', 'bz2']:
75 for i in ['zip', 'gz', 'bz2']:
67 if i in allowed or ui.configbool("web", "allow" + i):
76 if i in allowed or ui.configbool("web", "allow" + i):
68 yield {"type" : i, "node": nodeid, "url": url}
77 yield {"type" : i, "node": nodeid, "url": url}
69
78
70 def entries(sortcolumn="", descending=False, **map):
79 def entries(sortcolumn="", descending=False, **map):
71 rows = []
80 rows = []
72 parity = 0
81 parity = 0
73 for name, path in self.repos:
82 for name, path in self.repos:
74 u = ui.ui()
83 u = ui.ui()
75 try:
84 try:
76 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
85 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
77 except IOError:
86 except IOError:
78 pass
87 pass
79 get = u.config
88 get = u.config
80
89
81 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
90 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
82 .replace("//", "/"))
91 .replace("//", "/"))
83
92
84 # update time with local timezone
93 # update time with local timezone
85 try:
94 try:
86 d = (get_mtime(path), util.makedate()[1])
95 d = (get_mtime(path), util.makedate()[1])
87 except OSError:
96 except OSError:
88 continue
97 continue
89
98
90 contact = (get("ui", "username") or # preferred
99 contact = (get("ui", "username") or # preferred
91 get("web", "contact") or # deprecated
100 get("web", "contact") or # deprecated
92 get("web", "author", "")) # also
101 get("web", "author", "")) # also
93 description = get("web", "description", "")
102 description = get("web", "description", "")
94 name = get("web", "name", name)
103 name = get("web", "name", name)
95 row = dict(contact=contact or "unknown",
104 row = dict(contact=contact or "unknown",
96 contact_sort=contact.upper() or "unknown",
105 contact_sort=contact.upper() or "unknown",
97 name=name,
106 name=name,
98 name_sort=name,
107 name_sort=name,
99 url=url,
108 url=url,
100 description=description or "unknown",
109 description=description or "unknown",
101 description_sort=description.upper() or "unknown",
110 description_sort=description.upper() or "unknown",
102 lastchange=d,
111 lastchange=d,
103 lastchange_sort=d[1]-d[0],
112 lastchange_sort=d[1]-d[0],
104 archives=archivelist(u, "tip", url))
113 archives=archivelist(u, "tip", url))
105 if (not sortcolumn
114 if (not sortcolumn
106 or (sortcolumn, descending) == self.repos_sorted):
115 or (sortcolumn, descending) == self.repos_sorted):
107 # fast path for unsorted output
116 # fast path for unsorted output
108 row['parity'] = parity
117 row['parity'] = parity
109 parity = 1 - parity
118 parity = 1 - parity
110 yield row
119 yield row
111 else:
120 else:
112 rows.append((row["%s_sort" % sortcolumn], row))
121 rows.append((row["%s_sort" % sortcolumn], row))
113 if rows:
122 if rows:
114 rows.sort()
123 rows.sort()
115 if descending:
124 if descending:
116 rows.reverse()
125 rows.reverse()
117 for key, row in rows:
126 for key, row in rows:
118 row['parity'] = parity
127 row['parity'] = parity
119 parity = 1 - parity
128 parity = 1 - parity
120 yield row
129 yield row
121
130
122 virtual = req.env.get("PATH_INFO", "").strip('/')
131 virtual = req.env.get("PATH_INFO", "").strip('/')
123 if virtual:
132 if virtual:
124 real = dict(self.repos).get(virtual)
133 real = dict(self.repos).get(virtual)
125 if real:
134 if real:
126 try:
135 try:
127 hgweb(real).run(req)
136 hgweb(real).run_wsgi(req)
128 except IOError, inst:
137 except IOError, inst:
129 req.write(tmpl("error", error=inst.strerror))
138 req.write(tmpl("error", error=inst.strerror))
130 except hg.RepoError, inst:
139 except hg.RepoError, inst:
131 req.write(tmpl("error", error=str(inst)))
140 req.write(tmpl("error", error=str(inst)))
132 else:
141 else:
133 req.write(tmpl("notfound", repo=virtual))
142 req.write(tmpl("notfound", repo=virtual))
134 else:
143 else:
135 if req.form.has_key('static'):
144 if req.form.has_key('static'):
136 static = os.path.join(templater.templatepath(), "static")
145 static = os.path.join(templater.templatepath(), "static")
137 fname = req.form['static'][0]
146 fname = req.form['static'][0]
138 req.write(staticfile(static, fname, req)
147 req.write(staticfile(static, fname, req)
139 or tmpl("error", error="%r not found" % fname))
148 or tmpl("error", error="%r not found" % fname))
140 else:
149 else:
141 sortable = ["name", "description", "contact", "lastchange"]
150 sortable = ["name", "description", "contact", "lastchange"]
142 sortcolumn, descending = self.repos_sorted
151 sortcolumn, descending = self.repos_sorted
143 if req.form.has_key('sort'):
152 if req.form.has_key('sort'):
144 sortcolumn = req.form['sort'][0]
153 sortcolumn = req.form['sort'][0]
145 descending = sortcolumn.startswith('-')
154 descending = sortcolumn.startswith('-')
146 if descending:
155 if descending:
147 sortcolumn = sortcolumn[1:]
156 sortcolumn = sortcolumn[1:]
148 if sortcolumn not in sortable:
157 if sortcolumn not in sortable:
149 sortcolumn = ""
158 sortcolumn = ""
150
159
151 sort = [("sort_%s" % column,
160 sort = [("sort_%s" % column,
152 "%s%s" % ((not descending and column == sortcolumn)
161 "%s%s" % ((not descending and column == sortcolumn)
153 and "-" or "", column))
162 and "-" or "", column))
154 for column in sortable]
163 for column in sortable]
155 req.write(tmpl("index", entries=entries,
164 req.write(tmpl("index", entries=entries,
156 sortcolumn=sortcolumn, descending=descending,
165 sortcolumn=sortcolumn, descending=descending,
157 **dict(sort)))
166 **dict(sort)))
@@ -1,90 +1,90 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from mercurial.demandload import demandload
9 from mercurial.demandload import demandload
10 demandload(globals(), "socket sys cgi os errno")
10 demandload(globals(), "socket sys cgi os errno")
11 from mercurial.i18n import gettext as _
11 from mercurial.i18n import gettext as _
12
12
13 class wsgiapplication(object):
13 class wsgiapplication(object):
14 def __init__(self, destmaker):
14 def __init__(self, destmaker):
15 self.destmaker = destmaker
15 self.destmaker = destmaker
16
16
17 def __call__(self, wsgienv, start_response):
17 def __call__(self, wsgienv, start_response):
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
18 return _wsgirequest(self.destmaker(), wsgienv, start_response)
19
19
20 class _wsgioutputfile(object):
20 class _wsgioutputfile(object):
21 def __init__(self, request):
21 def __init__(self, request):
22 self.request = request
22 self.request = request
23
23
24 def write(self, data):
24 def write(self, data):
25 self.request.write(data)
25 self.request.write(data)
26 def writelines(self, lines):
26 def writelines(self, lines):
27 for line in lines:
27 for line in lines:
28 self.write(line)
28 self.write(line)
29 def flush(self):
29 def flush(self):
30 return None
30 return None
31 def close(self):
31 def close(self):
32 return None
32 return None
33
33
34 class _wsgirequest(object):
34 class _wsgirequest(object):
35 def __init__(self, destination, wsgienv, start_response):
35 def __init__(self, destination, wsgienv, start_response):
36 version = wsgienv['wsgi.version']
36 version = wsgienv['wsgi.version']
37 if (version < (1,0)) or (version >= (2, 0)):
37 if (version < (1,0)) or (version >= (2, 0)):
38 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
38 raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
39 % version)
39 % version)
40 self.inp = wsgienv['wsgi.input']
40 self.inp = wsgienv['wsgi.input']
41 self.out = _wsgioutputfile(self)
41 self.out = _wsgioutputfile(self)
42 self.server_write = None
42 self.server_write = None
43 self.err = wsgienv['wsgi.errors']
43 self.err = wsgienv['wsgi.errors']
44 self.threaded = wsgienv['wsgi.multithread']
44 self.threaded = wsgienv['wsgi.multithread']
45 self.multiprocess = wsgienv['wsgi.multiprocess']
45 self.multiprocess = wsgienv['wsgi.multiprocess']
46 self.run_once = wsgienv['wsgi.run_once']
46 self.run_once = wsgienv['wsgi.run_once']
47 self.env = wsgienv
47 self.env = wsgienv
48 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
48 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
49 self.start_response = start_response
49 self.start_response = start_response
50 self.headers = []
50 self.headers = []
51 destination.run(self)
51 destination.run_wsgi(self)
52
52
53 def __iter__(self):
53 def __iter__(self):
54 return iter([])
54 return iter([])
55
55
56 def read(self, count=-1):
56 def read(self, count=-1):
57 return self.inp.read(count)
57 return self.inp.read(count)
58
58
59 def write(self, *things):
59 def write(self, *things):
60 for thing in things:
60 for thing in things:
61 if hasattr(thing, "__iter__"):
61 if hasattr(thing, "__iter__"):
62 for part in thing:
62 for part in thing:
63 self.write(part)
63 self.write(part)
64 else:
64 else:
65 thing = str(thing)
65 thing = str(thing)
66 if self.server_write is None:
66 if self.server_write is None:
67 if not self.headers:
67 if not self.headers:
68 raise RuntimeError("request.write called before headers sent (%s)." % thing)
68 raise RuntimeError("request.write called before headers sent (%s)." % thing)
69 self.server_write = self.start_response('200 Script output follows',
69 self.server_write = self.start_response('200 Script output follows',
70 self.headers)
70 self.headers)
71 self.start_response = None
71 self.start_response = None
72 self.headers = None
72 self.headers = None
73 try:
73 try:
74 self.server_write(thing)
74 self.server_write(thing)
75 except socket.error, inst:
75 except socket.error, inst:
76 if inst[0] != errno.ECONNRESET:
76 if inst[0] != errno.ECONNRESET:
77 raise
77 raise
78
78
79 def header(self, headers=[('Content-type','text/html')]):
79 def header(self, headers=[('Content-type','text/html')]):
80 self.headers.extend(headers)
80 self.headers.extend(headers)
81
81
82 def httphdr(self, type, filename=None, length=0, headers={}):
82 def httphdr(self, type, filename=None, length=0, headers={}):
83 headers = headers.items()
83 headers = headers.items()
84 headers.append(('Content-type', type))
84 headers.append(('Content-type', type))
85 if filename:
85 if filename:
86 headers.append(('Content-disposition', 'attachment; filename=%s' %
86 headers.append(('Content-disposition', 'attachment; filename=%s' %
87 filename))
87 filename))
88 if length:
88 if length:
89 headers.append(('Content-length', str(length)))
89 headers.append(('Content-length', str(length)))
90 self.header(headers)
90 self.header(headers)
@@ -1,524 +1,524 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12
12
13 esctable = {
13 esctable = {
14 '\\': '\\',
14 '\\': '\\',
15 'r': '\r',
15 'r': '\r',
16 't': '\t',
16 't': '\t',
17 'n': '\n',
17 'n': '\n',
18 'v': '\v',
18 'v': '\v',
19 }
19 }
20
20
21 def parsestring(s, quoted=True):
21 def parsestring(s, quoted=True):
22 '''parse a string using simple c-like syntax.
22 '''parse a string using simple c-like syntax.
23 string must be in quotes if quoted is True.'''
23 string must be in quotes if quoted is True.'''
24 fp = cStringIO.StringIO()
24 fp = cStringIO.StringIO()
25 if quoted:
25 if quoted:
26 first = s[0]
26 first = s[0]
27 if len(s) < 2: raise SyntaxError(_('string too short'))
27 if len(s) < 2: raise SyntaxError(_('string too short'))
28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
30 s = s[1:-1]
30 s = s[1:-1]
31 escape = False
31 escape = False
32 for c in s:
32 for c in s:
33 if escape:
33 if escape:
34 fp.write(esctable.get(c, c))
34 fp.write(esctable.get(c, c))
35 escape = False
35 escape = False
36 elif c == '\\': escape = True
36 elif c == '\\': escape = True
37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
38 else: fp.write(c)
38 else: fp.write(c)
39 if escape: raise SyntaxError(_('unterminated escape'))
39 if escape: raise SyntaxError(_('unterminated escape'))
40 return fp.getvalue()
40 return fp.getvalue()
41
41
42 class templater(object):
42 class templater(object):
43 '''template expansion engine.
43 '''template expansion engine.
44
44
45 template expansion works like this. a map file contains key=value
45 template expansion works like this. a map file contains key=value
46 pairs. if value is quoted, it is treated as string. otherwise, it
46 pairs. if value is quoted, it is treated as string. otherwise, it
47 is treated as name of template file.
47 is treated as name of template file.
48
48
49 templater is asked to expand a key in map. it looks up key, and
49 templater is asked to expand a key in map. it looks up key, and
50 looks for atrings like this: {foo}. it expands {foo} by looking up
50 looks for atrings like this: {foo}. it expands {foo} by looking up
51 foo in map, and substituting it. expansion is recursive: it stops
51 foo in map, and substituting it. expansion is recursive: it stops
52 when there is no more {foo} to replace.
52 when there is no more {foo} to replace.
53
53
54 expansion also allows formatting and filtering.
54 expansion also allows formatting and filtering.
55
55
56 format uses key to expand each item in list. syntax is
56 format uses key to expand each item in list. syntax is
57 {key%format}.
57 {key%format}.
58
58
59 filter uses function to transform value. syntax is
59 filter uses function to transform value. syntax is
60 {key|filter1|filter2|...}.'''
60 {key|filter1|filter2|...}.'''
61
61
62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
63 '''set up template engine.
63 '''set up template engine.
64 mapfile is name of file to read map definitions from.
64 mapfile is name of file to read map definitions from.
65 filters is dict of functions. each transforms a value into another.
65 filters is dict of functions. each transforms a value into another.
66 defaults is dict of default map definitions.'''
66 defaults is dict of default map definitions.'''
67 self.mapfile = mapfile or 'template'
67 self.mapfile = mapfile or 'template'
68 self.cache = cache.copy()
68 self.cache = cache.copy()
69 self.map = {}
69 self.map = {}
70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
71 self.filters = filters
71 self.filters = filters
72 self.defaults = defaults
72 self.defaults = defaults
73
73
74 if not mapfile:
74 if not mapfile:
75 return
75 return
76 i = 0
76 i = 0
77 for l in file(mapfile):
77 for l in file(mapfile):
78 l = l.strip()
78 l = l.strip()
79 i += 1
79 i += 1
80 if not l or l[0] in '#;': continue
80 if not l or l[0] in '#;': continue
81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
82 if m:
82 if m:
83 key, val = m.groups()
83 key, val = m.groups()
84 if val[0] in "'\"":
84 if val[0] in "'\"":
85 try:
85 try:
86 self.cache[key] = parsestring(val)
86 self.cache[key] = parsestring(val)
87 except SyntaxError, inst:
87 except SyntaxError, inst:
88 raise SyntaxError('%s:%s: %s' %
88 raise SyntaxError('%s:%s: %s' %
89 (mapfile, i, inst.args[0]))
89 (mapfile, i, inst.args[0]))
90 else:
90 else:
91 self.map[key] = os.path.join(self.base, val)
91 self.map[key] = os.path.join(self.base, val)
92 else:
92 else:
93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
94
94
95 def __contains__(self, key):
95 def __contains__(self, key):
96 return key in self.cache
96 return key in self.cache
97
97
98 def __call__(self, t, **map):
98 def __call__(self, t, **map):
99 '''perform expansion.
99 '''perform expansion.
100 t is name of map element to expand.
100 t is name of map element to expand.
101 map is added elements to use during expansion.'''
101 map is added elements to use during expansion.'''
102 m = self.defaults.copy()
102 m = self.defaults.copy()
103 m.update(map)
103 m.update(map)
104 try:
104 try:
105 tmpl = self.cache[t]
105 tmpl = self.cache[t]
106 except KeyError:
106 except KeyError:
107 try:
107 try:
108 tmpl = self.cache[t] = file(self.map[t]).read()
108 tmpl = self.cache[t] = file(self.map[t]).read()
109 except IOError, inst:
109 except IOError, inst:
110 raise IOError(inst.args[0], _('template file %s: %s') %
110 raise IOError(inst.args[0], _('template file %s: %s') %
111 (self.map[t], inst.args[1]))
111 (self.map[t], inst.args[1]))
112 return self.template(tmpl, self.filters, **m)
112 return self.template(tmpl, self.filters, **m)
113
113
114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
117
117
118 def template(self, tmpl, filters={}, **map):
118 def template(self, tmpl, filters={}, **map):
119 lm = map.copy()
119 lm = map.copy()
120 while tmpl:
120 while tmpl:
121 m = self.template_re.search(tmpl)
121 m = self.template_re.search(tmpl)
122 if m:
122 if m:
123 start, end = m.span(0)
123 start, end = m.span(0)
124 s, e = tmpl[start], tmpl[end - 1]
124 s, e = tmpl[start], tmpl[end - 1]
125 key = m.group(1)
125 key = m.group(1)
126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
128 (s, e, key))
128 (s, e, key))
129 if start:
129 if start:
130 yield tmpl[:start]
130 yield tmpl[:start]
131 v = map.get(key, "")
131 v = map.get(key, "")
132 v = callable(v) and v(**map) or v
132 v = callable(v) and v(**map) or v
133
133
134 format = m.group(2)
134 format = m.group(2)
135 fl = m.group(4)
135 fl = m.group(4)
136
136
137 if format:
137 if format:
138 q = v.__iter__
138 q = v.__iter__
139 for i in q():
139 for i in q():
140 lm.update(i)
140 lm.update(i)
141 yield self(format[1:], **lm)
141 yield self(format[1:], **lm)
142
142
143 v = ""
143 v = ""
144
144
145 elif fl:
145 elif fl:
146 for f in fl.split("|")[1:]:
146 for f in fl.split("|")[1:]:
147 v = filters[f](v)
147 v = filters[f](v)
148
148
149 yield v
149 yield v
150 tmpl = tmpl[end:]
150 tmpl = tmpl[end:]
151 else:
151 else:
152 yield tmpl
152 yield tmpl
153 break
153 break
154
154
155 agescales = [("second", 1),
155 agescales = [("second", 1),
156 ("minute", 60),
156 ("minute", 60),
157 ("hour", 3600),
157 ("hour", 3600),
158 ("day", 3600 * 24),
158 ("day", 3600 * 24),
159 ("week", 3600 * 24 * 7),
159 ("week", 3600 * 24 * 7),
160 ("month", 3600 * 24 * 30),
160 ("month", 3600 * 24 * 30),
161 ("year", 3600 * 24 * 365)]
161 ("year", 3600 * 24 * 365)]
162
162
163 agescales.reverse()
163 agescales.reverse()
164
164
165 def age(date):
165 def age(date):
166 '''turn a (timestamp, tzoff) tuple into an age string.'''
166 '''turn a (timestamp, tzoff) tuple into an age string.'''
167
167
168 def plural(t, c):
168 def plural(t, c):
169 if c == 1:
169 if c == 1:
170 return t
170 return t
171 return t + "s"
171 return t + "s"
172 def fmt(t, c):
172 def fmt(t, c):
173 return "%d %s" % (c, plural(t, c))
173 return "%d %s" % (c, plural(t, c))
174
174
175 now = time.time()
175 now = time.time()
176 then = date[0]
176 then = date[0]
177 delta = max(1, int(now - then))
177 delta = max(1, int(now - then))
178
178
179 for t, s in agescales:
179 for t, s in agescales:
180 n = delta / s
180 n = delta / s
181 if n >= 2 or s == 1:
181 if n >= 2 or s == 1:
182 return fmt(t, n)
182 return fmt(t, n)
183
183
184 def stringify(thing):
184 def stringify(thing):
185 '''turn nested template iterator into string.'''
185 '''turn nested template iterator into string.'''
186 cs = cStringIO.StringIO()
186 cs = cStringIO.StringIO()
187 def walk(things):
187 def walk(things):
188 for t in things:
188 for t in things:
189 if hasattr(t, '__iter__'):
189 if hasattr(t, '__iter__'):
190 walk(t)
190 walk(t)
191 else:
191 else:
192 cs.write(t)
192 cs.write(t)
193 walk(thing)
193 walk(thing)
194 return cs.getvalue()
194 return cs.getvalue()
195
195
196 para_re = None
196 para_re = None
197 space_re = None
197 space_re = None
198
198
199 def fill(text, width):
199 def fill(text, width):
200 '''fill many paragraphs.'''
200 '''fill many paragraphs.'''
201 global para_re, space_re
201 global para_re, space_re
202 if para_re is None:
202 if para_re is None:
203 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
203 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
204 space_re = re.compile(r' +')
204 space_re = re.compile(r' +')
205
205
206 def findparas():
206 def findparas():
207 start = 0
207 start = 0
208 while True:
208 while True:
209 m = para_re.search(text, start)
209 m = para_re.search(text, start)
210 if not m:
210 if not m:
211 w = len(text)
211 w = len(text)
212 while w > start and text[w-1].isspace(): w -= 1
212 while w > start and text[w-1].isspace(): w -= 1
213 yield text[start:w], text[w:]
213 yield text[start:w], text[w:]
214 break
214 break
215 yield text[start:m.start(0)], m.group(1)
215 yield text[start:m.start(0)], m.group(1)
216 start = m.end(1)
216 start = m.end(1)
217
217
218 fp = cStringIO.StringIO()
218 fp = cStringIO.StringIO()
219 for para, rest in findparas():
219 for para, rest in findparas():
220 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
220 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
221 fp.write(rest)
221 fp.write(rest)
222 return fp.getvalue()
222 return fp.getvalue()
223
223
224 def isodate(date):
224 def isodate(date):
225 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
225 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
226 return util.datestr(date, format='%Y-%m-%d %H:%M')
226 return util.datestr(date, format='%Y-%m-%d %H:%M')
227
227
228 def hgdate(date):
228 def hgdate(date):
229 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
229 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
230 return "%d %d" % date
230 return "%d %d" % date
231
231
232 def nl2br(text):
232 def nl2br(text):
233 '''replace raw newlines with xhtml line breaks.'''
233 '''replace raw newlines with xhtml line breaks.'''
234 return text.replace('\n', '<br/>\n')
234 return text.replace('\n', '<br/>\n')
235
235
236 def obfuscate(text):
236 def obfuscate(text):
237 return ''.join(['&#%d;' % ord(c) for c in text])
237 return ''.join(['&#%d;' % ord(c) for c in text])
238
238
239 def domain(author):
239 def domain(author):
240 '''get domain of author, or empty string if none.'''
240 '''get domain of author, or empty string if none.'''
241 f = author.find('@')
241 f = author.find('@')
242 if f == -1: return ''
242 if f == -1: return ''
243 author = author[f+1:]
243 author = author[f+1:]
244 f = author.find('>')
244 f = author.find('>')
245 if f >= 0: author = author[:f]
245 if f >= 0: author = author[:f]
246 return author
246 return author
247
247
248 def email(author):
248 def email(author):
249 '''get email of author.'''
249 '''get email of author.'''
250 r = author.find('>')
250 r = author.find('>')
251 if r == -1: r = None
251 if r == -1: r = None
252 return author[author.find('<')+1:r]
252 return author[author.find('<')+1:r]
253
253
254 def person(author):
254 def person(author):
255 '''get name of author, or else username.'''
255 '''get name of author, or else username.'''
256 f = author.find('<')
256 f = author.find('<')
257 if f == -1: return util.shortuser(author)
257 if f == -1: return util.shortuser(author)
258 return author[:f].rstrip()
258 return author[:f].rstrip()
259
259
260 def shortdate(date):
260 def shortdate(date):
261 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
261 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
262 return util.datestr(date, format='%Y-%m-%d', timezone=False)
262 return util.datestr(date, format='%Y-%m-%d', timezone=False)
263
263
264 def indent(text, prefix):
264 def indent(text, prefix):
265 '''indent each non-empty line of text after first with prefix.'''
265 '''indent each non-empty line of text after first with prefix.'''
266 fp = cStringIO.StringIO()
266 fp = cStringIO.StringIO()
267 lines = text.splitlines()
267 lines = text.splitlines()
268 num_lines = len(lines)
268 num_lines = len(lines)
269 for i in xrange(num_lines):
269 for i in xrange(num_lines):
270 l = lines[i]
270 l = lines[i]
271 if i and l.strip(): fp.write(prefix)
271 if i and l.strip(): fp.write(prefix)
272 fp.write(l)
272 fp.write(l)
273 if i < num_lines - 1 or text.endswith('\n'):
273 if i < num_lines - 1 or text.endswith('\n'):
274 fp.write('\n')
274 fp.write('\n')
275 return fp.getvalue()
275 return fp.getvalue()
276
276
277 common_filters = {
277 common_filters = {
278 "addbreaks": nl2br,
278 "addbreaks": nl2br,
279 "basename": os.path.basename,
279 "basename": os.path.basename,
280 "age": age,
280 "age": age,
281 "date": lambda x: util.datestr(x),
281 "date": lambda x: util.datestr(x),
282 "domain": domain,
282 "domain": domain,
283 "email": email,
283 "email": email,
284 "escape": lambda x: cgi.escape(x, True),
284 "escape": lambda x: cgi.escape(x, True),
285 "fill68": lambda x: fill(x, width=68),
285 "fill68": lambda x: fill(x, width=68),
286 "fill76": lambda x: fill(x, width=76),
286 "fill76": lambda x: fill(x, width=76),
287 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
287 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
288 "tabindent": lambda x: indent(x, '\t'),
288 "tabindent": lambda x: indent(x, '\t'),
289 "hgdate": hgdate,
289 "hgdate": hgdate,
290 "isodate": isodate,
290 "isodate": isodate,
291 "obfuscate": obfuscate,
291 "obfuscate": obfuscate,
292 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
292 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
293 "person": person,
293 "person": person,
294 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
294 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
295 "short": lambda x: x[:12],
295 "short": lambda x: x[:12],
296 "shortdate": shortdate,
296 "shortdate": shortdate,
297 "stringify": stringify,
297 "stringify": stringify,
298 "strip": lambda x: x.strip(),
298 "strip": lambda x: x.strip(),
299 "urlescape": lambda x: urllib.quote(x),
299 "urlescape": lambda x: urllib.quote(x),
300 "user": lambda x: util.shortuser(x),
300 "user": lambda x: util.shortuser(x),
301 }
301 }
302
302
303 def templatepath(name=None):
303 def templatepath(name=None):
304 '''return location of template file or directory (if no name).
304 '''return location of template file or directory (if no name).
305 returns None if not found.'''
305 returns None if not found.'''
306
306
307 # executable version (py2exe) doesn't support __file__
307 # executable version (py2exe) doesn't support __file__
308 if hasattr(sys, 'frozen'):
308 if hasattr(sys, 'frozen'):
309 module = sys.executable
309 module = sys.executable
310 else:
310 else:
311 module = __file__
311 module = __file__
312 for f in 'templates', '../templates':
312 for f in 'templates', '../templates':
313 fl = f.split('/')
313 fl = f.split('/')
314 if name: fl.append(name)
314 if name: fl.append(name)
315 p = os.path.join(os.path.dirname(module), *fl)
315 p = os.path.join(os.path.dirname(module), *fl)
316 if (name and os.path.exists(p)) or os.path.isdir(p):
316 if (name and os.path.exists(p)) or os.path.isdir(p):
317 return os.path.normpath(p)
317 return os.path.normpath(p)
318
318
319 class changeset_templater(object):
319 class changeset_templater(object):
320 '''format changeset information.'''
320 '''format changeset information.'''
321
321
322 def __init__(self, ui, repo, mapfile, dest=None):
322 def __init__(self, ui, repo, mapfile, dest=None):
323 self.t = templater(mapfile, common_filters,
323 self.t = templater(mapfile, common_filters,
324 cache={'parent': '{rev}:{node|short} ',
324 cache={'parent': '{rev}:{node|short} ',
325 'manifest': '{rev}:{node|short}'})
325 'manifest': '{rev}:{node|short}'})
326 self.ui = ui
326 self.ui = ui
327 self.dest = dest
327 self.dest = dest
328 self.repo = repo
328 self.repo = repo
329
329
330 def use_template(self, t):
330 def use_template(self, t):
331 '''set template string to use'''
331 '''set template string to use'''
332 self.t.cache['changeset'] = t
332 self.t.cache['changeset'] = t
333
333
334 def write(self, thing, header=False):
334 def write(self, thing, header=False):
335 '''write expanded template.
335 '''write expanded template.
336 uses in-order recursive traverse of iterators.'''
336 uses in-order recursive traverse of iterators.'''
337 dest = self.dest or self.ui
337 dest = self.dest or self.ui
338 for t in thing:
338 for t in thing:
339 if hasattr(t, '__iter__'):
339 if hasattr(t, '__iter__'):
340 self.write(t, header=header)
340 self.write(t, header=header)
341 elif header:
341 elif header:
342 dest.write_header(t)
342 dest.write_header(t)
343 else:
343 else:
344 dest.write(t)
344 dest.write(t)
345
345
346 def write_header(self, thing):
346 def write_header(self, thing):
347 self.write(thing, header=True)
347 self.write(thing, header=True)
348
348
349 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
349 def show(self, rev=0, changenode=None, brinfo=None, changes=None,
350 **props):
350 **props):
351 '''show a single changeset or file revision'''
351 '''show a single changeset or file revision'''
352 log = self.repo.changelog
352 log = self.repo.changelog
353 if changenode is None:
353 if changenode is None:
354 changenode = log.node(rev)
354 changenode = log.node(rev)
355 elif not rev:
355 elif not rev:
356 rev = log.rev(changenode)
356 rev = log.rev(changenode)
357 if changes is None:
357 if changes is None:
358 changes = log.read(changenode)
358 changes = log.read(changenode)
359
359
360 def showlist(name, values, plural=None, **args):
360 def showlist(name, values, plural=None, **args):
361 '''expand set of values.
361 '''expand set of values.
362 name is name of key in template map.
362 name is name of key in template map.
363 values is list of strings or dicts.
363 values is list of strings or dicts.
364 plural is plural of name, if not simply name + 's'.
364 plural is plural of name, if not simply name + 's'.
365
365
366 expansion works like this, given name 'foo'.
366 expansion works like this, given name 'foo'.
367
367
368 if values is empty, expand 'no_foos'.
368 if values is empty, expand 'no_foos'.
369
369
370 if 'foo' not in template map, return values as a string,
370 if 'foo' not in template map, return values as a string,
371 joined by space.
371 joined by space.
372
372
373 expand 'start_foos'.
373 expand 'start_foos'.
374
374
375 for each value, expand 'foo'. if 'last_foo' in template
375 for each value, expand 'foo'. if 'last_foo' in template
376 map, expand it instead of 'foo' for last key.
376 map, expand it instead of 'foo' for last key.
377
377
378 expand 'end_foos'.
378 expand 'end_foos'.
379 '''
379 '''
380 if plural: names = plural
380 if plural: names = plural
381 else: names = name + 's'
381 else: names = name + 's'
382 if not values:
382 if not values:
383 noname = 'no_' + names
383 noname = 'no_' + names
384 if noname in self.t:
384 if noname in self.t:
385 yield self.t(noname, **args)
385 yield self.t(noname, **args)
386 return
386 return
387 if name not in self.t:
387 if name not in self.t:
388 if isinstance(values[0], str):
388 if isinstance(values[0], str):
389 yield ' '.join(values)
389 yield ' '.join(values)
390 else:
390 else:
391 for v in values:
391 for v in values:
392 yield dict(v, **args)
392 yield dict(v, **args)
393 return
393 return
394 startname = 'start_' + names
394 startname = 'start_' + names
395 if startname in self.t:
395 if startname in self.t:
396 yield self.t(startname, **args)
396 yield self.t(startname, **args)
397 vargs = args.copy()
397 vargs = args.copy()
398 def one(v, tag=name):
398 def one(v, tag=name):
399 try:
399 try:
400 vargs.update(v)
400 vargs.update(v)
401 except (AttributeError, ValueError):
401 except (AttributeError, ValueError):
402 try:
402 try:
403 for a, b in v:
403 for a, b in v:
404 vargs[a] = b
404 vargs[a] = b
405 except ValueError:
405 except ValueError:
406 vargs[name] = v
406 vargs[name] = v
407 return self.t(tag, **vargs)
407 return self.t(tag, **vargs)
408 lastname = 'last_' + name
408 lastname = 'last_' + name
409 if lastname in self.t:
409 if lastname in self.t:
410 last = values.pop()
410 last = values.pop()
411 else:
411 else:
412 last = None
412 last = None
413 for v in values:
413 for v in values:
414 yield one(v)
414 yield one(v)
415 if last is not None:
415 if last is not None:
416 yield one(last, tag=lastname)
416 yield one(last, tag=lastname)
417 endname = 'end_' + names
417 endname = 'end_' + names
418 if endname in self.t:
418 if endname in self.t:
419 yield self.t(endname, **args)
419 yield self.t(endname, **args)
420
420
421 if brinfo:
421 if brinfo:
422 def showbranches(**args):
422 def showbranches(**args):
423 if changenode in brinfo:
423 if changenode in brinfo:
424 for x in showlist('branch', brinfo[changenode],
424 for x in showlist('branch', brinfo[changenode],
425 plural='branches', **args):
425 plural='branches', **args):
426 yield x
426 yield x
427 else:
427 else:
428 showbranches = ''
428 showbranches = ''
429
429
430 if self.ui.debugflag:
430 if self.ui.debugflag:
431 def showmanifest(**args):
431 def showmanifest(**args):
432 args = args.copy()
432 args = args.copy()
433 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
433 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
434 node=hex(changes[0])))
434 node=hex(changes[0])))
435 yield self.t('manifest', **args)
435 yield self.t('manifest', **args)
436 else:
436 else:
437 showmanifest = ''
437 showmanifest = ''
438
438
439 def showparents(**args):
439 def showparents(**args):
440 parents = [[('rev', log.rev(p)), ('node', hex(p))]
440 parents = [[('rev', log.rev(p)), ('node', hex(p))]
441 for p in log.parents(changenode)
441 for p in log.parents(changenode)
442 if self.ui.debugflag or p != nullid]
442 if self.ui.debugflag or p != nullid]
443 if (not self.ui.debugflag and len(parents) == 1 and
443 if (not self.ui.debugflag and len(parents) == 1 and
444 parents[0][0][1] == rev - 1):
444 parents[0][0][1] == rev - 1):
445 return
445 return
446 for x in showlist('parent', parents, **args):
446 for x in showlist('parent', parents, **args):
447 yield x
447 yield x
448
448
449 def showtags(**args):
449 def showtags(**args):
450 for x in showlist('tag', self.repo.nodetags(changenode), **args):
450 for x in showlist('tag', self.repo.nodetags(changenode), **args):
451 yield x
451 yield x
452
452
453 if self.ui.debugflag:
453 if self.ui.debugflag:
454 files = self.repo.changes(log.parents(changenode)[0], changenode)
454 files = self.repo.changes(log.parents(changenode)[0], changenode)
455 def showfiles(**args):
455 def showfiles(**args):
456 for x in showlist('file', files[0], **args): yield x
456 for x in showlist('file', files[0], **args): yield x
457 def showadds(**args):
457 def showadds(**args):
458 for x in showlist('file_add', files[1], **args): yield x
458 for x in showlist('file_add', files[1], **args): yield x
459 def showdels(**args):
459 def showdels(**args):
460 for x in showlist('file_del', files[2], **args): yield x
460 for x in showlist('file_del', files[2], **args): yield x
461 else:
461 else:
462 def showfiles(**args):
462 def showfiles(**args):
463 for x in showlist('file', changes[3], **args): yield x
463 for x in showlist('file', changes[3], **args): yield x
464 showadds = ''
464 showadds = ''
465 showdels = ''
465 showdels = ''
466
466
467 defprops = {
467 defprops = {
468 'author': changes[1],
468 'author': changes[1],
469 'branches': showbranches,
469 'branches': showbranches,
470 'date': changes[2],
470 'date': changes[2],
471 'desc': changes[4],
471 'desc': changes[4],
472 'file_adds': showadds,
472 'file_adds': showadds,
473 'file_dels': showdels,
473 'file_dels': showdels,
474 'files': showfiles,
474 'files': showfiles,
475 'manifest': showmanifest,
475 'manifest': showmanifest,
476 'node': hex(changenode),
476 'node': hex(changenode),
477 'parents': showparents,
477 'parents': showparents,
478 'rev': rev,
478 'rev': rev,
479 'tags': showtags,
479 'tags': showtags,
480 }
480 }
481 props = props.copy()
481 props = props.copy()
482 props.update(defprops)
482 props.update(defprops)
483
483
484 try:
484 try:
485 if self.ui.debugflag and 'header_debug' in self.t:
485 if self.ui.debugflag and 'header_debug' in self.t:
486 key = 'header_debug'
486 key = 'header_debug'
487 elif self.ui.quiet and 'header_quiet' in self.t:
487 elif self.ui.quiet and 'header_quiet' in self.t:
488 key = 'header_quiet'
488 key = 'header_quiet'
489 elif self.ui.verbose and 'header_verbose' in self.t:
489 elif self.ui.verbose and 'header_verbose' in self.t:
490 key = 'header_verbose'
490 key = 'header_verbose'
491 elif 'header' in self.t:
491 elif 'header' in self.t:
492 key = 'header'
492 key = 'header'
493 else:
493 else:
494 key = ''
494 key = ''
495 if key:
495 if key:
496 self.write_header(self.t(key, **props))
496 self.write_header(self.t(key, **props))
497 if self.ui.debugflag and 'changeset_debug' in self.t:
497 if self.ui.debugflag and 'changeset_debug' in self.t:
498 key = 'changeset_debug'
498 key = 'changeset_debug'
499 elif self.ui.quiet and 'changeset_quiet' in self.t:
499 elif self.ui.quiet and 'changeset_quiet' in self.t:
500 key = 'changeset_quiet'
500 key = 'changeset_quiet'
501 elif self.ui.verbose and 'changeset_verbose' in self.t:
501 elif self.ui.verbose and 'changeset_verbose' in self.t:
502 key = 'changeset_verbose'
502 key = 'changeset_verbose'
503 else:
503 else:
504 key = 'changeset'
504 key = 'changeset'
505 self.write(self.t(key, **props))
505 self.write(self.t(key, **props))
506 except KeyError, inst:
506 except KeyError, inst:
507 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
507 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
508 inst.args[0]))
508 inst.args[0]))
509 except SyntaxError, inst:
509 except SyntaxError, inst:
510 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
510 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
511
511
512 class stringio(object):
512 class stringio(object):
513 '''wrap cStringIO for use by changeset_templater.'''
513 '''wrap cStringIO for use by changeset_templater.'''
514 def __init__(self):
514 def __init__(self):
515 self.fp = cStringIO.StringIO()
515 self.fp = cStringIO.StringIO()
516
516
517 def write(self, *args):
517 def write(self, *args):
518 for a in args:
518 for a in args:
519 self.fp.write(a)
519 self.fp.write(a)
520
520
521 write_header = write
521 write_header = write
522
522
523 def __getattr__(self, key):
523 def __getattr__(self, key):
524 return getattr(self.fp, key)
524 return getattr(self.fp, key)
@@ -1,16 +1,16 b''
1 header = header-raw.tmpl
1 header = header-raw.tmpl
2 footer = ''
2 footer = ''
3 changeset = changeset-raw.tmpl
3 changeset = changeset-raw.tmpl
4 difflineplus = '#line#'
4 difflineplus = '#line#'
5 difflineminus = '#line#'
5 difflineminus = '#line#'
6 difflineat = '#line#'
6 difflineat = '#line#'
7 diffline = '#line#'
7 diffline = '#line#'
8 changesetparent = '# Parent #node#'
8 changesetparent = '# Parent #node#'
9 changesetchild = '# Child #node#'
9 changesetchild = '# Child #node#'
10 filenodelink = ''
10 filenodelink = ''
11 filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#'
11 filerevision = '#rawfileheader##raw#'
12 fileline = '#line#'
12 fileline = '#line#'
13 diffblock = '#lines#'
13 diffblock = '#lines#'
14 filediff = filediff-raw.tmpl
14 filediff = filediff-raw.tmpl
15 fileannotate = fileannotate-raw.tmpl
15 fileannotate = fileannotate-raw.tmpl
16 annotateline = '#author#@#rev#: #line#'
16 annotateline = '#author#@#rev#: #line#'
General Comments 0
You need to be logged in to leave comments. Login now