D6776: bookmarks: validate changes on push (issue6193) (BC)
idlsoft (Sandu Turcan)
phabricator at mercurial-scm.org
Sat Sep 14 00:53:04 EDT 2019
idlsoft updated this revision to Diff 16534.
CHANGES SINCE LAST UPDATE
https://phab.mercurial-scm.org/D6776?vs=16533&id=16534
CHANGES SINCE LAST ACTION
https://phab.mercurial-scm.org/D6776/new/
REVISION DETAIL
https://phab.mercurial-scm.org/D6776
AFFECTED FILES
mercurial/bundle2.py
mercurial/exchange.py
mercurial/localrepo.py
mercurial/repository.py
mercurial/wireprotov1peer.py
mercurial/wireprotov1server.py
tests/test-bookmarks-conflict.t
tests/test-bookmarks-pushpull.t
tests/test-hook.t
CHANGE DETAILS
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -545,6 +545,7 @@
HG_URL=file:$TESTTMP/a
pushkey hook: HG_BUNDLE2=1
+ HG_FORCE=0
HG_HOOKNAME=pushkey
HG_HOOKTYPE=pushkey
HG_KEY=foo
@@ -632,6 +633,7 @@
HG_TXNNAME=push
prepushkey.forbid hook: HG_BUNDLE2=1
+ HG_FORCE=0
HG_HOOKNAME=prepushkey
HG_HOOKTYPE=prepushkey
HG_KEY=baz
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -813,7 +813,7 @@
Z 0d2164f0ce0d
foo
foobar
- $ hg push -B Z http://localhost:$HGPORT/
+ $ hg push -B Z http://localhost:$HGPORT/ --force
pushing to http://localhost:$HGPORT/
searching for changes
no changes found
diff --git a/tests/test-bookmarks-conflict.t b/tests/test-bookmarks-conflict.t
new file mode 100644
--- /dev/null
+++ b/tests/test-bookmarks-conflict.t
@@ -0,0 +1,142 @@
+initialize
+ $ make_changes() {
+ > d=`pwd`
+ > [ ! -z $1 ] && cd $1
+ > echo "test `basename \`pwd\``" >> test
+ > hg commit -Am"${2:-test}"
+ > r=$?
+ > cd $d
+ > return $r
+ > }
+ $ ls -1a
+ .
+ ..
+ $ hg init a
+ $ cd a
+ $ echo 'test' > test; hg commit -Am'test'
+ adding test
+ $ hg book @
+
+clone to b
+
+ $ mkdir ../b
+ $ cd ../b
+ $ hg clone ../a .
+ updating to bookmark @
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ make_changes
+ $ hg book bk_b
+
+clone to c
+ $ mkdir ../c
+ $ cd ../c
+ $ hg clone ../a .
+ updating to bookmark @
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ make_changes
+ $ hg book bk_c
+
+push from b
+ $ cd ../b
+ $ hg push -B .
+ pushing to $TESTTMP/a
+ searching for changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files
+ updating bookmark @
+ exporting bookmark bk_b
+ $ hg -R ../a id -r @
+ e11a942451be tip @/bk_b
+
+push from c
+ $ cd ../c
+ $ hg push -B .
+ pushing to $TESTTMP/a
+ searching for changes
+ remote has heads on branch 'default' that are not known locally: e11a942451be
+ adding changesets
+ adding manifests
+ adding file changes
+ added 1 changesets with 1 changes to 1 files (+1 heads)
+ exporting bookmark bk_c
+ $ hg push -B @
+ pushing to $TESTTMP/a
+ searching for changes
+ no changes found
+ abort: push rejected: bookmark "@" has changed
+ (run 'hg pull', resolve conflicts, and push again)
+ [255]
+ $ hg -R ../a log -G -T '{rev} {bookmarks}'
+ o 2 bk_c
+ |
+ | o 1 @ bk_b
+ |/
+ @ 0
+
+
+ $ hg push -B @ --force
+ pushing to $TESTTMP/a
+ searching for changes
+ no changes found
+ updating bookmark @
+ [1]
+ $ hg -R ../a log -G -T '{rev} {bookmarks}'
+ o 2 @ bk_c
+ |
+ | o 1 bk_b
+ |/
+ @ 0
+
+
+## push using ssh
+ $ hg -R ../b push -B @ --force
+ pushing to $TESTTMP/a
+ searching for changes
+ no changes found
+ updating bookmark @
+ [1]
+ $ hg push -B @ ssh://user@dummy/a -e"$PYTHON $TESTDIR/dummyssh"
+ pushing to ssh://user@dummy/a
+ searching for changes
+ no changes found
+ remote: push rejected: bookmark "@" has changed
+ remote: (run 'hg pull', resolve conflicts, and push again)
+ abort: push failed on remote
+ [255]
+ $ hg push --force -B @ ssh://user@dummy/a -e"$PYTHON $TESTDIR/dummyssh"
+ pushing to ssh://user@dummy/a
+ searching for changes
+ no changes found
+ updating bookmark @
+ [1]
+
+# push using http
+ $ cat <<EOF > ../a/.hg/hgrc
+ > [web]
+ > push_ssl = false
+ > allow_push = *
+ > EOF
+ $ hg serve -R ../a -p $HGPORT -d --pid-file=../hg.pid
+ $ cat ../hg.pid >> $DAEMON_PIDS
+ $ hg -R ../b push -B @ --force
+ pushing to $TESTTMP/a
+ searching for changes
+ no changes found
+ updating bookmark @
+ [1]
+ $ hg push -B @ http://localhost:$HGPORT
+ pushing to http://localhost:$HGPORT/
+ searching for changes
+ no changes found
+ remote: push rejected: bookmark "@" has changed
+ remote: (run 'hg pull', resolve conflicts, and push again)
+ abort: push failed on remote
+ [255]
+ $ hg push --force -B @ http://localhost:$HGPORT
+ pushing to http://localhost:$HGPORT/
+ searching for changes
+ no changes found
+ updating bookmark @
+ [1]
diff --git a/mercurial/wireprotov1server.py b/mercurial/wireprotov1server.py
--- a/mercurial/wireprotov1server.py
+++ b/mercurial/wireprotov1server.py
@@ -548,8 +548,8 @@
return wireprototypes.streamreslegacy(
streamclone.generatev1wireproto(repo))
- at wireprotocommand('unbundle', 'heads', permission='push')
-def unbundle(repo, proto, heads):
+ at wireprotocommand('unbundle', 'heads *', permission='push')
+def unbundle(repo, proto, heads, others):
their_heads = wireprototypes.decodelist(heads)
with proto.mayberedirectstdio() as output:
@@ -594,7 +594,7 @@
hint=bundle2requiredhint)
r = exchange.unbundle(repo, gen, their_heads, 'serve',
- proto.client())
+ proto.client(), others.get('force') == '1')
if util.safehasattr(r, 'addpart'):
# The return looks streamable, we are in the bundle2 case
# and should return a stream.
diff --git a/mercurial/wireprotov1peer.py b/mercurial/wireprotov1peer.py
--- a/mercurial/wireprotov1peer.py
+++ b/mercurial/wireprotov1peer.py
@@ -445,7 +445,7 @@
else:
return changegroupmod.cg1unpacker(f, 'UN')
- def unbundle(self, bundle, heads, url):
+ def unbundle(self, bundle, heads, url, force=None):
'''Send cg (a readable file-like object representing the
changegroup to push, typically a chunkbuffer object) to the
remote server as a bundle.
@@ -464,10 +464,12 @@
['hashed', hashlib.sha1(''.join(sorted(heads))).digest()])
else:
heads = wireprototypes.encodelist(heads)
-
+ args = {'heads': heads}
+ if force:
+ args['force'] = '1'
if util.safehasattr(bundle, 'deltaheader'):
# this a bundle10, do the old style call sequence
- ret, output = self._callpush("unbundle", bundle, heads=heads)
+ ret, output = self._callpush("unbundle", bundle, **args)
if ret == "":
raise error.ResponseError(
_('push failed:'), output)
@@ -481,7 +483,7 @@
self.ui.status(_('remote: '), l)
else:
# bundle2 push. Send a stream, fetch a stream.
- stream = self._calltwowaystream('unbundle', bundle, heads=heads)
+ stream = self._calltwowaystream('unbundle', bundle, **args)
ret = bundle2.getunbundler(self.ui, stream)
return ret
diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -191,7 +191,7 @@
Successful result should be a generator of data chunks.
"""
- def unbundle(bundle, heads, url):
+ def unbundle(bundle, heads, url, force=None):
"""Transfer repository data to the peer.
This is how the bulk of data during a push is transferred.
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -306,14 +306,14 @@
raise error.Abort(_('cannot perform stream clone against local '
'peer'))
- def unbundle(self, bundle, heads, url):
+ def unbundle(self, bundle, heads, url, force=None):
"""apply a bundle on a repo
This function handles the repo locking itself."""
try:
try:
bundle = exchange.readbundle(self.ui, bundle, None)
- ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
+ ret = exchange.unbundle(self._repo, bundle, heads, 'push', url, force=force)
if util.safehasattr(ret, 'getchunks'):
# This is a bundle20 object, turn it into an unbundler.
# This little dance should be dropped eventually when the
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1159,6 +1159,7 @@
'bundle': stream,
'heads': ['force'],
'url': pushop.remote.url(),
+ 'force': pushop.force,
}).result()
except error.BundleValueError as exc:
raise error.Abort(_('missing support for %s') % exc)
@@ -2375,7 +2376,7 @@
raise error.PushRaced('repository changed while %s - '
'please try again' % context)
-def unbundle(repo, cg, heads, source, url):
+def unbundle(repo, cg, heads, source, url, force=None):
"""Apply a bundle to a repo.
this function makes sure the repo is locked during the application and have
@@ -2424,7 +2425,8 @@
op = bundle2.bundleoperation(repo, gettransaction,
captureoutput=captureoutput,
- source='push')
+ source='push',
+ force=force)
try:
op = bundle2.processbundle(repo, cg, op=op)
finally:
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -299,7 +299,7 @@
* a way to construct a bundle response when applicable.
"""
- def __init__(self, repo, transactiongetter, captureoutput=True, source=''):
+ def __init__(self, repo, transactiongetter, captureoutput=True, source='', force=False):
self.repo = repo
self.ui = repo.ui
self.records = unbundlerecords()
@@ -310,6 +310,7 @@
# carries value that can modify part behavior
self.modes = {}
self.source = source
+ self.force = force
def gettransaction(self):
transaction = self._gettransaction()
@@ -2135,17 +2136,23 @@
if bookmarksmode == 'apply':
tr = op.gettransaction()
bookstore = op.repo._bookmarks
+ allhooks = []
+ for book, node in changes:
+ old_node = bookstore.get(book, '')
+ if not op.force and old_node != '' and node is not None:
+ if not bookmarks.validdest(op.repo, op.repo[old_node], op.repo[node]):
+ raise error.Abort(_('push rejected: bookmark "%s" has changed') % book,
+ hint=_("run 'hg pull', resolve conflicts, and push again"))
+ hookargs = tr.hookargs.copy()
+ hookargs['pushkeycompat'] = '1'
+ hookargs['namespace'] = 'bookmarks'
+ hookargs['key'] = book
+ hookargs['old'] = nodemod.hex(old_node)
+ hookargs['new'] = nodemod.hex(node if node is not None else '')
+ hookargs['force'] = op.force
+ allhooks.append(hookargs)
+
if pushkeycompat:
- allhooks = []
- for book, node in changes:
- hookargs = tr.hookargs.copy()
- hookargs['pushkeycompat'] = '1'
- hookargs['namespace'] = 'bookmarks'
- hookargs['key'] = book
- hookargs['old'] = nodemod.hex(bookstore.get(book, ''))
- hookargs['new'] = nodemod.hex(node if node is not None else '')
- allhooks.append(hookargs)
-
for hookargs in allhooks:
op.repo.hook('prepushkey', throw=True,
**pycompat.strkwargs(hookargs))
To: idlsoft, #hg-reviewers, durin42
Cc: pulkit, durin42, valentin.gatienbaron, mercurial-devel
More information about the Mercurial-devel
mailing list