bookmarks.py
621 lines
| 19.9 KiB
| text/x-python
|
PythonLexer
/ mercurial / bookmarks.py
|
r13350 | # Mercurial bookmark support code | ||
# | ||||
# 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. | ||||
|
r25917 | from __future__ import absolute_import | ||
import errno | ||||
from .i18n import _ | ||||
from .node import ( | ||||
bin, | ||||
hex, | ||||
) | ||||
from . import ( | ||||
encoding, | ||||
|
r29354 | error, | ||
|
r25917 | lock as lockmod, | ||
obsolete, | ||||
|
r31052 | txnutil, | ||
|
r25917 | util, | ||
) | ||||
|
r13350 | |||
|
r27186 | def _getbkfile(repo): | ||
"""Hook so that extensions that mess with the store can hook bm storage. | ||||
For core, this just handles wether we should see pending | ||||
bookmarks or the committed ones. Other extensions (like share) | ||||
may need to tweak this behavior further. | ||||
""" | ||||
|
r31052 | fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks') | ||
return fp | ||||
|
r27186 | |||
|
r17922 | class bmstore(dict): | ||
"""Storage for bookmarks. | ||||
|
r27698 | This object should do all bookmark-related reads and writes, so | ||
that it's fairly simple to replace the storage underlying | ||||
bookmarks without having to clone the logic surrounding | ||||
bookmarks. This type also should manage the active bookmark, if | ||||
any. | ||||
|
r17922 | |||
This particular bmstore implementation stores bookmarks as | ||||
{hash}\s{name}\n (the same format as localtags) in | ||||
.hg/bookmarks. The mapping is stored as {name: nodeid}. | ||||
""" | ||||
|
r13351 | |||
|
r17922 | def __init__(self, repo): | ||
dict.__init__(self) | ||||
self._repo = repo | ||||
r32735 | nm = repo.changelog.nodemap | |||
tonode = bin # force local lookup | ||||
|
r17922 | try: | ||
|
r27186 | bkfile = _getbkfile(repo) | ||
|
r23360 | for line in bkfile: | ||
|
r17922 | line = line.strip() | ||
if not line: | ||||
continue | ||||
try: | ||||
r32736 | sha, refspec = line.split(' ', 1) | |||
r32735 | node = tonode(sha) | |||
if node in nm: | ||||
refspec = encoding.tolocal(refspec) | ||||
self[refspec] = node | ||||
except (TypeError, ValueError): | ||||
# - bin(...) can raise TypeError | ||||
# - node in nm can raise ValueError for non-20-bytes entry | ||||
r32736 | # - split(...) can raise ValueError for string without ' ' | |||
r32735 | repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') | |||
% line) | ||||
|
r25660 | except IOError as inst: | ||
|
r17922 | if inst.errno != errno.ENOENT: | ||
raise | ||||
|
r27187 | self._clean = True | ||
|
r27698 | self._active = _readactive(repo, self) | ||
self._aclean = True | ||||
@property | ||||
def active(self): | ||||
return self._active | ||||
@active.setter | ||||
def active(self, mark): | ||||
if mark is not None and mark not in self: | ||||
raise AssertionError('bookmark %s does not exist!' % mark) | ||||
self._active = mark | ||||
self._aclean = False | ||||
|
r27187 | |||
def __setitem__(self, *args, **kwargs): | ||||
self._clean = False | ||||
return dict.__setitem__(self, *args, **kwargs) | ||||
def __delitem__(self, key): | ||||
self._clean = False | ||||
return dict.__delitem__(self, key) | ||||
|
r17922 | |||
|
r22665 | def recordchange(self, tr): | ||
"""record that bookmarks have been changed in a transaction | ||||
The transaction is then responsible for updating the file content.""" | ||||
tr.addfilegenerator('bookmarks', ('bookmarks',), self._write, | ||||
|
r23317 | location='plain') | ||
|
r22941 | tr.hookargs['bookmark_moved'] = '1' | ||
|
r22665 | |||
|
r23469 | def _writerepo(self, repo): | ||
"""Factored out for extensibility""" | ||||
|
r27698 | rbm = repo._bookmarks | ||
if rbm.active not in self: | ||||
rbm.active = None | ||||
rbm._writeactive() | ||||
|
r17922 | |||
|
r27799 | with repo.wlock(): | ||
|
r29300 | file_ = repo.vfs('bookmarks', 'w', atomictemp=True, | ||
checkambig=True) | ||||
|
r27188 | try: | ||
self._write(file_) | ||||
except: # re-raises | ||||
file_.discard() | ||||
raise | ||||
finally: | ||||
file_.close() | ||||
|
r17922 | |||
|
r27698 | def _writeactive(self): | ||
if self._aclean: | ||||
return | ||||
|
r27800 | with self._repo.wlock(): | ||
|
r27698 | if self._active is not None: | ||
|
r29300 | f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True, | ||
checkambig=True) | ||||
|
r27698 | try: | ||
f.write(encoding.fromlocal(self._active)) | ||||
finally: | ||||
f.close() | ||||
else: | ||||
|
r31544 | self._repo.vfs.tryunlink('bookmarks.current') | ||
|
r27698 | self._aclean = True | ||
|
r22664 | def _write(self, fp): | ||
for name, node in self.iteritems(): | ||||
fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name))) | ||||
|
r27187 | self._clean = True | ||
|
r29066 | self._repo.invalidatevolatilesets() | ||
|
r22664 | |||
|
r28182 | def expandname(self, bname): | ||
if bname == '.': | ||||
|
r29354 | if self.active: | ||
return self.active | ||||
else: | ||||
raise error.Abort(_("no active bookmark")) | ||||
|
r28182 | return bname | ||
|
r27698 | def _readactive(repo, marks): | ||
|
r24946 | """ | ||
Get the active bookmark. We can have an active bookmark that updates | ||||
itself as we commit. This function returns the name of that bookmark. | ||||
It is stored in .hg/bookmarks.current | ||||
""" | ||||
|
r13351 | mark = None | ||
|
r14027 | try: | ||
|
r23877 | file = repo.vfs('bookmarks.current') | ||
|
r25660 | except IOError as inst: | ||
|
r14027 | if inst.errno != errno.ENOENT: | ||
raise | ||||
return None | ||||
try: | ||||
|
r27685 | # No readline() in osutil.posixfile, reading everything is | ||
# cheap. | ||||
# Note that it's possible for readlines() here to raise | ||||
# IOError, since we might be reading the active mark over | ||||
# static-http which only tries to load the file when we try | ||||
# to read from it. | ||||
|
r13381 | mark = encoding.tolocal((file.readlines() or [''])[0]) | ||
|
r27698 | if mark == '' or mark not in marks: | ||
|
r13351 | mark = None | ||
|
r27685 | except IOError as inst: | ||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
return None | ||||
|
r14027 | finally: | ||
|
r13351 | file.close() | ||
return mark | ||||
|
r24945 | def activate(repo, mark): | ||
""" | ||||
Set the given bookmark to be 'active', meaning that this bookmark will | ||||
follow new commits that are made. | ||||
|
r13350 | The name is recorded in .hg/bookmarks.current | ||
|
r24945 | """ | ||
|
r27698 | repo._bookmarks.active = mark | ||
repo._bookmarks._writeactive() | ||||
|
r13352 | |||
|
r24944 | def deactivate(repo): | ||
""" | ||||
|
r26781 | Unset the active bookmark in this repository. | ||
|
r24944 | """ | ||
|
r27698 | repo._bookmarks.active = None | ||
repo._bookmarks._writeactive() | ||||
|
r16191 | |||
|
r24986 | def isactivewdirparent(repo): | ||
""" | ||||
Tell whether the 'active' bookmark (the one that follows new commits) | ||||
points to one of the parents of the current working directory (wdir). | ||||
|
r18471 | |||
|
r24986 | While this is normally the case, it can on occasion be false; for example, | ||
immediately after a pull, the active bookmark can be moved to point | ||||
to a place different than the wdir. This is solved by running `hg update`. | ||||
""" | ||||
mark = repo._activebookmark | ||||
|
r18471 | marks = repo._bookmarks | ||
|
r24986 | parents = [p.node() for p in repo[None].parents()] | ||
|
r18471 | return (mark in marks and marks[mark] in parents) | ||
|
r18513 | def deletedivergent(repo, deletefrom, bm): | ||
'''Delete divergent versions of bm on nodes in deletefrom. | ||||
Return True if at least one bookmark was deleted, False otherwise.''' | ||||
deleted = False | ||||
marks = repo._bookmarks | ||||
divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]] | ||||
for mark in divergent: | ||||
|
r21843 | if mark == '@' or '@' not in mark: | ||
# can't be divergent by definition | ||||
continue | ||||
|
r18513 | if mark and marks[mark] in deletefrom: | ||
if mark != bm: | ||||
del marks[mark] | ||||
deleted = True | ||||
return deleted | ||||
|
r32381 | def headsforactive(repo): | ||
"""Given a repo with an active bookmark, return divergent bookmark nodes. | ||||
Args: | ||||
repo: A repository with an active bookmark. | ||||
Returns: | ||||
A list of binary node ids that is the full list of other | ||||
revisions with bookmarks divergent from the active bookmark. If | ||||
there were no divergent bookmarks, then this list will contain | ||||
only one entry. | ||||
""" | ||||
if not repo._activebookmark: | ||||
raise ValueError( | ||||
'headsforactive() only makes sense with an active bookmark') | ||||
name = repo._activebookmark.split('@', 1)[0] | ||||
heads = [] | ||||
for mark, n in repo._bookmarks.iteritems(): | ||||
if mark.split('@', 1)[0] == name: | ||||
heads.append(n) | ||||
return heads | ||||
|
r19523 | def calculateupdate(ui, repo, checkout): | ||
'''Return a tuple (targetrev, movemarkfrom) indicating the rev to | ||||
check out and where to move the active bookmark from, if needed.''' | ||||
movemarkfrom = None | ||||
if checkout is None: | ||||
|
r25100 | activemark = repo._activebookmark | ||
|
r24986 | if isactivewdirparent(repo): | ||
|
r19523 | movemarkfrom = repo['.'].node() | ||
|
r25100 | elif activemark: | ||
ui.status(_("updating to active bookmark %s\n") % activemark) | ||||
checkout = activemark | ||||
|
r19523 | return (checkout, movemarkfrom) | ||
|
r13352 | def update(repo, parents, node): | ||
|
r19110 | deletefrom = parents | ||
|
r13352 | marks = repo._bookmarks | ||
update = False | ||||
|
r27698 | active = marks.active | ||
|
r25100 | if not active: | ||
|
r16706 | return False | ||
|
r25100 | if marks[active] in parents: | ||
|
r18513 | new = repo[node] | ||
|
r19110 | divs = [repo[b] for b in marks | ||
|
r25100 | if b.split('@', 1)[0] == active.split('@', 1)[0]] | ||
|
r19110 | anc = repo.changelog.ancestors([new.rev()]) | ||
deletefrom = [b.node() for b in divs if b.rev() in anc or b == new] | ||||
|
r25100 | if validdest(repo, repo[marks[active]], new): | ||
marks[active] = new.node() | ||||
|
r18513 | update = True | ||
|
r25100 | if deletedivergent(repo, deletefrom, active): | ||
|
r18513 | update = True | ||
|
r13352 | if update: | ||
|
r26999 | lock = tr = None | ||
try: | ||||
lock = repo.lock() | ||||
tr = repo.transaction('bookmark') | ||||
marks.recordchange(tr) | ||||
tr.close() | ||||
finally: | ||||
lockmod.release(tr, lock) | ||||
|
r15621 | return update | ||
|
r13353 | |||
|
r30481 | def listbinbookmarks(repo): | ||
# We may try to list bookmarks on a repo type that does not | ||||
# support it (e.g., statichttprepository). | ||||
marks = getattr(repo, '_bookmarks', {}) | ||||
hasnode = repo.changelog.hasnode | ||||
for k, v in marks.iteritems(): | ||||
# don't expose local divergent bookmarks | ||||
if hasnode(v) and ('@' not in k or k.endswith('@')): | ||||
yield k, v | ||||
|
r13353 | def listbookmarks(repo): | ||
d = {} | ||||
|
r30482 | for book, node in listbinbookmarks(repo): | ||
d[book] = hex(node) | ||||
|
r13353 | return d | ||
def pushbookmark(repo, key, old, new): | ||||
|
r22667 | w = l = tr = None | ||
|
r13353 | try: | ||
|
r22667 | w = repo.wlock() | ||
l = repo.lock() | ||||
tr = repo.transaction('bookmarks') | ||||
|
r13353 | marks = repo._bookmarks | ||
|
r22364 | existing = hex(marks.get(key, '')) | ||
if existing != old and existing != new: | ||||
|
r13353 | return False | ||
if new == '': | ||||
del marks[key] | ||||
else: | ||||
if new not in repo: | ||||
return False | ||||
marks[key] = repo[new].node() | ||||
|
r22667 | marks.recordchange(tr) | ||
tr.close() | ||||
|
r13353 | return True | ||
finally: | ||||
|
r22667 | lockmod.release(tr, l, w) | ||
|
r13354 | |||
|
r30583 | def comparebookmarks(repo, srcmarks, dstmarks, targets=None): | ||
|
r20024 | '''Compare bookmarks between srcmarks and dstmarks | ||
This returns tuple "(addsrc, adddst, advsrc, advdst, diverge, | ||||
differ, invalid)", each are list of bookmarks below: | ||||
:addsrc: added on src side (removed on dst side, perhaps) | ||||
:adddst: added on dst side (removed on src side, perhaps) | ||||
:advsrc: advanced on src side | ||||
:advdst: advanced on dst side | ||||
:diverge: diverge | ||||
:differ: changed, but changeset referred on src is unknown on dst | ||||
:invalid: unknown on both side | ||||
|
r23081 | :same: same on both side | ||
|
r20024 | |||
Each elements of lists in result tuple is tuple "(bookmark name, | ||||
changeset ID on source side, changeset ID on destination | ||||
side)". Each changeset IDs are 40 hexadecimal digit string or | ||||
None. | ||||
Changeset IDs of tuples in "addsrc", "adddst", "differ" or | ||||
"invalid" list may be unknown for repo. | ||||
If "targets" is specified, only bookmarks listed in it are | ||||
examined. | ||||
''' | ||||
if targets: | ||||
bset = set(targets) | ||||
else: | ||||
srcmarkset = set(srcmarks) | ||||
dstmarkset = set(dstmarks) | ||||
|
r23081 | bset = srcmarkset | dstmarkset | ||
|
r20024 | |||
|
r23081 | results = ([], [], [], [], [], [], [], []) | ||
|
r20024 | addsrc = results[0].append | ||
adddst = results[1].append | ||||
advsrc = results[2].append | ||||
advdst = results[3].append | ||||
diverge = results[4].append | ||||
differ = results[5].append | ||||
invalid = results[6].append | ||||
|
r23081 | same = results[7].append | ||
|
r20024 | |||
for b in sorted(bset): | ||||
if b not in srcmarks: | ||||
if b in dstmarks: | ||||
|
r30583 | adddst((b, None, dstmarks[b])) | ||
|
r20024 | else: | ||
invalid((b, None, None)) | ||||
elif b not in dstmarks: | ||||
|
r30583 | addsrc((b, srcmarks[b], None)) | ||
|
r20024 | else: | ||
|
r30583 | scid = srcmarks[b] | ||
dcid = dstmarks[b] | ||||
|
r23081 | if scid == dcid: | ||
same((b, scid, dcid)) | ||||
elif scid in repo and dcid in repo: | ||||
|
r20024 | sctx = repo[scid] | ||
dctx = repo[dcid] | ||||
if sctx.rev() < dctx.rev(): | ||||
if validdest(repo, sctx, dctx): | ||||
advdst((b, scid, dcid)) | ||||
else: | ||||
diverge((b, scid, dcid)) | ||||
else: | ||||
if validdest(repo, dctx, sctx): | ||||
advsrc((b, scid, dcid)) | ||||
else: | ||||
diverge((b, scid, dcid)) | ||||
else: | ||||
# it is too expensive to examine in detail, in this case | ||||
differ((b, scid, dcid)) | ||||
return results | ||||
|
r24355 | def _diverge(ui, b, path, localmarks, remotenode): | ||
|
r24353 | '''Return appropriate diverged bookmark for specified ``path`` | ||
This returns None, if it is failed to assign any divergent | ||||
bookmark name. | ||||
|
r24355 | |||
This reuses already existing one with "@number" suffix, if it | ||||
refers ``remotenode``. | ||||
|
r24353 | ''' | ||
|
r20025 | if b == '@': | ||
b = '' | ||||
# try to use an @pathalias suffix | ||||
# if an @pathalias already exists, we overwrite (update) it | ||||
|
r22629 | if path.startswith("file:"): | ||
path = util.url(path).path | ||||
|
r20025 | for p, u in ui.configitems("paths"): | ||
|
r22629 | if u.startswith("file:"): | ||
u = util.url(u).path | ||||
|
r20025 | if path == u: | ||
|
r24354 | return '%s@%s' % (b, p) | ||
# assign a unique "@number" suffix newly | ||||
for x in range(1, 100): | ||||
n = '%s@%d' % (b, x) | ||||
|
r24355 | if n not in localmarks or localmarks[n] == remotenode: | ||
|
r24354 | return n | ||
return None | ||||
|
r20025 | |||
|
r30583 | def unhexlifybookmarks(marks): | ||
binremotemarks = {} | ||||
for name, node in marks.items(): | ||||
binremotemarks[name] = bin(node) | ||||
return binremotemarks | ||||
|
r22666 | def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()): | ||
|
r13646 | ui.debug("checking for updated bookmarks\n") | ||
|
r17922 | localmarks = repo._bookmarks | ||
|
r23081 | (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same | ||
|
r30583 | ) = comparebookmarks(repo, remotemarks, localmarks) | ||
|
r15614 | |||
|
r22644 | status = ui.status | ||
warn = ui.warn | ||||
if ui.configbool('ui', 'quietbookmarkmove', False): | ||||
status = warn = ui.debug | ||||
|
r22659 | explicit = set(explicit) | ||
|
r20025 | changed = [] | ||
for b, scid, dcid in addsrc: | ||||
if scid in repo: # add remote bookmarks for changes we already have | ||||
|
r30583 | changed.append((b, scid, status, | ||
|
r20025 | _("adding remote bookmark %s\n") % (b))) | ||
|
r25564 | elif b in explicit: | ||
explicit.remove(b) | ||||
ui.warn(_("remote bookmark %s points to locally missing %s\n") | ||||
|
r30583 | % (b, hex(scid)[:12])) | ||
|
r25564 | |||
|
r20025 | for b, scid, dcid in advsrc: | ||
|
r30583 | changed.append((b, scid, status, | ||
|
r20025 | _("updating bookmark %s\n") % (b))) | ||
|
r22659 | # remove normal movement from explicit set | ||
explicit.difference_update(d[0] for d in changed) | ||||
|
r20025 | for b, scid, dcid in diverge: | ||
|
r22659 | if b in explicit: | ||
explicit.discard(b) | ||||
|
r30583 | changed.append((b, scid, status, | ||
|
r23199 | _("importing bookmark %s\n") % (b))) | ||
|
r22659 | else: | ||
|
r30583 | db = _diverge(ui, b, path, localmarks, scid) | ||
|
r24353 | if db: | ||
|
r30583 | changed.append((db, scid, warn, | ||
|
r24353 | _("divergent bookmark %s stored as %s\n") % | ||
(b, db))) | ||||
else: | ||||
warn(_("warning: failed to assign numbered name " | ||||
"to divergent bookmark %s\n") % (b)) | ||||
|
r22659 | for b, scid, dcid in adddst + advdst: | ||
if b in explicit: | ||||
explicit.discard(b) | ||||
|
r30583 | changed.append((b, scid, status, | ||
|
r23199 | _("importing bookmark %s\n") % (b))) | ||
|
r25564 | for b, scid, dcid in differ: | ||
if b in explicit: | ||||
explicit.remove(b) | ||||
ui.warn(_("remote bookmark %s points to locally missing %s\n") | ||||
|
r30583 | % (b, hex(scid)[:12])) | ||
|
r22659 | |||
|
r13646 | if changed: | ||
|
r22666 | tr = trfunc() | ||
|
r20025 | for b, node, writer, msg in sorted(changed): | ||
localmarks[b] = node | ||||
writer(msg) | ||||
|
r22666 | localmarks.recordchange(tr) | ||
|
r13646 | |||
|
r24397 | def incoming(ui, repo, other): | ||
'''Show bookmarks incoming from other to repo | ||||
''' | ||||
ui.status(_("searching for changed bookmarks\n")) | ||||
|
r30583 | remotemarks = unhexlifybookmarks(other.listkeys('bookmarks')) | ||
r = comparebookmarks(repo, remotemarks, repo._bookmarks) | ||||
|
r24397 | addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r | ||
incomings = [] | ||||
if ui.debugflag: | ||||
getid = lambda id: id | ||||
else: | ||||
getid = lambda id: id[:12] | ||||
|
r24660 | if ui.verbose: | ||
def add(b, id, st): | ||||
incomings.append(" %-25s %s %s\n" % (b, getid(id), st)) | ||||
else: | ||||
def add(b, id, st): | ||||
incomings.append(" %-25s %s\n" % (b, getid(id))) | ||||
|
r24397 | for b, scid, dcid in addsrc: | ||
|
r24832 | # i18n: "added" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('added')) | ||
|
r24657 | for b, scid, dcid in advsrc: | ||
|
r24832 | # i18n: "advanced" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('advanced')) | ||
|
r24657 | for b, scid, dcid in diverge: | ||
|
r24832 | # i18n: "diverged" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('diverged')) | ||
|
r24657 | for b, scid, dcid in differ: | ||
|
r24832 | # i18n: "changed" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('changed')) | ||
|
r24397 | |||
if not incomings: | ||||
ui.status(_("no changed bookmarks found\n")) | ||||
return 1 | ||||
for s in sorted(incomings): | ||||
ui.write(s) | ||||
return 0 | ||||
|
r24398 | def outgoing(ui, repo, other): | ||
'''Show bookmarks outgoing from repo to other | ||||
''' | ||||
ui.status(_("searching for changed bookmarks\n")) | ||||
|
r30583 | remotemarks = unhexlifybookmarks(other.listkeys('bookmarks')) | ||
r = comparebookmarks(repo, repo._bookmarks, remotemarks) | ||||
|
r24398 | addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r | ||
outgoings = [] | ||||
if ui.debugflag: | ||||
getid = lambda id: id | ||||
else: | ||||
getid = lambda id: id[:12] | ||||
|
r24661 | if ui.verbose: | ||
def add(b, id, st): | ||||
outgoings.append(" %-25s %s %s\n" % (b, getid(id), st)) | ||||
else: | ||||
def add(b, id, st): | ||||
outgoings.append(" %-25s %s\n" % (b, getid(id))) | ||||
|
r24398 | for b, scid, dcid in addsrc: | ||
|
r24832 | # i18n: "added refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('added')) | ||
|
r24658 | for b, scid, dcid in adddst: | ||
|
r24832 | # i18n: "deleted" refers to a bookmark | ||
|
r24661 | add(b, ' ' * 40, _('deleted')) | ||
|
r24658 | for b, scid, dcid in advsrc: | ||
|
r24832 | # i18n: "advanced" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('advanced')) | ||
|
r24658 | for b, scid, dcid in diverge: | ||
|
r24832 | # i18n: "diverged" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('diverged')) | ||
|
r24658 | for b, scid, dcid in differ: | ||
|
r24832 | # i18n: "changed" refers to a bookmark | ||
|
r30583 | add(b, hex(scid), _('changed')) | ||
|
r24398 | |||
if not outgoings: | ||||
ui.status(_("no changed bookmarks found\n")) | ||||
return 1 | ||||
for s in sorted(outgoings): | ||||
ui.write(s) | ||||
return 0 | ||||
|
r24400 | def summary(repo, other): | ||
'''Compare bookmarks between repo and other for "hg summary" output | ||||
This returns "(# of incoming, # of outgoing)" tuple. | ||||
''' | ||||
|
r30583 | remotemarks = unhexlifybookmarks(other.listkeys('bookmarks')) | ||
r = comparebookmarks(repo, remotemarks, repo._bookmarks) | ||||
|
r24400 | addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r | ||
return (len(addsrc), len(adddst)) | ||||
|
r17550 | def validdest(repo, old, new): | ||
"""Is the new bookmark destination a valid update from the old one""" | ||||
|
r18008 | repo = repo.unfiltered() | ||
|
r17551 | if old == new: | ||
# Old == new -> nothing to update. | ||||
|
r17625 | return False | ||
|
r17551 | elif not old: | ||
# old is nullrev, anything is valid. | ||||
# (new != nullrev has been excluded by the previous check) | ||||
|
r17625 | return True | ||
|
r17551 | elif repo.obsstore: | ||
|
r18984 | return new.node() in obsolete.foreground(repo, [old.node()]) | ||
|
r17551 | else: | ||
|
r24180 | # still an independent clause as it is lazier (and therefore faster) | ||
|
r17627 | return old.descendant(new) | ||