# HG changeset patch # User Eric Sumner # Date 1416613838 28800 # Node ID 743736fc7c41fd3d315ab422f284040fc433939f # Parent 6e0ecb9a2e19e07a2f81f3ba77ff070e95f6d45a 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. diff -r 6e0ecb9a2e19 -r 743736fc7c41 mercurial/bundle2.py --- 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): diff -r 6e0ecb9a2e19 -r 743736fc7c41 mercurial/exchange.py --- 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: diff -r 6e0ecb9a2e19 -r 743736fc7c41 tests/test-bundle2-pushback.t --- /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 < [ui] + > ssh = python "$TESTDIR/dummyssh" + > username = nobody + > + > [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 < [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] + + + +