# Part of the A-A-P recipe executive: Generic Version Control

# Copyright (C) 2002 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 version control system and put them back.
# Most of the work is directed to one of the modules for a specific version
# control system.
# Uploading/downloading is done by functions in Remote.py.
#

import os
import string

from Process import recipe_error
from Cvs import *
from Work import getwork
from Node import Node


def separate_scheme(url):
   """Isolate the scheme of the URL."""
   # TODO: how about "file:~user"?
   i = string.find(url, "://")
   if i < 0:
       raise UserError, _('No :// found in "%s"') % url
   scheme = string.lower(url[:i])
   for c in scheme:
       if not c in string.lowercase:
           raise UserError, _('Illegal character before colon in "%s"') % url
   return scheme, url[i + 3:]


def verscont_command(globals, dict, node, use_cache, action):
   """Try performing "action" on node "node" from the semi-URL "dict["name"]".
      May Use a cached file when "use_cache" is non-zero.
      Returns non-zero when it succeeds."""
   scheme, name = separate_scheme(dict["name"])

   # Handle a scheme for which there is a scheme_command() function.
   if globals.has_key(scheme + "_command"):
       return globals[scheme + "_command"](name, dict, [ node ], command)

   # Handle the "cvs://" scheme.
   if scheme == "cvs":
       return cvs_command(name, dict, [ node ], action)

   # Assume it's a normal URL, try downloading/uploading/removing the file.
   if action in ["refresh", "checkout"]:
       return download_file(globals, dict, node, use_cache)
   if action in ["commit", "checkin", "publish", "add"]:
       return upload_file(globals)
   if action == "remove":
       return remote_remove(globals)

   return 0


def handle_node(rpstack, globals, node, use_cache, action, attrnames):
   """Common code for refreshing, committing, etc.
      "action" is the name  of the command: "refresh", "commit", etc.
      "attrnames" is a list of attribute names that can be used.
      """
   # Use the first attribute that exists and isn't empty.
   attr = ''
   for n in attrnames:
       if node.attributes.has_key(n):
           attr = node.attributes[n]
           if attr:
               break
   if not attr:
       recipe_error(rpstack, _("Missing %s attribute for %s")
                                          % (attrnames[0], node.short_name()))

   # Replace all occurences of "%file%" with the node name (relative to the
   # recipe it was specified in).
   while 1:
       i = string.find(attr, "%file%")
       if i < 0:
           break
       attr = attr[:i] + node.name + attr[i+6:]

   from Dictlist import string2dictlist
   list = string2dictlist(rpstack, attr)
   if not list:
       recipe_error(rpstack, _("%s attribute for %s is empty")
                                              % (attrname, node.short_name()))

   # Loop over the list of refresh locations.  Quit as soon as refreshing
   # worked.
   for org in list:
       # Try downloading this node, return when it worked
       if verscont_command(globals, org, node, use_cache, action):
           return 1
   return 0


def refresh_node(rpstack, globals, node, use_cache):
   """Refresh "node" according to its "refresh" attribute.
      When there is no "refresh" attribute use "commit".
      Only use cached files when "use_cache" is non-zero.
      Return non-zero for success."""
   return handle_node(rpstack, globals, node, use_cache,
                                           "refresh", [ "refresh", "commit" ])


def verscont_node(rpstack, globals, node, action):
   """Checkout "node" according to its "commit" attribute.
      When there is no "commit" attribute use "refresh".
      Return non-zero for success."""
   return handle_node(rpstack, globals, node, 0, action, [ "commit" ])


def publish_node(rpstack, globals, node):
   """Publish "node" according to its "publish" attribute.
      When there is no "publish" attribute use "commit".
      Return non-zero for success."""
   return handle_node(rpstack, globals, node, 0,
                                           "publish", [ "publish", "commit" ])


def verscont_removeall(rpstack, globals, dir, recursive):
   """Remove all files in directory "dir" of VCS that don't belong there.
      "dir" is a dictionary for the directory and its attributes.
      Enter directories recursively when "recursive" is non-zero.
      """
   if not dir.has_key("commit"):
       recipe_error(rpstack, _("no commit attribute for %s") % dir["name"])

   from Dictlist import string2dictlist
   commit_list = string2dictlist(rpstack, dir["commit"])
   if not commit_list:
       recipe_error(rpstack, _("commit attribute for %s is empty")
                                                                % dir["name"])

   dirname = os.path.abspath(dir["name"])
   list = None
   for commit_item in commit_list:
       scheme, name = separate_scheme(commit_item["name"])

       # Handle a scheme for which there is a scheme_list() function.
       if globals.has_key(scheme + "_list"):
           list = globals[scheme + "_list"](name,
                                              commit_item, dirname, recursive)
           break

       # Handle the "cvs://" scheme.
       if scheme == "cvs":
           list = cvs_list(name, commit_item, dirname, recursive)
           break

   if list is None:
       recipe_error(rpstack, _("No working item in commit attribute for %s")
                                                                % dir["name"])

   # Loop over all items found in the VCS.
   work = getwork(globals)
   for item in list:
       node = work.find_node(item)
       if not node or not node.attributes.has_key("commit"):
           if not node:
               node = Node(item)
           # Do something with errors?
           verscont_command(globals, commit_item, node, 0, "remove")

# vim: set sw=4 sts=4 tw=79 fo+=l: