Index: mercurial/bundle2.py =================================================================== --- mercurial/bundle2.py +++ 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)) Index: mercurial/exchange.py =================================================================== --- mercurial/exchange.py +++ 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: Index: mercurial/localrepo.py =================================================================== --- mercurial/localrepo.py +++ 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 Index: mercurial/repository.py =================================================================== --- mercurial/repository.py +++ 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. Index: mercurial/wireprotov1peer.py =================================================================== --- mercurial/wireprotov1peer.py +++ 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. Index: tests/test-bookmarks-conflict.t =================================================================== --- /dev/null +++ tests/test-bookmarks-conflict.t @@ -0,0 +1,91 @@ +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 +