# HG changeset patch # User Matt Mackall # Date 2011-02-10 19:46:28 # Node ID d4ab9486e514dd24e21a2ca3b6c439ea13d85cab # Parent cef73cd9c268244c90de8b7d529119c57273f739 bookmarks: move push/pull command features to core diff --git a/hgext/bookmarks.py b/hgext/bookmarks.py deleted file mode 100644 --- a/hgext/bookmarks.py +++ /dev/null @@ -1,216 +0,0 @@ -# Mercurial extension to provide the 'hg bookmark' command -# -# Copyright 2008 David Soria Parra -# -# 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]')), -} diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -457,6 +457,100 @@ def bisect(ui, repo, rev=None, extra=Non 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 @@ def pull(ui, repo, source="default", **o 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 @@ def pull(ui, repo, source="default", **o 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 @@ def push(ui, repo, dest=None, **opts): 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 @@ def push(ui, repo, dest=None, **opts): 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 @@ table = { _('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 @@ table = { _('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 @@ table = { ('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')), diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- a/mercurial/extensions.py +++ b/mercurial/extensions.py @@ -11,7 +11,7 @@ from i18n import _, gettext _extensions = {} _order = [] -_ignore = ['hbisect'] +_ignore = ['hbisect', 'bookmarks'] def extensions(): for name in _order: 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 @@ -71,14 +71,21 @@ delete a remote bookmark $ 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] 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 @@ Show all commands except debug commands archive backout bisect + bookmarks branch branches bundle @@ -187,8 +188,8 @@ Show all commands + options 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 @@ Show all commands + options 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 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 @@ Testing -h/--help: 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 @@ Testing -h/--help: 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 @@ Short help: 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 @@ Short help: 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 @@ Test that default list of commands omits 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-ssh.t b/tests/test-ssh.t --- a/tests/test-ssh.t +++ b/tests/test-ssh.t @@ -233,6 +233,9 @@ test pushkeys and bookmarks 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 @@ push should succeed even though it has a 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