Merge with crew.
--- a/contrib/macosx/Readme.html Wed Jun 28 12:29:48 2006 +0200
+++ b/contrib/macosx/Readme.html Fri Jun 30 21:35:28 2006 +0200
@@ -18,13 +18,10 @@
<p class="p2"><br></p>
<p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p>
<p class="p2"><br></p>
-<p class="p3">To use it, you must have the “official unofficial” MacPython 2.4.1 installed.</p>
+<p class="p3">To use it, you must have the Universal MacPython 2.4.3 from <a href="http://www.python.org">www.python.org</a> installed.</p>
<p class="p2"><br></p>
-<p class="p3">You can download MacPython 2.4.1 from here:</p>
-<p class="p4"><span class="s1"><a href="http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg">http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg</a></span></p>
-<p class="p2"><br></p>
-<p class="p3">For more information on MacPython, go here:</p>
-<p class="p4"><span class="s1"><a href="http://undefined.org/python/">http://undefined.org/python</a></span></p>
+<p class="p3">You can download MacPython 2.4.3 from here:</p>
+<p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg">http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg</a></span></p>
<p class="p2"><br></p>
<p class="p1"><b>After you install</b></p>
<p class="p2"><br></p>
--- a/contrib/macosx/Welcome.html Wed Jun 28 12:29:48 2006 +0200
+++ b/contrib/macosx/Welcome.html Fri Jun 30 21:35:28 2006 +0200
@@ -12,6 +12,6 @@
<body>
<p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p>
<p class="p2"><br></p>
-<p class="p1">It is based on Mercurial 0.8.</p>
+<p class="p1">It is based on Mercurial 0.9.</p>
</body>
</html>
--- a/contrib/mercurial.el Wed Jun 28 12:29:48 2006 +0200
+++ b/contrib/mercurial.el Fri Jun 30 21:35:28 2006 +0200
@@ -653,7 +653,7 @@
you're already familiar with VC, the same keybindings and functions
will generally work.
-Below is a list of many common SCM tasks. In the list, `G/L'
+Below is a list of many common SCM tasks. In the list, `G/L\'
indicates whether a key binding is global (G) to a repository or local
(L) to a file. Many commands take a prefix argument.
@@ -682,6 +682,8 @@
Update working directory after pull G C-c h u hg-update
See changes that can be pushed G C-c h . hg-outgoing
Push changes G C-c h > hg-push"
+ (unless vc-make-backup-files
+ (set (make-local-variable 'backup-inhibited) t))
(run-hooks 'hg-mode-hook))
(defun hg-find-file-hook ()
@@ -729,6 +731,8 @@
(goto-char 0)
(cd (hg-root path)))
(when update
+ (unless vc-make-backup-files
+ (set (make-local-variable 'backup-inhibited) t))
(with-current-buffer buf
(hg-mode-line)))))
@@ -968,6 +972,7 @@
(cd (hg-root path)))
(when update
(with-current-buffer buf
+ (set (make-local-variable 'backup-inhibited) nil)
(hg-mode-line)))))
(defun hg-incoming (&optional repo)
--- a/hgext/mq.py Wed Jun 28 12:29:48 2006 +0200
+++ b/hgext/mq.py Fri Jun 30 21:35:28 2006 +0200
@@ -214,7 +214,6 @@
return pp[0]
if p1 in arevs:
return pp[1]
- return None
return pp[0]
def mergepatch(self, repo, mergeq, series, wlock):
@@ -386,15 +385,21 @@
self.ui.write("Local changes found, refresh first\n")
sys.exit(1)
def new(self, repo, patch, msg=None, force=None):
- if not force:
- self.check_localchanges(repo)
+ commitfiles = []
+ (c, a, r, d, u) = repo.changes(None, None)
+ if c or a or d or r:
+ if not force:
+ raise util.Abort(_("Local changes found, refresh first"))
+ else:
+ commitfiles = c + a + r
self.check_toppatch(repo)
wlock = repo.wlock()
insert = self.series_end()
if msg:
- n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
+ n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
+ wlock=wlock)
else:
- n = repo.commit([],
+ n = repo.commit(commitfiles,
"New patch: %s" % patch, force=True, wlock=wlock)
if n == None:
self.ui.warn("repo commit failed\n")
@@ -412,6 +417,8 @@
wlock = None
r = self.qrepo()
if r: r.add([patch])
+ if commitfiles:
+ self.refresh(repo, short=True)
def strip(self, repo, rev, update=True, backup="all", wlock=None):
def limitheads(chlog, stop):
--- a/hgweb.cgi Wed Jun 28 12:29:48 2006 +0200
+++ b/hgweb.cgi Fri Jun 30 21:35:28 2006 +0200
@@ -6,7 +6,11 @@
cgitb.enable()
# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
-from mercurial import hgweb
+from mercurial.hgweb.hgweb_mod import hgweb
+from mercurial.hgweb.request import wsgiapplication
+import mercurial.hgweb.wsgicgi as wsgicgi
-h = hgweb.hgweb("/path/to/repo", "repository name")
-h.run()
+def make_web_app():
+ return hgweb("/path/to/repo", "repository name")
+
+wsgicgi.launch(wsgiapplication(make_web_app))
--- a/hgwebdir.cgi Wed Jun 28 12:29:48 2006 +0200
+++ b/hgwebdir.cgi Fri Jun 30 21:35:28 2006 +0200
@@ -6,7 +6,9 @@
cgitb.enable()
# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
-from mercurial import hgweb
+from mercurial.hgweb.hgwebdir_mod import hgwebdir
+from mercurial.hgweb.request import wsgiapplication
+import mercurial.hgweb.wsgicgi as wsgicgi
# The config file looks like this. You can have paths to individual
# repos, collections of repos in a directory tree, or both.
@@ -27,5 +29,7 @@
# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
# or use a dictionary with entries like 'virtual/path': '/real/path'
-h = hgweb.hgwebdir("hgweb.config")
-h.run()
+def make_web_app():
+ return hgwebdir("hgweb.config")
+
+wsgicgi.launch(wsgiapplication(make_web_app))
--- a/mercurial/changelog.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/changelog.py Fri Jun 30 21:35:28 2006 +0200
@@ -39,21 +39,10 @@
def add(self, manifest, list, desc, transaction, p1=None, p2=None,
user=None, date=None):
if date:
- # validate explicit (probably user-specified) date and
- # time zone offset. values must fit in signed 32 bits for
- # current 32-bit linux runtimes. timezones go from UTC-12
- # to UTC+14
- try:
- when, offset = map(int, date.split(' '))
- except ValueError:
- raise ValueError(_('invalid date: %r') % date)
- if abs(when) > 0x7fffffff:
- raise ValueError(_('date exceeds 32 bits: %d') % when)
- if offset < -50400 or offset > 43200:
- raise ValueError(_('impossible time zone offset: %d') % offset)
+ parseddate = "%d %d" % util.parsedate(date)
else:
- date = "%d %d" % util.makedate()
+ parseddate = "%d %d" % util.makedate()
list.sort()
- l = [hex(manifest), user, date] + list + ["", desc]
+ l = [hex(manifest), user, parseddate] + list + ["", desc]
text = "\n".join(l)
return self.addrevision(text, transaction, self.count(), p1, p2)
--- a/mercurial/commands.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/commands.py Fri Jun 30 21:35:28 2006 +0200
@@ -12,7 +12,7 @@
demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
demandload(globals(), "fnmatch mdiff random signal tempfile time")
demandload(globals(), "traceback errno socket version struct atexit sets bz2")
-demandload(globals(), "archival changegroup")
+demandload(globals(), "archival cStringIO changegroup email.Parser")
demandload(globals(), "hgweb.server sshserver")
class UnknownCommand(Exception):
@@ -1719,11 +1719,16 @@
If there are outstanding changes in the working directory, import
will abort unless given the -f flag.
- If a patch looks like a mail message (its first line starts with
- "From " or looks like an RFC822 header), it will not be applied
- unless the -f option is used. The importer neither parses nor
- discards mail headers, so use -f only to override the "mailness"
- safety check, not to import a real mail message.
+ You can import a patch straight from a mail message. Even patches
+ as attachments work (body part must be type text/plain or
+ text/x-patch to be used). From and Subject headers of email
+ message are used as default committer and commit message. All
+ text/plain body parts before first diff are added to commit
+ message.
+
+ If imported patch was generated by hg export, user and description
+ from patch override values from message headers and body. Values
+ given on command line with -m and -u override these.
To read a patch from standard input, use patch name "-".
"""
@@ -1739,79 +1744,98 @@
# attempt to detect the start of a patch
# (this heuristic is borrowed from quilt)
- diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
+ diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
'retrieving revision [0-9]+(\.[0-9]+)*$|' +
- '(---|\*\*\*)[ \t])')
+ '(---|\*\*\*)[ \t])', re.MULTILINE)
for patch in patches:
pf = os.path.join(d, patch)
- message = []
+ message = None
user = None
date = None
hgpatch = False
+
+ p = email.Parser.Parser()
if pf == '-':
- f = sys.stdin
- fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
- pf = tmpname
- tmpfp = os.fdopen(fd, 'w')
+ msg = p.parse(sys.stdin)
ui.status(_("applying patch from stdin\n"))
else:
- f = open(pf)
- tmpfp, tmpname = None, None
+ msg = p.parse(file(pf))
ui.status(_("applying %s\n") % patch)
+
+ fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
+ tmpfp = os.fdopen(fd, 'w')
try:
- while True:
- line = f.readline()
- if not line: break
- if tmpfp: tmpfp.write(line)
- line = line.rstrip()
- if (not message and not hgpatch and
- mailre.match(line) and not opts['force']):
- if len(line) > 35:
- line = line[:32] + '...'
- raise util.Abort(_('first line looks like a '
- 'mail header: ') + line)
- if diffre.match(line):
+ message = msg['Subject']
+ if message:
+ message = message.replace('\n\t', ' ')
+ ui.debug('Subject: %s\n' % message)
+ user = msg['From']
+ if user:
+ ui.debug('From: %s\n' % user)
+ diffs_seen = 0
+ ok_types = ('text/plain', 'text/x-patch')
+ for part in msg.walk():
+ content_type = part.get_content_type()
+ ui.debug('Content-Type: %s\n' % content_type)
+ if content_type not in ok_types:
+ continue
+ payload = part.get_payload(decode=True)
+ m = diffre.search(payload)
+ if m:
+ ui.debug(_('found patch at byte %d\n') % m.start(0))
+ diffs_seen += 1
+ hgpatch = False
+ fp = cStringIO.StringIO()
+ if message:
+ fp.write(message)
+ fp.write('\n')
+ for line in payload[:m.start(0)].splitlines():
+ if line.startswith('# HG changeset patch'):
+ ui.debug(_('patch generated by hg export\n'))
+ hgpatch = True
+ # drop earlier commit message content
+ fp.seek(0)
+ fp.truncate()
+ elif hgpatch:
+ if line.startswith('# User '):
+ user = line[7:]
+ ui.debug('From: %s\n' % user)
+ elif line.startswith("# Date "):
+ date = line[7:]
+ if not line.startswith('# '):
+ fp.write(line)
+ fp.write('\n')
+ message = fp.getvalue()
if tmpfp:
- for chunk in util.filechunkiter(f):
- tmpfp.write(chunk)
- break
- elif hgpatch:
- # parse values when importing the result of an hg export
- if line.startswith("# User "):
- user = line[7:]
- ui.debug(_('User: %s\n') % user)
- elif line.startswith("# Date "):
- date = line[7:]
- elif not line.startswith("# ") and line:
- message.append(line)
- hgpatch = False
- elif line == '# HG changeset patch':
- hgpatch = True
- message = [] # We may have collected garbage
- elif message or line:
- message.append(line)
+ tmpfp.write(payload)
+ if not payload.endswith('\n'):
+ tmpfp.write('\n')
+ elif not diffs_seen and message and content_type == 'text/plain':
+ message += '\n' + payload
if opts['message']:
# pickup the cmdline msg
message = opts['message']
elif message:
# pickup the patch msg
- message = '\n'.join(message).rstrip()
+ message = message.strip()
else:
# launch the editor
message = None
ui.debug(_('message:\n%s\n') % message)
- if tmpfp: tmpfp.close()
- files = util.patch(strip, pf, ui)
-
+ tmpfp.close()
+ if not diffs_seen:
+ raise util.Abort(_('no diffs found'))
+
+ files = util.patch(strip, tmpname, ui)
if len(files) > 0:
addremove_lock(ui, repo, files, {})
repo.commit(files, message, user, date)
finally:
- if tmpname: os.unlink(tmpname)
+ os.unlink(tmpname)
def incoming(ui, repo, source="default", **opts):
"""show new changesets found in source
@@ -1851,7 +1875,10 @@
# use the created uncompressed bundlerepo
other = bundlerepo.bundlerepository(ui, repo.root, fname)
- o = other.changelog.nodesbetween(incoming)[0]
+ revs = None
+ if opts['rev']:
+ revs = [other.lookup(rev) for rev in opts['rev']]
+ o = other.changelog.nodesbetween(incoming, revs)[0]
if opts['newest_first']:
o.reverse()
displayer = show_changeset(ui, other, opts)
@@ -2061,13 +2088,16 @@
ui.setconfig("ui", "ssh", opts['ssh'])
if opts['remotecmd']:
ui.setconfig("ui", "remotecmd", opts['remotecmd'])
+ revs = None
+ if opts['rev']:
+ revs = [repo.lookup(rev) for rev in opts['rev']]
other = hg.repository(ui, dest)
o = repo.findoutgoing(other, force=opts['force'])
if not o:
ui.status(_("no changes found\n"))
return
- o = repo.changelog.nodesbetween(o)[0]
+ o = repo.changelog.nodesbetween(o, revs)[0]
if opts['newest_first']:
o.reverse()
displayer = show_changeset(ui, repo, opts)
@@ -2998,11 +3028,13 @@
('n', 'newest-first', None, _('show newest record first')),
('', 'bundle', '', _('file to store the bundles into')),
('p', 'patch', None, _('show patch')),
+ ('r', 'rev', [], _('a specific revision you would like to pull')),
('', 'template', '', _('display with template')),
('e', 'ssh', '', _('specify ssh command to use')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
- _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
+ _('hg incoming [-p] [-n] [-M] [-r REV]...'
+ '[--bundle FILENAME] [SOURCE]')),
"^init": (init, [], _('hg init [DEST]')),
"locate":
(locate,
@@ -3040,12 +3072,13 @@
_('run even when remote repository is unrelated')),
('p', 'patch', None, _('show patch')),
('', 'style', '', _('display using template map file')),
+ ('r', 'rev', [], _('a specific revision you would like to push')),
('n', 'newest-first', None, _('show newest record first')),
('', 'template', '', _('display with template')),
('e', 'ssh', '', _('specify ssh command to use')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
- _('hg outgoing [-M] [-p] [-n] [DEST]')),
+ _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
"^parents":
(parents,
[('b', 'branches', None, _('show branches')),
--- a/mercurial/hgweb/common.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/hgweb/common.py Fri Jun 30 21:35:28 2006 +0200
@@ -17,7 +17,7 @@
else:
return os.stat(hg_path).st_mtime
-def staticfile(directory, fname):
+def staticfile(directory, fname, req):
"""return a file inside directory with guessed content-type header
fname always uses '/' as directory separator and isn't allowed to
@@ -36,7 +36,9 @@
try:
os.stat(path)
ct = mimetypes.guess_type(path)[0] or "text/plain"
- return "Content-type: %s\n\n%s" % (ct, file(path).read())
+ req.header([('Content-type', ct),
+ ('Content-length', os.path.getsize(path))])
+ return file(path).read()
except (TypeError, OSError):
# illegal fname or unreadable file
return ""
--- a/mercurial/hgweb/hgweb_mod.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/hgweb/hgweb_mod.py Fri Jun 30 21:35:28 2006 +0200
@@ -10,9 +10,8 @@
import os.path
import mimetypes
from mercurial.demandload import demandload
-demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile")
+demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
-demandload(globals(), "mercurial.hgweb.request:hgrequest")
demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
from mercurial.node import *
from mercurial.i18n import gettext as _
@@ -651,9 +650,12 @@
raise Exception("suspicious path")
return p
- def run(self, req=hgrequest()):
+ def run(self, req):
def header(**map):
- yield self.t("header", **map)
+ header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
+ msg = mimetools.Message(header_file, 0)
+ req.header(msg.items())
+ yield header_file.read()
def footer(**map):
yield self.t("footer",
@@ -724,7 +726,6 @@
method(req)
else:
req.write(self.t("error"))
- req.done()
def do_changelog(self, req):
hi = self.repo.changelog.count() - 1
@@ -830,7 +831,7 @@
static = self.repo.ui.config("web", "static",
os.path.join(self.templatepath,
"static"))
- req.write(staticfile(static, fname)
+ req.write(staticfile(static, fname, req)
or self.t("error", error="%r not found" % fname))
def do_capabilities(self, req):
--- a/mercurial/hgweb/hgwebdir_mod.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py Fri Jun 30 21:35:28 2006 +0200
@@ -8,10 +8,9 @@
import os
from mercurial.demandload import demandload
-demandload(globals(), "ConfigParser")
+demandload(globals(), "ConfigParser mimetools cStringIO")
demandload(globals(), "mercurial:ui,hg,util,templater")
demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
-demandload(globals(), "mercurial.hgweb.request:hgrequest")
demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
from mercurial.i18n import gettext as _
@@ -47,9 +46,12 @@
self.repos.append((name.lstrip(os.sep), repo))
self.repos.sort()
- def run(self, req=hgrequest()):
+ def run(self, req):
def header(**map):
- yield tmpl("header", **map)
+ header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
+ msg = mimetools.Message(header_file, 0)
+ req.header(msg.items())
+ yield header_file.read()
def footer(**map):
yield tmpl("footer", motd=self.motd, **map)
@@ -133,7 +135,7 @@
if req.form.has_key('static'):
static = os.path.join(templater.templatepath(), "static")
fname = req.form['static'][0]
- req.write(staticfile(static, fname)
+ req.write(staticfile(static, fname, req)
or tmpl("error", error="%r not found" % fname))
else:
sortable = ["name", "description", "contact", "lastchange"]
--- a/mercurial/hgweb/request.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/hgweb/request.py Fri Jun 30 21:35:28 2006 +0200
@@ -10,13 +10,48 @@
demandload(globals(), "socket sys cgi os errno")
from mercurial.i18n import gettext as _
-class hgrequest(object):
- def __init__(self, inp=None, out=None, env=None):
- self.inp = inp or sys.stdin
- self.out = out or sys.stdout
- self.env = env or os.environ
+class wsgiapplication(object):
+ def __init__(self, destmaker):
+ self.destmaker = destmaker
+
+ def __call__(self, wsgienv, start_response):
+ return _wsgirequest(self.destmaker(), wsgienv, start_response)
+
+class _wsgioutputfile(object):
+ def __init__(self, request):
+ self.request = request
+
+ def write(self, data):
+ self.request.write(data)
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+ def flush(self):
+ return None
+ def close(self):
+ return None
+
+class _wsgirequest(object):
+ def __init__(self, destination, wsgienv, start_response):
+ version = wsgienv['wsgi.version']
+ if (version < (1,0)) or (version >= (2, 0)):
+ raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \
+ % version)
+ self.inp = wsgienv['wsgi.input']
+ self.out = _wsgioutputfile(self)
+ self.server_write = None
+ self.err = wsgienv['wsgi.errors']
+ self.threaded = wsgienv['wsgi.multithread']
+ self.multiprocess = wsgienv['wsgi.multiprocess']
+ self.run_once = wsgienv['wsgi.run_once']
+ self.env = wsgienv
self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
- self.will_close = True
+ self.start_response = start_response
+ self.headers = []
+ destination.run(self)
+
+ def __iter__(self):
+ return iter([])
def read(self, count=-1):
return self.inp.read(count)
@@ -27,23 +62,22 @@
for part in thing:
self.write(part)
else:
+ thing = str(thing)
+ if self.server_write is None:
+ if not self.headers:
+ raise RuntimeError("request.write called before headers sent (%s)." % thing)
+ self.server_write = self.start_response('200 Script output follows',
+ self.headers)
+ self.start_response = None
+ self.headers = None
try:
- self.out.write(str(thing))
+ self.server_write(thing)
except socket.error, inst:
if inst[0] != errno.ECONNRESET:
raise
- def done(self):
- if self.will_close:
- self.inp.close()
- self.out.close()
- else:
- self.out.flush()
-
def header(self, headers=[('Content-type','text/html')]):
- for header in headers:
- self.out.write("%s: %s\r\n" % header)
- self.out.write("\r\n")
+ self.headers.extend(headers)
def httphdr(self, type, filename=None, length=0, headers={}):
headers = headers.items()
@@ -51,12 +85,6 @@
if filename:
headers.append(('Content-disposition', 'attachment; filename=%s' %
filename))
- # we do not yet support http 1.1 chunked transfer, so we have
- # to force connection to close if content-length not known
if length:
headers.append(('Content-length', str(length)))
- self.will_close = False
- else:
- headers.append(('Connection', 'close'))
- self.will_close = True
self.header(headers)
--- a/mercurial/hgweb/server.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/hgweb/server.py Fri Jun 30 21:35:28 2006 +0200
@@ -10,7 +10,7 @@
import os, sys, errno
demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
demandload(globals(), "mercurial:ui,hg,util,templater")
-demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:hgrequest")
+demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
from mercurial.i18n import gettext as _
def _splitURI(uri):
@@ -25,6 +25,17 @@
path, query = uri, ''
return urllib.unquote(path), query
+class _error_logger(object):
+ def __init__(self, handler):
+ self.handler = handler
+ def flush(self):
+ pass
+ def write(str):
+ self.writelines(str.split('\n'))
+ def writelines(seq):
+ for msg in seq:
+ self.handler.log_error("HG error: %s", msg)
+
class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, *args, **kargs):
self.protocol_version = 'HTTP/1.1'
@@ -76,17 +87,72 @@
length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length
- accept = []
- for line in self.headers.getallmatchingheaders('accept'):
- if line[:1] in "\t\n\r ":
- accept.append(line.strip())
- else:
- accept = accept + line[7:].split(',')
- env['HTTP_ACCEPT'] = ','.join(accept)
+ for header in [h for h in self.headers.keys() \
+ if h not in ('content-type', 'content-length')]:
+ hkey = 'HTTP_' + header.replace('-', '_').upper()
+ hval = self.headers.getheader(header)
+ hval = hval.replace('\n', '').strip()
+ if hval:
+ env[hkey] = hval
+ env['SERVER_PROTOCOL'] = self.request_version
+ env['wsgi.version'] = (1, 0)
+ env['wsgi.url_scheme'] = 'http'
+ env['wsgi.input'] = self.rfile
+ env['wsgi.errors'] = _error_logger(self)
+ env['wsgi.multithread'] = isinstance(self.server,
+ SocketServer.ThreadingMixIn)
+ env['wsgi.multiprocess'] = isinstance(self.server,
+ SocketServer.ForkingMixIn)
+ env['wsgi.run_once'] = 0
+
+ self.close_connection = True
+ self.saved_status = None
+ self.saved_headers = []
+ self.sent_headers = False
+ self.length = None
+ req = self.server.reqmaker(env, self._start_response)
+ for data in req:
+ if data:
+ self._write(data)
- req = hgrequest(self.rfile, self.wfile, env)
- self.send_response(200, "Script output follows")
- self.close_connection = self.server.make_and_run_handler(req)
+ def send_headers(self):
+ if not self.saved_status:
+ raise AssertionError("Sending headers before start_response() called")
+ saved_status = self.saved_status.split(None, 1)
+ saved_status[0] = int(saved_status[0])
+ self.send_response(*saved_status)
+ should_close = True
+ for h in self.saved_headers:
+ self.send_header(*h)
+ if h[0].lower() == 'content-length':
+ should_close = False
+ self.length = int(h[1])
+ if should_close:
+ self.send_header('Connection', 'close')
+ self.close_connection = should_close
+ self.end_headers()
+ self.sent_headers = True
+
+ def _start_response(self, http_status, headers, exc_info=None):
+ code, msg = http_status.split(None, 1)
+ code = int(code)
+ self.saved_status = http_status
+ bad_headers = ('connection', 'transfer-encoding')
+ self.saved_headers = [ h for h in headers \
+ if h[0].lower() not in bad_headers ]
+ return self._write
+
+ def _write(self, data):
+ if not self.saved_status:
+ raise AssertionError("data written before start_response() called")
+ elif not self.sent_headers:
+ self.send_headers()
+ if self.length is not None:
+ if len(data) > self.length:
+ raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
+ self.length = self.length - len(data)
+ self.wfile.write(data)
+ self.wfile.flush()
def create_server(ui, repo):
use_threads = True
@@ -126,8 +192,9 @@
self.webdir_conf = webdir_conf
self.webdirmaker = hgwebdir
self.repoviewmaker = hgweb
+ self.reqmaker = wsgiapplication(self.make_handler)
- def make_and_run_handler(self, req):
+ def make_handler(self):
if self.webdir_conf:
hgwebobj = self.webdirmaker(self.webdir_conf)
elif self.repo is not None:
@@ -135,8 +202,7 @@
repo.origroot))
else:
raise hg.RepoError(_('no repo found'))
- hgwebobj.run(req)
- return req.will_close
+ return hgwebobj
class IPv6HTTPServer(MercurialHTTPServer):
address_family = getattr(socket, 'AF_INET6', None)
@@ -144,7 +210,7 @@
def __init__(self, *args, **kwargs):
if self.address_family is None:
raise hg.RepoError(_('IPv6 not available on this system'))
- super(IPv6HTTPServer, self).__init__(*args, **kargs)
+ super(IPv6HTTPServer, self).__init__(*args, **kwargs)
if use_ipv6:
return IPv6HTTPServer((address, port), _hgwebhandler)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/wsgicgi.py Fri Jun 30 21:35:28 2006 +0200
@@ -0,0 +1,69 @@
+# hgweb/wsgicgi.py - CGI->WSGI translator
+#
+# Copyright 2006 Eric Hopper <hopper@omnifarious.org>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# This was originally copied from the public domain code at
+# http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
+
+import os, sys
+
+def launch(application):
+
+ environ = dict(os.environ.items())
+ environ['wsgi.input'] = sys.stdin
+ environ['wsgi.errors'] = sys.stderr
+ environ['wsgi.version'] = (1,0)
+ environ['wsgi.multithread'] = False
+ environ['wsgi.multiprocess'] = True
+ environ['wsgi.run_once'] = True
+
+ if environ.get('HTTPS','off') in ('on','1'):
+ environ['wsgi.url_scheme'] = 'https'
+ else:
+ environ['wsgi.url_scheme'] = 'http'
+
+ headers_set = []
+ headers_sent = []
+
+ def write(data):
+ if not headers_set:
+ raise AssertionError("write() before start_response()")
+
+ elif not headers_sent:
+ # Before the first output, send the stored headers
+ status, response_headers = headers_sent[:] = headers_set
+ sys.stdout.write('Status: %s\r\n' % status)
+ for header in response_headers:
+ sys.stdout.write('%s: %s\r\n' % header)
+ sys.stdout.write('\r\n')
+
+ sys.stdout.write(data)
+ sys.stdout.flush()
+
+ def start_response(status,response_headers,exc_info=None):
+ if exc_info:
+ try:
+ if headers_sent:
+ # Re-raise original exception if headers sent
+ raise exc_info[0], exc_info[1], exc_info[2]
+ finally:
+ exc_info = None # avoid dangling circular ref
+ elif headers_set:
+ raise AssertionError("Headers already set!")
+
+ headers_set[:] = [status,response_headers]
+ return write
+
+ result = application(environ, start_response)
+ try:
+ for data in result:
+ if data: # don't send headers until body appears
+ write(data)
+ if not headers_sent:
+ write('') # send headers now if body was empty
+ finally:
+ if hasattr(result,'close'):
+ result.close()
--- a/mercurial/templater.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/templater.py Fri Jun 30 21:35:28 2006 +0200
@@ -225,6 +225,10 @@
'''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
return util.datestr(date, format='%Y-%m-%d %H:%M')
+def hgdate(date):
+ '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
+ return "%d %d" % date
+
def nl2br(text):
'''replace raw newlines with xhtml line breaks.'''
return text.replace('\n', '<br/>\n')
@@ -282,6 +286,7 @@
"fill76": lambda x: fill(x, width=76),
"firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
"tabindent": lambda x: indent(x, '\t'),
+ "hgdate": hgdate,
"isodate": isodate,
"obfuscate": obfuscate,
"permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
--- a/mercurial/util.py Wed Jun 28 12:29:48 2006 +0200
+++ b/mercurial/util.py Fri Jun 30 21:35:28 2006 +0200
@@ -859,6 +859,49 @@
s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
return s
+def strdate(string, format='%a %b %d %H:%M:%S %Y'):
+ """parse a localized time string and return a (unixtime, offset) tuple.
+ if the string cannot be parsed, ValueError is raised."""
+ def hastimezone(string):
+ return (string[-4:].isdigit() and
+ (string[-5] == '+' or string[-5] == '-') and
+ string[-6].isspace())
+
+ if hastimezone(string):
+ date, tz = string.rsplit(None, 1)
+ tz = int(tz)
+ offset = - 3600 * (tz / 100) - 60 * (tz % 100)
+ else:
+ date, offset = string, 0
+ when = int(time.mktime(time.strptime(date, format))) + offset
+ return when, offset
+
+def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')):
+ """parse a localized time string and return a (unixtime, offset) tuple.
+ The date may be a "unixtime offset" string or in one of the specified
+ formats."""
+ try:
+ when, offset = map(int, string.split(' '))
+ except ValueError:
+ for format in formats:
+ try:
+ when, offset = strdate(string, format)
+ except ValueError:
+ pass
+ else:
+ break
+ else:
+ raise ValueError(_('invalid date: %r') % string)
+ # validate explicit (probably user-specified) date and
+ # time zone offset. values must fit in signed 32 bits for
+ # current 32-bit linux runtimes. timezones go from UTC-12
+ # to UTC+14
+ if abs(when) > 0x7fffffff:
+ raise ValueError(_('date exceeds 32 bits: %d') % when)
+ if offset < -50400 or offset > 43200:
+ raise ValueError(_('impossible time zone offset: %d') % offset)
+ return when, offset
+
def shortuser(user):
"""Return a short representation of a user name or email address."""
f = user.find('@')
--- a/templates/changeset-raw.tmpl Wed Jun 28 12:29:48 2006 +0200
+++ b/templates/changeset-raw.tmpl Fri Jun 30 21:35:28 2006 +0200
@@ -1,7 +1,7 @@
#header#
# HG changeset patch
# User #author#
-# Date #date|date#
+# Date #date|hgdate#
# Node ID #node#
#parent%changesetparent#
#desc#
--- a/templates/map-raw Wed Jun 28 12:29:48 2006 +0200
+++ b/templates/map-raw Fri Jun 30 21:35:28 2006 +0200
@@ -5,8 +5,8 @@
difflineminus = '#line#'
difflineat = '#line#'
diffline = '#line#'
-changesetparent = '# parent: #node#'
-changesetchild = '# child: #node#'
+changesetparent = '# Parent #node#'
+changesetchild = '# Child #node#'
filenodelink = ''
filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#'
fileline = '#line#'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-import Fri Jun 30 21:35:28 2006 +0200
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+hg init a
+echo line 1 > a/a
+hg --cwd a ci -d '0 0' -Ama
+
+echo line 2 >> a/a
+hg --cwd a ci -u someone -d '1 0' -m'second change'
+
+echo % import exported patch
+hg clone -r0 a b
+hg --cwd a export tip > tip.patch
+hg --cwd b import ../tip.patch
+echo % message should be same
+hg --cwd b tip | grep 'second change'
+echo % committer should be same
+hg --cwd b tip | grep someone
+rm -rf b
+
+echo % import of plain diff should fail without message
+hg clone -r0 a b
+hg --cwd a diff -r0:1 > tip.patch
+hg --cwd b import ../tip.patch
+rm -rf b
+
+echo % import of plain diff should be ok with message
+hg clone -r0 a b
+hg --cwd a diff -r0:1 > tip.patch
+hg --cwd b import -mpatch ../tip.patch
+rm -rf b
+
+echo % import from stdin
+hg clone -r0 a b
+hg --cwd a export tip | hg --cwd b import -
+rm -rf b
+
+echo % override commit message
+hg clone -r0 a b
+hg --cwd a export tip | hg --cwd b import -m 'override' -
+hg --cwd b tip | grep override
+rm -rf b
+
+cat > mkmsg.py <<EOF
+import email.Message, sys
+msg = email.Message.Message()
+msg.set_payload('email commit message\n' + open('tip.patch').read())
+msg['Subject'] = 'email patch'
+msg['From'] = 'email patcher'
+sys.stdout.write(msg.as_string())
+EOF
+
+echo % plain diff in email, subject, message body
+hg clone -r0 a b
+hg --cwd a diff -r0:1 > tip.patch
+python mkmsg.py > msg.patch
+hg --cwd b import ../msg.patch
+hg --cwd b tip | grep email
+rm -rf b
+
+echo % plain diff in email, no subject, message body
+hg clone -r0 a b
+grep -v '^Subject:' msg.patch | hg --cwd b import -
+rm -rf b
+
+echo % plain diff in email, subject, no message body
+hg clone -r0 a b
+grep -v '^email ' msg.patch | hg --cwd b import -
+rm -rf b
+
+echo % plain diff in email, no subject, no message body, should fail
+hg clone -r0 a b
+grep -v '^\(Subject\|email\)' msg.patch | hg --cwd b import -
+rm -rf b
+
+echo % hg export in email, should use patch header
+hg clone -r0 a b
+hg --cwd a export tip > tip.patch
+python mkmsg.py | hg --cwd b import -
+hg --cwd b tip | grep second
+rm -rf b
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-import.out Fri Jun 30 21:35:28 2006 +0200
@@ -0,0 +1,103 @@
+adding a
+% import exported patch
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying ../tip.patch
+patching file a
+% message should be same
+summary: second change
+% committer should be same
+user: someone
+% import of plain diff should fail without message
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying ../tip.patch
+patching file a
+transaction abort!
+rollback completed
+% import of plain diff should be ok with message
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying ../tip.patch
+patching file a
+% import from stdin
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+% override commit message
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+summary: override
+% plain diff in email, subject, message body
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying ../msg.patch
+patching file a
+user: email patcher
+summary: email patch
+% plain diff in email, no subject, message body
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+% plain diff in email, subject, no message body
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+% plain diff in email, no subject, no message body, should fail
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+transaction abort!
+rollback completed
+% hg export in email, should use patch header
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+patching file a
+summary: second change
--- a/tests/test-incoming-outgoing Wed Jun 28 12:29:48 2006 +0200
+++ b/tests/test-incoming-outgoing Fri Jun 30 21:35:28 2006 +0200
@@ -14,8 +14,10 @@
hg init new
# http incoming
http_proxy= hg -R new incoming http://localhost:20059/
+http_proxy= hg -R new incoming -r 4 http://localhost:20059/
# local incoming
hg -R new incoming test
+hg -R new incoming -r 4 test
# test with --bundle
http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/
@@ -42,5 +44,6 @@
cd ..
hg -R test-dev outgoing test
http_proxy= hg -R test-dev outgoing http://localhost:20059/
+http_proxy= hg -R test-dev outgoing -r 11 http://localhost:20059/
kill `cat test/hg.pid`
--- a/tests/test-incoming-outgoing.out Wed Jun 28 12:29:48 2006 +0200
+++ b/tests/test-incoming-outgoing.out Fri Jun 30 21:35:28 2006 +0200
@@ -75,6 +75,31 @@
date: Mon Jan 12 13:46:40 1970 +0000
summary: 4
+changeset: 0:9cb21d99fe27
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 0
+
+changeset: 1:d717f5dfad6a
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 1
+
+changeset: 2:c0d6b86da426
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 2
+
+changeset: 3:dfacbd43b3fe
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 3
+
+changeset: 4:1f3a964b6022
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 4
+
changeset: 5:c028bcc7a28a
user: test
date: Mon Jan 12 13:46:40 1970 +0000
@@ -121,6 +146,31 @@
date: Mon Jan 12 13:46:40 1970 +0000
summary: 4
+changeset: 0:9cb21d99fe27
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 0
+
+changeset: 1:d717f5dfad6a
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 1
+
+changeset: 2:c0d6b86da426
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 2
+
+changeset: 3:dfacbd43b3fe
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 3
+
+changeset: 4:1f3a964b6022
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 4
+
changeset: 5:c028bcc7a28a
user: test
date: Mon Jan 12 13:46:40 1970 +0000
@@ -270,3 +320,19 @@
date: Mon Jan 12 13:46:40 1970 +0000
summary: 13
+searching for changes
+changeset: 9:3741c3ad1096
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 9
+
+changeset: 10:de4143c8d9a5
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 10
+
+changeset: 11:0e1c188b9a7a
+user: test
+date: Mon Jan 12 13:46:40 1970 +0000
+summary: 11
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-date Fri Jun 30 21:35:28 2006 +0200
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+hg init
+echo "test-parse-date" > a
+hg add a
+hg ci -d "2006-02-01 13:00:30" -m "rev 0"
+echo "hi!" >> a
+hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1"
+hg tag -d "2006-04-15 13:30" "Hi"
+hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1
+hg ci -d "1150000000 14400" -m "rev 4 (merge)"
+echo "fail" >> a
+hg ci -d "should fail" -m "fail"
+hg ci -d "100000000000000000 1400" -m "fail"
+hg ci -d "100000 1400000" -m "fail"
+hg log --template '{date|date}\n'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-date.out Fri Jun 30 21:35:28 2006 +0200
@@ -0,0 +1,19 @@
+reverting a
+changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8
+merging with changeset 2:99a1acecff55
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+abort: invalid date: 'should fail'
+transaction abort!
+rollback completed
+abort: date exceeds 32 bits: 100000000000000000
+transaction abort!
+rollback completed
+abort: impossible time zone offset: 1400000
+transaction abort!
+rollback completed
+Sun Jun 11 00:26:40 2006 -0400
+Sat Apr 15 13:30:00 2006 +0200
+Sat Apr 15 13:30:00 2006 +0000
+Wed Feb 01 13:00:30 2006 -0500
+Wed Feb 01 13:00:30 2006 +0000