bookmarks.py
234 lines
| 8.0 KiB
| text/x-python
|
PythonLexer
/ hgext / bookmarks.py
David Soria Parra
|
r7239 | # 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, incorporated herein by reference. | ||||
Joel Rosdahl
|
r7252 | |||
David Soria Parra
|
r7239 | '''mercurial bookmarks | ||
Joel Rosdahl
|
r7252 | Mercurial bookmarks are local moveable pointers 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 is forwarded to the new changeset. | ||||
David Soria Parra
|
r7239 | |||
Joel Rosdahl
|
r7252 | It is possible to use bookmark names in every revision lookup (e.g. hg | ||
merge, hg update). | ||||
David Soria Parra
|
r7239 | ''' | ||
Joel Rosdahl
|
r7252 | |||
David Soria Parra
|
r7239 | from mercurial.commands import templateopts, hex, short | ||
from mercurial.i18n import _ | ||||
from mercurial import cmdutil, util, commands, changelog | ||||
Joel Rosdahl
|
r7256 | from mercurial.node import nullid, nullrev | ||
David Soria Parra
|
r7239 | from mercurial.repo import RepoError | ||
import mercurial, mercurial.localrepo, mercurial.repair, os | ||||
def parse(repo): | ||||
'''Parse .hg/bookmarks file and return a dictionary | ||||
Joel Rosdahl
|
r7250 | |||
Joel Rosdahl
|
r7252 | Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values | ||
in the .hg/bookmarks file. They are read by the parse() method and | ||||
returned as a dictionary with name => hash values. | ||||
Joel Rosdahl
|
r7250 | |||
David Soria Parra
|
r7239 | The parsed dictionary is cached until a write() operation is done. | ||
''' | ||||
try: | ||||
if repo._bookmarks: | ||||
return repo._bookmarks | ||||
repo._bookmarks = {} | ||||
for line in repo.opener('bookmarks'): | ||||
sha, refspec = line.strip().split(' ', 1) | ||||
repo._bookmarks[refspec] = repo.lookup(sha) | ||||
except: | ||||
pass | ||||
return repo._bookmarks | ||||
def write(repo, refs): | ||||
'''Write bookmarks | ||||
Joel Rosdahl
|
r7250 | |||
David Soria Parra
|
r7239 | 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. | ||||
''' | ||||
Joel Rosdahl
|
r7253 | if os.path.exists(repo.join('bookmarks')): | ||
util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks')) | ||||
David Soria Parra
|
r7239 | file = repo.opener('bookmarks', 'w+') | ||
for refspec, node in refs.items(): | ||||
file.write("%s %s\n" % (hex(node), refspec)) | ||||
file.close() | ||||
Joel Rosdahl
|
r7255 | def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None): | ||
David Soria Parra
|
r7239 | '''mercurial bookmarks | ||
Joel Rosdahl
|
r7250 | |||
Joel Rosdahl
|
r7252 | Bookmarks are pointers to certain commits that move when | ||
commiting. Bookmarks are local. They can be renamed, copied and | ||||
deleted. It is possible to use bookmark names in 'hg merge' and 'hg | ||||
update' to update to a given bookmark. | ||||
Joel Rosdahl
|
r7250 | |||
Joel Rosdahl
|
r7257 | You can use 'hg bookmark NAME' to set a bookmark on the current | ||
tip with the given name. If you specify a revision using -r REV | ||||
(where REV may be an existing bookmark), the bookmark is set to | ||||
that revision. | ||||
David Soria Parra
|
r7239 | ''' | ||
hexfn = ui.debugflag and hex or short | ||||
marks = parse(repo) | ||||
cur = repo.changectx('.').node() | ||||
Joel Rosdahl
|
r7255 | if rename: | ||
if rename not in marks: | ||||
Joel Rosdahl
|
r7251 | raise util.Abort(_("a bookmark of this name does not exist")) | ||
David Soria Parra
|
r7239 | if mark in marks and not force: | ||
raise util.Abort(_("a bookmark of the same name already exists")) | ||||
Joel Rosdahl
|
r7254 | if mark is None: | ||
raise util.Abort(_("new bookmark name required")) | ||||
Joel Rosdahl
|
r7255 | marks[mark] = marks[rename] | ||
del marks[rename] | ||||
David Soria Parra
|
r7239 | write(repo, marks) | ||
return | ||||
Joel Rosdahl
|
r7250 | |||
David Soria Parra
|
r7239 | if delete: | ||
if mark == None: | ||||
raise util.Abort(_("bookmark name required")) | ||||
if mark not in marks: | ||||
Joel Rosdahl
|
r7251 | raise util.Abort(_("a bookmark of this name does not exist")) | ||
David Soria Parra
|
r7239 | del marks[mark] | ||
write(repo, marks) | ||||
return | ||||
if mark != None: | ||||
Joel Rosdahl
|
r7259 | if "\n" in mark: | ||
raise util.Abort(_("bookmark name cannot contain newlines")) | ||||
Joel Rosdahl
|
r7260 | mark = mark.strip() | ||
David Soria Parra
|
r7239 | if mark in marks and not force: | ||
raise util.Abort(_("a bookmark of the same name already exists")) | ||||
Joel Rosdahl
|
r7250 | if ((mark in repo.branchtags() or mark == repo.dirstate.branch()) | ||
David Soria Parra
|
r7239 | and not force): | ||
Joel Rosdahl
|
r7252 | raise util.Abort( | ||
_("a bookmark cannot have the name of an existing branch")) | ||||
David Soria Parra
|
r7239 | if rev: | ||
marks[mark] = repo.lookup(rev) | ||||
else: | ||||
marks[mark] = repo.changectx('.').node() | ||||
write(repo, marks) | ||||
return | ||||
if mark == None: | ||||
Joel Rosdahl
|
r7258 | if rev: | ||
raise util.Abort(_("bookmark name required")) | ||||
David Soria Parra
|
r7239 | if len(marks) == 0: | ||
ui.status("no bookmarks set\n") | ||||
else: | ||||
for bmark, n in marks.iteritems(): | ||||
prefix = (n == cur) and '*' or ' ' | ||||
Joel Rosdahl
|
r7252 | ui.write(" %s %-25s %d:%s\n" % ( | ||
prefix, bmark, repo.changelog.rev(n), hexfn(n))) | ||||
David Soria Parra
|
r7239 | return | ||
def _revstostrip(changelog, node): | ||||
srev = changelog.rev(node) | ||||
tostrip = [srev] | ||||
saveheads = [] | ||||
Benoit Boissinot
|
r7283 | for r in xrange(srev, len(changelog)): | ||
David Soria Parra
|
r7239 | 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: | ||||
Benoit Boissinot
|
r7283 | if p not in tostrip and p > srev: | ||
David Soria Parra
|
r7239 | saveheads.append(p) | ||
return [r for r in tostrip if r not in saveheads] | ||||
def strip(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 = parse(repo) | ||||
update = [] | ||||
for mark, n in marks.items(): | ||||
if repo.changelog.rev(n) in revisions: | ||||
update.append(mark) | ||||
Benoit Boissinot
|
r7280 | oldstrip(ui, repo, node, backup) | ||
David Soria Parra
|
r7239 | if len(update) > 0: | ||
for m in update: | ||||
marks[m] = repo.changectx('.').node() | ||||
write(repo, marks) | ||||
oldstrip = mercurial.repair.strip | ||||
mercurial.repair.strip = strip | ||||
def reposetup(ui, repo): | ||||
if not isinstance(repo, mercurial.localrepo.localrepository): | ||||
return | ||||
# init a bookmark cache as otherwise we would get a infinite reading | ||||
# in lookup() | ||||
repo._bookmarks = None | ||||
class bookmark_repo(repo.__class__): | ||||
def rollback(self): | ||||
if os.path.exists(self.join('undo.bookmarks')): | ||||
util.rename(self.join('undo.bookmarks'), self.join('bookmarks')) | ||||
Joel Rosdahl
|
r7250 | return super(bookmark_repo, self).rollback() | ||
David Soria Parra
|
r7239 | |||
def lookup(self, key): | ||||
if self._bookmarks is None: | ||||
self._bookmarks = parse(self) | ||||
if key in self._bookmarks: | ||||
key = self._bookmarks[key] | ||||
return super(bookmark_repo, self).lookup(key) | ||||
def commit(self, *k, **kw): | ||||
"""Add a revision to the repository and | ||||
move the bookmark""" | ||||
node = super(bookmark_repo, self).commit(*k, **kw) | ||||
Dmitriy Taychenachev
|
r7262 | if node == None: | ||
return None | ||||
David Soria Parra
|
r7239 | parents = repo.changelog.parents(node) | ||
Joel Rosdahl
|
r7256 | if parents[1] == nullid: | ||
parents = (parents[0],) | ||||
David Soria Parra
|
r7239 | marks = parse(repo) | ||
update = False | ||||
for mark, n in marks.items(): | ||||
if n in parents: | ||||
marks[mark] = node | ||||
update = True | ||||
if update: | ||||
write(repo, marks) | ||||
return node | ||||
def addchangegroup(self, source, srctype, url, emptyok=False): | ||||
try: | ||||
onode = repo.changectx('.').node() | ||||
except RepoError, inst: | ||||
pass | ||||
Joel Rosdahl
|
r7252 | result = super(bookmark_repo, self).addchangegroup( | ||
source, srctype, url, emptyok) | ||||
David Soria Parra
|
r7239 | if result > 1: | ||
# We have more heads than before | ||||
return result | ||||
node = repo.changelog.tip() | ||||
marks = parse(repo) | ||||
update = False | ||||
for mark, n in marks.items(): | ||||
if n == onode: | ||||
marks[mark] = node | ||||
update = True | ||||
if update: | ||||
write(repo, marks) | ||||
return result | ||||
repo.__class__ = bookmark_repo | ||||
cmdtable = { | ||||
"bookmarks": | ||||
(bookmark, | ||||
[('f', 'force', False, _('force')), | ||||
('r', 'rev', '', _('revision')), | ||||
('d', 'delete', False, _('delete a given bookmark')), | ||||
Joel Rosdahl
|
r7255 | ('m', 'rename', '', _('rename a given bookmark'))], | ||
David Soria Parra
|
r7239 | _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')), | ||
} | ||||