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