bundle2-push: provide transaction to reply unbundler
This patch series is intended to allow bundle2 push reply part handlers to
make changes to the local repository; it has been developed in parallel with
an extension that allows the server to rebase incoming changesets while applying
them.
This diff adds an experimental config option "bundle2.pushback" which provides
a transaction to the reply unbundler during a push operation. This behavior is
opt-in because of potential security issues: the response can contain any part
type that has a handler defined, allowing the server to make arbitrary changes
to the local repository.
--- a/mercurial/bundle2.py Mon Nov 24 16:04:44 2014 -0800
+++ b/mercurial/bundle2.py Fri Nov 21 15:50:38 2014 -0800
@@ -877,7 +877,7 @@
'b2x:remote-changegroup': ('http', 'https'),
}
-def getrepocaps(repo):
+def getrepocaps(repo, allowpushback=False):
"""return the bundle2 capabilities for a given repo
Exists to allow extensions (like evolution) to mutate the capabilities.
@@ -887,6 +887,8 @@
if obsolete.isenabled(repo, obsolete.exchangeopt):
supportedformat = tuple('V%i' % v for v in obsolete.formats)
caps['b2x:obsmarkers'] = supportedformat
+ if allowpushback:
+ caps['b2x:pushback'] = ()
return caps
def bundle2caps(remote):
--- a/mercurial/exchange.py Mon Nov 24 16:04:44 2014 -0800
+++ b/mercurial/exchange.py Fri Nov 21 15:50:38 2014 -0800
@@ -572,8 +572,12 @@
The only currently supported type of data is changegroup but this will
evolve in the future."""
bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
+ pushback = (pushop.trmanager
+ and pushop.ui.configbool('experimental', 'bundle2.pushback'))
+
# create reply capability
- capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
+ capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
+ allowpushback=pushback))
bundler.newpart('b2x:replycaps', data=capsblob)
replyhandlers = []
for partgenname in b2partsgenorder:
@@ -590,7 +594,10 @@
except error.BundleValueError, exc:
raise util.Abort('missing support for %s' % exc)
try:
- op = bundle2.processbundle(pushop.repo, reply)
+ trgetter = None
+ if pushback:
+ trgetter = pushop.trmanager.transaction
+ op = bundle2.processbundle(pushop.repo, reply, trgetter)
except error.BundleValueError, exc:
raise util.Abort('missing support for %s' % exc)
for rephand in replyhandlers:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bundle2-pushback.t Fri Nov 21 15:50:38 2014 -0800
@@ -0,0 +1,111 @@
+ $ cat > bundle2.py << EOF
+ > """A small extension to test bundle2 pushback parts.
+ > Current bundle2 implementation doesn't provide a way to generate those
+ > parts, so they must be created by extensions.
+ > """
+ > from mercurial import bundle2, pushkey, exchange, util
+ > def _newhandlechangegroup(op, inpart):
+ > """This function wraps the changegroup part handler for getbundle.
+ > It issues an additional b2x:pushkey part to send a new
+ > bookmark back to the client"""
+ > result = bundle2.handlechangegroup(op, inpart)
+ > if 'b2x:pushback' in op.reply.capabilities:
+ > params = {'namespace': 'bookmarks',
+ > 'key': 'new-server-mark',
+ > 'old': '',
+ > 'new': 'tip'}
+ > encodedparams = [(k, pushkey.encode(v)) for (k,v) in params.items()]
+ > op.reply.newpart('b2x:pushkey', mandatoryparams=encodedparams)
+ > else:
+ > op.reply.newpart('b2x:output', data='pushback not enabled')
+ > return result
+ > _newhandlechangegroup.params = bundle2.handlechangegroup.params
+ > bundle2.parthandlermapping['b2x:changegroup'] = _newhandlechangegroup
+ > EOF
+
+ $ cat >> $HGRCPATH <<EOF
+ > [ui]
+ > ssh = python "$TESTDIR/dummyssh"
+ > username = nobody <no.reply@example.com>
+ >
+ > [alias]
+ > tglog = log -G -T "{desc} [{phase}:{node|short}]"
+ > EOF
+
+Set up server repository
+
+ $ hg init server
+ $ cd server
+ $ echo c0 > f0
+ $ hg commit -Am 0
+ adding f0
+
+Set up client repository
+
+ $ cd ..
+ $ hg clone ssh://user@dummy/server client -q
+ $ cd client
+
+Enable extension
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > bundle2=$TESTTMP/bundle2.py
+ > [experimental]
+ > bundle2-exp = True
+ > EOF
+
+Without config
+
+ $ cd ../client
+ $ echo c1 > f1
+ $ hg commit -Am 1
+ adding f1
+ $ hg push
+ pushing to ssh://user@dummy/server
+ searching for changes
+ remote: pushback not enabled
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+ $ hg bookmark
+ no bookmarks set
+
+ $ cd ../server
+ $ hg tglog
+ o 1 [public:2b9c7234e035]
+ |
+ @ 0 [public:6cee5c8f3e5b]
+
+
+
+
+With config
+
+ $ cd ../client
+ $ echo '[experimental]' >> .hg/hgrc
+ $ echo 'bundle2.pushback = True' >> .hg/hgrc
+ $ echo c2 > f2
+ $ hg commit -Am 2
+ adding f2
+ $ hg push
+ pushing to ssh://user@dummy/server
+ searching for changes
+ remote: adding changesets
+ remote: adding manifests
+ remote: adding file changes
+ remote: added 1 changesets with 1 changes to 1 files
+ $ hg bookmark
+ new-server-mark 2:0a76dfb2e179
+
+ $ cd ../server
+ $ hg tglog
+ o 2 [public:0a76dfb2e179]
+ |
+ o 1 [public:2b9c7234e035]
+ |
+ @ 0 [public:6cee5c8f3e5b]
+
+
+
+