view treepkg/subversion.py @ 273:4b700b39c32f

Refactoring: Move the TagDetector class into the treepkg.subversion module
author Bernhard Herzog <bh@intevation.de>
date Thu, 07 May 2009 14:25:10 +0000
parents 97fd2584df5f
children f58f9adb7dc3
line wrap: on
line source
# Copyright (C) 2007, 2008, 2009 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with the software for details.

"""Collection of subversion utility code"""

import os
import shutil
import re

import run
from cmdexpand import cmdexpand
from util import extract_value_for_key


class SubversionError(Exception):
    pass

def list_url(url):
    """Runs svn list with the given url and returns files listed as a list"""
    output = run.capture_output(cmdexpand("svn list $url", **locals()))
    return output.splitlines()

def checkout(url, localdir, revision=None, recurse=True):
    """Runs svn to checkout the repository at url into the localdir"""
    args = []
    if revision:
        args.extend(["--revision", revision])
    if not recurse:
        args.append("-N")
    run.call(cmdexpand("svn checkout -q @args $url $localdir", **locals()))

def update(localdir, revision=None, recurse=True):
    """Runs svn update on the localdir.
    The parameter revision, if given, is passed to svn as the value of
    the --revision option.
    """
    args = []
    if revision:
        args.extend(["--revision", revision])
    if not recurse:
        args.append("-N")
    run.call(cmdexpand("svn update -q @args $localdir", **locals()))

def export(src, dest, revision=None, recurse=True):
    """Runs svn export src dest"""
    args = []
    if revision:
        args.extend(["--revision", revision])
    if not recurse:
        args.append("-N")
    run.call(cmdexpand("svn export -q @args $src $dest", **locals()))

def last_changed_revision(svn_working_copy):
    """return the last changed revision of an SVN working copy as an int"""
    # Make sure we run svn under the C locale to avoid localized
    # messages
    env = os.environ.copy()
    env["LANG"] = "C"

    output = run.capture_output(cmdexpand("svn info $svn_working_copy",
                                          **locals()),
                                env=env)
    str_rev = extract_value_for_key(output.splitlines(), "Last Changed Rev:")
    if str_rev is None:
        raise SubversionError("Cannot determine last changed revision for %r"
                              % svn_working_copy)
    return int(str_rev)


class SvnRepository(object):

    """Describes a subversion repository"""

    def __init__(self, url, external_subdirs=()):
        """Initialize the subversion repository description
        Parameters:
          url -- The url of the repository
          external_subdirs -- A list of subdirectories which are managed
                              by svn externals definitions
        """
        self.url = url
        self.external_subdirs = external_subdirs

    def checkout(self, localdir, revision=None):
        """Checks out the repository into localdir.  The revision
        parameter should be an and indicates the revision to check out.
        """
        checkout(self.url, localdir, revision=revision)

    def export(self, localdir, destdir):
        """Exports the working copy in localdir to destdir"""
        export(localdir, destdir)
        for subdir in self.external_subdirs:
            absdir = os.path.join(destdir, subdir)
            if not os.path.isdir(absdir):
                export(os.path.join(localdir, subdir), absdir)

    def last_changed_revision(self, localdir):
        """Returns the last changed revision of the working copy in localdir"""
        return max([last_changed_revision(os.path.join(localdir, d))
                    for d in [localdir] + list(self.external_subdirs)])


class SvnWorkingCopy(object):

    """Represents a checkout of a subversion repository"""

    def __init__(self, repository, localdir, logger=None):
        """
        Initialize the working copy.
        Parameters:
          repository -- The SvnRepository instance describing the
                        repository
          localdir -- The directory for the working copy
          logger -- logging object to use for some info/debug messages
        """
        self.repository = repository
        self.localdir = localdir
        self.logger = logger

    def log_info(self, *args):
        if self.logger is not None:
            self.logger.info(*args)

    def update_or_checkout(self, revision=None):
        """Updates the working copy or creates by checking out the repository"""
        if os.path.exists(self.localdir):
            self.log_info("Updating the working copy in %r", self.localdir)
            update(self.localdir, revision=revision)
        else:
            self.log_info("The working copy in %r doesn't exist yet."
                          "  Checking out from %r",
                          self.localdir, self.repository.url)
            self.repository.checkout(self.localdir, revision=revision)

    def export(self, destdir):
        """Exports the working copy to destdir"""
        self.repository.export(self.localdir, destdir)

    def last_changed_revision(self):
        """Returns the last changed rev of the working copy"""
        return self.repository.last_changed_revision(self.localdir)


class ManualWorkingCopy(object):

    """A manually managed working copy"""

    def __init__(self, directory):
        self.directory = directory

    def update_or_checkout(self, revision=None, recurse=True):
        """This method does nothing"""
        pass

    def export(self, destdir):
        """Copies the entire working copy to destdir"""
        shutil.copytree(self.directory, destdir)

    def last_changed_revision(self):
        """Always returns 0"""
        return 0


class TagDetector(object):

    """Class to automatically find SVN tags and help package them

    The tags are found using three parameters:
      url -- The base url of the SVN tags directory to use
      pattern -- A regular expression matching the subdirectories to
                 consider in the tag directory specified by the url
      subdir -- A subdirectory of the directory matched by pattern to
                export and use to determine revision number

    The subdir parameter is there to cope with the kdepim enterprise
    tags.  The URL for a tag is of the form
    .../tags/kdepim/enterprise4.0.<date>.<rev> .  Each such tag has
    subdirectories for kdepim, kdelibs, etc.  The url and pattern are
    used to match the URL for the tag, and the subdir is used to select
    which part of the tag is meant.

    The subdir also determines which SVN directory's revision number is
    used.  Normally, just appending the subdir to the tag URL would be
    enough for this, but the situation is more complex for
    kdebase_workspace and kdebase_runtime, whose code comes from
    different subdirectories of the kdebase-4.X-branch subdirectory (for
    enterprise4 tags).  Here the revision number must be taken from
    kdebase-4.X-branch, but the URL to use when exporting the sources,
    must refer to e.g. kdebase-4.1-branch/kdebase_workspace.  To achieve
    that, subdir may contain slashes to indicate subdirectories of
    subdirectories, but only the first part of subdir (up to the first
    slash) is used when determining the revision number.
    """

    def __init__(self, url, pattern, subdir):
        self.url = url
        self.pattern = re.compile(pattern)
        self.subdir = subdir

    def list_tags(self):
        matches = []
        if self.url:
            for tag in list_url(self.url):
                if self.pattern.match(tag.rstrip("/")):
                    matches.append(tag)
        return sorted(matches)

    def newest_tag_revision(self):
        """Determines the newest tag revision and returns (tagurl, revno)
        If no tag can be found, the method returns the tuple (None, None).
        """
        candidates = self.list_tags()
        urlrev = (None, None)
        if candidates:
            newest = candidates[-1]
            urlrev = self.determine_revision(self.url + "/" + newest,
                                             self.subdir)
        return urlrev

    def determine_revision(self, baseurl, subdir):
        urlrev = (None, None)
        revision_url = baseurl + "/" + subdir.split("/")[0]
        try:
            revision = last_changed_revision(revision_url)
            urlrev = (baseurl + "/" + subdir, revision)
        except SubversionError:
            pass
        return urlrev
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)