diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py --- a/mercurial/bundle2.py +++ b/mercurial/bundle2.py @@ -1879,3 +1879,17 @@ cache.write() op.ui.debug('applied %i hgtags fnodes cache entries\n' % count) + +@parthandler('pushvars') +def bundle2getvars(op, part): + '''unbundle a bundle2 containing shellvars on the server''' + # An option to disable unbundling on server-side for security reasons + if op.ui.configbool('push', 'pushvars.server', False): + hookargs = {} + for key, value in part.advisoryparams: + key = key.upper() + # We want pushed variables to have USERVAR_ prepended so we know + # they came from the --pushvar flag. + key = "USERVAR_" + key + hookargs[key] = value + op.addhookargs(hookargs) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -3970,6 +3970,7 @@ ('b', 'branch', [], _('a specific branch you would like to push'), _('BRANCH')), ('', 'new-branch', False, _('allow pushing a new branch')), + ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')), ] + remoteopts, _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')) def push(ui, repo, dest=None, **opts): @@ -4007,6 +4008,25 @@ Please see :hg:`help urls` for important details about ``ssh://`` URLs. If DESTINATION is omitted, a default path will be used. + .. container:: verbose + + The --pushvars option sends strings to the server that become + environment variables prepended with ``HG_USERVAR_``. For example, + ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with + ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment. + + pushvars can provide for user-overridable hooks as well as set debug + levels. One example is having a hook that blocks commits containing + conflict markers, but enables the user to override the hook if the file + is using conflict markers for testing purposes or the file format has + strings that look like conflict markers. + + By default, servers will ignore `--pushvars`. To enable it add the + following to your configuration file + + [push] + pushvars.server = true + Returns 0 if push was successful, 1 if nothing to push. """ @@ -4059,11 +4079,28 @@ return not result finally: del repo._subtoppath + + pushvars = opts.get('pushvars') + if pushvars: + shellvars = {} + for raw in pushvars: + if '=' not in raw: + msg = ("unable to parse variable '%s', should follow " + "'KEY=VALUE' or 'KEY=' format") + raise error.Abort(msg % raw) + k, v = raw.split('=', 1) + shellvars[k] = v + + repo._shellvars = shellvars + pushop = exchange.push(repo, other, opts.get('force'), revs=revs, newbranch=opts.get('new_branch'), bookmarks=opts.get('bookmark', ()), opargs=opts.get('opargs')) + if pushvars: + del repo._shellvars + result = not pushop.cgresult if pushop.bkresult is not None: diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -891,6 +891,14 @@ pushop.bkresult = 1 return handlereply +@b2partsgenerator('pushvars', idx=0) +def _getbundlesendvars(pushop, bundler): + '''send shellvars via bundle2''' + if getattr(pushop.repo, '_shellvars', ()): + part = bundler.newpart('pushvars') + + for key, value in pushop.repo._shellvars.iteritems(): + part.addparam(key, value, mandatory=False) def _pushbundle2(pushop): """push data to the remote using bundle2 diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -228,7 +228,7 @@ log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude merge: force, rev, preview, tool pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure - push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure + push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure remove: after, force, subrepos, include, exclude serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template diff --git a/tests/test-pushvars.t b/tests/test-pushvars.t new file mode 100644 --- /dev/null +++ b/tests/test-pushvars.t @@ -0,0 +1,71 @@ +Setup + + $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH + $ export PYTHONPATH + + $ cat > $TESTTMP/pretxnchangegroup.sh << EOF + > #!/bin/sh + > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort + > exit 0 + > EOF + $ chmod +x $TESTTMP/pretxnchangegroup.sh + $ cat >> $HGRCPATH << EOF + > [hooks] + > pretxnchangegroup = $TESTTMP/pretxnchangegroup.sh + > [experimental] + > bundle2-exp = true + > EOF + + $ hg init repo + $ hg clone -q repo child + $ cd child + +Test pushing vars to repo with pushvars.server not set + + $ echo b > a + $ hg commit -Aqm a + $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true" + pushing to $TESTTMP/repo (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + +Setting pushvars.sever = true and then pushing. + + $ echo [push] >> $HGRCPATH + $ echo "pushvars.server = true" >> $HGRCPATH + $ echo b >> a + $ hg commit -Aqm a + $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true" + pushing to $TESTTMP/repo (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + HG_USERVAR_BYPASS_REVIEW=true + HG_USERVAR_DEBUG=1 + +Test pushing var with empty right-hand side + + $ echo b >> a + $ hg commit -Aqm a + $ hg push --pushvars "DEBUG=" + pushing to $TESTTMP/repo (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + HG_USERVAR_DEBUG= + +Test pushing bad vars + + $ echo b >> a + $ hg commit -Aqm b + $ hg push --pushvars "DEBUG" + pushing to $TESTTMP/repo (glob) + abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format + [255]