diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -21,7 +21,7 @@ import minirst, revset, fileset
 import dagparser, context, simplemerge, graphmod, copies
 import random
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
-import phases, obsolete, exchange, bundle2, repair
+import phases, obsolete, exchange, bundle2, repair, lock as lockmod
 import ui as uimod
 
 table = {}
@@ -976,11 +976,14 @@ def bookmark(ui, repo, *names, **opts):
         raise util.Abort(_("bookmark name required"))
 
     if delete or rename or names or inactive:
-        wlock = repo.wlock()
+        wlock = lock = tr = None
         try:
+            wlock = repo.wlock()
+            lock = repo.lock()
             cur = repo.changectx('.').node()
             marks = repo._bookmarks
             if delete:
+                tr = repo.transaction('bookmark')
                 for mark in names:
                     if mark not in marks:
                         raise util.Abort(_("bookmark '%s' does not exist") %
@@ -988,9 +991,9 @@ def bookmark(ui, repo, *names, **opts):
                     if mark == repo._activebookmark:
                         bookmarks.deactivate(repo)
                     del marks[mark]
-                marks.write()
 
             elif rename:
+                tr = repo.transaction('bookmark')
                 if not names:
                     raise util.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
@@ -1003,9 +1006,8 @@ def bookmark(ui, repo, *names, **opts):
                 if repo._activebookmark == rename and not inactive:
                     bookmarks.activate(repo, mark)
                 del marks[rename]
-                marks.write()
-
             elif names:
+                tr = repo.transaction('bookmark')
                 newact = None
                 for mark in names:
                     mark = checkformat(mark)
@@ -1023,8 +1025,6 @@ def bookmark(ui, repo, *names, **opts):
                     bookmarks.activate(repo, newact)
                 elif cur != tgt and newact == repo._activebookmark:
                     bookmarks.deactivate(repo)
-                marks.write()
-
             elif inactive:
                 if len(marks) == 0:
                     ui.status(_("no bookmarks set\n"))
@@ -1032,8 +1032,11 @@ def bookmark(ui, repo, *names, **opts):
                     ui.status(_("no active bookmark\n"))
                 else:
                     bookmarks.deactivate(repo)
+            if tr is not None:
+                marks.recordchange(tr)
+                tr.close()
         finally:
-            wlock.release()
+            lockmod.release(tr, lock, wlock)
     else: # show bookmarks
         fm = ui.formatter('bookmarks', opts)
         hexfn = fm.hexfunc
diff --git a/tests/test-bookmarks-strip.t b/tests/test-bookmarks-strip.t
--- a/tests/test-bookmarks-strip.t
+++ b/tests/test-bookmarks-strip.t
@@ -63,55 +63,3 @@ list bookmarks
   $ hg book
      test                      0:5c9ad3787638
      test2                     0:5c9ad3787638
-
-immediate rollback and reentrancy issue
-
-  $ echo "mq=!" >> $HGRCPATH
-  $ hg init repo
-  $ cd repo
-  $ echo a > a
-  $ hg ci -Am adda
-  adding a
-  $ echo b > b
-  $ hg ci -Am addb
-  adding b
-  $ hg bookmarks markb
-  $ hg rollback
-  repository tip rolled back to revision 0 (undo commit)
-  working directory now based on revision 0
-
-are you there?
-
-  $ hg bookmarks
-  no bookmarks set
-
-can we commit? (issue2692)
-
-  $ echo c > c
-  $ hg ci -Am rockon
-  adding c
-
-can you be added again?
-
-  $ hg bookmarks markb
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-rollback dry run with rollback information
-
-  $ hg rollback -n
-  repository tip rolled back to revision 0 (undo commit)
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-rollback dry run with rollback information and no commit undo
-
-  $ rm .hg/store/undo
-  $ hg rollback -n
-  no rollback information available
-  [1]
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-  $ cd ..
-
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -403,10 +403,15 @@ test id
 test rollback
 
   $ echo foo > f1
+  $ hg bookmark tmp-rollback
   $ hg ci -Amr
   adding f1
-  $ hg bookmark -f Y -r 1
-  $ hg bookmark -f Z -r 1
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+   * tmp-rollback              3:2bf5cfec5864
+     x  y                      2:db815d6d32e6
   $ hg rollback
   repository tip rolled back to revision 2 (undo commit)
   working directory now based on revision 2
@@ -414,7 +419,18 @@ test rollback
      X2                        1:925d80f479bb
      Y                         2:db815d6d32e6
      Z                         2:db815d6d32e6
+   * tmp-rollback              2:db815d6d32e6
      x  y                      2:db815d6d32e6
+  $ hg bookmark -f Z -r 1
+  $ hg rollback
+  repository tip rolled back to revision 2 (undo bookmark)
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+   * tmp-rollback              2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+  $ hg bookmark -d tmp-rollback
 
 activate bookmark on working dir parent without --force
 
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -173,36 +173,66 @@ pull empty
 add extra data to test their exchange during push
 
   $ hg -R main bookmark --rev eea13746799a book_eea1
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
   pre-close-tip:02de42196ebe draft 
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 02de42196ebe book_02de
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 32af7686d403 book_32af
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
 
   $ hg -R other bookmark --rev cd010b8cd998 book_eea1
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_02de
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_42cc
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_32af
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
 
   $ hg -R main phase --public eea13746799a
   pre-close-tip:02de42196ebe draft book_02de
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -247,6 +247,9 @@ listkeys hook
 
   $ echo "listkeys = printenv.py listkeys" >> .hg/hgrc
   $ hg bookmark -r null bar
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ cd ../b
   $ hg pull -B bar ../a
   pulling from ../a
@@ -279,6 +282,9 @@ test that prelistkeys can prevent listin
 
   $ echo "prelistkeys = printenv.py prelistkeys.forbid 1" >> .hg/hgrc
   $ hg bookmark -r null quux
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ cd ../b
   $ hg pull -B quux ../a
   pulling from ../a
diff --git a/tests/test-rollback.t b/tests/test-rollback.t
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -74,16 +74,16 @@ working dir unaffected by rollback: do n
   $ hg bookmark foo
   $ hg commit -m'modify a again'
   $ echo b > b
+  $ hg bookmark bar -r default #making bar active, before the transaction
   $ hg commit -Am'add b'
   adding b
   $ hg log --template '{rev}  {branch}  {desc|firstline}\n'
   2  test  add b
   1  test  modify a again
   0  default  add a again
-  $ hg update default
+  $ hg update bar
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (leaving bookmark foo)
-  $ hg bookmark bar
+  (activating bookmark bar)
   $ cat .hg/undo.branch ; echo
   test
   $ hg rollback -f
@@ -94,7 +94,7 @@ working dir unaffected by rollback: do n
   default
   $ cat .hg/bookmarks.current ; echo
   bar
-  $ hg bookmark --delete foo
+  $ hg bookmark --delete foo bar
 
 rollback by pretxncommit saves commit message (issue1635)