mercurial/bookmarks.py
changeset 27698 dad6404ccddb
parent 27685 9fbae70faf65
child 27799 24b4dbb16c60
--- a/mercurial/bookmarks.py	Thu Jan 07 20:02:47 2016 -0800
+++ b/mercurial/bookmarks.py	Wed Nov 11 21:18:02 2015 -0500
@@ -44,16 +44,15 @@
 class bmstore(dict):
     """Storage for bookmarks.
 
-    This object should do all bookmark reads and writes, so that it's
-    fairly simple to replace the storage underlying bookmarks without
-    having to clone the logic surrounding bookmarks.
+    This object should do all bookmark-related reads and writes, so
+    that it's fairly simple to replace the storage underlying
+    bookmarks without having to clone the logic surrounding
+    bookmarks. This type also should manage the active bookmark, if
+    any.
 
     This particular bmstore implementation stores bookmarks as
     {hash}\s{name}\n (the same format as localtags) in
     .hg/bookmarks. The mapping is stored as {name: nodeid}.
-
-    This class does NOT handle the "active" bookmark state at this
-    time.
     """
 
     def __init__(self, repo):
@@ -79,6 +78,20 @@
             if inst.errno != errno.ENOENT:
                 raise
         self._clean = True
+        self._active = _readactive(repo, self)
+        self._aclean = True
+
+    @property
+    def active(self):
+        return self._active
+
+    @active.setter
+    def active(self, mark):
+        if mark is not None and mark not in self:
+            raise AssertionError('bookmark %s does not exist!' % mark)
+
+        self._active = mark
+        self._aclean = False
 
     def __setitem__(self, *args, **kwargs):
         self._clean = False
@@ -107,6 +120,9 @@
         '''
         msg = 'bm.write() is deprecated, use bm.recordchange(transaction)'
         self._repo.ui.deprecwarn(msg, '3.7')
+        # TODO: writing the active bookmark should probably also use a
+        # transaction.
+        self._writeactive()
         if self._clean:
             return
         repo = self._repo
@@ -128,8 +144,10 @@
 
     def _writerepo(self, repo):
         """Factored out for extensibility"""
-        if repo._activebookmark not in self:
-            deactivate(repo)
+        rbm = repo._bookmarks
+        if rbm.active not in self:
+            rbm.active = None
+            rbm._writeactive()
 
         wlock = repo.wlock()
         try:
@@ -146,12 +164,33 @@
         finally:
             wlock.release()
 
+    def _writeactive(self):
+        if self._aclean:
+            return
+        wlock = self._repo.wlock()
+        try:
+            if self._active is not None:
+                f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True)
+                try:
+                    f.write(encoding.fromlocal(self._active))
+                finally:
+                    f.close()
+            else:
+                try:
+                    self._repo.vfs.unlink('bookmarks.current')
+                except OSError as inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
+        finally:
+            wlock.release()
+        self._aclean = True
+
     def _write(self, fp):
         for name, node in self.iteritems():
             fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
         self._clean = True
 
-def readactive(repo):
+def _readactive(repo, marks):
     """
     Get the active bookmark. We can have an active bookmark that updates
     itself as we commit. This function returns the name of that bookmark.
@@ -172,7 +211,7 @@
         # static-http which only tries to load the file when we try
         # to read from it.
         mark = encoding.tolocal((file.readlines() or [''])[0])
-        if mark == '' or mark not in repo._bookmarks:
+        if mark == '' or mark not in marks:
             mark = None
     except IOError as inst:
         if inst.errno != errno.ENOENT:
@@ -188,35 +227,15 @@
     follow new commits that are made.
     The name is recorded in .hg/bookmarks.current
     """
-    if mark not in repo._bookmarks:
-        raise AssertionError('bookmark %s does not exist!' % mark)
-
-    active = repo._activebookmark
-    if active == mark:
-        return
-
-    wlock = repo.wlock()
-    try:
-        file = repo.vfs('bookmarks.current', 'w', atomictemp=True)
-        file.write(encoding.fromlocal(mark))
-        file.close()
-    finally:
-        wlock.release()
-    repo._activebookmark = mark
+    repo._bookmarks.active = mark
+    repo._bookmarks._writeactive()
 
 def deactivate(repo):
     """
     Unset the active bookmark in this repository.
     """
-    wlock = repo.wlock()
-    try:
-        repo.vfs.unlink('bookmarks.current')
-        repo._activebookmark = None
-    except OSError as inst:
-        if inst.errno != errno.ENOENT:
-            raise
-    finally:
-        wlock.release()
+    repo._bookmarks.active = None
+    repo._bookmarks._writeactive()
 
 def isactivewdirparent(repo):
     """
@@ -266,7 +285,7 @@
     deletefrom = parents
     marks = repo._bookmarks
     update = False
-    active = repo._activebookmark
+    active = marks.active
     if not active:
         return False