view hgsubversion/util.py @ 746:174f03c288d4

svnmeta: abort when no UUID given and none is stored on disk. Previously, not passing a UUID when instantiating an SVNMeta instance would cause it to succeed even if no UUID was previously known. First when the UUID was actually read would an exception be raised. This slight refactoring of _set_uuid() makes it so an exception is raised immediately. While at it, the exception message is changed to be slightly more accurate and helpful.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Thu, 11 Nov 2010 21:32:22 +0100
parents aa24148a7454
children bb64ad4ce982
line wrap: on
line source

import re
import os
import urllib

from mercurial import cmdutil
from mercurial import error
from mercurial import hg
from mercurial import node
from mercurial import util as hgutil

try:
    from mercurial import revset
except ImportError:
    pass

b_re = re.compile(r'^\+\+\+ b\/([^\n]*)', re.MULTILINE)
a_re = re.compile(r'^--- a\/([^\n]*)', re.MULTILINE)
devnull_re = re.compile(r'^([-+]{3}) /dev/null', re.MULTILINE)
header_re = re.compile(r'^diff --git .* b\/(.*)', re.MULTILINE)
newfile_devnull_re = re.compile(r'^--- /dev/null\n\+\+\+ b/([^\n]*)',
                                re.MULTILINE)


def formatrev(rev):
    if rev == -1:
        return '\t(working copy)'
    return '\t(revision %d)' % rev


def filterdiff(diff, oldrev, newrev):
    diff = newfile_devnull_re.sub(r'--- \1\t(revision 0)' '\n'
                                  r'+++ \1\t(working copy)',
                                  diff)
    oldrev = formatrev(oldrev)
    newrev = formatrev(newrev)
    diff = a_re.sub(r'--- \1'+ oldrev, diff)
    diff = b_re.sub(r'+++ \1' + newrev, diff)
    diff = devnull_re.sub(r'\1 /dev/null\t(working copy)', diff)
    diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff)
    return diff


def parentrev(ui, repo, meta, hashes):
    """Find the svn parent revision of the repo's dirstate.
    """
    workingctx = repo.parents()[0]
    outrev = outgoing_revisions(repo, hashes, workingctx.node())
    if outrev:
        workingctx = repo[outrev[-1]].parents()[0]
    return workingctx


def islocalrepo(url):
    if not url.startswith('file:///'):
        return False
    if '#' in url.split('/')[-1]: # strip off #anchor
        url = url[:url.rfind('#')]
    path = url[len('file://'):]
    path = urllib.url2pathname(path).replace(os.sep, '/')
    while '/' in path:
        if reduce(lambda x,y: x and y,
                  map(lambda p: os.path.exists(os.path.join(path, p)),
                      ('hooks', 'format', 'db', ))):
            return True
        path = path.rsplit('/', 1)[0]
    return False


def version(ui):
    """Return version information if available."""
    try:
        import __version__
        return __version__.version
    except ImportError:
        try:
            dn = os.path.dirname
            repo = hg.repository(ui, dn(dn(__file__)))
            ver = repo.dirstate.parents()[0]
            return node.hex(ver)[:12]
        except:
            return 'unknown'


def normalize_url(url):
    if url.startswith('svn+http://') or url.startswith('svn+https://'):
        url = url[4:]
    url, revs, checkout = parseurl(url)
    url = url.rstrip('/')
    if checkout:
        url = '%s#%s' % (url, checkout)
    return url

# TODO remove when we drop 1.3 support
def progress(ui, *args, **kwargs):
    if getattr(ui, 'progress', False):
        return ui.progress(*args, **kwargs)

# TODO remove when we drop 1.5 support
remoteui = getattr(cmdutil, 'remoteui', getattr(hg, 'remoteui', False))
if not remoteui:
    raise ImportError('Failed to import remoteui')

def parseurl(url, heads=[]):
    parsed = hg.parseurl(url, heads)
    if len(parsed) == 3:
        # old hg, remove when we can be 1.5-only
        svn_url, heads, checkout = parsed
    else:
        svn_url, heads = parsed
        if isinstance(heads, tuple) and len(heads) == 2:
            # hg 1.6 or later
            _junk, heads = heads
        if heads:
            checkout = heads[0]
        else:
            checkout = None
    return svn_url, heads, checkout


class PrefixMatch(object):
    def __init__(self, prefix):
        self.p = prefix

    def files(self):
        return []

    def __call__(self, fn):
        return fn.startswith(self.p)

def outgoing_revisions(repo, reverse_map, sourcerev):
    """Given a repo and an hg_editor, determines outgoing revisions for the
    current working copy state.
    """
    outgoing_rev_hashes = []
    if sourcerev in reverse_map:
        return
    sourcerev = repo[sourcerev]
    while (not sourcerev.node() in reverse_map
           and sourcerev.node() != node.nullid):
        outgoing_rev_hashes.append(sourcerev.node())
        sourcerev = sourcerev.parents()
        if len(sourcerev) != 1:
            raise hgutil.Abort("Sorry, can't find svn parent of a merge revision.")
        sourcerev = sourcerev[0]
    if sourcerev.node() != node.nullid:
        return outgoing_rev_hashes

default_commit_msg = '*** empty log message ***'

def describe_commit(ui, h, b):
    ui.note(' committed to "%s" as %s\n' % ((b or 'default'), node.short(h)))


def swap_out_encoding(new_encoding="UTF-8"):
    from mercurial import encoding
    old = encoding.encoding
    encoding.encoding = new_encoding
    return old


def issamefile(parentctx, childctx, f):
    """Return True if f exists and is the same in childctx and parentctx"""
    if f not in parentctx or f not in childctx:
        return False
    if parentctx == childctx:
        return True
    if parentctx.rev() > childctx.rev():
        parentctx, childctx = childctx, parentctx

    def selfandancestors(selfctx):
        yield selfctx
        for ctx in selfctx.ancestors():
            yield ctx

    for pctx in selfandancestors(childctx):
        if pctx.rev() <= parentctx.rev():
            return True
        if f in pctx.files():
            return False
    # parentctx is not an ancestor of childctx, files are unrelated
    return False

def _templatehelper(ctx, kw):
    '''
    Helper function for displaying information about converted changesets.
    '''
    convertinfo = ctx.extra().get('convert_revision', '')

    if not convertinfo or not convertinfo.startswith('svn:'):
        return ''

    if kw == 'svnuuid':
        return convertinfo[4:40]
    elif kw == 'svnpath':
        return convertinfo[40:].rsplit('@', 1)[0]
    elif kw == 'svnrev':
        return convertinfo[40:].rsplit('@', 1)[-1]
    else:
        raise hgutil.Abort('unrecognized hgsubversion keyword %s' % kw)

templatekeywords = {
    'svnrev': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnrev')),
    'svnpath': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnpath')),
    'svnuuid': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnuuid')),
}

def revset_fromsvn(repo, subset, x):
    args = revset.getargs(x, 0, 0, "fromsvn takes no arguments")

    def matches(r):
        convertinfo = repo[r].extra().get('convert_revision', '')
        return convertinfo[:4] == 'svn:'

    return [r for r in subset if matches(r)]

def revset_svnrev(repo, subset, x):
    args = revset.getargs(x, 1, 1, "svnrev takes one argument")

    rev = revset.getstring(args[0],
                           "the argument to svnrev() must be a number")
    try:
        rev = int(rev)
    except ValueError:
        raise error.ParseError("the argument to svnrev() must be a number")

    def matches(r):
        convertinfo = repo[r].extra().get('convert_revision', '')
        if convertinfo[:4] != 'svn:':
            return False
        return int(convertinfo[40:].rsplit('@', 1)[-1]) == rev

    return [r for r in subset if matches(r)]

revsets = {
    'fromsvn': revset_fromsvn,
    'svnrev': revset_svnrev,
}