bookmarks: move push/pull command features to core
authorMatt Mackall <mpm@selenic.com>
Thu, 10 Feb 2011 13:46:28 -0600
changeset 13368 d4ab9486e514
parent 13367 cef73cd9c268
child 13370 d13a533a0b11
child 13374 1c613c1ae43d
bookmarks: move push/pull command features to core
hgext/bookmarks.py
mercurial/commands.py
mercurial/extensions.py
tests/test-bookmarks-pushpull.t
tests/test-debugcomplete.t
tests/test-globalopts.t
tests/test-help.t
tests/test-ssh.t
--- a/hgext/bookmarks.py	Thu Feb 10 13:46:28 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-# Mercurial extension to provide the 'hg bookmark' command
-#
-# Copyright 2008 David Soria Parra <dsp@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 encoding
-from mercurial import bookmarks
-import os
-
-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:
-            bookmarks.setcurrent(repo, mark)
-        bookmarks.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:
-            bookmarks.setcurrent(repo, None)
-        del marks[mark]
-        bookmarks.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()
-        bookmarks.setcurrent(repo, mark)
-        bookmarks.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 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()
-        bookmarks.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 uisetup(ui):
-    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')))
-
-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]')),
-}
--- a/mercurial/commands.py	Thu Feb 10 13:46:28 2011 -0600
+++ b/mercurial/commands.py	Thu Feb 10 13:46:28 2011 -0600
@@ -457,6 +457,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. 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:
+            bookmarks.setcurrent(repo, mark)
+        bookmarks.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:
+            bookmarks.setcurrent(repo, None)
+        del marks[mark]
+        bookmarks.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()
+        bookmarks.setcurrent(repo, mark)
+        bookmarks.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 branch(ui, repo, label=None, **opts):
     """set or show the current branch name
 
@@ -2806,6 +2900,16 @@
     other = hg.repository(hg.remoteui(repo, opts), source)
     ui.status(_('pulling from %s\n') % url.hidepassword(source))
     revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
+
+    if opts.get('bookmark'):
+        if not revs:
+            revs = []
+        rb = other.listkeys('bookmarks')
+        for b in opts['bookmark']:
+            if b not in rb:
+                raise util.Abort(_('remote bookmark %s not found!') % b)
+            revs.append(rb[b])
+
     if revs:
         try:
             revs = [other.lookup(rev) for rev in revs]
@@ -2819,10 +2923,21 @@
         checkout = str(repo.changelog.rev(other.lookup(checkout)))
     repo._subtoppath = source
     try:
-        return postincoming(ui, repo, modheads, opts.get('update'), checkout)
+        ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
+
     finally:
         del repo._subtoppath
 
+    # 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()
+        bookmarks.write(repo)
+
+    return ret
+
 def push(ui, repo, dest=None, **opts):
     """push changes to the specified destination
 
@@ -2852,6 +2967,17 @@
 
     Returns 0 if push was successful, 1 if nothing to push.
     """
+
+    if opts.get('bookmark'):
+        for b in opts['bookmark']:
+            # translate -B options to -r so changesets get pushed
+            if b in repo._bookmarks:
+                opts.setdefault('rev', []).append(b)
+            else:
+                # if we try to push a deleted bookmark, translate it to null
+                # this lets simultaneous -r, -b options continue working
+                opts.setdefault('rev', []).append("null")
+
     dest = ui.expandpath(dest or 'default-push', dest or 'default')
     dest, branches = hg.parseurl(dest, opts.get('branch'))
     revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
@@ -2870,9 +2996,33 @@
                 return False
     finally:
         del repo._subtoppath
-    r = repo.push(other, opts.get('force'), revs=revs,
-                  newbranch=opts.get('new_branch'))
-    return r == 0
+    result = repo.push(other, opts.get('force'), revs=revs,
+                       newbranch=opts.get('new_branch'))
+
+    result = (result == 0)
+
+    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, '')
+            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 recover(ui, repo):
     """roll back an interrupted transaction
@@ -4091,6 +4241,13 @@
            _('use command to check changeset state'), _('CMD')),
           ('U', 'noupdate', False, _('do not update to target'))],
          _("[-gbsr] [-U] [-c CMD] [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]')),
     "branch":
         (branch,
          [('f', 'force', None,
@@ -4396,6 +4553,7 @@
            _('run even when remote repository is unrelated')),
           ('r', 'rev', [],
            _('a remote changeset intended to be added'), _('REV')),
+          ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
           ('b', 'branch', [],
            _('a specific branch you would like to pull'), _('BRANCH')),
          ] + remoteopts,
@@ -4406,6 +4564,7 @@
           ('r', 'rev', [],
            _('a changeset intended to be included in the destination'),
            _('REV')),
+          ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
           ('b', 'branch', [],
            _('a specific branch you would like to push'), _('BRANCH')),
           ('', 'new-branch', False, _('allow pushing a new branch')),
--- a/mercurial/extensions.py	Thu Feb 10 13:46:28 2011 -0600
+++ b/mercurial/extensions.py	Thu Feb 10 13:46:28 2011 -0600
@@ -11,7 +11,7 @@
 
 _extensions = {}
 _order = []
-_ignore = ['hbisect']
+_ignore = ['hbisect', 'bookmarks']
 
 def extensions():
     for name in _order:
--- a/tests/test-bookmarks-pushpull.t	Thu Feb 10 13:46:28 2011 -0600
+++ b/tests/test-bookmarks-pushpull.t	Thu Feb 10 13:46:28 2011 -0600
@@ -71,14 +71,21 @@
 
   $ hg book -d W
   $ hg push -B W ../a
+  pushing to ../a
+  searching for changes
+  no changes found
   deleting remote bookmark W
 
 push/pull name that doesn't exist
 
   $ hg push -B badname ../a
+  pushing to ../a
+  searching for changes
+  no changes found
   bookmark badname does not exist on the local or remote repository!
   [2]
   $ hg pull -B anotherbadname ../a
+  pulling from ../a
   abort: remote bookmark anotherbadname not found!
   [255]
 
--- a/tests/test-debugcomplete.t	Thu Feb 10 13:46:28 2011 -0600
+++ b/tests/test-debugcomplete.t	Thu Feb 10 13:46:28 2011 -0600
@@ -6,6 +6,7 @@
   archive
   backout
   bisect
+  bookmarks
   branch
   branches
   bundle
@@ -187,8 +188,8 @@
   init: ssh, remotecmd, insecure
   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, insecure
-  push: force, rev, branch, new-branch, ssh, remotecmd, insecure
+  pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
+  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   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, insecure
--- a/tests/test-globalopts.t	Thu Feb 10 13:46:28 2011 -0600
+++ b/tests/test-globalopts.t	Thu Feb 10 13:46:28 2011 -0600
@@ -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
--- a/tests/test-help.t	Thu Feb 10 13:46:28 2011 -0600
+++ b/tests/test-help.t	Thu Feb 10 13:46:28 2011 -0600
@@ -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
--- a/tests/test-ssh.t	Thu Feb 10 13:46:28 2011 -0600
+++ b/tests/test-ssh.t	Thu Feb 10 13:46:28 2011 -0600
@@ -233,6 +233,9 @@
   importing bookmark foo
   $ hg book -d foo
   $ hg push -B foo
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  no changes found
   deleting remote bookmark foo
 
 a bad, evil hook that prints to stdout
@@ -287,5 +290,3 @@
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
-  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
-  Got arguments 1:user@dummy 2:hg -R remote serve --stdio