[PATCH 1 of 8 v2] bookmarks: move bookmarks to core

David Soria Parra dsp at php.net
Tue Feb 1 16:21:53 CST 2011


# HG changeset patch
# User David Soria Parra <dsp at php.net>
# Date 1296598799 -3600
# Node ID f98c0ab906e7165565b6f4380e7a1fe7a54c898e
# Parent  0d1dca7d2a041cb1cb6c6bd90608aa87068bde02
bookmarks: move bookmarks to core

diff --git a/hgext/bookmarks.py b/hgext/bookmarks.py
deleted file mode 100644
--- a/hgext/bookmarks.py
+++ /dev/null
@@ -1,581 +0,0 @@
-# Mercurial extension to provide the 'hg bookmark' command
-#
-# Copyright 2008 David Soria Parra <dsp at php.net>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-'''track a line of development with movable markers
-
-Bookmarks are local movable markers to changesets. Every bookmark
-points to a changeset identified by its hash. If you commit a
-changeset that is based on a changeset that has a bookmark on it, the
-bookmark shifts to the new changeset.
-
-It is possible to use bookmark names in every revision lookup (e.g.
-:hg:`merge`, :hg:`update`).
-
-By default, when several bookmarks point to the same changeset, they
-will all move forward together. It is possible to obtain a more
-git-like experience by adding the following configuration option to
-your configuration file::
-
-  [bookmarks]
-  track.current = True
-
-This will cause Mercurial to track the bookmark that you are currently
-using, and only update it. This is similar to git's approach to
-branching.
-'''
-
-from mercurial.i18n import _
-from mercurial.node import nullid, nullrev, bin, hex, short
-from mercurial import util, commands, repair, extensions, pushkey, hg, url
-from mercurial import revset, encoding
-import os
-
-def write(repo):
-    '''Write bookmarks
-
-    Write the given bookmark => hash dictionary to the .hg/bookmarks file
-    in a format equal to those of localtags.
-
-    We also store a backup of the previous state in undo.bookmarks that
-    can be copied back on rollback.
-    '''
-    refs = repo._bookmarks
-
-    try:
-        bms = repo.opener('bookmarks').read()
-    except IOError:
-        bms = ''
-    repo.opener('undo.bookmarks', 'w').write(bms)
-
-    if repo._bookmarkcurrent not in refs:
-        setcurrent(repo, None)
-    wlock = repo.wlock()
-    try:
-        file = repo.opener('bookmarks', 'w', atomictemp=True)
-        for refspec, node in refs.iteritems():
-            file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
-        file.rename()
-
-        # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
-        try:
-            os.utime(repo.sjoin('00changelog.i'), None)
-        except OSError:
-            pass
-
-    finally:
-        wlock.release()
-
-def setcurrent(repo, mark):
-    '''Set the name of the bookmark that we are currently on
-
-    Set the name of the bookmark that we are on (hg update <bookmark>).
-    The name is recorded in .hg/bookmarks.current
-    '''
-    current = repo._bookmarkcurrent
-    if current == mark:
-        return
-
-    refs = repo._bookmarks
-
-    # do not update if we do update to a rev equal to the current bookmark
-    if (mark and mark not in refs and
-        current and refs[current] == repo.changectx('.').node()):
-        return
-    if mark not in refs:
-        mark = ''
-    wlock = repo.wlock()
-    try:
-        file = repo.opener('bookmarks.current', 'w', atomictemp=True)
-        file.write(mark)
-        file.rename()
-    finally:
-        wlock.release()
-    repo._bookmarkcurrent = mark
-
-def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
-    '''track a line of development with movable markers
-
-    Bookmarks are pointers to certain commits that move when
-    committing. Bookmarks are local. They can be renamed, copied and
-    deleted. It is possible to use bookmark names in :hg:`merge` and
-    :hg:`update` to merge and update respectively to a given bookmark.
-
-    You can use :hg:`bookmark NAME` to set a bookmark on the working
-    directory's parent revision with the given name. If you specify
-    a revision using -r REV (where REV may be an existing bookmark),
-    the bookmark is assigned to that revision.
-
-    Bookmarks can be pushed and pulled between repositories (see :hg:`help
-    push` and :hg:`help pull`). This requires the bookmark extension to be
-    enabled for both the local and remote repositories.
-    '''
-    hexfn = ui.debugflag and hex or short
-    marks = repo._bookmarks
-    cur   = repo.changectx('.').node()
-
-    if rename:
-        if rename not in marks:
-            raise util.Abort(_("a bookmark of this name does not exist"))
-        if mark in marks and not force:
-            raise util.Abort(_("a bookmark of the same name already exists"))
-        if mark is None:
-            raise util.Abort(_("new bookmark name required"))
-        marks[mark] = marks[rename]
-        del marks[rename]
-        if repo._bookmarkcurrent == rename:
-            setcurrent(repo, mark)
-        write(repo)
-        return
-
-    if delete:
-        if mark is None:
-            raise util.Abort(_("bookmark name required"))
-        if mark not in marks:
-            raise util.Abort(_("a bookmark of this name does not exist"))
-        if mark == repo._bookmarkcurrent:
-            setcurrent(repo, None)
-        del marks[mark]
-        write(repo)
-        return
-
-    if mark is not None:
-        if "\n" in mark:
-            raise util.Abort(_("bookmark name cannot contain newlines"))
-        mark = mark.strip()
-        if not mark:
-            raise util.Abort(_("bookmark names cannot consist entirely of "
-                               "whitespace"))
-        if mark in marks and not force:
-            raise util.Abort(_("a bookmark of the same name already exists"))
-        if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
-            and not force):
-            raise util.Abort(
-                _("a bookmark cannot have the name of an existing branch"))
-        if rev:
-            marks[mark] = repo.lookup(rev)
-        else:
-            marks[mark] = repo.changectx('.').node()
-        setcurrent(repo, mark)
-        write(repo)
-        return
-
-    if mark is None:
-        if rev:
-            raise util.Abort(_("bookmark name required"))
-        if len(marks) == 0:
-            ui.status(_("no bookmarks set\n"))
-        else:
-            for bmark, n in marks.iteritems():
-                if ui.configbool('bookmarks', 'track.current'):
-                    current = repo._bookmarkcurrent
-                    if bmark == current and n == cur:
-                        prefix, label = '*', 'bookmarks.current'
-                    else:
-                        prefix, label = ' ', ''
-                else:
-                    if n == cur:
-                        prefix, label = '*', 'bookmarks.current'
-                    else:
-                        prefix, label = ' ', ''
-
-                if ui.quiet:
-                    ui.write("%s\n" % bmark, label=label)
-                else:
-                    ui.write(" %s %-25s %d:%s\n" % (
-                        prefix, bmark, repo.changelog.rev(n), hexfn(n)),
-                        label=label)
-        return
-
-def _revstostrip(changelog, node):
-    srev = changelog.rev(node)
-    tostrip = [srev]
-    saveheads = []
-    for r in xrange(srev, len(changelog)):
-        parents = changelog.parentrevs(r)
-        if parents[0] in tostrip or parents[1] in tostrip:
-            tostrip.append(r)
-            if parents[1] != nullrev:
-                for p in parents:
-                    if p not in tostrip and p > srev:
-                        saveheads.append(p)
-    return [r for r in tostrip if r not in saveheads]
-
-def strip(oldstrip, ui, repo, node, backup="all"):
-    """Strip bookmarks if revisions are stripped using
-    the mercurial.strip method. This usually happens during
-    qpush and qpop"""
-    revisions = _revstostrip(repo.changelog, node)
-    marks = repo._bookmarks
-    update = []
-    for mark, n in marks.iteritems():
-        if repo.changelog.rev(n) in revisions:
-            update.append(mark)
-    oldstrip(ui, repo, node, backup)
-    if len(update) > 0:
-        for m in update:
-            marks[m] = repo.changectx('.').node()
-        write(repo)
-
-def reposetup(ui, repo):
-    if not repo.local():
-        return
-
-    class bookmark_repo(repo.__class__):
-
-        @util.propertycache
-        def _bookmarks(self):
-            '''Parse .hg/bookmarks file and return a dictionary
-
-            Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
-            in the .hg/bookmarks file.
-            Read the file and return a (name=>nodeid) dictionary
-            '''
-            try:
-                bookmarks = {}
-                for line in self.opener('bookmarks'):
-                    sha, refspec = line.strip().split(' ', 1)
-                    refspec = encoding.tolocal(refspec)
-                    bookmarks[refspec] = self.changelog.lookup(sha)
-            except:
-                pass
-            return bookmarks
-
-        @util.propertycache
-        def _bookmarkcurrent(self):
-            '''Get the current bookmark
-
-            If we use gittishsh branches we have a current bookmark that
-            we are on. This function returns the name of the bookmark. It
-            is stored in .hg/bookmarks.current
-            '''
-            mark = None
-            if os.path.exists(self.join('bookmarks.current')):
-                file = self.opener('bookmarks.current')
-                # No readline() in posixfile_nt, reading everything is cheap
-                mark = (file.readlines() or [''])[0]
-                if mark == '':
-                    mark = None
-                file.close()
-            return mark
-
-        def rollback(self, dryrun=False):
-            if os.path.exists(self.join('undo.bookmarks')):
-                if not dryrun:
-                    util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
-                elif not os.path.exists(self.sjoin("undo")):
-                    # avoid "no rollback information available" message
-                    return 0
-            return super(bookmark_repo, self).rollback(dryrun)
-
-        def lookup(self, key):
-            if key in self._bookmarks:
-                key = self._bookmarks[key]
-            return super(bookmark_repo, self).lookup(key)
-
-        def _bookmarksupdate(self, parents, node):
-            marks = self._bookmarks
-            update = False
-            if ui.configbool('bookmarks', 'track.current'):
-                mark = self._bookmarkcurrent
-                if mark and marks[mark] in parents:
-                    marks[mark] = node
-                    update = True
-            else:
-                for mark, n in marks.items():
-                    if n in parents:
-                        marks[mark] = node
-                        update = True
-            if update:
-                write(self)
-
-        def commitctx(self, ctx, error=False):
-            """Add a revision to the repository and
-            move the bookmark"""
-            wlock = self.wlock() # do both commit and bookmark with lock held
-            try:
-                node  = super(bookmark_repo, self).commitctx(ctx, error)
-                if node is None:
-                    return None
-                parents = self.changelog.parents(node)
-                if parents[1] == nullid:
-                    parents = (parents[0],)
-
-                self._bookmarksupdate(parents, node)
-                return node
-            finally:
-                wlock.release()
-
-        def pull(self, remote, heads=None, force=False):
-            result = super(bookmark_repo, self).pull(remote, heads, force)
-
-            self.ui.debug("checking for updated bookmarks\n")
-            rb = remote.listkeys('bookmarks')
-            changed = False
-            for k in rb.keys():
-                if k in self._bookmarks:
-                    nr, nl = rb[k], self._bookmarks[k]
-                    if nr in self:
-                        cr = self[nr]
-                        cl = self[nl]
-                        if cl.rev() >= cr.rev():
-                            continue
-                        if cr in cl.descendants():
-                            self._bookmarks[k] = cr.node()
-                            changed = True
-                            self.ui.status(_("updating bookmark %s\n") % k)
-                        else:
-                            self.ui.warn(_("not updating divergent"
-                                           " bookmark %s\n") % k)
-            if changed:
-                write(repo)
-
-            return result
-
-        def push(self, remote, force=False, revs=None, newbranch=False):
-            result = super(bookmark_repo, self).push(remote, force, revs,
-                                                     newbranch)
-
-            self.ui.debug("checking for updated bookmarks\n")
-            rb = remote.listkeys('bookmarks')
-            for k in rb.keys():
-                if k in self._bookmarks:
-                    nr, nl = rb[k], hex(self._bookmarks[k])
-                    if nr in self:
-                        cr = self[nr]
-                        cl = self[nl]
-                        if cl in cr.descendants():
-                            r = remote.pushkey('bookmarks', k, nr, nl)
-                            if r:
-                                self.ui.status(_("updating bookmark %s\n") % k)
-                            else:
-                                self.ui.warn(_('updating bookmark %s'
-                                               ' failed!\n') % k)
-
-            return result
-
-        def addchangegroup(self, *args, **kwargs):
-            result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
-            if result > 1:
-                # We have more heads than before
-                return result
-            node = self.changelog.tip()
-            parents = self.dirstate.parents()
-            self._bookmarksupdate(parents, node)
-            return result
-
-        def _findtags(self):
-            """Merge bookmarks with normal tags"""
-            (tags, tagtypes) = super(bookmark_repo, self)._findtags()
-            tags.update(self._bookmarks)
-            return (tags, tagtypes)
-
-        if hasattr(repo, 'invalidate'):
-            def invalidate(self):
-                super(bookmark_repo, self).invalidate()
-                for attr in ('_bookmarks', '_bookmarkcurrent'):
-                    if attr in self.__dict__:
-                        delattr(self, attr)
-
-    repo.__class__ = bookmark_repo
-
-def listbookmarks(repo):
-    # We may try to list bookmarks on a repo type that does not
-    # support it (e.g., statichttprepository).
-    if not hasattr(repo, '_bookmarks'):
-        return {}
-
-    d = {}
-    for k, v in repo._bookmarks.iteritems():
-        d[k] = hex(v)
-    return d
-
-def pushbookmark(repo, key, old, new):
-    w = repo.wlock()
-    try:
-        marks = repo._bookmarks
-        if hex(marks.get(key, '')) != old:
-            return False
-        if new == '':
-            del marks[key]
-        else:
-            if new not in repo:
-                return False
-            marks[key] = repo[new].node()
-        write(repo)
-        return True
-    finally:
-        w.release()
-
-def pull(oldpull, ui, repo, source="default", **opts):
-    # translate bookmark args to rev args for actual pull
-    if opts.get('bookmark'):
-        # this is an unpleasant hack as pull will do this internally
-        source, branches = hg.parseurl(ui.expandpath(source),
-                                       opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), source)
-        rb = other.listkeys('bookmarks')
-
-        for b in opts['bookmark']:
-            if b not in rb:
-                raise util.Abort(_('remote bookmark %s not found!') % b)
-            opts.setdefault('rev', []).append(b)
-
-    result = oldpull(ui, repo, source, **opts)
-
-    # update specified bookmarks
-    if opts.get('bookmark'):
-        for b in opts['bookmark']:
-            # explicit pull overrides local bookmark if any
-            ui.status(_("importing bookmark %s\n") % b)
-            repo._bookmarks[b] = repo[rb[b]].node()
-        write(repo)
-
-    return result
-
-def push(oldpush, ui, repo, dest=None, **opts):
-    dopush = True
-    if opts.get('bookmark'):
-        dopush = False
-        for b in opts['bookmark']:
-            if b in repo._bookmarks:
-                dopush = True
-                opts.setdefault('rev', []).append(b)
-
-    result = 0
-    if dopush:
-        result = oldpush(ui, repo, dest, **opts)
-
-    if opts.get('bookmark'):
-        # this is an unpleasant hack as push will do this internally
-        dest = ui.expandpath(dest or 'default-push', dest or 'default')
-        dest, branches = hg.parseurl(dest, opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), dest)
-        rb = other.listkeys('bookmarks')
-        for b in opts['bookmark']:
-            # explicit push overrides remote bookmark if any
-            if b in repo._bookmarks:
-                ui.status(_("exporting bookmark %s\n") % b)
-                new = repo[b].hex()
-            elif b in rb:
-                ui.status(_("deleting remote bookmark %s\n") % b)
-                new = '' # delete
-            else:
-                ui.warn(_('bookmark %s does not exist on the local '
-                          'or remote repository!\n') % b)
-                return 2
-            old = rb.get(b, '')
-            r = other.pushkey('bookmarks', b, old, new)
-            if not r:
-                ui.warn(_('updating bookmark %s failed!\n') % b)
-                if not result:
-                    result = 2
-
-    return result
-
-def diffbookmarks(ui, repo, remote):
-    ui.status(_("searching for changed bookmarks\n"))
-
-    lmarks = repo.listkeys('bookmarks')
-    rmarks = remote.listkeys('bookmarks')
-
-    diff = sorted(set(rmarks) - set(lmarks))
-    for k in diff:
-        ui.write("   %-25s %s\n" % (k, rmarks[k][:12]))
-
-    if len(diff) <= 0:
-        ui.status(_("no changed bookmarks found\n"))
-        return 1
-    return 0
-
-def incoming(oldincoming, ui, repo, source="default", **opts):
-    if opts.get('bookmarks'):
-        source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), source)
-        ui.status(_('comparing with %s\n') % url.hidepassword(source))
-        return diffbookmarks(ui, repo, other)
-    else:
-        return oldincoming(ui, repo, source, **opts)
-
-def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
-    if opts.get('bookmarks'):
-        dest = ui.expandpath(dest or 'default-push', dest or 'default')
-        dest, branches = hg.parseurl(dest, opts.get('branch'))
-        other = hg.repository(hg.remoteui(repo, opts), dest)
-        ui.status(_('comparing with %s\n') % url.hidepassword(dest))
-        return diffbookmarks(ui, other, repo)
-    else:
-        return oldoutgoing(ui, repo, dest, **opts)
-
-def uisetup(ui):
-    extensions.wrapfunction(repair, "strip", strip)
-    if ui.configbool('bookmarks', 'track.current'):
-        extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
-
-    entry = extensions.wrapcommand(commands.table, 'pull', pull)
-    entry[1].append(('B', 'bookmark', [],
-                     _("bookmark to import"),
-                     _('BOOKMARK')))
-    entry = extensions.wrapcommand(commands.table, 'push', push)
-    entry[1].append(('B', 'bookmark', [],
-                     _("bookmark to export"),
-                     _('BOOKMARK')))
-    entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
-    entry[1].append(('B', 'bookmarks', False,
-                     _("compare bookmark")))
-    entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
-    entry[1].append(('B', 'bookmarks', False,
-                     _("compare bookmark")))
-
-    pushkey.register('bookmarks', pushbookmark, listbookmarks)
-
-def updatecurbookmark(orig, ui, repo, *args, **opts):
-    '''Set the current bookmark
-
-    If the user updates to a bookmark we update the .hg/bookmarks.current
-    file.
-    '''
-    res = orig(ui, repo, *args, **opts)
-    rev = opts['rev']
-    if not rev and len(args) > 0:
-        rev = args[0]
-    setcurrent(repo, rev)
-    return res
-
-def bmrevset(repo, subset, x):
-    """``bookmark([name])``
-    The named bookmark or all bookmarks.
-    """
-    # i18n: "bookmark" is a keyword
-    args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
-    if args:
-        bm = revset.getstring(args[0],
-                              # i18n: "bookmark" is a keyword
-                              _('the argument to bookmark must be a string'))
-        bmrev = listbookmarks(repo).get(bm, None)
-        if bmrev:
-            bmrev = repo.changelog.rev(bin(bmrev))
-        return [r for r in subset if r == bmrev]
-    bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
-    return [r for r in subset if r in bms]
-
-def extsetup(ui):
-    revset.symbols['bookmark'] = bmrevset
-
-cmdtable = {
-    "bookmarks":
-        (bookmark,
-         [('f', 'force', False, _('force')),
-          ('r', 'rev', '', _('revision'), _('REV')),
-          ('d', 'delete', False, _('delete a given bookmark')),
-          ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
-         _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
-}
-
-colortable = {'bookmarks.current': 'green'}
-
-# tell hggettext to extract docstrings from these functions:
-i18nfunctions = [bmrevset]
diff --git a/hgext/color.py b/hgext/color.py
--- a/hgext/color.py
+++ b/hgext/color.py
@@ -96,6 +96,7 @@
            'branches.closed': 'black bold',
            'branches.current': 'green',
            'branches.inactive': 'none',
+           'bookmarks.current': 'green',
            'diff.changed': 'white',
            'diff.deleted': 'red',
            'diff.diffline': 'bold',
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
new file mode 100644
--- /dev/null
+++ b/mercurial/bookmarks.py
@@ -0,0 +1,52 @@
+# bookmarks.py - pushkey functions for bookmarks
+#
+# Copyright 2010 David Soria Parra <dsp at php.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from node import hex
+from i18n import _
+import pushkey
+
+def pushbookmark(repo, key, old, new):
+    w = repo.wlock()
+    try:
+        marks = repo.bookmarks
+        if hex(marks.get(key, '')) != old:
+            return False
+        if new == '':
+            del marks[key]
+        else:
+            if new not in repo:
+                return False
+            marks[key] = repo[new].node()
+        repo.bookmarkswrite()
+        return True
+    finally:
+        w.release()
+
+def listbookmarks(repo):
+    # We may try to list bookmarks on a repo type that does not
+    # support it (e.g., statichttprepository).
+    if not hasattr(repo, 'bookmarks'):
+        return {}
+
+    d = {}
+    for k, v in repo.bookmarks.iteritems():
+        d[k] = hex(v)
+    return d
+
+def diffbookmarks(ui, repo, other):
+    ui.status(_("searching for changed bookmarks\n"))
+
+    lmarks = repo.listkeys('bookmarks')
+    rmarks = other.listkeys('bookmarks')
+
+    diff = sorted(set(rmarks) - set(lmarks))
+    for k in diff:
+        ui.write("   %-25s %s\n" % (k, rmarks[k][:12]))
+
+    if len(diff) <= 0:
+        ui.status(_("no changed bookmarks found\n"))
+        return 1
+    return 0
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -14,7 +14,7 @@
 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
 import merge as mergemod
 import minirst, revset
-import dagparser
+import dagparser, bookmarks
 
 # Commands start here, listed alphabetically
 
@@ -461,6 +461,100 @@
             cmdutil.bail_if_changed(repo)
             return hg.clean(repo, node)
 
+def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
+        rename=None):
+    '''track a line of development with movable markers
+
+    Bookmarks are pointers to certain commits that move when
+    committing. They can be renamed, copied and
+    deleted. It is possible to use bookmark names in :hg:`merge` and
+    :hg:`update` to merge and update respectively to a given bookmark.
+
+    You can use :hg:`bookmark NAME` to set a bookmark on the working
+    directory's parent revision with the given name. If you specify
+    a revision using -r REV (where REV may be an existing bookmark),
+    the bookmark is assigned to that revision.
+
+    Bookmarks can be pushed between repositories (see :hg:`help
+    push`). Remote bookmarks that match a local name will be forwarded
+    on pull.
+    '''
+    hexfn = ui.debugflag and hex or short
+    cur   = repo.changectx('.').node()
+
+    if rename:
+        if rename not in repo.bookmarks:
+            raise util.Abort(_("a bookmark of this name does not exist"))
+        if mark in repo.bookmarks and not force:
+            raise util.Abort(_("a bookmark of the same name already exists"))
+        if mark is None:
+            raise util.Abort(_("new bookmark name required"))
+        repo.bookmarks[mark] = repo.bookmarks[rename]
+        del repo.bookmarks[rename]
+        if repo.bookmarkcurrent == rename:
+            repo.bookmarksetcurrent(mark)
+        repo.bookmarkswrite()
+        return
+
+    if delete:
+        if mark is None:
+            raise util.Abort(_("bookmark name required"))
+        if mark not in repo.bookmarks:
+            raise util.Abort(_("a bookmark of this name does not exist"))
+        if mark == repo.bookmarkcurrent:
+            repo.bookmarksetcurrent(None)
+        del repo.bookmarks[mark]
+        repo.bookmarkswrite()
+        return
+
+    if mark is not None:
+        if "\n" in mark:
+            raise util.Abort(_("bookmark name cannot contain newlines"))
+        mark = mark.strip()
+        if not mark:
+            raise util.Abort(_("bookmark names cannot consist entirely of "
+                               "whitespace"))
+        if mark in repo.bookmarks and not force:
+            raise util.Abort(_("a bookmark of the same name already exists"))
+        if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
+            and not force):
+            raise util.Abort(
+                _("a bookmark cannot have the name of an existing branch"))
+        if rev:
+            repo.bookmarks[mark] = repo.lookup(rev)
+        else:
+            repo.bookmarks[mark] = repo.changectx('.').node()
+        repo.bookmarksetcurrent(mark)
+        repo.bookmarkswrite()
+        return
+
+    if mark is None:
+        if rev:
+            raise util.Abort(_("bookmark name required"))
+        if len(repo.bookmarks) == 0:
+            ui.status(_("no bookmarks set\n"))
+        else:
+            for bmark, n in repo.bookmarks.iteritems():
+                if ui.configbool('bookmarks', 'track.current'):
+                    current = repo.bookmarkcurrent
+                    if bmark == current and n == cur:
+                        prefix, label = '*', 'bookmarks.current'
+                    else:
+                        prefix, label = ' ', ''
+                else:
+                    if n == cur:
+                        prefix, label = '*', 'bookmarks.current'
+                    else:
+                        prefix, label = ' ', ''
+
+                if ui.quiet:
+                    ui.write("%s\n" % bmark, label=label)
+                else:
+                    ui.write(" %s %-25s %d:%s\n" % (
+                        prefix, bmark, repo.changelog.rev(n), hexfn(n)),
+                        label=label)
+        return
+
 def branch(ui, repo, label=None, **opts):
     """set or show the current branch name
 
@@ -2403,7 +2497,13 @@
     if opts.get('bundle') and opts.get('subrepos'):
         raise util.Abort(_('cannot combine --bundle and --subrepos'))
 
-    ret = hg.incoming(ui, repo, source, opts)
+    if opts.get("bookmarks"):
+        source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
+        other = hg.repository(hg.remoteui(repo, opts), source)
+        ui.status(_('comparing with %s\n') % url.hidepassword(source))
+        return bookmarks.diffbookmarks(ui, repo, other)
+    else:
+        ret = hg.incoming(ui, repo, source, opts)
     return ret
 
 def init(ui, dest=".", **opts):
@@ -2679,7 +2779,14 @@
 
     Returns 0 if there are outgoing changes, 1 otherwise.
     """
-    ret = hg.outgoing(ui, repo, dest, opts)
+    if opts.get("bookmarks"):
+        dest = ui.expandpath(dest or 'default-push', dest or 'default')
+        dest, branches = hg.parseurl(dest, opts.get('branch'))
+        other = hg.repository(hg.remoteui(repo, opts), dest)
+        ui.status(_('comparing with %s\n') % url.hidepassword(dest))
+        return bookmarks.diffbookmarks(ui, other, repo)
+    else:
+        ret = hg.outgoing(ui, repo, dest, opts)
     return ret
 
 def parents(ui, repo, file_=None, **opts):
@@ -2793,6 +2900,14 @@
     """
     source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
     other = hg.repository(hg.remoteui(repo, opts), source)
+
+    if opts.get('bookmark'):
+        rb = other.listkeys('bookmarks')
+        for b in opts['bookmark']:
+            if b not in rb:
+                raise util.Abort(_('remote bookmark %s not found!') % b)
+            opts.setdefault('rev', []).append(b)
+
     ui.status(_('pulling from %s\n') % url.hidepassword(source))
     revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
     if revs:
@@ -2804,6 +2919,15 @@
             raise util.Abort(err)
 
     modheads = repo.pull(other, heads=revs, force=opts.get('force'))
+
+    # update specified bookmarks
+    if opts.get('bookmark'):
+        for b in opts['bookmark']:
+            # explicit pull overrides local bookmark if any
+            ui.status(_("importing bookmark %s\n") % b)
+            repo.bookmarks[b] = repo[rb[b]].node()
+        repo.bookmarkswrite()
+
     if checkout:
         checkout = str(repo.changelog.rev(other.lookup(checkout)))
     repo._subtoppath = source
@@ -2845,22 +2969,55 @@
     dest, branches = hg.parseurl(dest, opts.get('branch'))
     revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
     other = hg.repository(hg.remoteui(repo, opts), dest)
-    ui.status(_('pushing to %s\n') % url.hidepassword(dest))
-    if revs:
-        revs = [repo.lookup(rev) for rev in revs]
-
-    repo._subtoppath = dest
-    try:
-        # push subrepos depth-first for coherent ordering
-        c = repo['']
-        subs = c.substate # only repos that are committed
-        for s in sorted(subs):
-            if not c.sub(s).push(opts.get('force')):
-                return False
-    finally:
-        del repo._subtoppath
-    r = repo.push(other, opts.get('force'), revs=revs,
-                  newbranch=opts.get('new_branch'))
+
+    dopush = True
+    if opts.get('bookmark'):
+        dopush = False
+        for b in opts['bookmark']:
+            if b in repo.bookmarks:
+                dopush = True
+                opts.setdefault('rev', []).append(b)
+
+    r = -1
+    if dopush:
+        ui.status(_('pushing to %s\n') % url.hidepassword(dest))
+        if revs:
+            revs = [repo.lookup(rev) for rev in revs]
+
+        repo._subtoppath = dest
+        try:
+            # push subrepos depth-first for coherent ordering
+            c = repo['']
+            subs = c.substate # only repos that are committed
+            for s in sorted(subs):
+                if not c.sub(s).push(opts.get('force')):
+                    return False
+        finally:
+            del repo._subtoppath
+
+        r = repo.push(other, opts.get('force'), revs=revs,
+                    newbranch=opts.get('new_branch'))
+
+    if opts.get('bookmark'):
+        rb = other.listkeys('bookmarks')
+        for b in opts['bookmark']:
+            # explicit push overrides remote bookmark if any
+            if b in repo.bookmarks:
+                ui.status(_("exporting bookmark %s\n") % b)
+                new = repo[b].hex()
+            elif b in rb:
+                ui.status(_("deleting remote bookmark %s\n") % b)
+                new = '' # delete
+            else:
+                ui.warn(_('bookmark %s does not exist on the local '
+                          'or remote repository!\n') % b)
+                return 2
+            old = rb.get(b, '')
+            res = other.pushkey('bookmarks', b, old, new)
+            if not res:
+                ui.warn(_('updating bookmark %s failed!\n') % b)
+                if not r:
+                    return 2
     return r == 0
 
 def recover(ui, repo):
@@ -3865,6 +4022,7 @@
     if not rev:
         rev = node
 
+    mark = rev
     rev = cmdutil.revsingle(repo, rev, rev).rev()
 
     if check and clean:
@@ -3882,9 +4040,12 @@
         rev = cmdutil.finddate(ui, repo, date)
 
     if clean or check:
-        return hg.clean(repo, rev)
+        res = hg.clean(repo, rev)
     else:
-        return hg.update(repo, rev)
+        res = hg.update(repo, rev)
+    if mark in repo.bookmarks:
+        repo.bookmarksetcurrent(mark)
+    return res
 
 def verify(ui, repo):
     """verify the integrity of the repository
@@ -4063,6 +4224,13 @@
            _('revision to backout'), _('REV')),
          ] + walkopts + commitopts + commitopts2,
          _('[OPTION]... [-r] REV')),
+    "bookmarks":
+        (bookmark,
+         [('f', 'force', False, _('force')),
+          ('r', 'rev', '', _('revision'), _('REV')),
+          ('d', 'delete', False, _('delete a given bookmark')),
+          ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
+         _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
     "bisect":
         (bisect,
          [('r', 'reset', False, _('reset bisect state')),
@@ -4291,6 +4459,8 @@
            _('a remote changeset intended to be added'), _('REV')),
           ('b', 'branch', [],
            _('a specific branch you would like to pull'), _('BRANCH')),
+          ('B', 'bookmarks', False,
+           _("compare bookmark")),
          ] + logopts + remoteopts + subrepoopts,
          _('[-p] [-n] [-M] [-f] [-r REV]...'
            ' [--bundle FILENAME] [SOURCE]')),
@@ -4359,6 +4529,8 @@
           ('n', 'newest-first', None, _('show newest record first')),
           ('b', 'branch', [],
            _('a specific branch you would like to push'), _('BRANCH')),
+          ('B', 'bookmarks', False,
+           _("compare bookmark")),
          ] + logopts + remoteopts + subrepoopts,
          _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
     "parents":
@@ -4378,6 +4550,9 @@
            _('a remote changeset intended to be added'), _('REV')),
           ('b', 'branch', [],
            _('a specific branch you would like to pull'), _('BRANCH')),
+          ('B', 'bookmark', [],
+                     _("bookmark to import"),
+                     _('BOOKMARK')),
          ] + remoteopts,
          _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
     "^push":
@@ -4388,6 +4563,9 @@
            _('REV')),
           ('b', 'branch', [],
            _('a specific branch you would like to push'), _('BRANCH')),
+          ('B', 'bookmark', [],
+                     _("bookmark to export"),
+                     _('BOOKMARK')),
           ('', 'new-branch', False, _('allow pushing a new branch')),
          ] + remoteopts,
          _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -355,6 +355,7 @@
         tags['tip'] = self.changelog.tip()
         tagtypes = dict([(encoding.tolocal(name), value)
                          for (name, value) in tagtypes.iteritems()])
+        tags.update(self.bookmarks)
         return (tags, tagtypes)
 
     def tagtype(self, tagname):
@@ -501,7 +502,157 @@
                 bheads = [b for b in bheads if b not in reachable]
             partial[branch] = bheads
 
+    def _bookmarkspull(self, remote):
+        self.ui.debug("checking for updated bookmarks\n")
+        rb = remote.listkeys('bookmarks')
+        changed = False
+        for k in rb.keys():
+            if k in self.bookmarks:
+                nr, nl = rb[k], self.bookmarks[k]
+                if nr in self:
+                    cr = self[nr]
+                    cl = self[nl]
+                    if cl.rev() >= cr.rev():
+                        continue
+                    if cr in cl.descendants():
+                        self.bookmarks[k] = cr.node()
+                        changed = True
+                        self.ui.status(_("updating bookmark %s\n") % k)
+                    else:
+                        self.ui.warn(_("not updating divergent"
+                                       " bookmark %s\n") % k)
+        if changed:
+            self.bookmarkswrite()
+
+    def _bookmarkspush(self, remote):
+        self.ui.debug("checking for updated bookmarks\n")
+        rb = remote.listkeys('bookmarks')
+        for k in rb.keys():
+            if k in self.bookmarks:
+                nr, nl = rb[k], hex(self.bookmarks[k])
+                if nr in self:
+                    cr = self[nr]
+                    cl = self[nl]
+                    if cl in cr.descendants():
+                        r = remote.pushkey('bookmarks', k, nr, nl)
+                        if r:
+                            self.ui.status(_("updating bookmark %s\n") % k)
+                        else:
+                            self.ui.warn(_('updating bookmark %s'
+                                           ' failed!\n') % k)
+
+    def _bookmarksupdate(self, node):
+        parents = self.changelog.parents(node)
+        if parents[1] == nullid:
+            parents = (parents[0],)
+        marks = self.bookmarks
+        update = False
+        if self.ui.configbool('bookmarks', 'track.current'):
+            mark = self.bookmarkcurrent
+            if mark and mark in marks and marks[mark] in parents:
+                marks[mark] = node
+                update = True
+        else:
+            for mark, n in marks.items():
+                if n in parents:
+                    marks[mark] = node
+                    update = True
+        if update:
+            self.bookmarkswrite()
+
+    @propertycache
+    def bookmarks(self):
+        '''Parse .hg/bookmarks file and return a dictionary
+
+        Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
+        in the .hg/bookmarks file.
+        Read the file and return a (name=>nodeid) dictionary
+        '''
+        bookmarks = {}
+        if os.path.exists(self.join('bookmarks')):
+            for line in self.opener('bookmarks'):
+                sha, name = line.strip().split(' ', 1)
+                ref = encoding.tolocal(name)
+                bookmarks[ref] = self.changelog.lookup(sha)
+        return bookmarks
+
+    @propertycache
+    def bookmarkcurrent(self):
+        mark = None
+        if os.path.exists(self.join('bookmarks.current')):
+            file = self.opener('bookmarks.current')
+            # No readline() in posixfile_nt, reading everything is cheap
+            mark = (file.readlines() or [''])[0]
+            if mark == '':
+                mark = None
+            file.close()
+        return mark
+
+    def bookmarksetcurrent(self, mark):
+        '''Set the name of the bookmark that we are currently on
+
+        Set the name of the bookmark that we are on (hg update <bookmark>).
+        The name is recorded in .hg/bookmarks.current
+        '''
+        current = self.bookmarkcurrent
+        if current == mark:
+            return
+
+        refs = self.bookmarks
+
+        # do not update if we do update to a rev equal to the current bookmark
+        if (mark and mark not in refs and
+            current and refs[current] == self.changectx('.').node()):
+            return
+        if mark not in refs:
+            mark = ''
+        wlock = self.wlock()
+        try:
+            file = self.opener('bookmarks.current', 'w', atomictemp=True)
+            file.write(mark)
+            file.rename()
+        finally:
+            wlock.release()
+        self.bookmarkcurrent = mark
+
+    def bookmarkswrite(self):
+        '''Write bookmarks
+
+        Write the given bookmark => hash dictionary to the .hg/bookmarks file
+        in a format equal to those of localtags.
+
+        We also store a backup of the previous state in undo.bookmarks that
+        can be copied back on rollback.
+        '''
+        refs = self.bookmarks
+
+        try:
+            bms = self.opener('bookmarks').read()
+        except IOError:
+            bms = ''
+        self.opener('undo.bookmarks', 'w').write(bms)
+
+        if self.bookmarkcurrent not in refs:
+            self.bookmarksetcurrent(None)
+        wlock = self.wlock()
+        try:
+            file = self.opener('bookmarks', 'w', atomictemp=True)
+            for refspec, node in refs.iteritems():
+                file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
+            file.rename()
+
+            # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
+            try:
+                os.utime(self.sjoin('00changelog.i'), None)
+            except OSError:
+                pass
+
+        finally:
+            wlock.release()
+
     def lookup(self, key):
+        if key in self.bookmarks:
+            return self.bookmarks[key]
         if isinstance(key, int):
             return self.changelog.node(key)
         elif key == '.':
@@ -692,6 +843,12 @@
         try:
             wlock = self.wlock()
             lock = self.lock()
+            noinfo = True
+            if os.path.exists(self.join('undo.bookmarks')):
+                noinfo = False
+                if not dryrun:
+                    util.rename(self.join('undo.bookmarks'),
+                            self.join('bookmarks'))
             if os.path.exists(self.sjoin("undo")):
                 try:
                     args = self.opener("undo.desc", "r").read().splitlines()
@@ -720,7 +877,7 @@
                 self.invalidate()
                 self.dirstate.invalidate()
                 self.destroyed()
-            else:
+            elif noinfo:
                 self.ui.warn(_("no rollback information available\n"))
                 return 1
         finally:
@@ -734,7 +891,7 @@
         self._branchcachetip = None
 
     def invalidate(self):
-        for a in ("changelog", "manifest"):
+        for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
             if a in self.__dict__:
                 delattr(self, a)
         self.invalidatecaches()
@@ -978,6 +1135,9 @@
                         _('note: commit message saved in %s\n') % msgfn)
                 raise
 
+            # update bookmarks
+            self._bookmarksupdate(ret)
+
             # update dirstate and mergestate
             for f in changes[0] + changes[1]:
                 self.dirstate.normal(f)
@@ -1301,8 +1461,11 @@
                                        "other repository doesn't support "
                                        "changegroupsubset."))
                 cg = remote.changegroupsubset(fetch, heads, 'pull')
+
             return self.addchangegroup(cg, 'pull', remote.url(), lock=lock)
         finally:
+            if remote.capable('pushkey'):
+                self._bookmarkspull(remote)
             lock.release()
 
     def push(self, remote, force=False, revs=None, newbranch=False):
@@ -1342,11 +1505,15 @@
                     remote_heads = ['force']
                 # ssh: return remote's addchangegroup()
                 # http: return remote's addchangegroup() or 0 for error
-                return remote.unbundle(cg, remote_heads, 'push')
+                ret = remote.unbundle(cg, remote_heads, 'push')
             else:
                 # we return an integer indicating remote head count change
-                return remote.addchangegroup(cg, 'push', self.url(), lock=lock)
+                ret = remote.addchangegroup(cg, 'push', self.url(), lock=lock)
+
+            return ret
         finally:
+            if remote.capable('pushkey'):
+                self._bookmarkspush(remote)
             if lock is not None:
                 lock.release()
 
@@ -1827,6 +1994,10 @@
                 self.hook("incoming", node=hex(cl.node(i)),
                           source=srctype, url=url)
 
+        if newheads - oldheads <= 1:
+            node = self.changelog.tip()
+            self._bookmarksupdate(node)
+
         # never return 0 here:
         if newheads < oldheads:
             return newheads - oldheads - 1
diff --git a/mercurial/pushkey.py b/mercurial/pushkey.py
--- a/mercurial/pushkey.py
+++ b/mercurial/pushkey.py
@@ -4,6 +4,7 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+import bookmarks
 
 def _nslist(repo):
     n = {}
@@ -11,7 +12,8 @@
         n[k] = ""
     return n
 
-_namespaces = {"namespaces": (lambda *x: False, _nslist)}
+_namespaces = {"namespaces": (lambda *x: False, _nslist),
+        "bookmarks": (bookmarks.pushbookmark, bookmarks.listbookmarks)}
 
 def register(namespace, pushkey, listkeys):
     _namespaces[namespace] = (pushkey, listkeys)
diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -105,7 +105,11 @@
             saveheads.difference_update(parents)
             saveheads.add(r)
 
+    rmrevs = [cl.node(r) for r in tostrip if r not in saveheads]
     saveheads = [cl.node(r) for r in saveheads]
+    # read bookmarks before we strip. or lookups will fail
+    bookmarks = repo.bookmarks
+
     files = _collectfiles(repo, striprev)
 
     extranodes = _collectextranodes(repo, files, striprev)
@@ -164,4 +168,13 @@
                     % chgrpfile)
         raise
 
+    # update bookmarks
+    writebookmarks = False
+    if len(rmrevs) > 0:
+        for mark, n in bookmarks.iteritems():
+            if n in rmrevs:
+                writebookmarks = True
+                repo.bookmarks[mark] = repo.changectx('.').node()
+        if writebookmarks:
+            repo.bookmarkswrite()
     repo.destroyed()
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -9,6 +9,7 @@
 import parser, util, error, discovery
 import match as matchmod
 from i18n import _, gettext
+from mercurial.node import hex, bin
 
 elements = {
     "(": (20, ("group", 1, ")"), ("func", 1, ")")),
@@ -664,6 +665,33 @@
 def tagged(repo, subset, x):
     return tag(repo, subset, x)
 
+def bookmark(repo, subset, x):
+    """``bookmark([name])``
+    The named bookmark or all bookmarks.
+    """
+    # i18n: "bookmark" is a keyword
+    def listbookmarks(repo):
+        # We may try to list bookmarks on a repo type that does not
+        # support it (e.g., statichttprepository).
+        if not hasattr(repo, 'bookmarks'):
+            return {}
+
+        d = {}
+        for k, v in repo.bookmarks.iteritems():
+            d[k] = hex(v)
+        return d
+    args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
+    if args:
+        bm = getstring(args[0],
+                # i18n: "bookmark" is a keyword
+                _('the argument to bookmark must be a string'))
+        bmrev = listbookmarks(repo).get(bm, None)
+        if bmrev:
+            bmrev = repo.changelog.rev(bin(bmrev))
+        return [r for r in subset if r == bmrev]
+    bms = [repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()]
+    return [r for r in subset if r in set(bms)]
+
 symbols = {
     "adds": adds,
     "all": getall,
@@ -671,6 +699,7 @@
     "ancestors": ancestors,
     "author": author,
     "branch": branch,
+    "bookmark": bookmark,
     "children": children,
     "closed": closed,
     "contains": contains,
diff --git a/tests/test-bookmarks-current.t b/tests/test-bookmarks-current.t
--- a/tests/test-bookmarks-current.t
+++ b/tests/test-bookmarks-current.t
@@ -1,6 +1,3 @@
-  $ echo "[extensions]" >> $HGRCPATH
-  $ echo "bookmarks=" >> $HGRCPATH
-
   $ echo "[bookmarks]" >> $HGRCPATH
   $ echo "track.current = True" >> $HGRCPATH
 
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
@@ -1,6 +1,3 @@
-  $ echo "[extensions]" >> $HGRCPATH
-  $ echo "bookmarks=" >> $HGRCPATH
-
   $ echo "[bookmarks]" >> $HGRCPATH
   $ echo "track.current = True" >> $HGRCPATH
 
diff --git a/tests/test-bookmarks-rebase.t b/tests/test-bookmarks-rebase.t
--- a/tests/test-bookmarks-rebase.t
+++ b/tests/test-bookmarks-rebase.t
@@ -1,6 +1,5 @@
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "rebase=" >> $HGRCPATH
-  $ echo "bookmarks=" >> $HGRCPATH
 
 initialize repository
 
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
@@ -1,5 +1,4 @@
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "bookmarks=" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
 
   $ hg init
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -1,6 +1,3 @@
-  $ echo "[extensions]" >> $HGRCPATH
-  $ echo "bookmarks=" >> $HGRCPATH
-
   $ hg init
 
 no bookmarks
diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t
--- a/tests/test-debugcomplete.t
+++ b/tests/test-debugcomplete.t
@@ -6,6 +6,7 @@
   archive
   backout
   bisect
+  bookmarks
   branch
   branches
   bundle
@@ -187,8 +188,8 @@
   init: ssh, remotecmd
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, style, template, include, exclude
   merge: force, tool, rev, preview
-  pull: update, force, rev, branch, ssh, remotecmd
-  push: force, rev, branch, new-branch, ssh, remotecmd
+  pull: update, force, rev, branch, bookmark, ssh, remotecmd
+  push: force, rev, branch, bookmark, new-branch, ssh, remotecmd
   remove: after, force, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
@@ -198,6 +199,7 @@
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, command, noupdate
+  bookmarks: force, rev, delete, rename
   branch: force, clean
   branches: active, closed
   bundle: force, rev, branch, base, all, type, ssh, remotecmd
@@ -228,10 +230,10 @@
   help: 
   identify: rev, num, id, branch, tags
   import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity
-  incoming: force, newest-first, bundle, rev, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
+  incoming: force, newest-first, bundle, rev, branch, bookmarks, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
   locate: rev, print0, fullpath, include, exclude
   manifest: rev
-  outgoing: force, rev, newest-first, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
+  outgoing: force, rev, newest-first, branch, bookmarks, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, subrepos
   parents: rev, style, template
   paths: 
   recover: 
diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t
--- a/tests/test-globalopts.t
+++ b/tests/test-globalopts.t
@@ -284,6 +284,7 @@
    archive      create an unversioned archive of a repository revision
    backout      reverse effect of earlier changeset
    bisect       subdivision search of changesets
+   bookmarks    track a line of development with movable markers
    branch       set or show the current branch name
    branches     list repository named branches
    bundle       create a changegroup file
@@ -360,6 +361,7 @@
    archive      create an unversioned archive of a repository revision
    backout      reverse effect of earlier changeset
    bisect       subdivision search of changesets
+   bookmarks    track a line of development with movable markers
    branch       set or show the current branch name
    branches     list repository named branches
    bundle       create a changegroup file
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -55,6 +55,7 @@
    archive      create an unversioned archive of a repository revision
    backout      reverse effect of earlier changeset
    bisect       subdivision search of changesets
+   bookmarks    track a line of development with movable markers
    branch       set or show the current branch name
    branches     list repository named branches
    bundle       create a changegroup file
@@ -127,6 +128,7 @@
    archive      create an unversioned archive of a repository revision
    backout      reverse effect of earlier changeset
    bisect       subdivision search of changesets
+   bookmarks    track a line of development with movable markers
    branch       set or show the current branch name
    branches     list repository named branches
    bundle       create a changegroup file
@@ -649,6 +651,7 @@
    archive      create an unversioned archive of a repository revision
    backout      reverse effect of earlier changeset
    bisect       subdivision search of changesets
+   bookmarks    track a line of development with movable markers
    branch       set or show the current branch name
    branches     list repository named branches
    bundle       create a changegroup file
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -104,13 +104,21 @@
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob)
 
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -41,9 +41,6 @@
   > [server]
   > uncompressed = True
   > 
-  > [extensions]
-  > bookmarks =
-  > 
   > [hooks]
   > changegroup = python ../printenv.py changegroup-in-remote 0 ../dummylog
   > EOF
@@ -123,7 +120,6 @@
   $ echo "[ui]" >> .hg/hgrc
   $ echo "ssh = python ../dummyssh" >> .hg/hgrc
   $ echo '[extensions]' >> .hg/hgrc
-  $ echo 'bookmarks =' >> .hg/hgrc
 
 find outgoing
 
@@ -197,7 +193,6 @@
 
   $ cd ../local
   $ echo '[extensions]' >> ../remote/.hg/hgrc
-  $ echo 'bookmarks =' >> ../remote/.hg/hgrc
   $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote namespaces
   bookmarks	
   namespaces	
@@ -287,5 +282,3 @@
   Got arguments 1:user at dummy 2:hg -R remote serve --stdio
   Got arguments 1:user at dummy 2:hg -R remote serve --stdio
   Got arguments 1:user at dummy 2:hg -R remote serve --stdio
-  Got arguments 1:user at dummy 2:hg -R remote serve --stdio
-  Got arguments 1:user at dummy 2:hg -R remote serve --stdio


More information about the Mercurial-devel mailing list