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