diff mercurial/commands.py @ 13368:d4ab9486e514

bookmarks: move push/pull command features to core
author Matt Mackall <mpm@selenic.com>
date Thu, 10 Feb 2011 13:46:28 -0600
parents cef73cd9c268
children a184dbd9b2c5
line wrap: on
line diff
--- 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')),