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