view mercurial/bookmarks.py @ 13750:7eb82f88e157

# User Dan Villiom Podlaski Christiansen <danchr@gmail.com> # Date 1289564504 -3600 # Node ID b75264c15cc888cf38c3c7b8f619801e3c2589c7 # Parent 89b2e5d940f669e590096c6be70eee61c9172fff revsets: overload the branch() revset to also take a branch name. This should only change semantics in the specific case of a tag/branch conflict where the tag wasn't done on the branch with the same name. Previously, branch(whatever) would resolve to the branch of the tag in that case, whereas now it will resolve to the branch of the name. The previous behaviour, while documented, seemed very counter-intuitive to me. An alternate approach would be to introduce a new revset such as branchname() or namedbranch(). While this would retain backwards compatibility, the distinction between it and branch() would not be readily apparent to users. The most intuitive behaviour would be to have branch(x) require 'x' to be a branch name, and something like branchof(x) or samebranch(x) do what branch(x) currently does. Unfortunately, our backwards compatibility guarantees prevent us from doing that. Please note that while 'hg tag' guards against shadowing a branch, 'hg branch' does not. Besides, even if it did, that wouldn't solve the issue of conversions with such tags and branches...
author Matt Mackall <mpm@selenic.com>
date Wed, 23 Mar 2011 19:28:16 -0500
parents d16c99f16f00
children 97ed99d1f419
line wrap: on
line source

# 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.

from mercurial.i18n import _
from mercurial.node import nullid, nullrev, bin, hex, short
from mercurial import encoding, util
import os

def valid(mark):
    for c in (':', '\0', '\n', '\r'):
        if c in mark:
            return False
    return True

def read(repo):
    '''Parse .hg/bookmarks file and return a dictionary

    Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
    in the .hg/bookmarks file.
    Read the file and return a (name=>nodeid) dictionary
    '''
    try:
        bookmarks = {}
        for line in repo.opener('bookmarks'):
            sha, refspec = line.strip().split(' ', 1)
            refspec = encoding.tolocal(refspec)
            bookmarks[refspec] = repo.changelog.lookup(sha)
    except:
        pass
    return bookmarks

def readcurrent(repo):
    '''Get the current bookmark

    If we use gittishsh branches we have a current bookmark that
    we are on. This function returns the name of the bookmark. It
    is stored in .hg/bookmarks.current
    '''
    mark = None
    if os.path.exists(repo.join('bookmarks.current')):
        file = repo.opener('bookmarks.current')
        # No readline() in posixfile_nt, reading everything is cheap
        mark = encoding.tolocal((file.readlines() or [''])[0])
        if mark == '' or mark not in repo._bookmarks:
            mark = None
        file.close()
    return mark

def write(repo):
    '''Write bookmarks

    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.
    '''
    refs = repo._bookmarks

    try:
        bms = repo.opener('bookmarks').read()
    except IOError:
        bms = ''
    repo.opener('undo.bookmarks', 'w').write(bms)

    if repo._bookmarkcurrent not in refs:
        setcurrent(repo, None)
    for mark in refs.keys():
        if not valid(mark):
            raise util.Abort(_("bookmark '%s' contains illegal "
                "character" % mark))

    wlock = repo.wlock()
    try:

        file = repo.opener('bookmarks', 'w', atomictemp=True)
        for refspec, node in refs.iteritems():
            file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
        file.rename()

        # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
        try:
            os.utime(repo.sjoin('00changelog.i'), None)
        except OSError:
            pass

    finally:
        wlock.release()

def setcurrent(repo, mark):
    '''Set the name of the bookmark that we are currently on

    Set the name of the bookmark that we are on (hg update <bookmark>).
    The name is recorded in .hg/bookmarks.current
    '''
    current = repo._bookmarkcurrent
    if current == mark:
        return

    if mark not in repo._bookmarks:
        mark = ''
    if not valid(mark):
        raise util.Abort(_("bookmark '%s' contains illegal "
            "character" % mark))

    wlock = repo.wlock()
    try:
        file = repo.opener('bookmarks.current', 'w', atomictemp=True)
        file.write(mark)
        file.rename()
    finally:
        wlock.release()
    repo._bookmarkcurrent = mark

def updatecurrentbookmark(repo, oldnode, curbranch):
    try:
        update(repo, oldnode, repo.branchtags()[curbranch])
    except KeyError:
        if curbranch == "default": # no default branch!
            update(repo, oldnode, repo.lookup("tip"))
        else:
            raise util.Abort(_("branch %s not found") % curbranch)

def update(repo, parents, node):
    marks = repo._bookmarks
    update = False
    mark = repo._bookmarkcurrent
    if mark and marks[mark] in parents:
        old = repo[marks[mark]]
        new = repo[node]
        if new in old.descendants():
            marks[mark] = new.node()
            update = True
    if update:
        write(repo)

def listbookmarks(repo):
    # We may try to list bookmarks on a repo type that does not
    # support it (e.g., statichttprepository).
    if not hasattr(repo, '_bookmarks'):
        return {}

    d = {}
    for k, v in repo._bookmarks.iteritems():
        d[k] = hex(v)
    return d

def pushbookmark(repo, key, old, new):
    w = repo.wlock()
    try:
        marks = repo._bookmarks
        if hex(marks.get(key, '')) != old:
            return False
        if new == '':
            del marks[key]
        else:
            if new not in repo:
                return False
            marks[key] = repo[new].node()
        write(repo)
        return True
    finally:
        w.release()

def updatefromremote(ui, repo, remote):
    ui.debug("checking for updated bookmarks\n")
    rb = remote.listkeys('bookmarks')
    changed = False
    for k in rb.keys():
        if k in repo._bookmarks:
            nr, nl = rb[k], repo._bookmarks[k]
            if nr in repo:
                cr = repo[nr]
                cl = repo[nl]
                if cl.rev() >= cr.rev():
                    continue
                if cr in cl.descendants():
                    repo._bookmarks[k] = cr.node()
                    changed = True
                    ui.status(_("updating bookmark %s\n") % k)
                else:
                    ui.warn(_("not updating divergent"
                                   " bookmark %s\n") % k)
    if changed:
        write(repo)

def diff(ui, repo, remote):
    ui.status(_("searching for changed bookmarks\n"))

    lmarks = repo.listkeys('bookmarks')
    rmarks = remote.listkeys('bookmarks')

    diff = sorted(set(rmarks) - set(lmarks))
    for k in diff:
        ui.write("   %-25s %s\n" % (k, rmarks[k][:12]))

    if len(diff) <= 0:
        ui.status(_("no changed bookmarks found\n"))
        return 1
    return 0