VersContCvs.py :  » Build » A-A-P » aap-1.091 » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Build » A A P 
A A P » aap 1.091 » VersContCvs.py
# Part of the A-A-P recipe executive: CVS access

# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

#
# Functions to get files out of a CVS repository and put them back.
# See interface.txt for an explanation of what each function does.
#

import string
import os
import os.path

from Error import *
from Message import *
from Util import *

def cvs_command(recdict, server, url_dict, nodelist, action):
    """Handle CVS command "action".
       Return non-zero when it worked."""
    # Since CVS doesn't do locking, quite a few commands can be simplified:
    if action == "fetch":
        action = "checkout"    # "fetch" is exactly the same as "checkout"
    elif action in [ "checkin", "publish" ]:
        action = "commit"   # "checkin" and "publish" are the same as "commit"
    elif action == "unlock":
        return []           # unlocking is a no-op

    # All CVS commands require an argument to specify where the server is.
    if not server:
        # Obtain the previously used CVSROOT from CVS/Root.
        # There are several of these files that contain the same info, just use
        # the one in the current directory.
        try:
            f = open("CVS/Root")
        except StandardError, e:
            msg_extra(recdict,
                   _('Cannot open for obtaining CVSROOT: "CVS/Root"') + str(e))
        else:
            try:
                server = f.readline()
                f.close()
            except StandardError, e:
                msg_warning(recdict,
                   _('Cannot read for obtaining CVSROOT: "CVS/Root"') + str(e))
                server = ''     # in case something was read
            else:
                if server[-1] == '\n':
                    server = server[:-1]
    if server:
        serverarg = "-d" + server
    else:
        serverarg = ''

    #
    # Loop over the list of nodes and handle each separately.  This is required
    # to be able to do something useful with an error message.
    # For a "tag" command all nodes with the same tag are done at once to speed
    # it up.
    #
    failed = []

    if action == "tag":
        # Loop over todolist, taking out nodes with identical tags, until it's
        # empty.
        todolist = nodelist[:]
        while todolist:
            tag = ''
            thislist = []
            for node in todolist[:]:
                # Use the specified "tag" attribute for tagging.
                if not node.attributes.has_key("tag"):
                    msg_error(recdict, _('tag attribute missing for "%s"')
                                                           % node.short_name())
                    failed.extend(todolist)
                    failed.extend(thislist)
                    return failed
                if not tag or node.attributes["tag"] == tag:
                    thislist.append(node)
                    todolist.remove(node)
                    tag = node.attributes["tag"]
            f = cvs_tag(recdict, serverarg, tag, thislist)
            if f:
                failed.extend(f)

    else:
        for node in nodelist:
            if not cvs_command_node(recdict, serverarg, url_dict, node, action):
                failed.append(node)

    return failed


def cvs_prepare(recdict):
    """
    Prepare for using the cvs command.  Returns the program name.
    """
    # Install cvs when needed.
    from DoInstall import assert_pkg
    assert_pkg([], recdict, "cvs")

    # Create ~/.cvspass if it doesn't exist.  An empty file should be
    # sufficient for anonymous logins.
    if os.environ.get("HOME"):
        fn = os.path.expanduser("~/.cvspass")
        if not os.path.exists(fn):
            from Commands import touch_file
            touch_file(fn, 0644)

    n = get_var_val(0, recdict, "_no", "CVSCMD")
    if n:
        return n

    # Use $CVS if defined, otherwise use "cvs".
    return get_progname(recdict, "CVS", "cvs", "")


def cvs_tag(recdict, serverarg, tag, nodelist):
    """Handle CVS tag command for a list of nodes.
       Return list of nodes that failed."""
    msg_info(recdict, _('CVS tag for nodes %s')
                                % str(map(lambda x: x.short_name(), nodelist)))

    # Prepare for using CVS and get the command name.
    cvscmd = cvs_prepare(recdict)

    names = ''
    for node in nodelist:
        names = names + '"' + node.short_name() + '" '

    # TODO: check which of the nodes actually failed
    if logged_system(recdict,
                 '"%s" %s tag "%s" %s' % (cvscmd, serverarg, tag, names)) == 0:
        return []
    return nodelist


def cvs_get_repository(recdict, dir):
    """Get the first line of the CVS/Repository file in directory "dir"."""
    cvspath = ''
    fname = os.path.join(dir, "CVS/Repository")
    try:
        f = open(fname)
    except StandardError, e:
        # Only give this error when the directory exists, when it doesn't it's
        # probably the first time the files are checked out.
        if os.path.exists(os.path.join(dir, "CVS")):
            msg_warning(recdict,
                (_('Cannot open for obtaining path in module: "%s"')
                                                             % fname) + str(e))
    else:
        try:
            cvspath = f.readline()
            f.close()
        except StandardError, e:
            msg_warning(recdict,
                    (_('Cannot read for obtaining path in module: "%s"')
                                                             % fname) + str(e))
        else:
            if cvspath[-1] == '\n':
                cvspath = cvspath[:-1]
    return cvspath


def cvs_command_node(recdict, serverarg, url_dict, node, action):
    """Handle CVS command "action" for one node.
       Return non-zero when it worked."""

    msg_info(recdict, _('CVS %s for node "%s"') % (action, node.short_name()))

    # Count the number of directories in the node name.
    n = node.short_name()
    dirlevels = 0
    while n:
        prev = n
        n = os.path.dirname(n)
        if n == prev:
            break
        dirlevels = dirlevels + 1

    # A "checkout" only works reliably when in the top directory  of the
    # module.
    # "add" must be done in the current directory of the file.
    # Change to the directory where "path" + "node.name" is valid.
    if action == "checkout":
        cvspath = ''
        if url_dict.has_key("path"):
            # Use the specified "path" attribute.
            cvspath = url_dict["path"]
            dir_for_path = node.recipe_dir
        else:
            # Try to obtain the path from the CVS/Repository file.
            if os.path.isdir(os.path.join(node.absname, "CVS")):
                dir_for_path = node.absname
            else:
                dir_for_path = os.path.dirname(node.absname)

            cvspath = cvs_get_repository(recdict, dir_for_path)

        # Use node.recipe_dir and take off one part for each part in "path".
        adir = fname_fold(dir_for_path)
        path = fname_fold(cvspath)
        while path:
            if os.path.basename(adir) != os.path.basename(path):
                # This might happen when a module has an alias.
                msg_note(recdict, _('mismatch between path in cvs:// and tail of recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
                break
            ndir = os.path.dirname(adir)
            if ndir == adir:
                # This is probably an error somewhere...
                msg_error(recdict, _('path in cvs:// is longer than recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
                break
            adir = ndir
            npath = os.path.dirname(path)
            if npath == path:   # just in case: avoid getting stuck
                break
            path = npath
            if not path:
                break

            # Check that the CVS repository mentioned here is correct.  Bail
            # out here when it isn't, happens when a full path was used in
            # CVS/Repository (CVS sometimes does that for unknown reasons).
            # Also happens when a module "foo" is an alias for "bar/foo".
            # CVS/Repository then contains "bar/foo" but we must checkout
            # "foo", because the "bar" directory doesn't exist.
            p = cvs_get_repository(recdict, adir)
            if not p:
                break
            # Extra check for wrong assumptions about CVS/Repository contents.
            if fname_fold(os.path.basename(p)) != os.path.basename(path):
                msg_note(recdict, _('mismatch between contents of CVS/Repository at different levels: "%s" and "%s"') % (adir, path))
                break
    else:
        adir = os.path.dirname(node.absname)

    # Use the specified "logentry" attribute for a log message.
    # Only used for "commit" (also for add and remove).
    if url_dict.has_key("logentry"):
        logentry = url_dict["logentry"]
    elif node.attributes.has_key("logentry"):
        logentry = node.attributes["logentry"]
    else:
        logentry = get_var_val_int(recdict, "LOGENTRY")

    # Changing directory, don't return until going back!
    cwd = os.getcwd()
    if fname_equal(cwd, adir):
        cwd = ''        # we're already there, avoid a chdir()
    else:
        try:
            os.chdir(adir)
        except StandardError, e:
            msg_warning(recdict,
                      (_('Could not change to directory "%s"') % adir) + str(e))
            return 0

    msg_log(recdict, 'Cvs command in "%s"' % adir)

    node_name = node.short_name()

    tmpname = ''
    if action == "remove" and os.path.exists(node_name):
        # CVS refuses to remove a file that still exists, temporarily rename
        # it.  Careful: must always move it back when an exception is thrown!
        assert_aap_dir(recdict)
        tmpname = in_aap_dir(node_name)
        try:
            os.rename(node_name, tmpname)
        except:
            tmpname = ''

    try:
        # If the node has a "binary" attribute, give CVS the "-kb" argument for
        # an "add" action (also for commit with auto-add).
        if node.attributes.get("binary"):
            addbinarg = "-kb"
        else:
            addbinarg = ""

        ok = exec_cvs_cmd(recdict, serverarg, action, addbinarg,
                                    logentry, node_name, dirlevels = dirlevels)

        # For a remove we must commit it now, otherwise the local file will be
        # deleted when doing it later.  To be consistent, also do it for "add".
        if ok and action in [ "remove", "add" ]:
            ok = exec_cvs_cmd(recdict, serverarg, "commit", "", logentry,
                                node_name, dirlevels = dirlevels, auto_add = 0)
    finally:
        if tmpname:
            try:
                os.rename(tmpname, node_name)
            except StandardError, e:
                msg_error(recdict, (_('Could not move file "%s" back to "%s"')
                                              % (tmpname, node_name)) + str(e))

        if cwd:
            try:
                os.chdir(cwd)
            except StandardError, e:
                msg_error(recdict, (_('Could not go back to directory "%s"')
                                                               % cwd) + str(e))

    # TODO: how to check if it really worked?
    return ok


def exec_cvs_cmd(recdict, serverarg, action, addbinarg, logentry, node_name,
                                                  dirlevels = 1, auto_add = 1):
    """Execute the CVS command for "action".  Handle failure.
       For "commit" may create directories up to "dirlevels" upwards.
       When "auto_add" is non-zero and committing fails, try to add the file
       first.
       Return non-zero when it worked."""

    # Prepare for using CVS and get the command name.
    cvscmd = cvs_prepare(recdict)

    if logentry:
        logarg = '-m "%s"' % logentry
    else:
        logarg = ''

    if action == "commit":
        # If the file was never added to the repository we need to add it.
        # Since most files will exist in the repository, trying to commit and
        # handling the error is the best method.

        # Repeat this when the directory needs to be added.
        did_add_dir = 0
        while 1:
            # TODO: escaping special characters
            cmd = ('"%s" %s commit %s "%s"'
                                      % (cvscmd, serverarg, logarg, node_name))
            ok, text = redir_system_int(recdict, cmd)

            if text:
                msg_log(recdict, text)

                # If the directory for the file doesn't exist, CVS says "there
                # is no version here".  Need to create the directory first.
                # This is only done for the number of levels that were included
                # in the node name for the original directory.
                # Errors are ignored, the commit will fail later.
                if ((string.find(text, "no version here") >= 0
                             or string.find(text, "not open CVS/Entries") >= 0)
                         and not did_add_dir
                         and auto_add):
                    msg_info(recdict, _("Directory does not appear to exist in repository, adding it"))
                    commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd)
                    did_add_dir = 1
                    continue

                # If the file was never in the repository CVS says "nothing
                # known about".  If it was there before "use `cvs add' to
                # create an entry".
                if ok and (string.find(text, "nothing known about") >= 0
                                         or string.find(text, "cvs add") >= 0):
                    ok = 0
            break

        # Return if it worked, we are not doing automatic adds or the error
        # indicates that the file exists but our copy is not up-to-date.
        if ok or not auto_add or string.find(text, "Up-to-date check failed") >= 0:
            return ok

        try:
            msg_info(recdict,
                   _("File does not appear to exist in repository, adding it"))
            logged_system(recdict, '"%s" %s add %s "%s"'
                                   % (cvscmd, serverarg, addbinarg, node_name))
        except StandardError, e:
            msg_warning(recdict, _('Adding file failed: ') + str(e))


        # TODO: escaping special characters
        return logged_system(recdict, '"%s" %s commit %s "%s"'
                      % (cvscmd, serverarg, logarg, node_name)) == 0

    # TODO: escaping special characters
    if action != "add":
        addbinarg = ""
    return logged_system(recdict, '"%s" %s %s %s "%s"'
                      % (cvscmd, serverarg, action, addbinarg, node_name)) == 0


def commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd):
    """Commit to create the current directory.  If its parent is not in CVS
       either go up further."""
    cwd = os.getcwd()
    try:
        os.chdir("..")
        dirname = os.path.dirname(node_name)
        if not dirname:
            dirname = os.path.basename(cwd)

        if dirlevels > 0 and not os.path.isdir("CVS"):
            commit_dir(recdict, dirname, serverarg, dirlevels - 1, cvscmd)

        logged_system(recdict, '"%s" %s add "%s"'
                                                % (cvscmd, serverarg, dirname))
    except:
        pass
    os.chdir(cwd)


def cvs_list(recdict, name, commit_item, dirname, recursive):
    """Obtain a list of items in CVS for directory "dirname".
       Recursively entry directories if "recursive" is non-zero.
       "name" is not used, we don't access the server."""
    # We could use "cvs status" to obtain the actual entries in the repository,
    # but that is slow and the output is verbose and hard to parse.
    # Instead read the "CVS/Entries" file.  A disadvantage is that we might
    # list a file that is actually already removed from the repository if
    # another user removed it.
    fname = os.path.join(dirname, "CVS/Entries")
    try:
        f = open(fname)
    except StandardError, e:
        msg_error(recdict, (_('Cannot open "%s": ') % fname) + str(e))
        return []
    try:
        lines = f.readlines()
        f.close()
    except StandardError, e:
        msg_error(recdict, (_('Cannot read "%s": ') % fname) + str(e))
        return []

    # The format of the lines is:
    #   D/dirname////
    #   /itemname/vers/foo//
    # We only need to extract "dirname" or "itemname".
    res = []
    for line in lines:
        s = string.find(line, "/")
        if s < 0:
            continue
        s = s + 1
        e = string.find(line, "/", s)
        if e < 0:
            continue
        item = os.path.join(dirname, line[s:e])

        if line[0] == 'D' and recursive:
            res.extend(cvs_list(recdict, name, commit_item, item, 1))
        else:
            res.append(item)

    return res



# vim: set sw=4 et sts=4 tw=79 fo+=l:
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.