diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
index 1f30cbac34b655436b1479d4d43c6ab47dec04e2_bWVyY3VyaWFsL2J1bmRsZTIucHk=..496154e419683afc486debaf46b15bdb3e414f43_bWVyY3VyaWFsL2J1bmRsZTIucHk= 100644
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1982,8 +1982,8 @@
 def handlebookmark(op, inpart):
     """transmit bookmark information
 
-    The part contains binary encoded bookmark information. The bookmark
-    information is applied as is to the unbundling repository. Make sure a
-    'check:bookmarks' part is issued earlier to check for race condition in
-    such update.
+    The part contains binary encoded bookmark information.
+
+    The exact behavior of this part can be controlled by the 'bookmarks' mode
+    on the bundle operation.
 
@@ -1989,6 +1989,11 @@
 
-    This behavior is suitable for pushing. Semantic adjustment will be needed
-    for pull.
+    When mode is 'apply' (the default) the bookmark information is applied as
+    is to the unbundling repository. Make sure a 'check:bookmarks' part is
+    issued earlier to check for push races in such update. This behavior is
+    suitable for pushing.
+
+    When mode is 'records', the information is recorded into the 'bookmarks'
+    records of the bundle operation. This behavior is suitable for pulling.
     """
     changes = bookmarks.binarydecode(inpart)
 
@@ -1992,6 +1997,6 @@
     """
     changes = bookmarks.binarydecode(inpart)
 
-    tr = op.gettransaction()
-    bookstore = op.repo._bookmarks
+    pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
+    bookmarksmode = op.modes.get('bookmarks', 'apply')
 
@@ -1997,5 +2002,28 @@
 
-    pushkeycompat = op.repo.ui.configbool('server', 'bookmarks-pushkey-compat')
-    if pushkeycompat:
-        allhooks = []
+    if bookmarksmode == 'apply':
+        tr = op.gettransaction()
+        bookstore = op.repo._bookmarks
+        if pushkeycompat:
+            allhooks = []
+            for book, node in changes:
+                hookargs = tr.hookargs.copy()
+                hookargs['pushkeycompat'] = '1'
+                hookargs['namespace'] = 'bookmark'
+                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, **hookargs)
+
+        bookstore.applychanges(op.repo, op.gettransaction(), changes)
+
+        if pushkeycompat:
+            def runhook():
+                for hookargs in allhooks:
+                    op.repo.hook('prepushkey', **hookargs)
+            op.repo._afterlock(runhook)
+
+    elif bookmarksmode == 'records':
         for book, node in changes:
@@ -2001,21 +2029,8 @@
         for book, node in changes:
-            hookargs = tr.hookargs.copy()
-            hookargs['pushkeycompat'] = '1'
-            hookargs['namespace'] = 'bookmark'
-            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, **hookargs)
-
-    bookstore.applychanges(op.repo, tr, changes)
-
-    if pushkeycompat:
-        def runhook():
-            for hookargs in allhooks:
-                op.repo.hook('prepushkey', **hookargs)
-        op.repo._afterlock(runhook)
+            record = {'bookmark': book, 'node': node}
+            op.records.add('bookmarks', record)
+    else:
+        raise error.ProgrammingError('unkown bookmark mode: %s' % bookmarksmode)
 
 @parthandler('phase-heads')
 def handlephases(op, inpart):