merge with crew.
--- a/doc/hgrc.5.txt Tue Jun 20 23:58:21 2006 -0700
+++ b/doc/hgrc.5.txt Tue Jun 20 23:58:45 2006 -0700
@@ -381,6 +381,14 @@
Default is false.
allowpull;;
Whether to allow pulling from the repository. Default is true.
+ allow_push;;
+ Whether to allow pushing to the repository. If empty or not set,
+ push is not allowed. If the special value "*", any remote user
+ can push, including unauthenticated users. Otherwise, the remote
+ user must have been authenticated, and the authenticated user name
+ must be present in this list (separated by whitespace or ",").
+ The contents of the allow_push list are examined after the
+ deny_push list.
allowzip;;
(DEPRECATED) Whether to allow .zip downloading of repo revisions.
Default is false. This feature creates temporary files.
@@ -391,6 +399,13 @@
contact;;
Name or email address of the person in charge of the repository.
Default is "unknown".
+ deny_push;;
+ Whether to deny pushing to the repository. If empty or not set,
+ push is not denied. If the special value "*", all remote users
+ are denied push. Otherwise, unauthenticated users are all denied,
+ and any authenticated user name present in this list (separated by
+ whitespace or ",") is also denied. The contents of the deny_push
+ list are examined before the allow_push list.
description;;
Textual description of the repository's purpose or contents.
Default is "unknown".
@@ -407,6 +422,9 @@
Maximum number of files to list per changeset. Default is 10.
port;;
Port to listen on. Default is 8000.
+ push_ssl;;
+ Whether to require that inbound pushes be transported over SSL to
+ prevent password sniffing. Default is true.
style;;
Which template map style to use.
templates;;
--- a/mercurial/bdiff.c Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/bdiff.c Tue Jun 20 23:58:45 2006 -0700
@@ -10,6 +10,7 @@
*/
#include <Python.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
--- a/mercurial/commands.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/commands.py Tue Jun 20 23:58:45 2006 -0700
@@ -379,11 +379,20 @@
if node2:
change = repo.changelog.read(node2)
mmap2 = repo.manifest.read(change[0])
- date2 = util.datestr(change[2])
+ _date2 = util.datestr(change[2])
+ def date2(f):
+ return _date2
def read(f):
return repo.file(f).read(mmap2[f])
else:
- date2 = util.datestr()
+ tz = util.makedate()[1]
+ _date2 = util.datestr()
+ def date2(f):
+ try:
+ return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
+ except IOError, err:
+ if err.errno != errno.ENOENT: raise
+ return _date2
def read(f):
return repo.wread(f)
@@ -401,17 +410,17 @@
if f in mmap:
to = repo.file(f).read(mmap[f])
tn = read(f)
- fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
+ fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
showfunc=showfunc, ignorews=ignorews))
for f in added:
to = None
tn = read(f)
- fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
+ fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
showfunc=showfunc, ignorews=ignorews))
for f in removed:
to = repo.file(f).read(mmap[f])
tn = None
- fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
+ fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
showfunc=showfunc, ignorews=ignorews))
def trimuser(ui, name, rev, revcache):
--- a/mercurial/hg.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/hg.py Tue Jun 20 23:58:45 2006 -0700
@@ -11,34 +11,60 @@
from i18n import gettext as _
demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
+def bundle(ui, path):
+ if path.startswith('bundle://'):
+ path = path[9:]
+ else:
+ path = path[7:]
+ s = path.split("+", 1)
+ if len(s) == 1:
+ repopath, bundlename = "", s[0]
+ else:
+ repopath, bundlename = s
+ return bundlerepo.bundlerepository(ui, repopath, bundlename)
+
+def hg(ui, path):
+ ui.warn(_("hg:// syntax is deprecated, please use http:// instead\n"))
+ return httprepo.httprepository(ui, path.replace("hg://", "http://"))
+
+def local_(ui, path, create=0):
+ return localrepo.localrepository(ui, path, create)
+
+def old_http(ui, path):
+ ui.warn(_("old-http:// syntax is deprecated, "
+ "please use static-http:// instead\n"))
+ return statichttprepo.statichttprepository(
+ ui, path.replace("old-http://", "http://"))
+
+def static_http(ui, path):
+ return statichttprepo.statichttprepository(
+ ui, path.replace("static-http://", "http://"))
+
+protocols = {
+ 'bundle': bundle,
+ 'file': local_,
+ 'hg': hg,
+ 'http': lambda ui, path: httprepo.httprepository(ui, path),
+ 'https': lambda ui, path: httprepo.httpsrepository(ui, path),
+ 'old-http': old_http,
+ 'ssh': lambda ui, path: sshrepo.sshrepository(ui, path),
+ 'static-http': static_http,
+ None: local_,
+ }
+
def repository(ui, path=None, create=0):
- if path:
- if path.startswith("http://"):
- return httprepo.httprepository(ui, path)
- if path.startswith("https://"):
- return httprepo.httpsrepository(ui, path)
- if path.startswith("hg://"):
- ui.warn(_("hg:// syntax is deprecated, "
- "please use http:// instead\n"))
- return httprepo.httprepository(
- ui, path.replace("hg://", "http://"))
- if path.startswith("old-http://"):
- ui.warn(_("old-http:// syntax is deprecated, "
- "please use static-http:// instead\n"))
- return statichttprepo.statichttprepository(
- ui, path.replace("old-http://", "http://"))
- if path.startswith("static-http://"):
- return statichttprepo.statichttprepository(
- ui, path.replace("static-http://", "http://"))
- if path.startswith("ssh://"):
- return sshrepo.sshrepository(ui, path)
- if path.startswith("bundle://"):
- path = path[9:]
- s = path.split("+", 1)
- if len(s) == 1:
- repopath, bundlename = "", s[0]
- else:
- repopath, bundlename = s
- return bundlerepo.bundlerepository(ui, repopath, bundlename)
-
- return localrepo.localrepository(ui, path, create)
+ scheme = path
+ if scheme:
+ c = scheme.find(':')
+ scheme = c >= 0 and scheme[:c]
+ if not scheme: scheme = None
+ try:
+ ctor = protocols[scheme]
+ if create:
+ return ctor(ui, path, create)
+ return ctor(ui, path)
+ except KeyError:
+ raise util.Abort(_('protocol "%s" not known') % scheme)
+ except TypeError:
+ raise util.Abort(_('cannot create new repository over "%s" protocol') %
+ (scheme or 'file'))
--- a/mercurial/hgweb/hgweb_mod.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/hgweb/hgweb_mod.py Tue Jun 20 23:58:45 2006 -0700
@@ -10,7 +10,7 @@
import os.path
import mimetypes
from mercurial.demandload import demandload
-demandload(globals(), "re zlib ConfigParser cStringIO")
+demandload(globals(), "re zlib ConfigParser 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")
@@ -835,7 +835,97 @@
or self.t("error", error="%r not found" % fname))
def do_capabilities(self, req):
- resp = ''
+ resp = 'unbundle'
req.httphdr("application/mercurial-0.1", length=len(resp))
req.write(resp)
+ def check_perm(self, req, op, default):
+ '''check permission for operation based on user auth.
+ return true if op allowed, else false.
+ default is policy to use if no config given.'''
+
+ user = req.env.get('REMOTE_USER')
+
+ deny = self.repo.ui.config('web', 'deny_' + op, '')
+ deny = deny.replace(',', ' ').split()
+
+ if deny and (not user or deny == ['*'] or user in deny):
+ return False
+
+ allow = self.repo.ui.config('web', 'allow_' + op, '')
+ allow = allow.replace(',', ' ').split()
+
+ return (allow and (allow == ['*'] or user in allow)) or default
+
+ def do_unbundle(self, req):
+ def bail(response, headers={}):
+ length = int(req.env['CONTENT_LENGTH'])
+ for s in util.filechunkiter(req, limit=length):
+ # drain incoming bundle, else client will not see
+ # response when run outside cgi script
+ pass
+ req.httphdr("application/mercurial-0.1", headers=headers)
+ req.write('0\n')
+ req.write(response)
+
+ # require ssl by default, auth info cannot be sniffed and
+ # replayed
+ ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
+ if ssl_req and not req.env.get('HTTPS'):
+ bail(_('ssl required\n'))
+ return
+
+ # do not allow push unless explicitly allowed
+ if not self.check_perm(req, 'push', False):
+ bail(_('push not authorized\n'),
+ headers={'status': '401 Unauthorized'})
+ return
+
+ req.httphdr("application/mercurial-0.1")
+
+ their_heads = req.form['heads'][0].split(' ')
+
+ def check_heads():
+ heads = map(hex, self.repo.heads())
+ return their_heads == [hex('force')] or their_heads == heads
+
+ # fail early if possible
+ if not check_heads():
+ bail(_('unsynced changes\n'))
+ return
+
+ # do not lock repo until all changegroup data is
+ # streamed. save to temporary file.
+
+ fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
+ fp = os.fdopen(fd, 'wb+')
+ try:
+ length = int(req.env['CONTENT_LENGTH'])
+ for s in util.filechunkiter(req, limit=length):
+ fp.write(s)
+
+ lock = self.repo.lock()
+ try:
+ if not check_heads():
+ req.write('0\n')
+ req.write(_('unsynced changes\n'))
+ return
+
+ fp.seek(0)
+
+ # send addchangegroup output to client
+
+ old_stdout = sys.stdout
+ sys.stdout = cStringIO.StringIO()
+
+ try:
+ ret = self.repo.addchangegroup(fp, 'serve')
+ req.write('%d\n' % ret)
+ req.write(sys.stdout.getvalue())
+ finally:
+ sys.stdout = old_stdout
+ finally:
+ lock.release()
+ finally:
+ fp.close()
+ os.unlink(tempname)
--- a/mercurial/hgweb/request.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/hgweb/request.py Tue Jun 20 23:58:45 2006 -0700
@@ -18,6 +18,9 @@
self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
self.will_close = True
+ def read(self, count=-1):
+ return self.inp.read(count)
+
def write(self, *things):
for thing in things:
if hasattr(thing, "__iter__"):
@@ -42,9 +45,9 @@
self.out.write("%s: %s\r\n" % header)
self.out.write("\r\n")
- def httphdr(self, type, filename=None, length=0):
-
- headers = [('Content-type', type)]
+ def httphdr(self, type, filename=None, length=0, headers={}):
+ headers = headers.items()
+ headers.append(('Content-type', type))
if filename:
headers.append(('Content-disposition', 'attachment; filename=%s' %
filename))
--- a/mercurial/httprepo.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/httprepo.py Tue Jun 20 23:58:45 2006 -0700
@@ -10,7 +10,7 @@
from i18n import gettext as _
from demandload import *
demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
-demandload(globals(), "keepalive")
+demandload(globals(), "errno keepalive tempfile socket")
class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
def __init__(self, ui):
@@ -69,6 +69,22 @@
return userpass + '@' + hostport
return hostport
+class httpconnection(keepalive.HTTPConnection):
+ # must be able to send big bundle as stream.
+
+ def send(self, data):
+ if isinstance(data, str):
+ keepalive.HTTPConnection.send(self, data)
+ else:
+ # if auth required, some data sent twice, so rewind here
+ data.seek(0)
+ for chunk in util.filechunkiter(data):
+ keepalive.HTTPConnection.send(self, chunk)
+
+class httphandler(keepalive.HTTPHandler):
+ def http_open(self, req):
+ return self.do_open(httpconnection, req)
+
class httprepository(remoterepository):
def __init__(self, ui, path):
self.caps = None
@@ -86,7 +102,7 @@
proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
proxyauthinfo = None
- handler = keepalive.HTTPHandler()
+ handler = httphandler()
if proxyurl:
# proxy can be proper url or host[:port]
@@ -154,6 +170,8 @@
self.caps = self.do_read('capabilities').split()
except hg.RepoError:
self.caps = ()
+ self.ui.debug(_('capabilities: %s\n') %
+ (' '.join(self.caps or ['none'])))
return self.caps
capabilities = property(get_caps)
@@ -165,13 +183,19 @@
raise util.Abort(_('operation not supported over http'))
def do_cmd(self, cmd, **args):
+ data = args.pop('data', None)
+ headers = args.pop('headers', {})
self.ui.debug(_("sending %s command\n") % cmd)
q = {"cmd": cmd}
q.update(args)
qs = urllib.urlencode(q)
cu = "%s?%s" % (self.url, qs)
try:
- resp = urllib2.urlopen(cu)
+ resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
+ except urllib2.HTTPError, inst:
+ if inst.code == 401:
+ raise util.Abort(_('authorization failed'))
+ raise
except httplib.HTTPException, inst:
self.ui.debug(_('http error while sending %s command\n') % cmd)
self.ui.print_exc()
@@ -249,7 +273,34 @@
return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
def unbundle(self, cg, heads, source):
- raise util.Abort(_('operation not supported over http'))
+ # have to stream bundle to a temp file because we do not have
+ # http 1.1 chunked transfer.
+
+ fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
+ fp = os.fdopen(fd, 'wb+')
+ try:
+ for chunk in util.filechunkiter(cg):
+ fp.write(chunk)
+ length = fp.tell()
+ try:
+ rfp = self.do_cmd(
+ 'unbundle', data=fp,
+ headers={'content-length': length,
+ 'content-type': 'application/octet-stream'},
+ heads=' '.join(map(hex, heads)))
+ try:
+ ret = int(rfp.readline())
+ self.ui.write(rfp.read())
+ return ret
+ finally:
+ rfp.close()
+ except socket.error, err:
+ if err[0] in (errno.ECONNRESET, errno.EPIPE):
+ raise util.Abort(_('push failed: %s'), err[1])
+ raise util.Abort(err[1])
+ finally:
+ fp.close()
+ os.unlink(tempname)
class httpsrepository(httprepository):
pass
--- a/mercurial/localrepo.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/localrepo.py Tue Jun 20 23:58:45 2006 -0700
@@ -1115,9 +1115,8 @@
# servers, http servers).
if 'unbundle' in remote.capabilities:
- self.push_unbundle(remote, force, revs)
- else:
- self.push_addchangegroup(remote, force, revs)
+ return self.push_unbundle(remote, force, revs)
+ return self.push_addchangegroup(remote, force, revs)
def prepush(self, remote, force, revs):
base = {}
--- a/mercurial/mpatch.c Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/mpatch.c Tue Jun 20 23:58:45 2006 -0700
@@ -23,13 +23,15 @@
#include <Python.h>
#include <stdlib.h>
#include <string.h>
+
#ifdef _WIN32
-#ifdef _MSC_VER
-#define inline __inline
+# ifdef _MSC_VER
+/* msvc 6.0 has problems */
+# define inline __inline
typedef unsigned long uint32_t;
-#else
-#include <stdint.h>
-#endif
+# else
+# include <stdint.h>
+# endif
static uint32_t ntohl(uint32_t x)
{
return ((x & 0x000000ffUL) << 24) |
@@ -38,8 +40,10 @@
((x & 0xff000000UL) >> 24);
}
#else
-#include <sys/types.h>
-#include <arpa/inet.h>
+/* not windows */
+# include <sys/types.h>
+# include <arpa/inet.h>
+# include <stdint.h>
#endif
static char mpatch_doc[] = "Efficient binary patching.";
--- a/mercurial/util.py Tue Jun 20 23:58:21 2006 -0700
+++ b/mercurial/util.py Tue Jun 20 23:58:45 2006 -0700
@@ -821,16 +821,22 @@
s, self.buf = self.buf[:l], buffer(self.buf, l)
return s
-def filechunkiter(f, size = 65536):
- """Create a generator that produces all the data in the file size
- (default 65536) bytes at a time. Chunks may be less than size
- bytes if the chunk is the last chunk in the file, or the file is a
- socket or some other type of file that sometimes reads less data
- than is requested."""
- s = f.read(size)
- while len(s) > 0:
+def filechunkiter(f, size=65536, limit=None):
+ """Create a generator that produces the data in the file size
+ (default 65536) bytes at a time, up to optional limit (default is
+ to read all data). Chunks may be less than size bytes if the
+ chunk is the last chunk in the file, or the file is a socket or
+ some other type of file that sometimes reads less data than is
+ requested."""
+ assert size >= 0
+ assert limit is None or limit >= 0
+ while True:
+ if limit is None: nbytes = size
+ else: nbytes = min(limit, size)
+ s = nbytes and f.read(nbytes)
+ if not s: break
+ if limit: limit -= len(s)
yield s
- s = f.read(size)
def makedate():
lt = time.localtime()