Logo Search packages:      
Sourcecode: tailor version File versions  Download package

tla.py

# -*- mode: python; coding: utf-8 -*-
# :Progetto: vcpx -- tla (Arch 1.x) backend
# :Creato:   sab 13 ago 2005 12:16:16 CEST
# :Autore:   Robin Farine <robin.farine@terminus.org>
# :Licenza:  GNU General Public License
#

# Current limitations and pitfalls.
#
# - Target backend not implemented.
#
# - In-version continuations not supported (raises an exception); this
#   would probably require to compute a changeset with 'tla delta'
#   instead of using update.
#
# - Pika escaped file names. This implementations requires a version
#   of tla that supports pika escapes. For changesets created with a
#   version of tla that did not support pika escapes, if one of these
#   changeset contains a file name with a valid embedded pika escape
#   sequence, things will break.

"""
This module implements the backends for tla (Arch 1.x).

This backend interprets tailor's repository, module and revision arguments
as follows:

repository
  a registered archive name

module
  <category>--<branch>--<version>

revision
  <revision>
"""

__docformat__ = 'reStructuredText'

import os
import re
from datetime import datetime
from time import strptime
from tempfile import mkdtemp
from cStringIO import StringIO
from email.Parser import Parser
from email.Errors import MessageParseError

from changes import Changeset
from shwrap import ExternalCommand, PIPE
from source import UpdatableSourceWorkingDir, ChangesetApplicationFailure, \
     GetUpstreamChangesetsFailure, InvocationError
from target import TargetInitializationFailure


00056 class TlaWorkingDir(UpdatableSourceWorkingDir):
    """
    A working directory under ``tla``.
    """

    ## UpdatableSourceWorkingDir

00063     def _getUpstreamChangesets(self, sincerev):
        """
        Build the list of upstream changesets missing in the working directory.
        """

        changesets = []
        self.fqversion = '/'.join([self.repository.repository,
                                   self.repository.module])
        c = ExternalCommand(cwd=self.basedir,
                            command=self.repository.command("missing", "-f"))
        out, err = c.execute(stdout=PIPE, stderr=PIPE)
        if c.exit_status:
            raise GetUpstreamChangesetsFailure(
                "%s returned status %d saying\n%s" %
                (str(c), c.exit_status, err.read()))
        changesets = self.__parse_revision_logs(out.read().split())
        return changesets

00081     def _applyChangeset(self, changeset):
        """
        Do the actual work of applying the changeset to the working copy and
        record the changes in ``changeset``. Return a list of files involved
        in conflicts.
        """

        if self.shared_basedirs:
            tempdir = self.__hide_foreign_entries()
            try:
                conflicts = self.__apply_changeset(changeset)
            finally:
                if tempdir:
                    self.__restore_foreign_entries(tempdir)
        else:
            conflicts = self.__apply_changeset(changeset)
        return conflicts

00099     def _checkoutUpstreamRevision(self, revision):
        """
        Create the initial working directory during bootstrap.
        """

        fqrev = self.__initial_revision(revision)
        if self.shared_basedirs:
            tempdir = mkdtemp("", ",,tailor-", self.basedir)
            try:
                self.__checkout_initial_revision(fqrev, tempdir, "t")
            finally:
                newtree = os.path.join(tempdir, "t")
                if os.path.exists(newtree):
                    for e in os.listdir(newtree):
                        os.rename(os.path.join(newtree, e),
                                  os.path.join(self.basedir, e))
                    os.rmdir(newtree)
                os.rmdir(tempdir)
        else:
            root, destdir = os.path.split(self.basedir)
            self.__checkout_initial_revision(fqrev, root, destdir)
        return self.__parse_revision_logs([fqrev], False)[0]

    ## TlaWorkingDir private helper functions

00124     def __checkout_initial_revision(self, fqrev, root, destdir):
        if not os.path.exists(root):
            os.makedirs(root)
        cmd = self.repository.command("get", "--no-pristine", fqrev, destdir)
        c = ExternalCommand(cwd=root, command=cmd)
        out, err = c.execute(stdout=PIPE, stderr=PIPE)
        if c.exit_status:
            raise TargetInitializationFailure(
                "%s returned status %d saying\n%s" %
                (str(c), c.exit_status, err.read()))

    def __apply_changeset(self, changeset):
        c = ExternalCommand(cwd=self.basedir,
                            command=self.repository.command("update"))
        out, err = c.execute(changeset.revision, stdout=PIPE, stderr=PIPE)
        if not c.exit_status in [0, 1]:
            raise ChangesetApplicationFailure(
                "%s returned status %d saying\n%s" %
                (str(c), c.exit_status, err.read()))
        return self.__parse_apply_changeset_output(changeset, out)

    def __normalize_path(self, path):
        if len(path) > 2:
            if path[0:2] == "./":
                path = path[2:]
        if path.find("\(") != -1:
            cmd = self.repository.command("escape", "--unescaped", path)
            c = ExternalCommand(command=cmd)
            out, err = c.execute(stdout=PIPE, stderr=PIPE)
            if c.exit_status:
                raise GetUpstreamChangesetsFailure(
                    "%s returned status %d saying\n%s" %
                    (str(c), c.exit_status, err.read()))
            path = out.read()
        return path

    def __initial_revision(self, revision):
        fqversion = '/'.join([self.repository.repository,
                              self.repository.module])
        if revision in ['HEAD', 'INITIAL']:
            cmd = self.repository.command("revisions")
            if revision == 'HEAD':
                cmd.append("-r")
            cmd.append(fqversion)
            c = ExternalCommand(command=cmd)
            out, err = c.execute(stdout=PIPE, stderr=PIPE)
            if c.exit_status:
                raise TargetInitializationFailure(
                    "%s returned status %d saying\n%s" %
                    (str(c), c.exit_status, err.read()))
            revision = out.readline().strip()
        return '--'.join([fqversion, revision])

    def __parse_revision_logs(self, fqrevlist, update=True):
        changesets = []
        logparser = Parser()
        c = ExternalCommand(cwd=self.basedir,
                            command=self.repository.command("cat-archive-log"))
        for fqrev in fqrevlist:
            out, err = c.execute(fqrev, stdout=PIPE, stderr=PIPE)
            if c.exit_status:
                raise GetUpstreamChangesetsFailure(
                    "%s returned status %d saying\n%s" %
                    (str(c), c.exit_status, err.read()))
            err = None
            try:
                msg = logparser.parse(out)
            except Exception, err:
                pass
            if not err and msg.is_multipart():
                err = "unable to parse log description"
            if not err and update and msg.has_key('Continuation-of'):
                err = "in-version continuations not supported"
            if err:
                raise GetUpstreamChangesetsFailure(str(err))
            y,m,d,hh,mm,ss,d1,d2,d3 = strptime(msg['Standard-date'],
                                               "%Y-%m-%d %H:%M:%S %Z")
            date = datetime(y,m,d,hh,mm,ss)
            author = msg['Creator']
            revision = fqrev
            logmsg = [msg['Summary']]
            s  = msg.get('Keywords', "").strip()
            if s:
                logmsg.append('Keywords: ' + s)
            s = msg.get_payload().strip()
            if s:
                logmsg.append(s)
            logmsg = '\n'.join(logmsg)
            changesets.append(Changeset(revision, date, author, logmsg))
        return changesets

    def __hide_foreign_entries(self):
        c = ExternalCommand(cwd=self.basedir,
                            command=self.repository.command("tree-lint", "-tu"))
        out = c.execute(stdout=PIPE)[0]
        tempdir = None
        if c.exit_status:
            tempdir = mkdtemp("", "++tailor-", self.basedir)
            try:
                for e in out:
                    e = e.strip()
                    ht = os.path.split(e)
                    # only accept inventory violations at the root
                    if ht[0] and ht[1]:
                        raise ChangesetApplicationFailure(
                            "%s complains about \"%s\"" % (str(c), e))
                    os.rename(os.path.join(self.basedir, e),
                              os.path.join(tempdir, e))
            except:
                self.__restore_foreign_entries(tempdir)
                raise
        return tempdir

    def __restore_foreign_entries(self, tempdir):
        for e in os.listdir(tempdir):
            os.rename(os.path.join(tempdir, e), os.path.join(self.basedir, e))
        os.rmdir(tempdir)

    def __parse_apply_changeset_output(self, changeset, output):
        conflicts = []
        skip = True
        for line in output:
            # skip comment lines, detect beginning and end of change list
            if line[0] == '*':
                if line.startswith("* applying changeset"):
                    skip = False
                elif line.startswith("* reapplying local changes"):
                    break
                continue
            if skip:
                continue
            l = line.split()
            l1 = self.__normalize_path(l[1])
            l2 = None
            if len(l) > 2:
                l2 = self.__normalize_path(l[2])

            # ignore permission changes and changes in the {arch} directory
            if l[0] in ['--', '-/'] or l1.startswith("{arch}"):
                continue
            if self.repository.IGNORE_IDS and l1.find('.arch-ids') >= 0:
                continue
            rev = changeset.revision
            if l[0][0] == 'M' or l[0] in ['ch', 'cl']:
                # 'ch': file <-> symlink, 'cl': ChangeLog updated
                e = changeset.addEntry(l1, rev)
                e.action_kind = e.UPDATED
            elif l[0][0] == 'A':
                e = changeset.addEntry(l1, rev)
                e.action_kind = e.ADDED
            elif l[0][0] == 'D':
                e = changeset.addEntry(l1, rev)
                e.action_kind = e.DELETED
            elif l[0] in ['=>', '/>']:
                e = changeset.addEntry(l2, rev)
                e.old_name = l1
                e.action_kind = e.RENAMED
            elif l[0] in ['C', '?']:
                conflicts.append(l1)
                if l2:
                    conflicts.append(l2)
            else:
                raise ChangesetApplicationFailure(
                        "unhandled changeset operation: \"%s\"" %
                        line.strip())
        return conflicts

Generated by  Doxygen 1.6.0   Back to index